Commit 21fea824 authored by Alan McNatty's avatar Alan McNatty
Browse files

Started working on 2.0 .. account & sync

parent 4781139d
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="nz.net.catalyst.MaharaDroid"
android:versionCode="8"
android:versionName="1.8">
android:versionCode="10"
android:versionName="2.0">
<application android:icon="@drawable/icon" android:label="@string/app_name"
android:debuggable="false" >
android:debuggable="true" >
<activity android:name=".ui.ArtefactExpandableListAdapterActivity"
android:label="@string/app_name"
......@@ -15,7 +15,9 @@
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="nz.net.catalyst.MaharaDroid.LAUNCHER" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:name=".ui.about.AboutActivity"
......@@ -71,11 +73,47 @@
<activity android:name=".ui.EditPreferences"
android:label="@string/options_menu_prefs" />
<activity android:name=".authenticator.AuthenticatorActivity"
android:theme="@android:style/Theme.Dialog"
android:excludeFromRecents="true" />
<service android:name=".upload.TransferService" android:label="@string/filetransfers"/>
<service android:name=".authenticator.AccountAuthenticatorService"
android:exported="true">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator" />
</service>
<!-- sync-adapter service -->
<service android:name=".syncadapter.SyncAdapterService"
android:icon="@drawable/icon"
android:label="@string/app_name"
android:exported="true">
<intent-filter>
<action android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data android:name="android.content.SyncAdapter"
android:resource="@xml/syncadapter" />
</service>
<provider android:name=".provider.MaharaProvider"
android:authorities="nz.net.catalyst" android:enabled="true">
</provider>
</application>
<!-- API Level 4 aka 1.6 required for multi-send support -->
<uses-sdk android:minSdkVersion="4" />
<!-- API Level 5 aka 2.0 required for Account & Sync support -->
<uses-sdk android:minSdkVersion="5" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.READ_SYNC_STATS" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
</manifest>
\ No newline at end of file
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
#
# This file must be checked in Version Control Systems.
#
#
# To customize properties used by the Ant build system use,
# "build.properties", and override values to adapt the script to your
# "ant.properties", and override values to adapt the script to your
# project structure.
# Project target.
target=android-4
target=android-5
......@@ -20,13 +20,18 @@
<string name="pref_upload_url_summary">Enter the URL to post files to Mahara</string>
<string name="pref_upload_url_default">http://master.dev.mahara.org/artefact/file/mobileupload.php</string>
<string name="pref_upload_username_key">upload.username</string>
<string name="pref_upload_username_title">Username</string>
<string name="pref_upload_username_summary">Enter your Mahara username</string>
<string name="pref_sync_url_key">sync.uri</string>
<string name="pref_sync_url_title">Sync URI</string>
<string name="pref_sync_url_summary">Enter the sync URL to Mahara</string>
<string name="pref_sync_url_default">http://master.dev.mahara.org/artefact/file/mobilesync.php</string>
<string name="pref_auth_username_key">auth.username</string>
<string name="pref_auth_username_title">Username</string>
<string name="pref_auth_username_summary">Enter your Mahara username</string>
<string name="pref_upload_token_key">upload.token</string>
<string name="pref_upload_token_title">Token</string>
<string name="pref_upload_token_summary">Enter your Mahara upload token</string>
<string name="pref_auth_token_key">auth.token</string>
<string name="pref_auth_token_title">Token</string>
<string name="pref_auth_token_summary">Enter your Mahara mobile token</string>
<string name="pref_upload_connection_key">upload.connection</string>
<string name="pref_upload_connection_title">Connection Type</string>
......@@ -100,7 +105,15 @@
<string name="load_config_error">Sorry there was an error parsing the configuration file.</string>
<string name="load_config_success">Successfully loaded the configuration file.</string>
<string name="scan_not_available">Sorry you need to install ZXing Barcode Scanner for this.</string>
<string name="login_authenticating">Authenticating...</string>
<string name="only_one_account">Currently you may only have at most one account at any one time.</string>
<string name="auth_result_no_connection">Server address or port number incorrect</string>
<string name="auth_result_success">Authentication successful!</string>
<string name="auth_result_fail_short">Authentication failed</string>
<string name="auth_result_fail_long">Authentication failed, check your connection and user token.</string>
<!-- About page -->
<string name="about_title">About MaharaDroid</string>
<string name="about_top_section_title">MaharaDroid</string>
......
......@@ -49,8 +49,8 @@ Active text field border: #6398C1
</style>
<!-- Set the theme for the window title -->
<!-- NOTE: setting android:textAppearence to style defined above -->
<style name="MaharaWindowTitle" parent="android:WindowTitle">
<!-- NOTE: setting android:textAppearence to style defined above-->
<style name="MaharaWindowTitle"> <!-- parent="android:WindowTitle">-->
<item name="android:textAppearance">@style/MaharaWindowTitleText</item>
</style>
</resources>
\ No newline at end of file
<!-- API Version 7 requires the accountType attribute to be hard-coded as a string -->
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="nz.net.catalyst.MaharaDroid.account"
android:icon="@drawable/icon"
android:smallIcon="@drawable/icon"
android:label="@string/app_name"
/>
\ No newline at end of file
......@@ -9,13 +9,18 @@
android:summary="@string/pref_upload_url_summary"
android:defaultValue="@string/pref_upload_url_default" />
<EditTextPreference
android:key="@string/pref_upload_username_key"
android:title="@string/pref_upload_username_title"
android:summary="@string/pref_upload_username_summary" />
android:key="@string/pref_sync_url_key"
android:title="@string/pref_sync_url_title"
android:summary="@string/pref_sync_url_summary"
android:defaultValue="@string/pref_sync_url_default" />
<EditTextPreference
android:key="@string/pref_auth_username_key"
android:title="@string/pref_auth_username_title"
android:summary="@string/pref_auth_username_summary" />
<EditTextPreference
android:key="@string/pref_upload_token_key"
android:title="@string/pref_upload_token_title"
android:summary="@string/pref_upload_token_summary" />
android:key="@string/pref_auth_token_key"
android:title="@string/pref_auth_token_title"
android:summary="@string/pref_auth_token_summary" />
<ListPreference
android:key="@string/pref_upload_connection_key"
android:title="@string/pref_upload_connection_title"
......
<!-- API Version 7 requires the accountType attribute to be hard-coded as a string -->
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:contentAuthority="nz.net.catalyst"
android:accountType="nz.net.catalyst.MaharaDroid.account"/>
\ No newline at end of file
......@@ -47,4 +47,10 @@ public class GlobalResources {
public static int ERROR_DELAY_MS = 1000;
public static final int UPLOADER_ID = 243;
}
public static final String ACCOUNT_TYPE = "nz.net.catalyst.MaharaDroid.account";
public static final String AUTHTOKEN_TYPE = "nz.net.catalyst.MaharaDroid.account";
public static final String SYNC_AUTHORITY = "nz.net.catalyst";
public static final int REGISTRATION_TIMEOUT = 30 * 1000; // ms
}
\ No newline at end of file
......@@ -46,7 +46,7 @@ public class LogConfig {
* Rather than checking this directly, classes should use LogConfig.isDebug(TAG) to
* determine whether to log a DEBUG level.
*/
private static final boolean DEBUG = false;
private static final boolean DEBUG = true;
/**
* Whether the application should allow any tags to log at VERBOSE level.
......@@ -56,7 +56,7 @@ public class LogConfig {
* The Java compiler is unable to strip out calls to Log.isLoggable()
* (although, maybe the Dalvik compiler, or the JIT can ???)
*/
public static final boolean VERBOSE = false;
public static final boolean VERBOSE = true;
private static final int MAX_LOG_TAG_LENGTH = 23;
......
......@@ -23,6 +23,9 @@ package nz.net.catalyst.MaharaDroid;
import java.io.File;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
......@@ -113,4 +116,37 @@ public class Utils {
}
return file_path;
}
public static String updateTokenFromResult(JSONObject json, Context mContext) {
String newToken = null;
if (json == null || json.has("fail")) {
String err_str = null;
try {
err_str = (json == null) ? "Unknown Failure" : json.getString("fail");
} catch (JSONException e) {
err_str = "Unknown Failure";
}
Log.e(TAG, "Auth fail: " + err_str);
} else if ( json.has("success") ) {
try {
newToken = json.getString("success");
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
mPrefs.edit()
.putString(mContext.getResources().getString(R.string.pref_auth_token_key), newToken)
.commit()
;
// Here we want to check a check-sum for 'last-modified' and if newer content exists
// then process out new user-data
Log.e(TAG, "Token found, re-keying auth-token");
} catch (JSONException e) {
Log.e(TAG, "Failed to get success token from result.");
}
}
return newToken;
}
}
package nz.net.catalyst.MaharaDroid.authenticator;
import nz.net.catalyst.MaharaDroid.authenticator.AccountAuthenticator;
import nz.net.catalyst.MaharaDroid.authenticator.AuthenticatorActivity;
import nz.net.catalyst.MaharaDroid.authenticator.MaharaAuthHandler;
import nz.net.catalyst.MaharaDroid.GlobalResources;
import nz.net.catalyst.MaharaDroid.LogConfig;
import nz.net.catalyst.MaharaDroid.R;
import android.accounts.AbstractAccountAuthenticator;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.AccountManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
public class AccountAuthenticator extends AbstractAccountAuthenticator {
static final String TAG = LogConfig.getLogTag(AccountAuthenticator.class);
// whether DEBUG level logging is enabled (whether globally, or explicitly
// for this log tag)
static final boolean DEBUG = LogConfig.isDebug(TAG);
// whether VERBOSE level logging is enabled
static final boolean VERBOSE = LogConfig.VERBOSE;
private Context mContext;
public AccountAuthenticator(Context context) {
super(context);
mContext = context;
}
/**
* {@inheritDoc}
*/
@Override
public Bundle addAccount(AccountAuthenticatorResponse response,
String accountType, String authTokenType, String[] requiredFeatures,
Bundle options) {
Bundle reply = new Bundle();
if(accountExists()){//only one account is allowed as of now
reply.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_BAD_REQUEST);
reply.putString(AccountManager.KEY_ERROR_MESSAGE,
this.mContext.getResources().getString(R.string.only_one_account).toString());
response.onResult(reply);
return null;
}
final Intent intent = new Intent(mContext, AuthenticatorActivity.class);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE,
response);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}
/**
* {@inheritDoc}
*/
@Override
public Bundle confirmCredentials(AccountAuthenticatorResponse response,
Account account, Bundle options) {
// TODO Auto-generated method stub
return null;
}
/**
* {@inheritDoc}
*/
@Override
public Bundle editProperties(AccountAuthenticatorResponse response,
String accountType) {
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*/
@Override
public Bundle getAuthToken(AccountAuthenticatorResponse response,
Account account, String authTokenType, Bundle loginOptions) {
if (!authTokenType.equals(GlobalResources.AUTHTOKEN_TYPE)) {
final Bundle result = new Bundle();
result.putString(AccountManager.KEY_ERROR_MESSAGE,
"invalid authTokenType");
return result;
}
final AccountManager am = AccountManager.get(mContext);
final String password = am.getPassword(account);
if (password != null) {
final boolean verified =
onlineConfirmPassword(account.name, password);
if (verified) {
final Bundle result = new Bundle();
result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
result.putString(AccountManager.KEY_ACCOUNT_TYPE,
GlobalResources.ACCOUNT_TYPE);
result.putString(AccountManager.KEY_AUTHTOKEN, password);
return result;
}
}
// the password was missing or incorrect, return an Intent to an
// Activity that will prompt the user for the password.
final Intent intent = new Intent(mContext, AuthenticatorActivity.class);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE,
response);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}
/**
* {@inheritDoc}
*/
@Override
public String getAuthTokenLabel(String authTokenType) {
if (authTokenType.equals(GlobalResources.AUTHTOKEN_TYPE)) {
return mContext.getString(R.string.app_name);
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public Bundle hasFeatures(AccountAuthenticatorResponse response,
Account account, String[] features) {
final Bundle result = new Bundle();
result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
return result;
}
/**
* Validates user's password on the server
*/
private boolean onlineConfirmPassword(String username, String password) {
return MaharaAuthHandler.authenticate(username, null/* Handler */, null/* Context */);
}
/**
* {@inheritDoc}
*/
@Override
public Bundle updateCredentials(AccountAuthenticatorResponse response,
Account account, String authTokenType, Bundle loginOptions) {
final Intent intent = new Intent(mContext, AuthenticatorActivity.class);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}
private boolean accountExists(){
AccountManager am = AccountManager.get(mContext);
Account[] acts = am.getAccountsByType(GlobalResources.ACCOUNT_TYPE);
return acts.length > 0;
}
}
package nz.net.catalyst.MaharaDroid.authenticator;
import nz.net.catalyst.MaharaDroid.LogConfig;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
public class AccountAuthenticatorService extends Service {
private AccountAuthenticator mAccountAuthenticator;
static final String TAG = LogConfig.getLogTag(AccountAuthenticatorService.class);
// whether DEBUG level logging is enabled (whether globally, or explicitly
// for this log tag)
static final boolean DEBUG = LogConfig.isDebug(TAG);
// whether VERBOSE level logging is enabled
static final boolean VERBOSE = LogConfig.VERBOSE;
@Override
public void onCreate() {
if(VERBOSE) Log.v(TAG, "Service started ...");
mAccountAuthenticator = new AccountAuthenticator(this);
}
@Override
public IBinder onBind(Intent intent) {
if(VERBOSE) Log.v(TAG, "IBinder returned for AccountAuthenticator implementation.");
return mAccountAuthenticator.getIBinder();
}
}
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package nz.net.catalyst.MaharaDroid.authenticator;
import nz.net.catalyst.MaharaDroid.R;
import nz.net.catalyst.MaharaDroid.authenticator.AuthenticatorActivity;
import nz.net.catalyst.MaharaDroid.provider.MaharaProvider;
import nz.net.catalyst.MaharaDroid.ui.EditPreferences;
import nz.net.catalyst.MaharaDroid.GlobalResources;
import nz.net.catalyst.MaharaDroid.LogConfig;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorActivity;
import android.accounts.AccountManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ContentResolver;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
/**
* Activity which displays login screen to the user.
*/
public class AuthenticatorActivity extends AccountAuthenticatorActivity {
static final String TAG = LogConfig.getLogTag(AuthenticatorActivity.class);
// whether DEBUG level logging is enabled (whether globally, or explicitly
// for this log tag)
static final boolean DEBUG = LogConfig.isDebug(TAG);
// whether VERBOSE level logging is enabled
static final boolean VERBOSE = LogConfig.VERBOSE;
private AccountManager mAccountManager;
private NotificationManager mNM;
// Unique Identification Number for the Notification.
// We use it on Notification start, and to cancel it.
private int NOTIFICATION = R.string.login_authenticating;
/**
* If set we are just checking that the user knows their credentials; this
* doesn't cause the user's password to be changed on the device.
*/
//private Boolean mConfirmCredentials = false;
/** for posting authentication attempts back to UI thread */
private final Handler mHandler = new Handler();
/** Was the original caller asking for an entirely new account? */
protected boolean mRequestNewAccount = false;
private Account[] mAccounts;
/**
* {@inheritDoc}
*/
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
String mUsername = null;
mAccountManager = AccountManager.get(this);
//final Intent intent = getIntent();
mAccounts = mAccountManager.getAccountsByType(GlobalResources.ACCOUNT_TYPE);
if ( mAccounts.length > 0 ) {
// Just pick the first one .. support multiple accounts can come later.
mUsername = mAccounts[0].name;
}
mRequestNewAccount = mUsername == null;
if ( DEBUG ) Log.d(TAG, "AuthenticatorActivity request new: " + mRequestNewAccount);
mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
showNotification(getText(R.string.login_authenticating), null, null);
MaharaAuthHandler.attemptAuth(mUsername, mHandler, AuthenticatorActivity.this);
finish();
}
/**
*
* Called when response is received from the server for authentication
* request. See onAuthenticationResult(). Sets the
* AccountAuthenticatorResult which is sent back to the caller. Also sets
* the authToken in AccountManager for this account.
*
* @param the confirmCredentials result.
*/
protected void finishLogin(String username, String authToken) {
if ( DEBUG ) Log.d(TAG, "finishLogin()");
final Account account = new Account(username, GlobalResources.ACCOUNT_TYPE);
if (mRequestNewAccount) {
mAccountManager.addAccountExplicitly(account, authToken, null);
// Set contacts sync for this account.
ContentResolver.setSyncAutomatically(account, MaharaProvider.AUTHORITY, true);
ContentResolver.setIsSyncable(account, MaharaProvider.AUTHORITY, 1);
} else {
mAccountManager.setPassword(account, authToken);
}
final Intent intent = new Intent();
intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, username);
intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, GlobalResources.ACCOUNT_TYPE);
setAccountAuthenticatorResult(intent.getExtras());
setResult(RESULT_OK, intent);
finish();
}
/**
* Called when the authentication process completes (see attemptLogin()).
*/