fix bluetooth bootstrapping

Separate processing of sockets from accepting them so that when an ACL
CONN notification is received and we open a socket (but don't yet have a
Service running because the ACL thing is most likely for some other app)
we can set it aside to be processed once we do have a service. Use the
same block-until-non-null thing as in RelayService to keep that thread
free of NPEs.
This commit is contained in:
Eric House 2018-12-11 14:38:40 -08:00
parent 48c06acef5
commit 68bb8a1268

View file

@ -599,14 +599,13 @@ public class BTService extends XWService {
private static BTListenerThread[] s_listener = {null}; private static BTListenerThread[] s_listener = {null};
private BluetoothServerSocket m_serverSocket; private BluetoothServerSocket m_serverSocket;
private Context mContext; private Context mContext;
private BTService mService; private BTService[] mServiceHolder = { null };
private BTServiceHelper mHelper;
private volatile Thread mTimerThread; private volatile Thread mTimerThread;
private BTListenerThread( Context context, BTService service ) private BTListenerThread( Context context, BTService service )
{ {
mContext = context; mContext = context;
mService = service; mServiceHolder[0] = service;
// Started without a BTService instance? Run for only a little // Started without a BTService instance? Run for only a little
// while // while
@ -615,8 +614,7 @@ public class BTService extends XWService {
} }
} }
static void startYourself( Context context, BTService service, static void startYourself( Context context, BTService service )
BTServiceHelper helper )
{ {
Log.d( TAG, "startYourself(%s, %s)", context, service ); Log.d( TAG, "startYourself(%s, %s)", context, service );
DbgUtils.assertOnUIThread(); DbgUtils.assertOnUIThread();
@ -625,7 +623,7 @@ public class BTService extends XWService {
s_listener[0] = new BTListenerThread( context, service ); s_listener[0] = new BTListenerThread( context, service );
s_listener[0].start(); s_listener[0].start();
} else if ( null != service ) { } else if ( null != service ) {
s_listener[0].setService( context, service, helper ); s_listener[0].setService( context, service );
} }
} }
} }
@ -633,7 +631,7 @@ public class BTService extends XWService {
static void startYourself( Context context ) static void startYourself( Context context )
{ {
DbgUtils.assertOnUIThread(); DbgUtils.assertOnUIThread();
startYourself( context, null, null ); startYourself( context, null );
} }
static void stopYourself( BTListenerThread self ) static void stopYourself( BTListenerThread self )
@ -655,16 +653,20 @@ public class BTService extends XWService {
} }
} }
void setService( Context context, BTService service, BTServiceHelper helper ) void setService( Context context, BTService service )
{ {
if ( null == mService ) { synchronized ( mServiceHolder ) {
Log.d( TAG, "setService(): we didn't have one before. Do something!!!" ); if ( null == mServiceHolder[0] ) {
mService = service; Log.d( TAG, "setService(): setting. notifying all!!!" );
mHelper = helper;
Assert.assertNotNull( context ); Assert.assertNotNull( context );
mContext = context; // Use Service instead of Receiver (possibly) mContext = context; // Use Service instead of Receiver (possibly)
mServiceHolder[0] = service;
if ( service != null ) {
mServiceHolder.notifyAll();
}
} else { } else {
Assert.assertTrue( service == mService ); Assert.assertTrue( service == mServiceHolder[0] );
}
} }
} }
@ -672,13 +674,18 @@ public class BTService extends XWService {
{ {
stopKillTimer(); stopKillTimer();
if (! inForeground() ) {
startService( mContext, startService( mContext,
getIntentTo( mContext, getIntentTo( mContext,
BTAction.START_BACKGROUND ) ); BTAction.START_BACKGROUND ) );
} else { }
// Will be a race condition maybe?
Assert.assertFalse( BuildConfig.DEBUG ); private BTService blockForService() throws InterruptedException
{
synchronized( mServiceHolder ) {
while ( mServiceHolder[0] == null ) {
mServiceHolder.wait();
}
return mServiceHolder[0];
} }
} }
@ -694,7 +701,7 @@ public class BTService extends XWService {
Thread.sleep( 10 * 1000 ); Thread.sleep( 10 * 1000 );
stopYourself( BTListenerThread.this ); stopYourself( BTListenerThread.this );
} catch (InterruptedException ie) { } catch (InterruptedException ie) {
Log.e( TAG, "timer thread interrupted; exiting" ); Log.e( TAG, "kill timer thread exiting; we're live!" );
} }
} }
} ); } );
@ -733,40 +740,16 @@ public class BTService extends XWService {
BTCmd cmd = BTCmd.values()[inStream.readByte()]; BTCmd cmd = BTCmd.values()[inStream.readByte()];
Log.d( TAG, "BTListenerThread() got %s", cmd ); Log.d( TAG, "BTListenerThread() got %s", cmd );
if ( protoOK( proto, cmd ) ) { if ( protoOK( proto, cmd ) ) {
if ( null == mService ) { if ( null == mServiceHolder[0] ) {
Log.d( TAG, "dropping packet; starting service" );
startTheService(); startTheService();
} else {
switch( cmd ) {
case PING:
receivePing( socket );
break;
case INVITE:
receiveInvitation( proto, inStream, socket );
break;
case MESG_SEND:
receiveMessage( cmd, inStream, socket );
break;
case MESG_GAMEGONE:
receiveMessage( cmd, inStream, socket );
break;
default:
Log.e( TAG, "unexpected msg %s", cmd.toString());
break;
}
mService.updateStatusIn( true );
noteLastUsed( mContext );
} }
processWhenReady( socket, proto, inStream, cmd );
} else { } else {
DataOutputStream os = DataOutputStream os =
new DataOutputStream( socket.getOutputStream() ); new DataOutputStream( socket.getOutputStream() );
os.writeByte( BTCmd.BAD_PROTO.ordinal() ); os.writeByte( BTCmd.BAD_PROTO.ordinal() );
os.flush(); os.flush();
socket.close(); socket.close();
mService.sendBadProto( socket );
} }
} catch ( IOException ioe ) { } catch ( IOException ioe ) {
Log.w( TAG, "trying again..." ); Log.w( TAG, "trying again..." );
@ -787,6 +770,75 @@ public class BTService extends XWService {
} }
} // run() } // run()
private static class InPacket {
BluetoothSocket mSocket;
byte mProto;
DataInputStream mInStream;
BTCmd mCmd;
InPacket( BluetoothSocket socket, byte proto,
DataInputStream inStream, BTCmd cmd )
{
mSocket = socket;
mProto = proto;
mInStream = inStream;
mCmd = cmd;
}
}
private LinkedBlockingQueue<InPacket> mInQueue = new LinkedBlockingQueue<>();
private Thread mIgnored;
private void processWhenReady( BluetoothSocket socket, byte proto,
DataInputStream inStream, BTCmd cmd )
{
if ( mIgnored == null ) {
mIgnored = new Thread( new Runnable() {
@Override
public void run() {
for ( ; ; ) {
try {
InPacket elem = mInQueue.take();
BTService service = blockForService();
BTCmd cmd = elem.mCmd; // fired
switch( cmd ) {
case PING:
receivePing( service, elem.mSocket );
break;
case INVITE:
receiveInvitation( service, elem.mProto,
elem.mInStream, elem.mSocket );
break;
case MESG_SEND:
receiveMessage( service, cmd, elem.mInStream,
elem.mSocket );
break;
case MESG_GAMEGONE:
receiveMessage( service, cmd, elem.mInStream,
elem.mSocket );
break;
default:
Log.e( TAG, "unexpected msg %s", cmd.toString());
break;
}
service.updateStatusIn( true );
noteLastUsed( service );
} catch (IOException | InterruptedException ex) {
DbgUtils.printStack( TAG, ex );
break;
}
}
}
} );
mIgnored.start();
}
mInQueue.add( new InPacket( socket, proto, inStream, cmd ) );
}
public void stopListening() public void stopListening()
{ {
if ( null != m_serverSocket ) { if ( null != m_serverSocket ) {
@ -806,7 +858,7 @@ public class BTService extends XWService {
return ok; return ok;
} }
private void receivePing( BluetoothSocket socket ) throws IOException private void receivePing( BTService service, BluetoothSocket socket ) throws IOException
{ {
DataInputStream inStream = new DataInputStream( socket.getInputStream() ); DataInputStream inStream = new DataInputStream( socket.getInputStream() );
int gameID = inStream.readInt(); int gameID = inStream.readInt();
@ -818,11 +870,11 @@ public class BTService extends XWService {
os.flush(); os.flush();
socket.close(); socket.close();
mService.updateStatusOut( true ); service.updateStatusOut( true );
} }
private void receiveInvitation( byte proto, DataInputStream is, private void receiveInvitation( BTService service, byte proto,
BluetoothSocket socket ) DataInputStream is, BluetoothSocket socket )
throws IOException throws IOException
{ {
BTCmd result; BTCmd result;
@ -838,7 +890,8 @@ public class BTService extends XWService {
} }
BluetoothDevice host = socket.getRemoteDevice(); BluetoothDevice host = socket.getRemoteDevice();
result = mService.makeOrNotify( nli, host.getName(), host.getAddress() ); result = service
.makeOrNotify( nli, host.getName(), host.getAddress() );
DataOutputStream os = new DataOutputStream( socket.getOutputStream() ); DataOutputStream os = new DataOutputStream( socket.getOutputStream() );
os.writeByte( result.ordinal() ); os.writeByte( result.ordinal() );
@ -847,7 +900,8 @@ public class BTService extends XWService {
socket.close(); socket.close();
} // receiveInvitation } // receiveInvitation
private void receiveMessage( BTCmd cmd, DataInputStream dis, BluetoothSocket socket ) private void receiveMessage( BTService service, BTCmd cmd,
DataInputStream dis, BluetoothSocket socket )
{ {
try { try {
BTCmd result = null; BTCmd result = null;
@ -861,15 +915,15 @@ public class BTService extends XWService {
CommsAddrRec addr = new CommsAddrRec( host.getName(), CommsAddrRec addr = new CommsAddrRec( host.getName(),
host.getAddress() ); host.getAddress() );
XWServiceHelper.ReceiveResult rslt XWServiceHelper.ReceiveResult rslt
= mHelper.receiveMessage( mContext, = service.mHelper.receiveMessage( mContext, gameID,
gameID, mService.m_btMsgSink, service.m_btMsgSink,
buffer, addr ); buffer, addr );
result = rslt == XWServiceHelper.ReceiveResult.GAME_GONE ? result = rslt == XWServiceHelper.ReceiveResult.GAME_GONE ?
BTCmd.MESG_GAMEGONE : BTCmd.MESG_ACCPT; BTCmd.MESG_GAMEGONE : BTCmd.MESG_ACCPT;
break; break;
case MESG_GAMEGONE: case MESG_GAMEGONE:
mHelper.postEvent( MultiEvent.MESSAGE_NOGAME, gameID ); service.mHelper.postEvent( MultiEvent.MESSAGE_NOGAME, gameID );
result = BTCmd.MESG_ACCPT; result = BTCmd.MESG_ACCPT;
break; break;
default: default:
@ -1043,11 +1097,12 @@ public class BTService extends XWService {
socket.close(); socket.close();
} }
} catch ( IOException ioe ) { } catch ( IOException ioe ) {
logIOE( ioe ); Log.e( TAG, "sendPing() failure; %s", ioe.getMessage() );
DbgUtils.printStack( TAG, ioe );
} }
updateStatusOut( sendWorking ); updateStatusOut( sendWorking );
updateStatusIn( receiveWorking ); updateStatusIn( receiveWorking );
Log.d( TAG, "sendPing(%s) => %b", dev, gotReply ); Log.d( TAG, "sendPing(%s) => %b", dev.getName(), gotReply );
return gotReply; return gotReply;
} // sendPing } // sendPing
@ -1273,7 +1328,7 @@ public class BTService extends XWService {
private void startListener() private void startListener()
{ {
Assert.assertNotNull( mHelper ); Assert.assertNotNull( mHelper );
BTListenerThread.startYourself( this, this, mHelper ); BTListenerThread.startYourself( this, this );
} }
private void startSender() private void startSender()
@ -1314,29 +1369,34 @@ public class BTService extends XWService {
private DataOutputStream connect( BluetoothSocket socket, BTCmd cmd ) private DataOutputStream connect( BluetoothSocket socket, BTCmd cmd )
{ {
String name = socket.getRemoteDevice().getName(); String name = socket.getRemoteDevice().getName();
Log.w( TAG, "connect(%s) starting", name );
// DbgUtils.logf( "connecting to %s to send cmd %s", name, cmd.toString() ); // DbgUtils.logf( "connecting to %s to send cmd %s", name, cmd.toString() );
// Docs say always call cancelDiscovery before trying to connect // Docs say always call cancelDiscovery before trying to connect
m_adapter.cancelDiscovery(); m_adapter.cancelDiscovery();
DataOutputStream dos; DataOutputStream dos = null;
try {
for ( int ii = 0; ii < 3; ++ii ) { // Try for 8 seconds. Some devices take a long time to get ACL conn
// ACTION
for ( long end = 10000 + System.currentTimeMillis(); ; ) {
try { try {
socket.connect(); socket.connect();
break; Log.i( TAG, "connect(%s) succeeded", name );
} catch (IOException ioe) {
Thread.sleep( 2000 );
Log.d( TAG, "ioe on connect(); trying again" );
}
}
dos = new DataOutputStream( socket.getOutputStream() ); dos = new DataOutputStream( socket.getOutputStream() );
dos.writeByte( BT_PROTO ); dos.writeByte( BT_PROTO );
dos.writeByte( cmd.ordinal() ); dos.writeByte( cmd.ordinal() );
Log.i( TAG, "connect() to %s successful", name ); break; // success!!!
} catch ( IOException | InterruptedException ioe ) { } catch (IOException ioe) {
dos = null; if ( System.currentTimeMillis() > end ) {
Log.w( TAG, "BTService.connect() to %s failed", name ); break;
// logIOE( ioe ); }
Log.d( TAG, "connect(%s) trying again", name );
try {
Thread.sleep( 1000 );
} catch ( InterruptedException ex ) {
break;
}
}
} }
return dos; return dos;
} }
@ -1374,7 +1434,6 @@ public class BTService extends XWService {
mHandler.postAtTime( new Runnable() { mHandler.postAtTime( new Runnable() {
@Override @Override
public void run() { public void run() {
Log.d( TAG, "timeout timer fired" );
setTimeoutTimer(); setTimeoutTimer();
} }
}, this, dieTimeMillis ); }, this, dieTimeMillis );