package cgeo.geocaching.activity;
import cgeo.geocaching.Intents;
import cgeo.geocaching.R;
import cgeo.geocaching.network.Network;
import cgeo.geocaching.network.Parameters;
import cgeo.geocaching.ui.WeakReferenceHandler;
import cgeo.geocaching.ui.dialog.Dialogs;
import cgeo.geocaching.utils.AndroidRxUtils;
import cgeo.geocaching.utils.BundleUtils;
import cgeo.geocaching.utils.Log;
import cgeo.geocaching.utils.MatcherWrapper;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.NonNull;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import java.util.regex.Pattern;
import butterknife.BindView;
import okhttp3.Response;
import org.apache.commons.lang3.StringUtils;
public abstract class TokenAuthorizationActivity extends AbstractActivity {
private static final Pattern PATTERN_IS_ERROR = Pattern.compile("error ([\\d]+)");
private static final Pattern PATTERN_TOKEN = Pattern.compile("([\\w]+)");
public static final int NOT_AUTHENTICATED = 0;
public static final int AUTHENTICATED = 1;
private static final int ERROR_EXT_MSG = 2;
@NonNull private String urlToken = StringUtils.EMPTY;
@NonNull private String fieldUsername = StringUtils.EMPTY;
@NonNull private String fieldPassword = StringUtils.EMPTY;
@BindView(R.id.start) protected Button startButton;
@BindView(R.id.register) protected Button registerButton;
@BindView(R.id.auth_1) protected TextView auth1;
@BindView(R.id.auth_2) protected TextView auth2;
@BindView(R.id.username) protected EditText usernameEditText;
@BindView(R.id.password) protected EditText passwordEditText;
private ProgressDialog requestTokenDialog = null;
protected final Handler requestTokenHandler = new RequestTokenHandler(this);
private static final class RequestTokenHandler extends WeakReferenceHandler<TokenAuthorizationActivity> {
RequestTokenHandler(final TokenAuthorizationActivity activity) {
super(activity);
}
@Override
public void handleMessage(final Message msg) {
final TokenAuthorizationActivity activity = getReference();
if (activity != null) {
Dialogs.dismiss(activity.requestTokenDialog);
final Button startButton = activity.startButton;
startButton.setOnClickListener(new StartListener(activity));
startButton.setEnabled(true);
if (msg.what == AUTHENTICATED) {
activity.showToast(activity.getAuthDialogCompleted());
activity.setResult(RESULT_OK);
activity.finish();
} else if (msg.what == ERROR_EXT_MSG) {
String errMsg = activity.getErrAuthInitialize();
errMsg += msg.obj != null ? '\n' + msg.obj.toString() : "";
activity.showToast(errMsg);
startButton.setText(activity.getAuthStart());
} else {
activity.showToast(activity.getErrAuthProcess());
startButton.setText(activity.getAuthStart());
}
}
}
}
@Override
public void onCreate(final Bundle savedInstanceState) {
onCreate(savedInstanceState, R.layout.authorization_token_activity);
final Bundle extras = getIntent().getExtras();
if (extras != null) {
urlToken = BundleUtils.getString(extras, Intents.EXTRA_TOKEN_AUTH_URL_TOKEN, urlToken);
fieldUsername = BundleUtils.getString(extras, Intents.EXTRA_TOKEN_AUTH_USERNAME, fieldUsername);
fieldPassword = BundleUtils.getString(extras, Intents.EXTRA_TOKEN_AUTH_PASSWORD, fieldPassword);
}
setTitle(getAuthTitle());
auth1.setText(getAuthExplainShort());
auth2.setText(getAuthExplainLong());
startButton.setText(getAuthAuthorize());
startButton.setOnClickListener(new StartListener(this));
enableStartButtonIfReady();
if (StringUtils.isEmpty(getCreateAccountUrl())) {
registerButton.setVisibility(View.GONE);
} else {
registerButton.setText(getAuthRegister());
registerButton.setEnabled(true);
registerButton.setOnClickListener(new RegisterListener());
}
startButton.setText(StringUtils.isBlank(getToken()) ? getAuthStart() : getAuthAgain());
final EnableStartButtonWatcher enableStartButtonWatcher = new EnableStartButtonWatcher();
usernameEditText.addTextChangedListener(enableStartButtonWatcher);
passwordEditText.addTextChangedListener(enableStartButtonWatcher);
}
@Override
public void onNewIntent(final Intent intent) {
setIntent(intent);
}
protected void requestToken(final String username, final String password) {
if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
ActivityMixin.showToast(this, R.string.err_missing_auth);
requestTokenHandler.sendEmptyMessage(NOT_AUTHENTICATED);
return;
}
final String nam = StringUtils.defaultString(username);
final String pwd = StringUtils.defaultString(password);
final Parameters params = new Parameters(fieldUsername, nam, fieldPassword, pwd);
int status = NOT_AUTHENTICATED;
String message = StringUtils.EMPTY;
try {
final Response response = Network.postRequest(urlToken, params).blockingGet();
if (response.isSuccessful()) {
final String line = StringUtils.defaultString(Network.getResponseData(response));
final MatcherWrapper errorMatcher = new MatcherWrapper(getPatternIsError(), line);
final MatcherWrapper tokenMatcher = new MatcherWrapper(getPatternToken(), line);
if (errorMatcher.find()) {
status = ERROR_EXT_MSG;
message = getExtendedErrorMsg(errorMatcher.group(1));
} else if (tokenMatcher.find()) {
status = AUTHENTICATED;
setToken(tokenMatcher.group(1));
}
} else {
message = getExtendedErrorMsg(response);
}
} catch (final Exception e) {
Log.e("TokenAuthorizationActivity:", e);
}
if (StringUtils.isNotBlank(message)) {
final Message msg = requestTokenHandler.obtainMessage(status, message);
requestTokenHandler.sendMessage(msg);
} else {
requestTokenHandler.sendEmptyMessage(status);
}
}
private static class StartListener extends WeakReferenceHandler<TokenAuthorizationActivity> implements View.OnClickListener {
StartListener(final TokenAuthorizationActivity activity) {
super(activity);
}
@Override
public void onClick(final View view) {
final TokenAuthorizationActivity activity = getReference();
if (activity != null) {
if (activity.requestTokenDialog == null) {
activity.requestTokenDialog = new ProgressDialog(activity);
activity.requestTokenDialog.setCancelable(false);
activity.requestTokenDialog.setMessage(activity.getAuthDialogWait());
}
activity.requestTokenDialog.show();
final Button startButton = activity.startButton;
startButton.setEnabled(false);
startButton.setOnTouchListener(null);
startButton.setOnClickListener(null);
final String username = activity.usernameEditText.getText().toString();
final String password = activity.passwordEditText.getText().toString();
AndroidRxUtils.networkScheduler.scheduleDirect(new Runnable() {
@Override
public void run() {
activity.requestToken(username, password);
}
});
}
}
}
private class RegisterListener implements View.OnClickListener {
@Override
public void onClick(final View view) {
final Activity activity = TokenAuthorizationActivity.this;
try {
activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(getCreateAccountUrl())));
} catch (final ActivityNotFoundException e) {
Log.e("Cannot find suitable activity", e);
ActivityMixin.showToast(activity, R.string.err_application_no);
}
}
}
// get resources from derived class
protected abstract String getCreateAccountUrl();
protected abstract void setToken(String token);
protected abstract String getToken();
protected abstract String getAuthTitle();
protected Pattern getPatternIsError() {
return PATTERN_IS_ERROR;
}
protected Pattern getPatternToken() {
return PATTERN_TOKEN;
}
protected String getAuthAgain() {
return getString(R.string.auth_again);
}
protected String getErrAuthInitialize() {
return getString(R.string.err_auth_initialize);
}
protected String getAuthStart() {
return getString(R.string.auth_start);
}
protected abstract String getAuthDialogCompleted();
protected String getErrAuthProcess() {
return res.getString(R.string.err_auth_process);
}
/**
* Allows deriving classes to check the response for error messages specific to their Token implementation
*
* @param response
* The error response of the token request
* @return String with a more detailed error message (user-facing, localized), can be empty
*/
protected String getExtendedErrorMsg(final Response response) {
return StringUtils.EMPTY;
}
protected String getExtendedErrorMsg(@SuppressWarnings("unused") final String response) {
return StringUtils.EMPTY;
}
protected String getAuthDialogWait() {
return res.getString(R.string.auth_dialog_waiting, getAuthTitle());
}
protected String getAuthExplainShort() {
return res.getString(R.string.auth_token_explain_short, getAuthTitle());
}
protected String getAuthExplainLong() {
return res.getString(R.string.auth_token_explain_long, getAuthTitle());
}
protected String getAuthAuthorize() {
return res.getString(R.string.auth_authorize);
}
protected String getAuthRegister() {
return res.getString(R.string.auth_register);
}
/**
* Enable or disable the start button depending on login/password field.
* If both fields are not empty, button is enabled.
*
*/
protected void enableStartButtonIfReady() {
startButton.setEnabled(StringUtils.isNotEmpty(usernameEditText.getText().toString()) &&
StringUtils.isNotEmpty(passwordEditText.getText().toString()));
}
/**
* A TextWatcher to monitor changes on usernameEditText and passwordEditText for enabling start button
* dynamically.
*/
private class EnableStartButtonWatcher implements TextWatcher {
@Override
public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
// empty
}
@Override
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
// empty
}
@Override
public void afterTextChanged(final Editable s) {
enableStartButtonIfReady();
}
}
public static class TokenAuthParameters {
@NonNull public final String urlToken;
@NonNull public final String fieldUsername;
@NonNull public final String fieldPassword;
public TokenAuthParameters(@NonNull final String urlToken,
@NonNull final String fieldUsername,
@NonNull final String fieldPassword) {
this.urlToken = urlToken;
this.fieldUsername = fieldUsername;
this.fieldPassword = fieldPassword;
}
public void setTokenAuthExtras(final Intent intent) {
if (intent != null) {
intent.putExtra(Intents.EXTRA_TOKEN_AUTH_URL_TOKEN, urlToken);
intent.putExtra(Intents.EXTRA_TOKEN_AUTH_USERNAME, fieldUsername);
intent.putExtra(Intents.EXTRA_TOKEN_AUTH_PASSWORD, fieldPassword);
}
}
}
@Override
public void finish() {
Dialogs.dismiss(requestTokenDialog);
super.finish();
}
}