/*
* Copyright 2014 Bevbot LLC <info@bevbot.com>
*
* This file is part of the Kegtab package from the Kegbot project. For
* more information on Kegtab or Kegbot, see <http://kegbot.org/>.
*
* Kegtab is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free
* Software Foundation, version 2.
*
* Kegtab is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with Kegtab. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kegbot.app;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.google.common.base.Strings;
import com.hoho.android.usbserial.util.HexDump;
import org.kegbot.app.config.AppConfiguration;
import org.kegbot.backend.BackendException;
import org.kegbot.core.AuthenticationToken;
import org.kegbot.core.KegbotCore;
import org.kegbot.proto.Models.KegTap;
import org.kegbot.proto.Models.User;
/**
* Activity shown while authenticating a user.
*
* @author mike wakerly (opensource@hoho.com)
*/
public class AuthenticatingActivity extends Activity {
private static final String TAG = AuthenticatingActivity.class.getSimpleName();
private static final long AUTO_FINISH_DELAY_MILLIS = 10000;
private static final int REQUEST_SELECT_USER_TO_BIND = 100;
private static final String EXTRA_USERNAME = "username";
private static final String EXTRA_AUTH_DEVICE = "auth_device";
private static final String EXTRA_TOKEN_VALUE = "token";
private static final String EXTRA_TAP_ID = "tap";
private static final String AUTH_TYPE_NFC = "nfc";
private AppConfiguration mConfig;
private ViewGroup mButtonGroup;
private TextView mBeginTitle;
private TextView mFailTitle;
private TextView mMessage;
private ProgressBar mProgressBar;
private Button mCancelButton;
private Button mAssignButton;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final Runnable mFinishRunnable = new Runnable() {
@Override
public void run() {
finish();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.authenticating_activity);
mBeginTitle = (TextView) findViewById(R.id.authenticatingBeginTitle);
mFailTitle = (TextView) findViewById(R.id.authenticatingFailTitle);
mMessage = (TextView) findViewById(R.id.authenticatingMessage);
mProgressBar = (ProgressBar) findViewById(R.id.authenticatingProgress);
mButtonGroup = (ViewGroup) findViewById(R.id.buttonGroup);
mCancelButton = (Button) findViewById(R.id.cancelButton);
mCancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
mAssignButton = (Button) findViewById(R.id.assignButton);
// getWindow().setBackgroundDrawable(new ColorDrawable(0));
handleIntent();
}
@Override
protected void onStart() {
super.onStart();
mConfig = ((KegbotApplication) getApplicationContext()).getConfig();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.d(TAG, "onNewIntent: " + intent);
setIntent(intent);
handleIntent();
}
private void handleIntent() {
final Intent intent = getIntent();
Log.d(TAG, "Handling intent: " + intent);
setAuthenticating();
if (intent.hasExtra(NfcAdapter.EXTRA_TAG)) {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
byte[] id = tag.getId();
if (id != null && id.length > 0) {
String tagId = HexDump.toHexString(id).toLowerCase();
Log.d(TAG, "Read NFC tag with id: " + tagId);
// TODO: use tag technology as part of id?
intent.putExtra(EXTRA_TOKEN_VALUE, tagId); // needed by onActivityResult
authenticateTokenAsync(AUTH_TYPE_NFC, tagId);
} else {
setFail("Unknown NFC tag.");
}
} else if (intent.hasExtra(EXTRA_USERNAME)) {
final String username = intent.getStringExtra(EXTRA_USERNAME);
if (username.isEmpty()) {
activateUser(username);
finish();
} else {
authenticateUsernameAsync(intent.getStringExtra(EXTRA_USERNAME));
}
} else if (intent.hasExtra(EXTRA_AUTH_DEVICE)) {
authenticateTokenAsync(intent.getStringExtra(EXTRA_AUTH_DEVICE),
intent.getStringExtra(EXTRA_TOKEN_VALUE));
} else {
Log.e(TAG, "Unknown start intent. Aborting.");
finish();
}
}
/**
* Processes a launch request from {@link #startAndAuthenticate(android.content.Context, String,
* String)}.
*
* @param username
*/
private void authenticateUsernameAsync(final String username) {
new AsyncTask<Void, Void, User>() {
@Override
protected User doInBackground(Void... params) {
final KegbotCore core = KegbotCore.getInstance(AuthenticatingActivity.this);
return core.getAuthenticationManager().authenticateUsername(username);
}
@Override
protected void onPostExecute(User user) {
if (user == null) {
Log.d(TAG, "Null result from auth manager.");
setFail("User not found.");
} else {
Log.d(TAG, "Auth manager returned " + user.getUsername());
activateUser(user.getUsername());
finish();
}
}
}.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
}
private void authenticateTokenAsync(final String authDevice, final String tokenValue) {
final AuthenticationToken authToken = new AuthenticationToken(authDevice, tokenValue);
new AsyncTask<Void, Void, User>() {
@Override
protected User doInBackground(Void... params) {
final KegbotCore core = KegbotCore.getInstance(AuthenticatingActivity.this);
return core.getAuthenticationManager().authenticateToken(authToken);
}
@Override
protected void onPostExecute(User user) {
if (user == null) {
Log.d(TAG, "Null result from auth manager.");
setFail("User not found.");
} else {
Log.d(TAG, "Auth manager returned " + user.getUsername());
activateUser(user.getUsername());
finish();
}
}
}.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
}
private void activateUser(String username) {
final int tapId = getIntent().getIntExtra(EXTRA_TAP_ID, 0);
final KegbotCore core = KegbotCore.getInstance(this);
if (tapId > 0) {
final KegTap tap = core.getTapManager().getTap(tapId);
core.getFlowManager().activateUserAtTap(tap, username);
} else {
core.getFlowManager().activateUserAmbiguousTap(username);
}
}
private void setAuthenticating() {
mBeginTitle.setVisibility(View.VISIBLE);
mFailTitle.setVisibility(View.GONE);
mButtonGroup.setVisibility(View.GONE);
mMessage.setVisibility(View.GONE);
mProgressBar.setVisibility(View.VISIBLE);
mHandler.removeCallbacks(mFinishRunnable);
}
private void setFail(String message) {
mBeginTitle.setVisibility(View.GONE);
mFailTitle.setVisibility(View.VISIBLE);
mProgressBar.setVisibility(View.GONE);
mButtonGroup.setVisibility(View.VISIBLE);
if (!Strings.isNullOrEmpty(message)) {
mMessage.setText(message);
Log.d(TAG, mMessage.getText().toString());
mMessage.setVisibility(View.VISIBLE);
} else {
mMessage.setVisibility(View.GONE);
}
mHandler.postDelayed(mFinishRunnable, AUTO_FINISH_DELAY_MILLIS);
}
@Override
protected void onResume() {
super.onResume();
if (mConfig.getAllowRegistration()) {
mAssignButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final Intent intent = KegtabCommon
.getAuthDrinkerActivityIntent(AuthenticatingActivity.this);
Log.d(TAG, "Starting auth drinker activity");
startActivityForResult(intent, REQUEST_SELECT_USER_TO_BIND);
}
});
} else {
mAssignButton.setVisibility(View.GONE);
}
}
@Override
protected void onPause() {
super.onPause();
mHandler.removeCallbacks(mFinishRunnable);
}
private void assignUserToTokenAsync(final String username) {
final String authDevice = Strings.nullToEmpty(getIntent().getStringExtra(EXTRA_AUTH_DEVICE));
final String tokenValue = Strings.nullToEmpty(getIntent().getStringExtra(EXTRA_TOKEN_VALUE));
// TODO(mikey): Determine how this can happen (have seen one report).
if (authDevice.isEmpty() || tokenValue.isEmpty()) {
setFail("Token missing, please try again.");
return;
}
setAuthenticating();
new AsyncTask<Void, Void, Boolean>() {
@Override
protected Boolean doInBackground(Void... params) {
Log.d(TAG, "Assigning authDevice=" + authDevice + " tokenValue=" + tokenValue
+ "to username=" + username);
final KegbotCore core = KegbotCore.getInstance(AuthenticatingActivity.this);
try {
core.getBackend().assignToken(authDevice, tokenValue, username);
} catch (BackendException e) {
Log.w(TAG, "Assignment failed!", e);
return Boolean.FALSE;
}
Log.w(TAG, "Assignment succeeded, authenticating.");
startAndAuthenticate(AuthenticatingActivity.this, username, (KegTap) null);
return Boolean.TRUE;
}
@Override
protected void onPostExecute(Boolean result) {
if (result == Boolean.TRUE) {
setAuthenticating();
} else {
setFail("Token assignment failed.");
}
}
}.execute();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(TAG, "onActivityResult");
switch (requestCode) {
case REQUEST_SELECT_USER_TO_BIND:
if (resultCode == RESULT_OK && data != null) {
final String username = data
.getStringExtra(KegtabCommon.ACTIVITY_AUTH_DRINKER_RESULT_EXTRA_USERNAME);
if (!Strings.isNullOrEmpty(username)) {
assignUserToTokenAsync(username);
}
}
break;
default:
super.onActivityResult(requestCode, resultCode, data);
}
}
/**
* Starts the authentication activity for the supplied username. Upon successful authentication, a
* new flow will be started.
*
* @param context
* @param username
*/
public static void startAndAuthenticate(Context context, String username) {
startAndAuthenticate(context, username, (KegTap) null);
}
/**
* Starts the authentication activity for the supplied username. Upon successful authentication, a
* new flow will be started.
*
* @param context
* @param username
* @param tap tap to authenticate against, or {@code null} if ambiguous
*/
public static void startAndAuthenticate(Context context, String username, KegTap tap) {
final Intent intent = new Intent(context, AuthenticatingActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(EXTRA_USERNAME, username);
if (tap != null) {
intent.putExtra(EXTRA_TAP_ID, Integer.valueOf(tap.getId()));
}
context.startActivity(intent);
}
/**
* Starts the authentication activity for the supplied token. Upon successful authentication, a
* new flow will be started.
*
* @param context
* @param authDevice
* @param tokenValue
*/
public static void startAndAuthenticate(Context context, String authDevice, String tokenValue) {
final Intent intent = new Intent(context, AuthenticatingActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(EXTRA_AUTH_DEVICE, authDevice);
intent.putExtra(EXTRA_TOKEN_VALUE, tokenValue);
context.startActivity(intent);
}
public static Intent getStartForNfcIntent(Context context) {
final Intent intent = new Intent(context, AuthenticatingActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(EXTRA_AUTH_DEVICE, AUTH_TYPE_NFC);
return intent;
}
}