package eu.hgross.blaubot.android.nfc;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.NfcEvent;
import android.os.Vibrator;
import android.util.Base64;
import eu.hgross.blaubot.android.IBlaubotAndroidComponent;
import eu.hgross.blaubot.core.BeaconHelper;
import eu.hgross.blaubot.core.Blaubot;
import eu.hgross.blaubot.core.IBlaubotAdapter;
import eu.hgross.blaubot.core.acceptor.ConnectionMetaDataDTO;
import eu.hgross.blaubot.core.acceptor.IBlaubotIncomingConnectionListener;
import eu.hgross.blaubot.core.acceptor.IBlaubotListeningStateListener;
import eu.hgross.blaubot.core.acceptor.discovery.BeaconMessage;
import eu.hgross.blaubot.core.acceptor.discovery.IBlaubotBeacon;
import eu.hgross.blaubot.core.acceptor.discovery.IBlaubotBeaconStore;
import eu.hgross.blaubot.core.acceptor.discovery.IBlaubotDiscoveryEventListener;
import eu.hgross.blaubot.core.statemachine.states.IBlaubotState;
import eu.hgross.blaubot.util.Log;
/**
* NFC Beacon
* This beacon needs an android context so be aware that you have to update the blaubot instance on
* some lifecycle events. See IBlaubotAndroidComponent and AndroidBlaubot.
*/
public class BlaubotNFCBeacon implements IBlaubotBeacon, IBlaubotAndroidComponent {
private static final String SCHEME = "blaubot";
private static final String LOG_TAG = "BlaubotNFCBeacon";
private static final long VIBRATION_TIME_ON_SUCCESS = 200; // ms
private boolean discoveryActive;
private Blaubot blaubot;
private IBlaubotBeaconStore beaconStore;
private IBlaubotListeningStateListener listeningStateListener;
private IBlaubotIncomingConnectionListener acceptorListener;
private IBlaubotDiscoveryEventListener discoveryEventListener;
private volatile boolean isStarted;
private Context currentContext;
private Vibrator vibratorService;
public BlaubotNFCBeacon() {
this.discoveryActive = false;
}
@Override
public void setBlaubot(Blaubot blaubot) {
this.blaubot = blaubot;
}
@Override
public void setBeaconStore(IBlaubotBeaconStore beaconStore) {
this.beaconStore = beaconStore;
}
@Override
public IBlaubotAdapter getAdapter() {
return null;
}
@Override
public void startListening() {
isStarted = true;
if (this.listeningStateListener != null) {
this.listeningStateListener.onListeningStarted(this);
}
}
@Override
public void stopListening() {
isStarted = false;
if (this.listeningStateListener != null) {
this.listeningStateListener.onListeningStopped(this);
}
}
@Override
public boolean isStarted() {
return isStarted;
}
@Override
public void setListeningStateListener(IBlaubotListeningStateListener stateListener) {
this.listeningStateListener = stateListener;
}
@Override
public void setAcceptorListener(IBlaubotIncomingConnectionListener acceptorListener) {
this.acceptorListener = acceptorListener;
}
@Override
public ConnectionMetaDataDTO getConnectionMetaData() {
// TODO remove acceptor interface from beacon interface
NFCConnectionMetaDataDTO connectionMetaDataDTO = new NFCConnectionMetaDataDTO();
return connectionMetaDataDTO;
}
@Override
public void setDiscoveryEventListener(IBlaubotDiscoveryEventListener discoveryEventListener) {
this.discoveryEventListener = discoveryEventListener;
}
private volatile Uri currentUri;
@Override
public void onConnectionStateMachineStateChanged(IBlaubotState state) {
final BeaconMessage currentBeaconMessage = blaubot.getConnectionStateMachine().getBeaconService().getCurrentBeaconMessage();
final String beaconMessage64 = Base64.encodeToString(currentBeaconMessage.toBytes(), Base64.URL_SAFE);
// create and store uri
StringBuilder sb = new StringBuilder();
sb.append(SCHEME);
sb.append("://");
sb.append("beacon");
sb.append("?beaconMessage=");
sb.append(beaconMessage64);
currentUri = Uri.parse(sb.toString());
}
@Override
public void setDiscoveryActivated(boolean active) {
this.discoveryActive = active;
}
@Override
public void setCurrentContext(Context context) {
}
@Override
public void onResume(Activity context) {
this.currentContext = context;
vibratorService = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
final NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(context);
if (nfcAdapter != null) {
// Create a PendingIntent object so the Android system can populate it with the details of the tag when it is scanned.
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, new Intent(context, context.getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
// Declare intent filters to handle the intents that the developer wants to intercept.
IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
ndef.addDataScheme("blaubot");
final IntentFilter[] intentFiltersArray = {ndef};
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Setting NFC callbacks");
}
nfcAdapter.enableForegroundDispatch(context, pendingIntent, intentFiltersArray, null);
nfcAdapter.setNdefPushMessageCallback(nfcCallback, context);
} else {
throw new RuntimeException("Could not get NFCAdapter - NFC not supported?");
}
}
@Override
public void onPause(Activity context) {
final NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(context);
if (nfcAdapter != null) {
nfcAdapter.disableForegroundDispatch(context);
}
}
@Override
public void onNewIntent(Intent intent) {
if (intent != null) {
if (intent.getAction().equals(NfcAdapter.ACTION_NDEF_DISCOVERED)) {
final String dataString = intent.getDataString();
// parse
final Uri uri = Uri.parse(dataString);
final String beaconMessage64 = uri.getQueryParameter("beaconMessage");
final byte[] beaconMessageBytes = Base64.decode(beaconMessage64, Base64.URL_SAFE);
final BeaconMessage beaconMessage = BeaconMessage.fromBytes(beaconMessageBytes);
BeaconHelper.populateEventsFromBeaconMessage(beaconMessage, discoveryEventListener);
if (vibratorService != null) {
vibratorService.vibrate(VIBRATION_TIME_ON_SUCCESS);
}
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Got NFC data from " + beaconMessage.getCurrentState().name() + " " + beaconMessage.getUniqueDeviceId());
}
}
}
}
private NfcAdapter.CreateNdefMessageCallback nfcCallback = new NfcAdapter.CreateNdefMessageCallback() {
@Override
public NdefMessage createNdefMessage(NfcEvent event) {
if (currentUri == null) { // || !isStarted) ignore ...
if (Log.logErrorMessages()) {
Log.e(LOG_TAG, "I have no URI, can't send NFC message. ");
}
return null; // don't provide a message
}
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "NFC transaction is taking place ...");
}
NdefMessage msg = new NdefMessage(
new NdefRecord[]{
NdefRecord.createUri(currentUri)
});
return msg;
}
};
}