From 11447ec08100ac1804112362b0c5ea67151dbe17 Mon Sep 17 00:00:00 2001 From: Eugene Popovich Date: Wed, 2 Oct 2013 15:02:10 +0300 Subject: [PATCH] Issue #459 (Card 7) (Multi-site login) app: - AccountLogin.LogInFragment, AccountSignup.NewUserFragment: now it implements LoginActionHandler - AccountLogin.LogInFragment, AccountSignup.NewUserFragment: added static fields sCurrentInstance and sCurrentInstanceAccessor - AccountLogin.LogInFragment, AccountSignup.NewUserFragment: added new fields mDelayedLoginProcess, mLastResponse - AccountLogin.LogInFragment, AccountSignup.NewUserFragment: added implementation of onCreate and onDestroy to init sCurrentInstance static field - AccountLogin.LogInFragment, AccountSignup.NewUserFragment, GoogleLoginFragment: added new methods processLoginCredentials, processLoginResponse, onViewCreated - AccountLogin.LogInFragment.LogInUserTask, AccountSignup.NewUserFragment.NewUserTask, GoogleLoginFragment.LogInUserTask: reworked onSuccessPostExecuteAdditional method to call processLoginResonse or schedule delayed login processing depend on fragment-activity attached state - GoogleLoginFragment: renamed currentInstance field to sCurrentInstance, requestCode to mRequestCode, delayedLoggedIn to mDelayedLoginProcessing - GoogleLoginFragment: added new static field sCurrentInstanceAccessor - GoogleLoginFragment: added new field mLastResponse - GoogleLoginFragment: removed method onLoggedIn and reworked onViewCreated to call processLoginResonse - SetupActivity: replaced getActivity with getSupportActivity call for OAuthUtils.askOAuth call - Credentials: added - AccountTroveboxResponse: removed different credentials field and added mCredentials field - AccountTroveboxResponse: reworked constructor to support array of credentials in the json result - OAuthUtils: replaced Activity reference in import with HE one - LoginUtils: added new methods: onLoggedIn, processSuccessfulLoginResult, performLogin - LoginUtils.SelectAccountSelectedActionHandler: added - LoginUtils.LoginActionHandler: added - LoginUtils.SelectAccountDialogFragment: added - res/values/strings.xml, res/values-ru/strings.xml: added select_trovebox_account constant test: - CredentialsTest: added - AccountTroveboxApiTest: modified to use credentials reference from the modified AccountTroveboxResponse - AccountTroveboxResponseTest: added - res/raw/json_credentials.txt: added - res/raw/json_login_multiple.txt: added - res/raw/json_login_simple.txt: added --- app/res/values-ru/strings.xml | 1 + app/res/values/strings.xml | 1 + .../trovebox/android/app/AccountLogin.java | 76 +++++++- .../trovebox/android/app/AccountSignup.java | 76 ++++++-- .../android/app/GoogleLoginFragment.java | 101 ++++++----- .../trovebox/android/app/SetupActivity.java | 19 +- .../android/app/model/Credentials.java | 145 +++++++++++++++ .../net/account/AccountTroveboxResponse.java | 107 ++--------- .../android/app/oauth/OAuthUtils.java | 4 +- .../trovebox/android/app/util/LoginUtils.java | 166 +++++++++++++++++- test/res/raw/json_credentials.txt | 7 + test/res/raw/json_login_multiple.txt | 18 ++ test/res/raw/json_login_simple.txt | 10 ++ .../android/test/model/CredentialsTest.java | 62 +++++++ .../net/account/AccountTroveboxApiTest.java | 13 +- .../account/AccountTroveboxResponseTest.java | 41 +++++ 16 files changed, 680 insertions(+), 167 deletions(-) create mode 100644 app/src/com/trovebox/android/app/model/Credentials.java create mode 100644 test/res/raw/json_credentials.txt create mode 100644 test/res/raw/json_login_multiple.txt create mode 100644 test/res/raw/json_login_simple.txt create mode 100644 test/src/com/trovebox/android/test/model/CredentialsTest.java create mode 100644 test/src/com/trovebox/android/test/net/account/AccountTroveboxResponseTest.java diff --git a/app/res/values-ru/strings.xml b/app/res/values-ru/strings.xml index 15152b5..ceeaf10 100644 --- a/app/res/values-ru/strings.xml +++ b/app/res/values-ru/strings.xml @@ -239,6 +239,7 @@ Введите email чтобы восстановить пароль Зарегистрируйтесь или войдите в одно касание]]> Пожалуйста выберите аккаунт google для входа + Пожалуйста выберите аккаунт trovebox для входа email пароль diff --git a/app/res/values/strings.xml b/app/res/values/strings.xml index dcefb76..0ebb122 100644 --- a/app/res/values/strings.xml +++ b/app/res/values/strings.xml @@ -240,6 +240,7 @@ Enter your email to recover your password Sign up or sign in with a single tap]]> Please select google account to login with + Please select trovebox account to login with email password diff --git a/app/src/com/trovebox/android/app/AccountLogin.java b/app/src/com/trovebox/android/app/AccountLogin.java index faa2f34..7021e31 100644 --- a/app/src/com/trovebox/android/app/AccountLogin.java +++ b/app/src/com/trovebox/android/app/AccountLogin.java @@ -1,6 +1,8 @@ package com.trovebox.android.app; +import java.lang.ref.WeakReference; + import org.holoeverywhere.app.Activity; import org.holoeverywhere.widget.TextView; @@ -20,6 +22,8 @@ import com.trovebox.android.app.util.CommonUtils; import com.trovebox.android.app.util.GuiUtils; import com.trovebox.android.app.util.LoginUtils; +import com.trovebox.android.app.util.LoginUtils.LoginActionHandler; +import com.trovebox.android.app.util.ObjectAccessor; import com.trovebox.android.app.util.TrackerUtils; public class AccountLogin extends CommonActivity { @@ -122,9 +126,44 @@ public RecoverPasswordFragment getRecoverPasswordFragment() { * The log in fragment with the retained instance across configuration * change */ - public static class LogInFragment extends CommonRetainedFragmentWithTaskAndProgress { + public static class LogInFragment extends CommonRetainedFragmentWithTaskAndProgress implements + LoginActionHandler { private static final String TAG = LogInFragment.class.getSimpleName(); + static WeakReference sCurrentInstance; + static ObjectAccessor sCurrentInstanceAccessor = new ObjectAccessor() { + private static final long serialVersionUID = 1L; + + @Override + public LogInFragment run() { + return sCurrentInstance == null ? null : sCurrentInstance.get(); + } + }; + + boolean mDelayedLoginProcessing = false; + AccountTroveboxResponse mLastResponse; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + sCurrentInstance = new WeakReference(this); + } + + @Override + public void onDestroy() { + if (sCurrentInstance != null) { + if (sCurrentInstance.get() == LogInFragment.this + || sCurrentInstance.get() == null) { + CommonUtils.debug(TAG, "Nullify current instance"); + sCurrentInstance = null; + } else { + CommonUtils.debug(TAG, + "Skipped nullify of current instance, such as it is not the same"); + } + } + super.onDestroy(); + } + @Override public String getLoadingMessage() { return CommonUtils.getStringResource(R.string.logging_in_message); @@ -134,6 +173,26 @@ public void doLogin(String user, String pwd) { startRetainedTask(new LogInUserTask(new Credentials(user, pwd))); } + @Override + public void processLoginCredentials(com.trovebox.android.app.model.Credentials credentials) { + Activity activity = getSupportActivity(); + credentials.saveCredentials(activity); + LoginUtils.onLoggedIn(activity, true); + } + + void processLoginResonse(Activity activity) { + LoginUtils.processSuccessfulLoginResult(mLastResponse, sCurrentInstanceAccessor, + activity); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + if (mDelayedLoginProcessing) { + mDelayedLoginProcessing = false; + processLoginResonse(getSupportActivity()); + } + } class LogInUserTask extends RetainedTask { private Credentials credentials; AccountTroveboxResponse result; @@ -161,15 +220,14 @@ protected Boolean doInBackground(Void... params) { protected void onSuccessPostExecuteAdditional() { try { + mLastResponse = result; Activity activity = getSupportActivity(); - // save credentials. - result.saveCredentials(activity); - - // start new activity - startActivity(new Intent(activity, - MainActivity.class)); - LoginUtils.sendLoggedInBroadcast(activity); - activity.finish(); + if (activity != null) { + processLoginResonse(activity); + } else { + TrackerUtils.trackErrorEvent("activity_null", TAG); + mDelayedLoginProcessing = true; + } } catch (Exception e) { GuiUtils.error(TAG, e); diff --git a/app/src/com/trovebox/android/app/AccountSignup.java b/app/src/com/trovebox/android/app/AccountSignup.java index 9e355a5..03222d5 100644 --- a/app/src/com/trovebox/android/app/AccountSignup.java +++ b/app/src/com/trovebox/android/app/AccountSignup.java @@ -1,9 +1,10 @@ package com.trovebox.android.app; +import java.lang.ref.WeakReference; + import org.holoeverywhere.app.Activity; -import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.EditText; @@ -17,6 +18,8 @@ import com.trovebox.android.app.util.CommonUtils; import com.trovebox.android.app.util.GuiUtils; import com.trovebox.android.app.util.LoginUtils; +import com.trovebox.android.app.util.LoginUtils.LoginActionHandler; +import com.trovebox.android.app.util.ObjectAccessor; import com.trovebox.android.app.util.TrackerUtils; /** @@ -83,9 +86,43 @@ NewUserFragment getNewUserFragment() { * The create new user fragment with the retained instance across * configuration change */ - public static class NewUserFragment extends CommonRetainedFragmentWithTaskAndProgress { + public static class NewUserFragment extends CommonRetainedFragmentWithTaskAndProgress implements + LoginActionHandler { private static final String TAG = NewUserFragment.class.getSimpleName(); + static WeakReference sCurrentInstance; + static ObjectAccessor sCurrentInstanceAccessor = new ObjectAccessor() { + private static final long serialVersionUID = 1L; + + @Override + public NewUserFragment run() { + return sCurrentInstance == null ? null : sCurrentInstance.get(); + } + }; + + boolean mDelayedLoginProcessing = false; + AccountTroveboxResponse mLastResponse; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + sCurrentInstance = new WeakReference(this); + } + + @Override + public void onDestroy() { + if (sCurrentInstance != null) { + if (sCurrentInstance.get() == NewUserFragment.this || sCurrentInstance.get() == null) { + CommonUtils.debug(TAG, "Nullify current instance"); + sCurrentInstance = null; + } else { + CommonUtils.debug(TAG, + "Skipped nullify of current instance, such as it is not the same"); + } + } + super.onDestroy(); + } + @Override public String getLoadingMessage() { return CommonUtils.getStringResource(R.string.signup_message); @@ -95,6 +132,26 @@ public void createNewUser(String username, String email, String password) { startRetainedTask(new NewUserTask(username, email, password)); } + @Override + public void processLoginCredentials(com.trovebox.android.app.model.Credentials credentials) { + Activity activity = getSupportActivity(); + credentials.saveCredentials(activity); + LoginUtils.onLoggedIn(activity, true); + } + + void processLoginResonse(Activity activity) { + LoginUtils.processSuccessfulLoginResult(mLastResponse, sCurrentInstanceAccessor, + activity); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + if (mDelayedLoginProcessing) { + mDelayedLoginProcessing = false; + processLoginResonse(getSupportActivity()); + } + } class NewUserTask extends RetainedTask { String username, password, email; AccountTroveboxResponse result; @@ -124,15 +181,14 @@ protected Boolean doInBackground(Void... params) { protected void onSuccessPostExecuteAdditional() { try { + mLastResponse = result; Activity activity = getSupportActivity(); - // save credentials. - result.saveCredentials(activity); - - // start new activity - startActivity(new Intent(activity, - MainActivity.class)); - LoginUtils.sendLoggedInBroadcast(activity); - activity.finish(); + if (activity != null) { + processLoginResonse(activity); + } else { + TrackerUtils.trackErrorEvent("activity_null", TAG); + mDelayedLoginProcessing = true; + } } catch (Exception e) { GuiUtils.error(TAG, e); diff --git a/app/src/com/trovebox/android/app/GoogleLoginFragment.java b/app/src/com/trovebox/android/app/GoogleLoginFragment.java index b84fec2..b2c9d83 100644 --- a/app/src/com/trovebox/android/app/GoogleLoginFragment.java +++ b/app/src/com/trovebox/android/app/GoogleLoginFragment.java @@ -4,14 +4,13 @@ import java.io.IOException; import java.lang.ref.WeakReference; +import org.holoeverywhere.app.Activity; import org.holoeverywhere.app.AlertDialog; import org.holoeverywhere.app.Dialog; import android.accounts.Account; import android.accounts.AccountManager; -import android.app.Activity; import android.content.DialogInterface; -import android.content.Intent; import android.os.Bundle; import android.view.View; @@ -29,6 +28,8 @@ import com.trovebox.android.app.util.CommonUtils; import com.trovebox.android.app.util.GuiUtils; import com.trovebox.android.app.util.LoginUtils; +import com.trovebox.android.app.util.LoginUtils.LoginActionHandler; +import com.trovebox.android.app.util.ObjectAccessor; import com.trovebox.android.app.util.TrackerUtils; /** @@ -36,16 +37,26 @@ * * @author Eugene Popovich */ -public class GoogleLoginFragment extends CommonRetainedFragmentWithTaskAndProgress { +public class GoogleLoginFragment extends CommonRetainedFragmentWithTaskAndProgress implements + LoginActionHandler { private static final String TAG = GoogleLoginFragment.class.getSimpleName(); public static final String SCOPE = "audience:server:client_id:" + CommonUtils.getStringResource(R.string.google_auth_server_client_id); - int requestCode; - boolean delayedLoggedIn = false; + static WeakReference sCurrentInstance; + static ObjectAccessor sCurrentInstanceAccessor = new ObjectAccessor() { + private static final long serialVersionUID = 1L; - static WeakReference currentInstance; + @Override + public GoogleLoginFragment run() { + return sCurrentInstance == null ? null : sCurrentInstance.get(); + } + }; + + int mRequestCode; + boolean mDelayedLoginProcessing = false; + AccountTroveboxResponse mLastResponse; /** * Empty constructor as per the Fragment documentation @@ -57,19 +68,19 @@ public GoogleLoginFragment() { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - currentInstance = new WeakReference(this); + sCurrentInstance = new WeakReference(this); } @Override public void onDestroy() { super.onDestroy(); - if (currentInstance != null) + if (sCurrentInstance != null) { - if (currentInstance.get() == GoogleLoginFragment.this - || currentInstance.get() == null) + if (sCurrentInstance.get() == GoogleLoginFragment.this + || sCurrentInstance.get() == null) { CommonUtils.debug(TAG, "Nullify current instance"); - currentInstance = null; + sCurrentInstance = null; } else { CommonUtils.debug(TAG, @@ -78,25 +89,8 @@ public void onDestroy() { } } - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - if (delayedLoggedIn) - { - delayedLoggedIn = false; - onLoggedIn(getActivity()); - } - } - - void onLoggedIn(Activity activity) { - // start new activity - startActivity(new Intent(activity, - MainActivity.class)); - LoginUtils.sendLoggedInBroadcast(activity); - } - public void doLogin(int requestCode) { - this.requestCode = requestCode; + this.mRequestCode = requestCode; int availabilityResult = GooglePlayServicesUtil .isGooglePlayServicesAvailable(getActivity()); if (availabilityResult == ConnectionResult.SUCCESS) @@ -134,7 +128,7 @@ public void showGooglePlayErrorDialog(int availabilityResult) { android.app.Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog( availabilityResult, getActivity(), - requestCode); + mRequestCode); errorDialog.show(); } catch (Exception ex) { @@ -188,6 +182,25 @@ public String getLoadingMessage() { return CommonUtils.getStringResource(R.string.logging_in_message); } + @Override + public void processLoginCredentials(com.trovebox.android.app.model.Credentials credentials) { + Activity activity = getSupportActivity(); + credentials.saveCredentials(activity); + LoginUtils.onLoggedIn(activity, true); + } + + void processLoginResonse(Activity activity) { + LoginUtils.processSuccessfulLoginResult(mLastResponse, sCurrentInstanceAccessor, activity); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + if (mDelayedLoginProcessing) { + mDelayedLoginProcessing = false; + processLoginResonse(getSupportActivity()); + } + } private class LogInUserTask extends RetainedTask { AccountTroveboxResponse result; @@ -199,17 +212,17 @@ public LogInUserTask(String accountName) { @Override protected void onSuccessPostExecuteAdditional() { - // save credentials. - result.saveCredentials(TroveboxApplication.getContext()); - - Activity activity = getActivity(); - if (activity != null) - { - onLoggedIn(activity); - } else - { - TrackerUtils.trackErrorEvent("activity_null", TAG); - delayedLoggedIn = true; + try { + mLastResponse = result; + Activity activity = getSupportActivity(); + if (activity != null) { + processLoginResonse(activity); + } else { + TrackerUtils.trackErrorEvent("activity_null", TAG); + mDelayedLoginProcessing = true; + } + } catch (Exception e) { + GuiUtils.error(TAG, e); } } @@ -238,7 +251,7 @@ protected Boolean doInBackground(Void... params) { */ protected String fetchToken() throws IOException { try { - Activity activity = getActivity(); + Activity activity = getSupportActivity(); if (activity == null || activity.isFinishing()) { return null; @@ -258,7 +271,7 @@ public void run() { } catch (UserRecoverableAuthException userRecoverableException) { // Unable to authenticate, but the user can fix this. // Forward the user to the appropriate activity. - startActivityForResult(userRecoverableException.getIntent(), requestCode); + startActivityForResult(userRecoverableException.getIntent(), mRequestCode); } catch (GoogleAuthException fatalException) { GuiUtils.error(TAG, CommonUtils.getStringResource( R.string.errorCouldNotFetchGoogleAccountToken, @@ -279,7 +292,7 @@ public SelectAccountSelectedActionHandler(String[] accountNames) { @Override public void itemSelected(int i) { - currentInstance.get().performLoginAction(getItems()[i]); + sCurrentInstance.get().performLoginAction(getItems()[i]); } @Override diff --git a/app/src/com/trovebox/android/app/SetupActivity.java b/app/src/com/trovebox/android/app/SetupActivity.java index bb257f9..984e966 100644 --- a/app/src/com/trovebox/android/app/SetupActivity.java +++ b/app/src/com/trovebox/android/app/SetupActivity.java @@ -5,15 +5,6 @@ import org.holoeverywhere.LayoutInflater; import org.holoeverywhere.app.ProgressDialog; -import com.trovebox.android.app.R; -import com.trovebox.android.app.common.CommonActivity; -import com.trovebox.android.app.common.CommonFragment; -import com.trovebox.android.app.oauth.OAuthUtils; -import com.trovebox.android.app.util.GuiUtils; -import com.trovebox.android.app.util.ProgressDialogLoadingControl; -import com.trovebox.android.app.util.TrackerUtils; -import com.trovebox.android.app.util.regex.Patterns; - import android.content.Intent; import android.net.Uri; import android.os.Bundle; @@ -24,6 +15,14 @@ import android.widget.Button; import android.widget.EditText; +import com.trovebox.android.app.common.CommonActivity; +import com.trovebox.android.app.common.CommonFragment; +import com.trovebox.android.app.oauth.OAuthUtils; +import com.trovebox.android.app.util.GuiUtils; +import com.trovebox.android.app.util.ProgressDialogLoadingControl; +import com.trovebox.android.app.util.TrackerUtils; +import com.trovebox.android.app.util.regex.Patterns; + /** * The activity that gets presented to the user in case the user is not logged * in to a server. - setup screen @@ -110,7 +109,7 @@ public void onClick(View v) } else { Preferences.setServer(getActivity(), server); - OAuthUtils.askOAuth(getActivity()); + OAuthUtils.askOAuth(getSupportActivity()); } break; } diff --git a/app/src/com/trovebox/android/app/model/Credentials.java b/app/src/com/trovebox/android/app/model/Credentials.java new file mode 100644 index 0000000..765cbb6 --- /dev/null +++ b/app/src/com/trovebox/android/app/model/Credentials.java @@ -0,0 +1,145 @@ +package com.trovebox.android.app.model; + +import org.json.JSONException; +import org.json.JSONObject; + +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; + +import com.trovebox.android.app.Preferences; +import com.trovebox.android.app.R; + +/** + * Class representing login credentials for Trovebox. + * + * @author Eugene Popovich + */ +public class Credentials implements Parcelable { + private String mServer; + private String mOAuthConsumerKey; + private String mOAuthConsumerSecret; + private String mOAuthToken; + private String mOAuthTokenSecret; + private String mEmail; + + private Credentials() { + } + + public Credentials(JSONObject json) throws JSONException { + mServer = "http://" + json.getString("host"); + mOAuthConsumerKey = json.getString("id"); + mOAuthConsumerSecret = json.getString("clientSecret"); + mOAuthToken = json.getString("userToken"); + mOAuthTokenSecret = json.getString("userSecret"); + mEmail = json.getString("owner"); + } + + public String getServer() { + return mServer; + } + + public void setServer(String server) { + this.mServer = server; + } + + public String getoAuthConsumerKey() { + return mOAuthConsumerKey; + } + + public void setoAuthConsumerKey(String oAuthConsumerKey) { + this.mOAuthConsumerKey = oAuthConsumerKey; + } + + public String getoAuthConsumerSecret() { + return mOAuthConsumerSecret; + } + + public void setoAuthConsumerSecret(String oAuthConsumerSecret) { + this.mOAuthConsumerSecret = oAuthConsumerSecret; + } + + public String getoAuthToken() { + return mOAuthToken; + } + + public void setoAuthToken(String oAuthToken) { + this.mOAuthToken = oAuthToken; + } + + public String getoAuthTokenSecret() { + return mOAuthTokenSecret; + } + + public void setoAuthTokenSecret(String oAuthTokenSecret) { + this.mOAuthTokenSecret = oAuthTokenSecret; + } + + public String getEmail() { + return mEmail; + } + + public void setEmail(String email) { + this.mEmail = email; + } + + public void saveCredentials(Context context) { + Preferences.setServer(context, this.getServer()); + + Preferences.getDefaultSharedPreferences(context).edit() + .putBoolean(context.getString(R.string.setting_account_loggedin_key), true) + .commit(); + + Preferences + .getSharedPreferences("oauth") + .edit() + .putString(context.getString(R.string.setting_oauth_consumer_key), + this.getoAuthConsumerKey()) + .putString(context.getString(R.string.setting_oauth_consumer_secret), + this.getoAuthConsumerSecret()) + .putString(context.getString(R.string.setting_oauth_token), + this.getoAuthToken()) + .putString(context.getString(R.string.setting_oauth_token_secret), + this.getoAuthTokenSecret()).commit(); + } + + /***************************** + * PARCELABLE IMPLEMENTATION * + *****************************/ + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeString(mServer); + out.writeString(mEmail); + out.writeString(mOAuthConsumerKey); + out.writeString(mOAuthConsumerSecret); + out.writeString(mOAuthToken); + out.writeString(mOAuthTokenSecret); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public Credentials createFromParcel(Parcel in) { + return new Credentials(in); + } + + @Override + public Credentials[] newArray(int size) { + return new Credentials[size]; + } + }; + + private Credentials(Parcel in) { + this(); + mServer = in.readString(); + mEmail = in.readString(); + mOAuthConsumerKey = in.readString(); + mOAuthConsumerSecret = in.readString(); + mOAuthToken = in.readString(); + mOAuthTokenSecret = in.readString(); + } +} \ No newline at end of file diff --git a/app/src/com/trovebox/android/app/net/account/AccountTroveboxResponse.java b/app/src/com/trovebox/android/app/net/account/AccountTroveboxResponse.java index 6ca4453..bec6be4 100644 --- a/app/src/com/trovebox/android/app/net/account/AccountTroveboxResponse.java +++ b/app/src/com/trovebox/android/app/net/account/AccountTroveboxResponse.java @@ -2,14 +2,13 @@ package com.trovebox.android.app.net.account; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import android.content.Context; - -import com.trovebox.android.app.Preferences; import com.trovebox.android.app.R; import com.trovebox.android.app.TroveboxApplication; +import com.trovebox.android.app.model.Credentials; import com.trovebox.android.app.net.TroveboxResponse; /** @@ -19,97 +18,25 @@ public class AccountTroveboxResponse extends TroveboxResponse { public static int SUCCESSFUL_CODE = 200; public static int INVALID_CREDENTIALS_CODE = 403; public static int UNKNOWN_ERROR_CODE = 500; - private String server; - private String oAuthConsumerKey; - private String oAuthConsumerSecret; - private String oAuthToken; - private String oAuthTokenSecret; - private String email; - - public String getServer() { - return server; - } - - public void setServer(String server) { - this.server = server; - } - - public String getoAuthConsumerKey() { - return oAuthConsumerKey; - } - - public void setoAuthConsumerKey(String oAuthConsumerKey) { - this.oAuthConsumerKey = oAuthConsumerKey; - } - - public String getoAuthConsumerSecret() { - return oAuthConsumerSecret; - } - - public void setoAuthConsumerSecret(String oAuthConsumerSecret) { - this.oAuthConsumerSecret = oAuthConsumerSecret; - } - - public String getoAuthToken() { - return oAuthToken; - } - - public void setoAuthToken(String oAuthToken) { - this.oAuthToken = oAuthToken; - } - - public String getoAuthTokenSecret() { - return oAuthTokenSecret; - } - - public void setoAuthTokenSecret(String oAuthTokenSecret) { - this.oAuthTokenSecret = oAuthTokenSecret; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } + private Credentials[] mCredentials; public AccountTroveboxResponse(RequestType requestType, JSONObject json) throws JSONException { super(requestType, json); - if (isSuccess() && json.get("result") instanceof JSONObject) { - JSONObject result = json.getJSONObject("result"); - server = "http://" + result.getString("host"); - oAuthConsumerKey = result.getString("id"); - oAuthConsumerSecret = result.getString("clientSecret"); - oAuthToken = result.getString("userToken"); - oAuthTokenSecret = result.getString("userSecret"); - email = result.getString("owner"); + if (isSuccess()) { + if (json.get("result") instanceof JSONArray) { + JSONArray array = json.getJSONArray("result"); + mCredentials = new Credentials[array.length()]; + for (int i = 0; i < mCredentials.length; i++) { + mCredentials[i] = new Credentials(array.getJSONObject(i)); + } + } else if (json.get("result") instanceof JSONObject) { + JSONObject result = json.getJSONObject("result"); + mCredentials = new Credentials[1]; + mCredentials[0] = new Credentials(result); + } } } - public void saveCredentials(Context context) { - Preferences.setServer(context, this.getServer()); - - Preferences - .getDefaultSharedPreferences(context) - .edit() - .putBoolean(context.getString(R.string.setting_account_loggedin_key), true) - .commit(); - - Preferences - .getSharedPreferences("oauth") - .edit() - .putString(context.getString(R.string.setting_oauth_consumer_key), - this.getoAuthConsumerKey()) - .putString(context.getString(R.string.setting_oauth_consumer_secret), - this.getoAuthConsumerSecret()) - .putString(context.getString(R.string.setting_oauth_token), - this.getoAuthToken()) - .putString(context.getString(R.string.setting_oauth_token_secret), - this.getoAuthTokenSecret()) - .commit(); - } - @Override public boolean isSuccess() { @@ -136,4 +63,8 @@ public String getAlertMessage() { return super.getAlertMessage(); } } + + public Credentials[] getCredentials() { + return mCredentials; + } } diff --git a/app/src/com/trovebox/android/app/oauth/OAuthUtils.java b/app/src/com/trovebox/android/app/oauth/OAuthUtils.java index a349d47..94a09b4 100644 --- a/app/src/com/trovebox/android/app/oauth/OAuthUtils.java +++ b/app/src/com/trovebox/android/app/oauth/OAuthUtils.java @@ -6,7 +6,9 @@ import oauth.signpost.OAuthConsumer; import oauth.signpost.OAuthProvider; import oauth.signpost.basic.DefaultOAuthConsumer; -import android.app.Activity; + +import org.holoeverywhere.app.Activity; + import android.content.Context; import android.content.Intent; import android.net.Uri; diff --git a/app/src/com/trovebox/android/app/util/LoginUtils.java b/app/src/com/trovebox/android/app/util/LoginUtils.java index 05b8d39..7bbff1b 100644 --- a/app/src/com/trovebox/android/app/util/LoginUtils.java +++ b/app/src/com/trovebox/android/app/util/LoginUtils.java @@ -2,14 +2,27 @@ package com.trovebox.android.app.util; -import android.app.Activity; +import org.holoeverywhere.app.Activity; +import org.holoeverywhere.app.AlertDialog; +import org.holoeverywhere.app.Dialog; + import android.content.BroadcastReceiver; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; +import android.os.Bundle; + +import com.trovebox.android.app.MainActivity; +import com.trovebox.android.app.R; +import com.trovebox.android.app.common.CommonDialogFragment; +import com.trovebox.android.app.model.Credentials; +import com.trovebox.android.app.net.account.AccountTroveboxResponse; public class LoginUtils { + public static final String TAG = LoginUtils.class.getSimpleName(); + public static String LOGIN_ACTION = "com.trovebox.ACTION_LOGIN"; public static BroadcastReceiver getAndRegisterDestroyOnLoginActionBroadcastReceiver( @@ -35,4 +48,155 @@ public static void sendLoggedInBroadcast(Activity activity) activity.sendBroadcast(intent); } + /** + * Should be called by external activities/fragments after the logged in + * action completed + * + * @param activity + * @param finishActivity whether to finish activity after the MainActivity + * started + */ + public static void onLoggedIn(Activity activity, boolean finishActivity) { + // start new activity + activity.startActivity(new Intent(activity, MainActivity.class)); + LoginUtils.sendLoggedInBroadcast(activity); + if (finishActivity) { + activity.finish(); + } + } + + /** + * Common successful AccountTroveboxResult processor + * + * @param result + * @param fragmentAccessor + * @param activity + */ + public static void processSuccessfulLoginResult(AccountTroveboxResponse result, + ObjectAccessor fragmentAccessor, Activity activity) { + Credentials[] credentials = result.getCredentials(); + if (credentials.length == 1) { + CommonUtils.debug(TAG, "processSuccessfulLoginResult: found one login credentials"); + performLogin(fragmentAccessor, credentials[0]); + } else { + CommonUtils + .debug(TAG, "processSuccessfulLoginResult: found multiple login credentials"); + SelectAccountDialogFragment selectionFragment = SelectAccountDialogFragment + .newInstance(new SelectAccountSelectedActionHandler(credentials, + fragmentAccessor)); + selectionFragment.show(activity); + } + } + + private static void performLogin( + ObjectAccessor loginActionHandlerAccessor, + Credentials credentials) { + LoginActionHandler handler = loginActionHandlerAccessor.run(); + if (handler != null) { + handler.processLoginCredentials(credentials); + } else { + String error = "Current instance accessor returned null"; + CommonUtils.error(TAG, error); + TrackerUtils.trackException(error); + } + } + + public static class SelectAccountSelectedActionHandler implements + SelectAccountDialogFragment.SelectedActionHandler { + + ObjectAccessor mLoginActionHandlerAccessor; + Credentials[] mCredentials; + + public SelectAccountSelectedActionHandler(Credentials[] credentials, + ObjectAccessor loginActoinHandlerAccessor) { + this.mCredentials = credentials; + this.mLoginActionHandlerAccessor = loginActoinHandlerAccessor; + } + + @Override + public void itemSelected(int i) { + performLogin(mLoginActionHandlerAccessor, getItems()[i]); + } + + @Override + public Credentials[] getItems() { + return mCredentials; + } + + @Override + public ObjectAccessor getLoginActionHandlerAccessor() { + return mLoginActionHandlerAccessor; + } + } + + /** + * The interface the login/signup fragments should implement to use + * processSuccessfulLoginResult method + */ + public static interface LoginActionHandler + { + void processLoginCredentials(Credentials credentials); + } + + /** + * The dialog fragment which shows list of retrieved accounts where user can + * select one to login + */ + public static class SelectAccountDialogFragment extends CommonDialogFragment { + public static final String HANDLER_ITEMS = "SelectAccountDialogFragment.handlerItems"; + public static final String LOGIN_HANDLER_ACCESSOR = "SelectAccountDialogFragment.loginHandlerAccessor"; + + public static interface SelectedActionHandler { + void itemSelected(int i); + + Credentials[] getItems(); + + ObjectAccessor getLoginActionHandlerAccessor(); + } + + private SelectedActionHandler handler; + + public static SelectAccountDialogFragment newInstance(SelectedActionHandler handler) { + SelectAccountDialogFragment frag = new SelectAccountDialogFragment(); + frag.handler = handler; + return frag; + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putParcelableArray(HANDLER_ITEMS, handler.getItems()); + outState.putSerializable(LOGIN_HANDLER_ACCESSOR, handler.getLoginActionHandlerAccessor()); + } + + @SuppressWarnings("unchecked") + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + if (savedInstanceState != null) { + handler = new SelectAccountSelectedActionHandler( + (Credentials[]) savedInstanceState.getParcelableArray(HANDLER_ITEMS), + (ObjectAccessor) savedInstanceState + .getSerializable(LOGIN_HANDLER_ACCESSOR)); + } + + final String[] items = new String[handler.getItems().length]; + for (int i = 0; i < items.length; i++) { + items[i] = handler.getItems()[i].getEmail(); + } + + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle(R.string.select_trovebox_account); + builder.setItems(items, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int item) { + if (handler == null) { + return; + } + handler.itemSelected(item); + } + }); + return builder.create(); + } + } + } diff --git a/test/res/raw/json_credentials.txt b/test/res/raw/json_credentials.txt new file mode 100644 index 0000000..7354804 --- /dev/null +++ b/test/res/raw/json_credentials.txt @@ -0,0 +1,7 @@ +{ "clientSecret" : "0f5d654bca", + "host" : "apigee.trovebox.com", + "id" : "102230629a6802fbca9825a4617bfe", + "owner" : "hello@trovebox.com", + "userSecret" : "6d1e8fc274", + "userToken" : "b662440d621f2f71352f8865888fe2" +} \ No newline at end of file diff --git a/test/res/raw/json_login_multiple.txt b/test/res/raw/json_login_multiple.txt new file mode 100644 index 0000000..840c853 --- /dev/null +++ b/test/res/raw/json_login_multiple.txt @@ -0,0 +1,18 @@ +{ "code" : 200, + "message" : "User credentials", + "result" : [ { "clientSecret" : "0f5d654bca", + "host" : "apigee.trovebox.com", + "id" : "102230629a6802fbca9825a4617bfe", + "owner" : "hello@trovebox.com", + "userSecret" : "6d1e8fc274", + "userToken" : "b662440d621f2f71352f8865888fe2" + }, + { "clientSecret" : "0f5d654bca", + "host" : "apigee.trovebox.com", + "id" : "102230629a6802fbca9825a4617bfe", + "owner" : "hello2@trovebox.com", + "userSecret" : "6d1e8fc274", + "userToken" : "b662440d621f2f71352f8865888fe2" + } + ] +} \ No newline at end of file diff --git a/test/res/raw/json_login_simple.txt b/test/res/raw/json_login_simple.txt new file mode 100644 index 0000000..a588ee8 --- /dev/null +++ b/test/res/raw/json_login_simple.txt @@ -0,0 +1,10 @@ +{ "code" : 200, + "message" : "User credentials", + "result" : { "clientSecret" : "0f5d654bca", + "host" : "apigee.trovebox.com", + "id" : "102230629a6802fbca9825a4617bfe", + "owner" : "hello@trovebox.com", + "userSecret" : "6d1e8fc274", + "userToken" : "b662440d621f2f71352f8865888fe2" + } +} \ No newline at end of file diff --git a/test/src/com/trovebox/android/test/model/CredentialsTest.java b/test/src/com/trovebox/android/test/model/CredentialsTest.java new file mode 100644 index 0000000..2f270e0 --- /dev/null +++ b/test/src/com/trovebox/android/test/model/CredentialsTest.java @@ -0,0 +1,62 @@ + +package com.trovebox.android.test.model; + +import org.json.JSONException; +import org.json.JSONObject; + +import android.os.Parcel; +import android.test.InstrumentationTestCase; + +import com.trovebox.android.app.model.Credentials; +import com.trovebox.android.test.R; +import com.trovebox.android.test.net.JSONUtils; + +public class CredentialsTest extends InstrumentationTestCase { + public void testFromJson() { + Credentials c; + try { + JSONObject json = JSONUtils +.getJson(getInstrumentation().getContext(), + R.raw.json_credentials); + c = new Credentials(json); + } catch (JSONException e) { + throw new AssertionError("This exception should not be thrown!"); + } + + checkCredentials(c, "hello@trovebox.com"); + } + + public void testCredentialsParcelable() { + Credentials c; + try { + JSONObject json = JSONUtils +.getJson(getInstrumentation().getContext(), + R.raw.json_credentials); + c = new Credentials(json); + } catch (JSONException e) { + throw new AssertionError("This exception should not be thrown!"); + } + + checkCredentials(c, "hello@trovebox.com"); + + Parcel parcel = Parcel.obtain(); + c.writeToParcel(parcel, 0); + // done writing, now reset parcel for reading + parcel.setDataPosition(0); + // finish round trip + Credentials createFromParcel = Credentials.CREATOR.createFromParcel(parcel); + + checkCredentials(createFromParcel, "hello@trovebox.com"); + } + + public static void checkCredentials(Credentials c, String email) { + assertNotNull(c); + assertEquals(c.getEmail(), email); + assertEquals(c.getServer(), "http://apigee.trovebox.com"); + assertEquals(c.getoAuthConsumerKey(), "102230629a6802fbca9825a4617bfe"); + assertEquals(c.getoAuthConsumerSecret(), "0f5d654bca"); + assertEquals(c.getoAuthToken(), "b662440d621f2f71352f8865888fe2"); + assertEquals(c.getoAuthTokenSecret(), "6d1e8fc274"); + + } +} diff --git a/test/src/com/trovebox/android/test/net/account/AccountTroveboxApiTest.java b/test/src/com/trovebox/android/test/net/account/AccountTroveboxApiTest.java index de4f19f..6338cbc 100644 --- a/test/src/com/trovebox/android/test/net/account/AccountTroveboxApiTest.java +++ b/test/src/com/trovebox/android/test/net/account/AccountTroveboxApiTest.java @@ -21,6 +21,7 @@ import com.google.api.client.json.gson.GsonFactory; import com.trovebox.android.app.R; import com.trovebox.android.app.TroveboxApplication; +import com.trovebox.android.app.model.Credentials; import com.trovebox.android.app.net.account.AccountTroveboxResponse; import com.trovebox.android.app.net.account.IAccountTroveboxApi; import com.trovebox.android.app.net.account.IAccountTroveboxApiFactory; @@ -87,10 +88,14 @@ public void testSignInViaGoogle() throws ClientProtocolException, IllegalStateEx tokenString); assertNotNull(response); assertTrue(response.isSuccess()); - checkoAuthString(response.getoAuthConsumerKey()); - checkoAuthString(response.getoAuthConsumerSecret()); - checkoAuthString(response.getoAuthToken()); - checkoAuthString(response.getoAuthConsumerSecret()); + Credentials[] credentials = response.getCredentials(); + assertNotNull(credentials); + assertTrue(credentials.length > 0); + Credentials c = credentials[0]; + checkoAuthString(c.getoAuthConsumerKey()); + checkoAuthString(c.getoAuthConsumerSecret()); + checkoAuthString(c.getoAuthToken()); + checkoAuthString(c.getoAuthConsumerSecret()); } diff --git a/test/src/com/trovebox/android/test/net/account/AccountTroveboxResponseTest.java b/test/src/com/trovebox/android/test/net/account/AccountTroveboxResponseTest.java new file mode 100644 index 0000000..72e99ca --- /dev/null +++ b/test/src/com/trovebox/android/test/net/account/AccountTroveboxResponseTest.java @@ -0,0 +1,41 @@ +package com.trovebox.android.test.net.account; + +import org.json.JSONException; +import org.json.JSONObject; + +import android.test.InstrumentationTestCase; + +import com.trovebox.android.app.model.Credentials; +import com.trovebox.android.app.net.TroveboxResponse.RequestType; +import com.trovebox.android.app.net.account.AccountTroveboxResponse; +import com.trovebox.android.test.R; +import com.trovebox.android.test.model.CredentialsTest; +import com.trovebox.android.test.net.JSONUtils; + +public class AccountTroveboxResponseTest extends InstrumentationTestCase { + + public void testSimpleResponse() throws JSONException { + JSONObject json = JSONUtils.getJson(getInstrumentation().getContext(), + R.raw.json_login_simple); + AccountTroveboxResponse response = new AccountTroveboxResponse(RequestType.UNKNOWN, json); + assertNotNull(response); + assertEquals(200, response.getCode()); + Credentials[] credentials = response.getCredentials(); + assertNotNull(credentials); + assertTrue(credentials.length == 1); + CredentialsTest.checkCredentials(credentials[0], "hello@trovebox.com"); + } + + public void testMultiResponse() throws JSONException { + JSONObject json = JSONUtils.getJson(getInstrumentation().getContext(), + R.raw.json_login_multiple); + AccountTroveboxResponse response = new AccountTroveboxResponse(RequestType.UNKNOWN, json); + assertNotNull(response); + assertEquals(200, response.getCode()); + Credentials[] credentials = response.getCredentials(); + assertNotNull(credentials); + assertTrue(credentials.length == 2); + CredentialsTest.checkCredentials(credentials[0], "hello@trovebox.com"); + CredentialsTest.checkCredentials(credentials[1], "hello2@trovebox.com"); + } +}