mirror of
git://xwords.git.sourceforge.net/gitroot/xwords/xwords
synced 2025-01-07 05:24:46 +01:00
refactor to avoid ClassNotFound crash where SDK<19
This commit is contained in:
parent
42da9b1ebf
commit
381efc9ddb
3 changed files with 641 additions and 641 deletions
|
@ -72,7 +72,7 @@ import org.eehouse.android.xw4.jni.XwJNI.GamePtr;
|
|||
import org.eehouse.android.xw4.jni.XwJNI;
|
||||
import org.eehouse.android.xw4.loc.LocUtils;
|
||||
import org.eehouse.android.xw4.TilePickAlert.TilePickState;
|
||||
import org.eehouse.android.xw4.NFCCardService.Wrapper;
|
||||
import org.eehouse.android.xw4.NFCUtils.Wrapper;
|
||||
|
||||
public class BoardDelegate extends DelegateBase
|
||||
implements TransportProcs.TPMsgHandler, View.OnClickListener,
|
||||
|
|
|
@ -21,204 +21,22 @@ package org.eehouse.android.xw4;
|
|||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.nfc.NfcAdapter;
|
||||
import android.nfc.Tag;
|
||||
import android.nfc.cardemulation.HostApduService;
|
||||
import android.nfc.tech.IsoDep;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.eehouse.android.xw4.NFCUtils.HEX_STR;
|
||||
import org.eehouse.android.xw4.NFCUtils.MsgToken;
|
||||
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
|
||||
|
||||
public class NFCCardService extends HostApduService {
|
||||
private static final String TAG = NFCCardService.class.getSimpleName();
|
||||
private static final boolean USE_BIGINTEGER = true;
|
||||
private static final int LEN_OFFSET = 4;
|
||||
private static final byte VERSION_1 = (byte)0x01;
|
||||
|
||||
private int mMyDevID;
|
||||
|
||||
private static enum HEX_STR {
|
||||
DEFAULT_CLA( "00" )
|
||||
, SELECT_INS( "A4" )
|
||||
, STATUS_FAILED( "6F00" )
|
||||
, CLA_NOT_SUPPORTED( "6E00" )
|
||||
, INS_NOT_SUPPORTED( "6D00" )
|
||||
, STATUS_SUCCESS( "9000" )
|
||||
, CMD_MSG_PART( "70FC" )
|
||||
;
|
||||
|
||||
private byte[] mBytes;
|
||||
private HEX_STR( String hex ) { mBytes = Utils.hexStr2ba(hex); }
|
||||
private byte[] asBA() { return mBytes; }
|
||||
private boolean matchesFrom( byte[] src )
|
||||
{
|
||||
return matchesFrom( src, 0 );
|
||||
}
|
||||
private boolean matchesFrom( byte[] src, int offset )
|
||||
{
|
||||
boolean result = offset + mBytes.length <= src.length;
|
||||
for ( int ii = 0; result && ii < mBytes.length; ++ii ) {
|
||||
result = src[offset + ii] == mBytes[ii];
|
||||
}
|
||||
// Log.d( TAG, "%s.matchesFrom(%s) => %b", this, src, result );
|
||||
return result;
|
||||
}
|
||||
int length() { return asBA().length; }
|
||||
}
|
||||
|
||||
private static int sNextMsgID = 0;
|
||||
private static synchronized int getNextMsgID()
|
||||
{
|
||||
return ++sNextMsgID;
|
||||
}
|
||||
|
||||
private static byte[] numTo( int num )
|
||||
{
|
||||
byte[] result;
|
||||
if ( USE_BIGINTEGER ) {
|
||||
BigInteger bi = BigInteger.valueOf( num );
|
||||
byte[] bibytes = bi.toByteArray();
|
||||
result = new byte[1 + bibytes.length];
|
||||
result[0] = (byte)bibytes.length;
|
||||
System.arraycopy( bibytes, 0, result, 1, bibytes.length );
|
||||
} else {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
DataOutputStream dos = new DataOutputStream( baos );
|
||||
try {
|
||||
dos.writeInt( num );
|
||||
dos.flush();
|
||||
} catch ( IOException ioe ) {
|
||||
Assert.assertFalse( BuildConfig.DEBUG );
|
||||
}
|
||||
result = baos.toByteArray();
|
||||
}
|
||||
// Log.d( TAG, "numTo(%d) => %s", num, DbgUtils.hexDump(result) );
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int numFrom( ByteArrayInputStream bais ) throws IOException
|
||||
{
|
||||
int biLen = bais.read();
|
||||
// Log.d( TAG, "numFrom(): read biLen: %d", biLen );
|
||||
byte[] bytes = new byte[biLen];
|
||||
bais.read( bytes );
|
||||
BigInteger bi = new BigInteger( bytes );
|
||||
int result = bi.intValue();
|
||||
|
||||
// Log.d( TAG, "numFrom() => %d", result );
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int numFrom( byte[] bytes, int start, int out[] )
|
||||
{
|
||||
int result;
|
||||
if ( USE_BIGINTEGER ) {
|
||||
byte biLen = bytes[start];
|
||||
byte[] rest = Arrays.copyOfRange( bytes, start + 1, start + 1 + biLen );
|
||||
BigInteger bi = new BigInteger(rest);
|
||||
out[0] = bi.intValue();
|
||||
result = biLen + 1;
|
||||
} else {
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream( bytes, start,
|
||||
bytes.length - start );
|
||||
DataInputStream dis = new DataInputStream( bais );
|
||||
try {
|
||||
out[0] = dis.readInt();
|
||||
} catch ( IOException ioe ) {
|
||||
Log.e( TAG, "from readInt(): %s", ioe.getMessage() );
|
||||
}
|
||||
result = bais.available() - start;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void testNumThing()
|
||||
{
|
||||
Log.d( TAG, "testNumThing() starting" );
|
||||
|
||||
int[] out = {0};
|
||||
for ( int ii = 1; ii > 0 && ii < Integer.MAX_VALUE; ii *= 2 ) {
|
||||
byte[] tmp = numTo( ii );
|
||||
numFrom( tmp, 0, out );
|
||||
if ( ii != out[0] ) {
|
||||
Log.d( TAG, "testNumThing(): %d failed; got %d", ii, out[0] );
|
||||
break;
|
||||
} else {
|
||||
Log.d( TAG, "testNumThing(): %d ok", ii );
|
||||
}
|
||||
}
|
||||
Log.d( TAG, "testNumThing() DONE" );
|
||||
}
|
||||
|
||||
private static class QueueElem {
|
||||
Context context;
|
||||
byte[] msg;
|
||||
QueueElem( Context pContext, byte[] pMsg ) {
|
||||
context = pContext;
|
||||
msg = pMsg;
|
||||
}
|
||||
}
|
||||
|
||||
private static LinkedBlockingQueue<QueueElem> sQueue = null;
|
||||
|
||||
private synchronized static void addToMsgThread( Context context, byte[] msg )
|
||||
{
|
||||
if ( 0 < msg.length ) {
|
||||
QueueElem elem = new QueueElem( context, msg );
|
||||
if ( null == sQueue ) {
|
||||
sQueue = new LinkedBlockingQueue<>();
|
||||
new Thread( new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.d( TAG, "addToMsgThread(): run starting" );
|
||||
for ( ; ; ) {
|
||||
try {
|
||||
QueueElem elem = sQueue.take();
|
||||
NFCUtils.receiveMsgs( elem.context, elem.msg );
|
||||
updateStatus( elem.context, true );
|
||||
} catch ( InterruptedException ie ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Log.d( TAG, "addToMsgThread(): run exiting" );
|
||||
}
|
||||
} ).start();
|
||||
}
|
||||
sQueue.add( elem );
|
||||
// } else {
|
||||
// // This is very common right now
|
||||
// Log.d( TAG, "addToMsgThread(): dropping 0-length msg" );
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateStatus( Context context, boolean in )
|
||||
{
|
||||
if ( in ) {
|
||||
ConnStatusHandler
|
||||
.updateStatusIn( context, CommsConnType.COMMS_CONN_NFC, true );
|
||||
} else {
|
||||
ConnStatusHandler
|
||||
.updateStatusOut( context, CommsConnType.COMMS_CONN_NFC, true );
|
||||
}
|
||||
}
|
||||
|
||||
// Remove this once we don't need logging to confirm stuff's loading
|
||||
@Override
|
||||
public void onCreate()
|
||||
|
@ -241,9 +59,9 @@ public class NFCCardService extends HostApduService {
|
|||
if ( null != apdu ) {
|
||||
if ( HEX_STR.CMD_MSG_PART.matchesFrom( apdu ) ) {
|
||||
resStr = HEX_STR.STATUS_SUCCESS;
|
||||
byte[] all = reassemble( this, apdu, HEX_STR.CMD_MSG_PART );
|
||||
byte[] all = NFCUtils.reassemble( this, apdu, HEX_STR.CMD_MSG_PART );
|
||||
if ( null != all ) {
|
||||
addToMsgThread( this, all );
|
||||
NFCUtils.addToMsgThread( this, all );
|
||||
}
|
||||
} else {
|
||||
Log.d( TAG, "processCommandApdu(): aid case?" );
|
||||
|
@ -268,11 +86,11 @@ public class NFCCardService extends HostApduService {
|
|||
if ( BuildConfig.NFC_AID.equals( aidStr ) ) {
|
||||
byte minVersion = (byte)bais.read();
|
||||
byte maxVersion = (byte)bais.read();
|
||||
if ( minVersion == VERSION_1 ) {
|
||||
int devID = numFrom( bais );
|
||||
if ( minVersion == NFCUtils.VERSION_1 ) {
|
||||
int devID = NFCUtils.numFrom( bais );
|
||||
Log.d( TAG, "processCommandApdu(): read "
|
||||
+ "remote devID: %d", devID );
|
||||
mGameID = numFrom( bais );
|
||||
mGameID = NFCUtils.numFrom( bais );
|
||||
Log.d( TAG, "read gameID: %d", mGameID );
|
||||
if ( 0 < bais.available() ) {
|
||||
Log.d( TAG, "processCommandApdu(): "
|
||||
|
@ -301,11 +119,11 @@ public class NFCCardService extends HostApduService {
|
|||
baos.write( resStr.asBA() );
|
||||
if ( HEX_STR.STATUS_SUCCESS == resStr ) {
|
||||
if ( isAidCase ) {
|
||||
baos.write( VERSION_1 ); // min
|
||||
baos.write( numTo( mMyDevID ) );
|
||||
baos.write( NFCUtils.VERSION_1 ); // min
|
||||
baos.write( NFCUtils.numTo( mMyDevID ) );
|
||||
} else {
|
||||
MsgToken token = NFCUtils.getMsgsFor( mGameID );
|
||||
byte[][] tmp = wrapMsg( token, Short.MAX_VALUE );
|
||||
byte[][] tmp = NFCUtils.wrapMsg( token, Short.MAX_VALUE );
|
||||
Assert.assertTrue( 1 == tmp.length || !BuildConfig.DEBUG );
|
||||
baos.write( tmp[0] );
|
||||
}
|
||||
|
@ -336,452 +154,4 @@ public class NFCCardService extends HostApduService {
|
|||
|
||||
Log.d( TAG, "onDeactivated(reason=%s)", str );
|
||||
}
|
||||
|
||||
private static Map<Integer, MsgToken> sSentTokens = new HashMap<>();
|
||||
private static void removeSentMsgs( Context context, int ack )
|
||||
{
|
||||
MsgToken msgs = null;
|
||||
if ( 0 != ack ) {
|
||||
Log.d( TAG, "removeSentMsgs(msgID=%d)", ack );
|
||||
synchronized ( sSentTokens ) {
|
||||
msgs = sSentTokens.remove( ack );
|
||||
Log.d( TAG, "removeSentMsgs(): removed %s, now have %s", msgs, keysFor() );
|
||||
}
|
||||
updateStatus( context, false );
|
||||
}
|
||||
if ( null != msgs ) {
|
||||
msgs.removeSentMsgs();
|
||||
}
|
||||
}
|
||||
|
||||
private static void remember( int msgID, MsgToken msgs )
|
||||
{
|
||||
if ( 0 != msgID ) {
|
||||
Log.d( TAG, "remember(msgID=%d)", msgID );
|
||||
synchronized ( sSentTokens ) {
|
||||
sSentTokens.put( msgID, msgs );
|
||||
Log.d( TAG, "remember(): now have %s", keysFor() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String keysFor()
|
||||
{
|
||||
String result = "";
|
||||
if ( BuildConfig.DEBUG ) {
|
||||
result = TextUtils.join( ",", sSentTokens.keySet() );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static byte[][] sParts = null;
|
||||
private static int sMsgID = 0;
|
||||
private synchronized static byte[] reassemble( Context context, byte[] part,
|
||||
HEX_STR cmd )
|
||||
{
|
||||
return reassemble( context, part, cmd.length() );
|
||||
}
|
||||
|
||||
private synchronized static byte[] reassemble( Context context, byte[] part,
|
||||
int offset )
|
||||
{
|
||||
part = Arrays.copyOfRange( part, offset, part.length );
|
||||
return reassemble( context, part );
|
||||
}
|
||||
|
||||
private synchronized static byte[] reassemble( Context context, byte[] part )
|
||||
{
|
||||
byte[] result = null;
|
||||
try {
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream( part );
|
||||
|
||||
final int cur = bais.read();
|
||||
final int count = bais.read();
|
||||
if ( 0 == cur ) {
|
||||
sMsgID = numFrom( bais );
|
||||
int ack = numFrom( bais );
|
||||
removeSentMsgs( context, ack );
|
||||
}
|
||||
|
||||
boolean inSequence = true;
|
||||
if ( sParts == null ) {
|
||||
if ( 0 == cur ) {
|
||||
sParts = new byte[count][];
|
||||
} else {
|
||||
Log.e( TAG, "reassemble(): out-of-order message 1" );
|
||||
inSequence = false;
|
||||
}
|
||||
} else if ( cur >= count || count != sParts.length || null != sParts[cur] ) {
|
||||
// result = HEX_STR.STATUS_FAILED;
|
||||
inSequence = false;
|
||||
Log.e( TAG, "reassemble(): out-of-order message 2" );
|
||||
}
|
||||
|
||||
if ( !inSequence ) {
|
||||
sParts = null; // so we can try again later
|
||||
} else {
|
||||
// write rest into array
|
||||
byte[] rest = new byte[bais.available()];
|
||||
bais.read( rest, 0, rest.length );
|
||||
sParts[cur] = rest;
|
||||
// Log.d( TAG, "addOrProcess(): added elem %d: %s", cur, DbgUtils.hexDump( rest ) );
|
||||
|
||||
// Done? Process!!
|
||||
if ( cur + 1 == count ) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
for ( int ii = 0; ii < sParts.length; ++ii ) {
|
||||
baos.write( sParts[ii] );
|
||||
}
|
||||
sParts = null;
|
||||
|
||||
result = baos.toByteArray();
|
||||
setLatestAck( sMsgID );
|
||||
if ( 0 != sMsgID ) {
|
||||
Log.d( TAG, "reassemble(): done reassembling msgID=%d: %s",
|
||||
sMsgID, DbgUtils.hexDump(result) );
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch ( IOException ioe ) {
|
||||
Assert.assertFalse( BuildConfig.DEBUG );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static AtomicInteger sLatestAck = new AtomicInteger(0);
|
||||
private static int getLatestAck()
|
||||
{
|
||||
int result = sLatestAck.getAndSet(0);
|
||||
if ( 0 != result ) {
|
||||
Log.d( TAG, "getLatestAck() => %d", result );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void setLatestAck( int ack )
|
||||
{
|
||||
if ( 0 != ack ) {
|
||||
Log.e( TAG, "setLatestAck(%d)", ack );
|
||||
}
|
||||
int oldVal = sLatestAck.getAndSet( ack );
|
||||
if ( 0 != oldVal ) {
|
||||
Log.e( TAG, "setLatestAck(%d): dropping ack msgID %d", ack, oldVal );
|
||||
}
|
||||
}
|
||||
|
||||
private static final int HEADER_SIZE = 10;
|
||||
private static byte[][] wrapMsg( MsgToken token, int maxLen )
|
||||
{
|
||||
byte[] msg = token.getMsgs();
|
||||
final int length = null == msg ? 0 : msg.length;
|
||||
final int msgID = (0 == length) ? 0 : getNextMsgID();
|
||||
if ( 0 < msgID ) {
|
||||
Log.d( TAG, "wrapMsg(%s); msgID=%d", DbgUtils.hexDump( msg ), msgID );
|
||||
}
|
||||
final int count = 1 + (length / (maxLen - HEADER_SIZE));
|
||||
byte[][] result = new byte[count][];
|
||||
try {
|
||||
int offset = 0;
|
||||
for ( int ii = 0; ii < count; ++ii ) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
baos.write( HEX_STR.CMD_MSG_PART.asBA() );
|
||||
baos.write( (byte)ii );
|
||||
baos.write( (byte)count );
|
||||
if ( 0 == ii ) {
|
||||
baos.write( numTo( msgID ) );
|
||||
int latestAck = getLatestAck();
|
||||
baos.write( numTo( latestAck ) );
|
||||
}
|
||||
Assert.assertTrue( HEADER_SIZE >= baos.toByteArray().length
|
||||
|| !BuildConfig.DEBUG );
|
||||
|
||||
int thisLen = Math.min( maxLen - HEADER_SIZE, length - offset );
|
||||
if ( 0 < thisLen ) {
|
||||
// Log.d( TAG, "writing %d bytes starting from offset %d",
|
||||
// thisLen, offset );
|
||||
baos.write( msg, offset, thisLen );
|
||||
offset += thisLen;
|
||||
}
|
||||
byte[] tmp = baos.toByteArray();
|
||||
// Log.d( TAG, "wrapMsg(): adding res[%d]: %s", ii, DbgUtils.hexDump(tmp) );
|
||||
result[ii] = tmp;
|
||||
}
|
||||
remember( msgID, token );
|
||||
} catch ( IOException ioe ) {
|
||||
Assert.assertFalse( BuildConfig.DEBUG );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static class Wrapper implements NfcAdapter.ReaderCallback,
|
||||
NFCUtils.HaveDataListener {
|
||||
private Activity mActivity;
|
||||
private boolean mHaveData;
|
||||
private Procs mProcs;
|
||||
private NfcAdapter mAdapter;
|
||||
private int mMinMS = 300;
|
||||
private int mMaxMS = 500;
|
||||
private boolean mConnected = false;
|
||||
private int mMyDevID;
|
||||
|
||||
public interface Procs {
|
||||
void onReadingChange( boolean nowReading );
|
||||
}
|
||||
|
||||
public static Wrapper init( Activity activity, Procs procs, int devID )
|
||||
{
|
||||
Wrapper instance = null;
|
||||
NfcAdapter adapter = NfcAdapter.getDefaultAdapter( activity );
|
||||
if ( null != adapter ) {
|
||||
instance = new Wrapper( activity, adapter, procs, devID );
|
||||
}
|
||||
Log.d( TAG, "Wrapper.init(devID=%d) => %s", devID, instance );
|
||||
return instance;
|
||||
}
|
||||
|
||||
static void setResumed( Wrapper instance, boolean resumed )
|
||||
{
|
||||
if ( null != instance ) {
|
||||
instance.setResumed( resumed );
|
||||
}
|
||||
}
|
||||
|
||||
static void setGameID( Wrapper instance, int gameID )
|
||||
{
|
||||
if ( null != instance ) {
|
||||
instance.setGameID( gameID );
|
||||
}
|
||||
}
|
||||
|
||||
private Wrapper( Activity activity, NfcAdapter adapter, Procs procs,
|
||||
int devID )
|
||||
{
|
||||
mActivity = activity;
|
||||
mAdapter = adapter;
|
||||
mProcs = procs;
|
||||
mMyDevID = devID;
|
||||
}
|
||||
|
||||
private void setResumed( boolean resumed )
|
||||
{
|
||||
if ( resumed ) {
|
||||
startReadModeThread();
|
||||
} else {
|
||||
stopReadModeThread();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHaveDataChanged( boolean haveData )
|
||||
{
|
||||
if ( mHaveData != haveData ) {
|
||||
mHaveData = haveData;
|
||||
Log.d( TAG, "onHaveDataChanged(): mHaveData now %b", mHaveData );
|
||||
interruptThread();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean haveData()
|
||||
{
|
||||
boolean result = mHaveData;
|
||||
// Log.d( TAG, "haveData() => %b", result );
|
||||
return result;
|
||||
}
|
||||
|
||||
private int mGameID;
|
||||
private void setGameID( int gameID )
|
||||
{
|
||||
Log.d( TAG, "setGameID(%d)", gameID );
|
||||
mGameID = gameID;
|
||||
NFCUtils.setHaveDataListener( gameID, this );
|
||||
interruptThread();
|
||||
}
|
||||
|
||||
private void interruptThread()
|
||||
{
|
||||
synchronized ( mThreadRef ) {
|
||||
if ( null != mThreadRef[0] ) {
|
||||
mThreadRef[0].interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTagDiscovered( Tag tag )
|
||||
{
|
||||
mConnected = true;
|
||||
IsoDep isoDep = IsoDep.get( tag );
|
||||
try {
|
||||
isoDep.connect();
|
||||
int maxLen = isoDep.getMaxTransceiveLength();
|
||||
Log.d( TAG, "onTagDiscovered() connected; max len: %d", maxLen );
|
||||
byte[] aidBytes = Utils.hexStr2ba( BuildConfig.NFC_AID );
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
baos.write( Utils.hexStr2ba( "00A40400" ) );
|
||||
baos.write( (byte)aidBytes.length );
|
||||
baos.write( aidBytes );
|
||||
baos.write( VERSION_1 ); // min
|
||||
baos.write( VERSION_1 ); // max
|
||||
baos.write( numTo( mMyDevID ) );
|
||||
baos.write( numTo( mGameID ) );
|
||||
byte[] msg = baos.toByteArray();
|
||||
Assert.assertTrue( msg.length < maxLen || !BuildConfig.DEBUG );
|
||||
byte[] response = isoDep.transceive( msg );
|
||||
|
||||
// The first reply from transceive() is special. If it starts
|
||||
// with STATUS_SUCCESS then it also includes the version we'll
|
||||
// be using to communicate, either what we sent over or
|
||||
// something lower (for older code on the other side), and the
|
||||
// remote's deviceID
|
||||
if ( HEX_STR.STATUS_SUCCESS.matchesFrom( response ) ) {
|
||||
int offset = HEX_STR.STATUS_SUCCESS.length();
|
||||
byte version = response[offset++];
|
||||
if ( version == VERSION_1 ) {
|
||||
int[] out = {0};
|
||||
offset += numFrom( response, offset, out );
|
||||
Log.d( TAG, "onTagDiscovered(): read remote devID: %d",
|
||||
out[0] );
|
||||
runMessageLoop( isoDep, maxLen );
|
||||
} else {
|
||||
Log.e( TAG, "onTagDiscovered(): remote sent version %d, "
|
||||
+ "not %d; exiting", version, VERSION_1 );
|
||||
}
|
||||
}
|
||||
isoDep.close();
|
||||
} catch ( IOException ioe ) {
|
||||
Log.e( TAG, "got ioe: " + ioe.getMessage() );
|
||||
}
|
||||
|
||||
mConnected = false;
|
||||
interruptThread(); // make sure we leave read mode!
|
||||
Log.d( TAG, "onTagDiscovered() DONE" );
|
||||
}
|
||||
|
||||
private void runMessageLoop( IsoDep isoDep, int maxLen ) throws IOException
|
||||
{
|
||||
outer:
|
||||
for ( ; ; ) {
|
||||
MsgToken token = NFCUtils.getMsgsFor( mGameID );
|
||||
// PENDING: no need for this Math.min thing once well tested
|
||||
byte[][] toFit = wrapMsg( token, Math.min( 50, maxLen ) );
|
||||
for ( int ii = 0; ii < toFit.length; ++ii ) {
|
||||
byte[] one = toFit[ii];
|
||||
Assert.assertTrue( one.length < maxLen || !BuildConfig.DEBUG );
|
||||
byte[] response = isoDep.transceive( one );
|
||||
if ( ! receiveAny( response ) ) {
|
||||
break outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean receiveAny( byte[] response )
|
||||
{
|
||||
boolean statusOK = HEX_STR.STATUS_SUCCESS.matchesFrom( response );
|
||||
if ( statusOK ) {
|
||||
int offset = HEX_STR.STATUS_SUCCESS.length();
|
||||
if ( HEX_STR.CMD_MSG_PART.matchesFrom( response, offset ) ) {
|
||||
byte[] all = reassemble( mActivity, response,
|
||||
offset + HEX_STR.CMD_MSG_PART.length() );
|
||||
Log.d( TAG, "receiveAny(%s) => %b", DbgUtils.hexDump( response ), statusOK );
|
||||
if ( null != all ) {
|
||||
addToMsgThread( mActivity, all );
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( !statusOK ) {
|
||||
Log.d( TAG, "receiveAny(%s) => %b", DbgUtils.hexDump( response ), statusOK );
|
||||
}
|
||||
return statusOK;
|
||||
}
|
||||
|
||||
private class ReadModeThread extends Thread {
|
||||
private boolean mShouldStop = false;
|
||||
private boolean mInReadMode = false;
|
||||
private final int mFlags = NfcAdapter.FLAG_READER_NFC_A
|
||||
| NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK;
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
Log.d( TAG, "ReadModeThread.run() starting" );
|
||||
Random random = new Random();
|
||||
|
||||
while ( !mShouldStop ) {
|
||||
boolean wantReadMode = mConnected || !mInReadMode && haveData();
|
||||
if ( wantReadMode && !mInReadMode ) {
|
||||
mAdapter.enableReaderMode( mActivity, Wrapper.this, mFlags, null );
|
||||
} else if ( mInReadMode && !wantReadMode ) {
|
||||
mAdapter.disableReaderMode( mActivity );
|
||||
}
|
||||
mInReadMode = wantReadMode;
|
||||
Log.d( TAG, "run(): inReadMode now: %b", mInReadMode );
|
||||
|
||||
// Now sleep. If we aren't going to want to toggle read
|
||||
// mode soon, sleep until interrupted by a state change,
|
||||
// e.g. getting data or losing connection.
|
||||
long intervalMS = Long.MAX_VALUE;
|
||||
if ( (mInReadMode && !mConnected) || haveData() ) {
|
||||
intervalMS = mMinMS + (Math.abs(random.nextInt())
|
||||
% (mMaxMS - mMinMS));
|
||||
}
|
||||
try {
|
||||
Thread.sleep( intervalMS );
|
||||
} catch ( InterruptedException ie ) {
|
||||
Log.d( TAG, "run interrupted" );
|
||||
}
|
||||
}
|
||||
|
||||
// Kill read mode on the way out
|
||||
if ( mInReadMode ) {
|
||||
mAdapter.disableReaderMode( mActivity );
|
||||
mInReadMode = false;
|
||||
}
|
||||
|
||||
// Clear the reference only if it's me
|
||||
synchronized ( mThreadRef ) {
|
||||
if ( mThreadRef[0] == this ) {
|
||||
mThreadRef[0] = null;
|
||||
}
|
||||
}
|
||||
Log.d( TAG, "ReadModeThread.run() exiting" );
|
||||
}
|
||||
|
||||
public void doStop()
|
||||
{
|
||||
mShouldStop = true;
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private ReadModeThread[] mThreadRef = {null};
|
||||
private void startReadModeThread()
|
||||
{
|
||||
synchronized ( mThreadRef ) {
|
||||
if ( null == mThreadRef[0] ) {
|
||||
mThreadRef[0] = new ReadModeThread();
|
||||
mThreadRef[0].start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void stopReadModeThread()
|
||||
{
|
||||
ReadModeThread thread;
|
||||
synchronized ( mThreadRef ) {
|
||||
thread = mThreadRef[0];
|
||||
mThreadRef[0] = null;
|
||||
}
|
||||
|
||||
if ( null != thread ) {
|
||||
thread.doStop();
|
||||
try {
|
||||
thread.join();
|
||||
} catch ( InterruptedException ex ) {
|
||||
Log.d( TAG, "stopReadModeThread(): %s", ex );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,8 +28,11 @@ import android.content.Intent;
|
|||
import android.nfc.NfcAdapter;
|
||||
import android.nfc.NfcEvent;
|
||||
import android.nfc.NfcManager;
|
||||
import android.nfc.Tag;
|
||||
import android.nfc.tech.IsoDep;
|
||||
import android.os.Build;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
@ -37,29 +40,37 @@ import java.io.DataInputStream;
|
|||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.eehouse.android.xw4.MultiService.MultiEvent;
|
||||
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
|
||||
import org.eehouse.android.xw4.jni.CommsAddrRec;
|
||||
import org.eehouse.android.xw4.loc.LocUtils;
|
||||
|
||||
public class NFCUtils {
|
||||
private static final String TAG = NFCUtils.class.getSimpleName();
|
||||
private static final boolean USE_BIGINTEGER = true;
|
||||
|
||||
private static final String NFC_TO_SELF_ACTION = "org.eehouse.nfc_to_self";
|
||||
private static final String NFC_TO_SELF_DATA = "nfc_data";
|
||||
|
||||
static final byte VERSION_1 = (byte)0x01;
|
||||
|
||||
private static final byte MESSAGE = 0x01;
|
||||
private static final byte INVITE = 0x02;
|
||||
private static final byte REPLY = 0x03;
|
||||
|
||||
private static final byte REPLY_NOGAME = 0x00;
|
||||
|
||||
private static boolean s_inSDK = 14 <= Build.VERSION.SDK_INT;
|
||||
private static boolean s_inSDK = 19 <= Build.VERSION.SDK_INT;
|
||||
private static boolean[] s_nfcAvail;
|
||||
|
||||
// Return array of two booleans, the first indicating whether the
|
||||
|
@ -430,6 +441,625 @@ public class NFCUtils {
|
|||
}
|
||||
}
|
||||
|
||||
static enum HEX_STR {
|
||||
DEFAULT_CLA( "00" )
|
||||
, SELECT_INS( "A4" )
|
||||
, STATUS_FAILED( "6F00" )
|
||||
, CLA_NOT_SUPPORTED( "6E00" )
|
||||
, INS_NOT_SUPPORTED( "6D00" )
|
||||
, STATUS_SUCCESS( "9000" )
|
||||
, CMD_MSG_PART( "70FC" )
|
||||
;
|
||||
|
||||
private byte[] mBytes;
|
||||
private HEX_STR( String hex ) { mBytes = Utils.hexStr2ba(hex); }
|
||||
byte[] asBA() { return mBytes; }
|
||||
boolean matchesFrom( byte[] src )
|
||||
{
|
||||
return matchesFrom( src, 0 );
|
||||
}
|
||||
boolean matchesFrom( byte[] src, int offset )
|
||||
{
|
||||
boolean result = offset + mBytes.length <= src.length;
|
||||
for ( int ii = 0; result && ii < mBytes.length; ++ii ) {
|
||||
result = src[offset + ii] == mBytes[ii];
|
||||
}
|
||||
// Log.d( TAG, "%s.matchesFrom(%s) => %b", this, src, result );
|
||||
return result;
|
||||
}
|
||||
int length() { return asBA().length; }
|
||||
}
|
||||
|
||||
private static int sNextMsgID = 0;
|
||||
private static synchronized int getNextMsgID()
|
||||
{
|
||||
return ++sNextMsgID;
|
||||
}
|
||||
|
||||
static byte[] numTo( int num )
|
||||
{
|
||||
byte[] result;
|
||||
if ( USE_BIGINTEGER ) {
|
||||
BigInteger bi = BigInteger.valueOf( num );
|
||||
byte[] bibytes = bi.toByteArray();
|
||||
result = new byte[1 + bibytes.length];
|
||||
result[0] = (byte)bibytes.length;
|
||||
System.arraycopy( bibytes, 0, result, 1, bibytes.length );
|
||||
} else {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
DataOutputStream dos = new DataOutputStream( baos );
|
||||
try {
|
||||
dos.writeInt( num );
|
||||
dos.flush();
|
||||
} catch ( IOException ioe ) {
|
||||
Assert.assertFalse( BuildConfig.DEBUG );
|
||||
}
|
||||
result = baos.toByteArray();
|
||||
}
|
||||
// Log.d( TAG, "numTo(%d) => %s", num, DbgUtils.hexDump(result) );
|
||||
return result;
|
||||
}
|
||||
|
||||
static int numFrom( ByteArrayInputStream bais ) throws IOException
|
||||
{
|
||||
int biLen = bais.read();
|
||||
// Log.d( TAG, "numFrom(): read biLen: %d", biLen );
|
||||
byte[] bytes = new byte[biLen];
|
||||
bais.read( bytes );
|
||||
BigInteger bi = new BigInteger( bytes );
|
||||
int result = bi.intValue();
|
||||
|
||||
// Log.d( TAG, "numFrom() => %d", result );
|
||||
return result;
|
||||
}
|
||||
|
||||
static int numFrom( byte[] bytes, int start, int out[] )
|
||||
{
|
||||
int result;
|
||||
if ( USE_BIGINTEGER ) {
|
||||
byte biLen = bytes[start];
|
||||
byte[] rest = Arrays.copyOfRange( bytes, start + 1, start + 1 + biLen );
|
||||
BigInteger bi = new BigInteger(rest);
|
||||
out[0] = bi.intValue();
|
||||
result = biLen + 1;
|
||||
} else {
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream( bytes, start,
|
||||
bytes.length - start );
|
||||
DataInputStream dis = new DataInputStream( bais );
|
||||
try {
|
||||
out[0] = dis.readInt();
|
||||
} catch ( IOException ioe ) {
|
||||
Log.e( TAG, "from readInt(): %s", ioe.getMessage() );
|
||||
}
|
||||
result = bais.available() - start;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// private static void testNumThing()
|
||||
// {
|
||||
// Log.d( TAG, "testNumThing() starting" );
|
||||
|
||||
// int[] out = {0};
|
||||
// for ( int ii = 1; ii > 0 && ii < Integer.MAX_VALUE; ii *= 2 ) {
|
||||
// byte[] tmp = numTo( ii );
|
||||
// numFrom( tmp, 0, out );
|
||||
// if ( ii != out[0] ) {
|
||||
// Log.d( TAG, "testNumThing(): %d failed; got %d", ii, out[0] );
|
||||
// break;
|
||||
// } else {
|
||||
// Log.d( TAG, "testNumThing(): %d ok", ii );
|
||||
// }
|
||||
// }
|
||||
// Log.d( TAG, "testNumThing() DONE" );
|
||||
// }
|
||||
|
||||
private static AtomicInteger sLatestAck = new AtomicInteger(0);
|
||||
static int getLatestAck()
|
||||
{
|
||||
int result = sLatestAck.getAndSet(0);
|
||||
if ( 0 != result ) {
|
||||
Log.d( TAG, "getLatestAck() => %d", result );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void setLatestAck( int ack )
|
||||
{
|
||||
if ( 0 != ack ) {
|
||||
Log.e( TAG, "setLatestAck(%d)", ack );
|
||||
}
|
||||
int oldVal = sLatestAck.getAndSet( ack );
|
||||
if ( 0 != oldVal ) {
|
||||
Log.e( TAG, "setLatestAck(%d): dropping ack msgID %d", ack, oldVal );
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateStatus( Context context, boolean in )
|
||||
{
|
||||
if ( in ) {
|
||||
ConnStatusHandler
|
||||
.updateStatusIn( context, CommsConnType.COMMS_CONN_NFC, true );
|
||||
} else {
|
||||
ConnStatusHandler
|
||||
.updateStatusOut( context, CommsConnType.COMMS_CONN_NFC, true );
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<Integer, MsgToken> sSentTokens = new HashMap<>();
|
||||
private static void removeSentMsgs( Context context, int ack )
|
||||
{
|
||||
MsgToken msgs = null;
|
||||
if ( 0 != ack ) {
|
||||
Log.d( TAG, "removeSentMsgs(msgID=%d)", ack );
|
||||
synchronized ( sSentTokens ) {
|
||||
msgs = sSentTokens.remove( ack );
|
||||
Log.d( TAG, "removeSentMsgs(): removed %s, now have %s", msgs, keysFor() );
|
||||
}
|
||||
updateStatus( context, false );
|
||||
}
|
||||
if ( null != msgs ) {
|
||||
msgs.removeSentMsgs();
|
||||
}
|
||||
}
|
||||
|
||||
private static void remember( int msgID, MsgToken msgs )
|
||||
{
|
||||
if ( 0 != msgID ) {
|
||||
Log.d( TAG, "remember(msgID=%d)", msgID );
|
||||
synchronized ( sSentTokens ) {
|
||||
sSentTokens.put( msgID, msgs );
|
||||
Log.d( TAG, "remember(): now have %s", keysFor() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String keysFor()
|
||||
{
|
||||
String result = "";
|
||||
if ( BuildConfig.DEBUG ) {
|
||||
result = TextUtils.join( ",", sSentTokens.keySet() );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static byte[][] sParts = null;
|
||||
private static int sMsgID = 0;
|
||||
synchronized static byte[] reassemble( Context context, byte[] part,
|
||||
HEX_STR cmd )
|
||||
{
|
||||
return reassemble( context, part, cmd.length() );
|
||||
}
|
||||
|
||||
synchronized static byte[] reassemble( Context context, byte[] part,
|
||||
int offset )
|
||||
{
|
||||
part = Arrays.copyOfRange( part, offset, part.length );
|
||||
return reassemble( context, part );
|
||||
}
|
||||
|
||||
synchronized static byte[] reassemble( Context context, byte[] part )
|
||||
{
|
||||
byte[] result = null;
|
||||
try {
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream( part );
|
||||
|
||||
final int cur = bais.read();
|
||||
final int count = bais.read();
|
||||
if ( 0 == cur ) {
|
||||
sMsgID = NFCUtils.numFrom( bais );
|
||||
int ack = NFCUtils.numFrom( bais );
|
||||
removeSentMsgs( context, ack );
|
||||
}
|
||||
|
||||
boolean inSequence = true;
|
||||
if ( sParts == null ) {
|
||||
if ( 0 == cur ) {
|
||||
sParts = new byte[count][];
|
||||
} else {
|
||||
Log.e( TAG, "reassemble(): out-of-order message 1" );
|
||||
inSequence = false;
|
||||
}
|
||||
} else if ( cur >= count || count != sParts.length || null != sParts[cur] ) {
|
||||
// result = HEX_STR.STATUS_FAILED;
|
||||
inSequence = false;
|
||||
Log.e( TAG, "reassemble(): out-of-order message 2" );
|
||||
}
|
||||
|
||||
if ( !inSequence ) {
|
||||
sParts = null; // so we can try again later
|
||||
} else {
|
||||
// write rest into array
|
||||
byte[] rest = new byte[bais.available()];
|
||||
bais.read( rest, 0, rest.length );
|
||||
sParts[cur] = rest;
|
||||
// Log.d( TAG, "addOrProcess(): added elem %d: %s", cur, DbgUtils.hexDump( rest ) );
|
||||
|
||||
// Done? Process!!
|
||||
if ( cur + 1 == count ) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
for ( int ii = 0; ii < sParts.length; ++ii ) {
|
||||
baos.write( sParts[ii] );
|
||||
}
|
||||
sParts = null;
|
||||
|
||||
result = baos.toByteArray();
|
||||
setLatestAck( sMsgID );
|
||||
if ( 0 != sMsgID ) {
|
||||
Log.d( TAG, "reassemble(): done reassembling msgID=%d: %s",
|
||||
sMsgID, DbgUtils.hexDump(result) );
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch ( IOException ioe ) {
|
||||
Assert.assertFalse( BuildConfig.DEBUG );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static final int HEADER_SIZE = 10;
|
||||
static byte[][] wrapMsg( MsgToken token, int maxLen )
|
||||
{
|
||||
byte[] msg = token.getMsgs();
|
||||
final int length = null == msg ? 0 : msg.length;
|
||||
final int msgID = (0 == length) ? 0 : getNextMsgID();
|
||||
if ( 0 < msgID ) {
|
||||
Log.d( TAG, "wrapMsg(%s); msgID=%d", DbgUtils.hexDump( msg ), msgID );
|
||||
}
|
||||
final int count = 1 + (length / (maxLen - HEADER_SIZE));
|
||||
byte[][] result = new byte[count][];
|
||||
try {
|
||||
int offset = 0;
|
||||
for ( int ii = 0; ii < count; ++ii ) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
baos.write( HEX_STR.CMD_MSG_PART.asBA() );
|
||||
baos.write( (byte)ii );
|
||||
baos.write( (byte)count );
|
||||
if ( 0 == ii ) {
|
||||
baos.write( numTo( msgID ) );
|
||||
int latestAck = getLatestAck();
|
||||
baos.write( numTo( latestAck ) );
|
||||
}
|
||||
Assert.assertTrue( HEADER_SIZE >= baos.toByteArray().length
|
||||
|| !BuildConfig.DEBUG );
|
||||
|
||||
int thisLen = Math.min( maxLen - HEADER_SIZE, length - offset );
|
||||
if ( 0 < thisLen ) {
|
||||
// Log.d( TAG, "writing %d bytes starting from offset %d",
|
||||
// thisLen, offset );
|
||||
baos.write( msg, offset, thisLen );
|
||||
offset += thisLen;
|
||||
}
|
||||
byte[] tmp = baos.toByteArray();
|
||||
// Log.d( TAG, "wrapMsg(): adding res[%d]: %s", ii, DbgUtils.hexDump(tmp) );
|
||||
result[ii] = tmp;
|
||||
}
|
||||
remember( msgID, token );
|
||||
} catch ( IOException ioe ) {
|
||||
Assert.assertFalse( BuildConfig.DEBUG );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static class QueueElem {
|
||||
Context context;
|
||||
byte[] msg;
|
||||
QueueElem( Context pContext, byte[] pMsg ) {
|
||||
context = pContext;
|
||||
msg = pMsg;
|
||||
}
|
||||
}
|
||||
|
||||
private static LinkedBlockingQueue<QueueElem> sQueue = null;
|
||||
synchronized static void addToMsgThread( Context context, byte[] msg )
|
||||
{
|
||||
if ( 0 < msg.length ) {
|
||||
QueueElem elem = new QueueElem( context, msg );
|
||||
if ( null == sQueue ) {
|
||||
sQueue = new LinkedBlockingQueue<>();
|
||||
new Thread( new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.d( TAG, "addToMsgThread(): run starting" );
|
||||
for ( ; ; ) {
|
||||
try {
|
||||
QueueElem elem = sQueue.take();
|
||||
NFCUtils.receiveMsgs( elem.context, elem.msg );
|
||||
updateStatus( elem.context, true );
|
||||
} catch ( InterruptedException ie ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Log.d( TAG, "addToMsgThread(): run exiting" );
|
||||
}
|
||||
} ).start();
|
||||
}
|
||||
sQueue.add( elem );
|
||||
// } else {
|
||||
// // This is very common right now
|
||||
// Log.d( TAG, "addToMsgThread(): dropping 0-length msg" );
|
||||
}
|
||||
}
|
||||
|
||||
public static class Wrapper {
|
||||
private Reader mReader;
|
||||
|
||||
public interface Procs {
|
||||
void onReadingChange( boolean nowReading );
|
||||
}
|
||||
|
||||
private Wrapper( Activity activity, Procs procs, int devID )
|
||||
{
|
||||
mReader = new Reader( activity, procs, devID );
|
||||
}
|
||||
|
||||
public static Wrapper init( Activity activity, Procs procs, int devID )
|
||||
{
|
||||
Wrapper instance = null;
|
||||
|
||||
if ( nfcAvail( activity )[1] ) {
|
||||
instance = new Wrapper( activity, procs, devID );
|
||||
}
|
||||
Log.d( TAG, "Wrapper.init(devID=%d) => %s", devID, instance );
|
||||
return instance;
|
||||
}
|
||||
|
||||
static void setResumed( Wrapper instance, boolean resumed )
|
||||
{
|
||||
if ( null != instance ) {
|
||||
instance.mReader.setResumed( resumed );
|
||||
}
|
||||
}
|
||||
|
||||
static void setGameID( Wrapper instance, int gameID )
|
||||
{
|
||||
if ( null != instance ) {
|
||||
instance.mReader.setGameID( gameID );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class Reader implements NfcAdapter.ReaderCallback,
|
||||
HaveDataListener {
|
||||
private Activity mActivity;
|
||||
private boolean mHaveData;
|
||||
private Wrapper.Procs mProcs;
|
||||
private NfcAdapter mAdapter;
|
||||
private int mMinMS = 300;
|
||||
private int mMaxMS = 500;
|
||||
private boolean mConnected = false;
|
||||
private int mMyDevID;
|
||||
|
||||
private Reader( Activity activity, Wrapper.Procs procs, int devID )
|
||||
{
|
||||
mActivity = activity;
|
||||
mProcs = procs;
|
||||
mMyDevID = devID;
|
||||
mAdapter = NfcAdapter.getDefaultAdapter( activity );
|
||||
}
|
||||
|
||||
private void setResumed( boolean resumed )
|
||||
{
|
||||
if ( resumed ) {
|
||||
startReadModeThread();
|
||||
} else {
|
||||
stopReadModeThread();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHaveDataChanged( boolean haveData )
|
||||
{
|
||||
if ( mHaveData != haveData ) {
|
||||
mHaveData = haveData;
|
||||
Log.d( TAG, "onHaveDataChanged(): mHaveData now %b", mHaveData );
|
||||
interruptThread();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean haveData()
|
||||
{
|
||||
boolean result = mHaveData;
|
||||
// Log.d( TAG, "haveData() => %b", result );
|
||||
return result;
|
||||
}
|
||||
|
||||
private int mGameID;
|
||||
private void setGameID( int gameID )
|
||||
{
|
||||
Log.d( TAG, "setGameID(%d)", gameID );
|
||||
mGameID = gameID;
|
||||
NFCUtils.setHaveDataListener( gameID, this );
|
||||
interruptThread();
|
||||
}
|
||||
|
||||
private void interruptThread()
|
||||
{
|
||||
synchronized ( mThreadRef ) {
|
||||
if ( null != mThreadRef[0] ) {
|
||||
mThreadRef[0].interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTagDiscovered( Tag tag )
|
||||
{
|
||||
mConnected = true;
|
||||
IsoDep isoDep = IsoDep.get( tag );
|
||||
try {
|
||||
isoDep.connect();
|
||||
int maxLen = isoDep.getMaxTransceiveLength();
|
||||
Log.d( TAG, "onTagDiscovered() connected; max len: %d", maxLen );
|
||||
byte[] aidBytes = Utils.hexStr2ba( BuildConfig.NFC_AID );
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
baos.write( Utils.hexStr2ba( "00A40400" ) );
|
||||
baos.write( (byte)aidBytes.length );
|
||||
baos.write( aidBytes );
|
||||
baos.write( VERSION_1 ); // min
|
||||
baos.write( VERSION_1 ); // max
|
||||
baos.write( numTo( mMyDevID ) );
|
||||
baos.write( numTo( mGameID ) );
|
||||
byte[] msg = baos.toByteArray();
|
||||
Assert.assertTrue( msg.length < maxLen || !BuildConfig.DEBUG );
|
||||
byte[] response = isoDep.transceive( msg );
|
||||
|
||||
// The first reply from transceive() is special. If it starts
|
||||
// with STATUS_SUCCESS then it also includes the version we'll
|
||||
// be using to communicate, either what we sent over or
|
||||
// something lower (for older code on the other side), and the
|
||||
// remote's deviceID
|
||||
if ( HEX_STR.STATUS_SUCCESS.matchesFrom( response ) ) {
|
||||
int offset = HEX_STR.STATUS_SUCCESS.length();
|
||||
byte version = response[offset++];
|
||||
if ( version == VERSION_1 ) {
|
||||
int[] out = {0};
|
||||
offset += numFrom( response, offset, out );
|
||||
Log.d( TAG, "onTagDiscovered(): read remote devID: %d",
|
||||
out[0] );
|
||||
runMessageLoop( isoDep, maxLen );
|
||||
} else {
|
||||
Log.e( TAG, "onTagDiscovered(): remote sent version %d, "
|
||||
+ "not %d; exiting", version, VERSION_1 );
|
||||
}
|
||||
}
|
||||
isoDep.close();
|
||||
} catch ( IOException ioe ) {
|
||||
Log.e( TAG, "got ioe: " + ioe.getMessage() );
|
||||
}
|
||||
|
||||
mConnected = false;
|
||||
interruptThread(); // make sure we leave read mode!
|
||||
Log.d( TAG, "onTagDiscovered() DONE" );
|
||||
}
|
||||
|
||||
private void runMessageLoop( IsoDep isoDep, int maxLen ) throws IOException
|
||||
{
|
||||
outer:
|
||||
for ( ; ; ) {
|
||||
MsgToken token = NFCUtils.getMsgsFor( mGameID );
|
||||
// PENDING: no need for this Math.min thing once well tested
|
||||
byte[][] toFit = wrapMsg( token, Math.min( 50, maxLen ) );
|
||||
for ( int ii = 0; ii < toFit.length; ++ii ) {
|
||||
byte[] one = toFit[ii];
|
||||
Assert.assertTrue( one.length < maxLen || !BuildConfig.DEBUG );
|
||||
byte[] response = isoDep.transceive( one );
|
||||
if ( ! receiveAny( response ) ) {
|
||||
break outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean receiveAny( byte[] response )
|
||||
{
|
||||
boolean statusOK = HEX_STR.STATUS_SUCCESS.matchesFrom( response );
|
||||
if ( statusOK ) {
|
||||
int offset = HEX_STR.STATUS_SUCCESS.length();
|
||||
if ( HEX_STR.CMD_MSG_PART.matchesFrom( response, offset ) ) {
|
||||
byte[] all = reassemble( mActivity, response,
|
||||
offset + HEX_STR.CMD_MSG_PART.length() );
|
||||
Log.d( TAG, "receiveAny(%s) => %b", DbgUtils.hexDump( response ), statusOK );
|
||||
if ( null != all ) {
|
||||
addToMsgThread( mActivity, all );
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( !statusOK ) {
|
||||
Log.d( TAG, "receiveAny(%s) => %b", DbgUtils.hexDump( response ), statusOK );
|
||||
}
|
||||
return statusOK;
|
||||
}
|
||||
|
||||
private class ReadModeThread extends Thread {
|
||||
private boolean mShouldStop = false;
|
||||
private boolean mInReadMode = false;
|
||||
private final int mFlags = NfcAdapter.FLAG_READER_NFC_A
|
||||
| NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK;
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
Log.d( TAG, "ReadModeThread.run() starting" );
|
||||
Random random = new Random();
|
||||
|
||||
while ( !mShouldStop ) {
|
||||
boolean wantReadMode = mConnected || !mInReadMode && haveData();
|
||||
if ( wantReadMode && !mInReadMode ) {
|
||||
mAdapter.enableReaderMode( mActivity, Reader.this, mFlags, null );
|
||||
} else if ( mInReadMode && !wantReadMode ) {
|
||||
mAdapter.disableReaderMode( mActivity );
|
||||
}
|
||||
mInReadMode = wantReadMode;
|
||||
Log.d( TAG, "run(): inReadMode now: %b", mInReadMode );
|
||||
|
||||
// Now sleep. If we aren't going to want to toggle read
|
||||
// mode soon, sleep until interrupted by a state change,
|
||||
// e.g. getting data or losing connection.
|
||||
long intervalMS = Long.MAX_VALUE;
|
||||
if ( (mInReadMode && !mConnected) || haveData() ) {
|
||||
intervalMS = mMinMS + (Math.abs(random.nextInt())
|
||||
% (mMaxMS - mMinMS));
|
||||
}
|
||||
try {
|
||||
Thread.sleep( intervalMS );
|
||||
} catch ( InterruptedException ie ) {
|
||||
Log.d( TAG, "run interrupted" );
|
||||
}
|
||||
}
|
||||
|
||||
// Kill read mode on the way out
|
||||
if ( mInReadMode ) {
|
||||
mAdapter.disableReaderMode( mActivity );
|
||||
mInReadMode = false;
|
||||
}
|
||||
|
||||
// Clear the reference only if it's me
|
||||
synchronized ( mThreadRef ) {
|
||||
if ( mThreadRef[0] == this ) {
|
||||
mThreadRef[0] = null;
|
||||
}
|
||||
}
|
||||
Log.d( TAG, "ReadModeThread.run() exiting" );
|
||||
}
|
||||
|
||||
public void doStop()
|
||||
{
|
||||
mShouldStop = true;
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private ReadModeThread[] mThreadRef = {null};
|
||||
private void startReadModeThread()
|
||||
{
|
||||
synchronized ( mThreadRef ) {
|
||||
if ( null == mThreadRef[0] ) {
|
||||
mThreadRef[0] = new ReadModeThread();
|
||||
mThreadRef[0].start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void stopReadModeThread()
|
||||
{
|
||||
ReadModeThread thread;
|
||||
synchronized ( mThreadRef ) {
|
||||
thread = mThreadRef[0];
|
||||
mThreadRef[0] = null;
|
||||
}
|
||||
|
||||
if ( null != thread ) {
|
||||
thread.doStop();
|
||||
try {
|
||||
thread.join();
|
||||
} catch ( InterruptedException ex ) {
|
||||
Log.d( TAG, "stopReadModeThread(): %s", ex );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class NFCServiceHelper extends XWServiceHelper {
|
||||
private CommsAddrRec mAddr
|
||||
= new CommsAddrRec( CommsAddrRec.CommsConnType.COMMS_CONN_NFC );
|
||||
|
|
Loading…
Reference in a new issue