launch service from ACTION_ACL_CONNECTED

Fire up the receiver thread, and start the service, on receipt of this
ACTION (if they're not already running.) On start, the service takes
over the thread and begins dispatching messages. Works to launch the app
when it's not running and in most cases, though messages received before
the service launches are currently dropped, things seem to work.
This commit is contained in:
Eric House 2018-12-05 20:14:57 -08:00
parent 3dbec328af
commit 68ae9790b4
2 changed files with 166 additions and 46 deletions

View file

@ -38,7 +38,7 @@ public class BTReceiver extends BroadcastReceiver {
action, intent.toString() ); action, intent.toString() );
if ( action.equals( BluetoothDevice.ACTION_ACL_CONNECTED ) ) { if ( action.equals( BluetoothDevice.ACTION_ACL_CONNECTED ) ) {
BTService.startService( context ); BTService.onACLConnected( context );
} else if ( action.equals( BluetoothAdapter.ACTION_STATE_CHANGED ) ) { } else if ( action.equals( BluetoothAdapter.ACTION_STATE_CHANGED ) ) {
int newState = int newState =
intent.getIntExtra( BluetoothAdapter.EXTRA_STATE, -1 ); intent.getIntExtra( BluetoothAdapter.EXTRA_STATE, -1 );

View file

@ -79,7 +79,8 @@ public class BTService extends XWService {
private static final String TAG = BTService.class.getSimpleName(); private static final String TAG = BTService.class.getSimpleName();
private static final String BOGUS_MARSHMALLOW_ADDR = "02:00:00:00:00:00"; private static final String BOGUS_MARSHMALLOW_ADDR = "02:00:00:00:00:00";
private static final String KEY_KEEPALIVE_UNTIL_SECS = "keep_secs"; private static final String KEY_KEEPALIVE_UNTIL_SECS = "keep_secs";
private static int DEFAULT_KEEPALIVE_SECONDS = 60 * 1; // 1 minute for testing; maybe 15 on ship? // half minute for testing; maybe 15 on ship? Or make it a debug config.
private static int DEFAULT_KEEPALIVE_SECONDS = 30;
private static final long RESEND_TIMEOUT = 5; // seconds private static final long RESEND_TIMEOUT = 5; // seconds
private static final int MAX_SEND_FAIL = 3; private static final int MAX_SEND_FAIL = 3;
@ -187,7 +188,6 @@ public class BTService extends XWService {
private BluetoothAdapter m_adapter; private BluetoothAdapter m_adapter;
private BTMsgSink m_btMsgSink; private BTMsgSink m_btMsgSink;
private BTListenerThread m_listener;
private BTSenderThread m_sender; private BTSenderThread m_sender;
private Notification m_notification; // make once use many private Notification m_notification; // make once use many
private Handler mHandler; private Handler mHandler;
@ -261,11 +261,10 @@ public class BTService extends XWService {
if ( null == sInForeground || inForeground() != inForeground ) { if ( null == sInForeground || inForeground() != inForeground ) {
sInForeground = inForeground; sInForeground = inForeground;
Intent intent = startService( context,
getIntentTo( context, getIntentTo( context,
inForeground ? BTAction.START_FOREGROUND inForeground ? BTAction.START_FOREGROUND
: BTAction.START_BACKGROUND ); : BTAction.START_BACKGROUND ) );;
startService( context, intent );
} }
} }
@ -293,6 +292,12 @@ public class BTService extends XWService {
} }
} }
public static void onACLConnected( Context context )
{
Log.d( TAG, "onACLConnected()" );
BTListenerThread.startYourself( context );
}
public static void radioChanged( Context context, boolean cameOn ) public static void radioChanged( Context context, boolean cameOn )
{ {
Intent intent = getIntentTo( context, BTAction.RADIO ); Intent intent = getIntentTo( context, BTAction.RADIO );
@ -309,8 +314,7 @@ public class BTService extends XWService {
public static void scan( Context context ) public static void scan( Context context )
{ {
Intent intent = getIntentTo( context, BTAction.SCAN ); startService( context, getIntentTo( context, BTAction.SCAN ) );
startService( context, intent );
} }
public static void pingHost( Context context, String hostAddr, int gameID ) public static void pingHost( Context context, String hostAddr, int gameID )
@ -411,6 +415,10 @@ public class BTService extends XWService {
@Override @Override
public void onCreate() public void onCreate()
{ {
Log.d( TAG, "%s.onCreate()", this );
super.onCreate();
m_btMsgSink = new BTMsgSink();
mHandler = new Handler(); mHandler = new Handler();
startForegroundIf(); startForegroundIf();
@ -438,24 +446,33 @@ public class BTService extends XWService {
return result; return result;
} }
@Override
public void onDestroy()
{
Log.d( TAG, "%s.onDestroy()", this );
super.onDestroy();
}
private int handleCommand( Intent intent ) private int handleCommand( Intent intent )
{ {
int result; int result;
if ( XWApp.BTSUPPORTED && null != intent ) { if ( XWApp.BTSUPPORTED && null != intent ) {
int ordinal = intent.getIntExtra( CMD_KEY, -1 ); int ordinal = intent.getIntExtra( CMD_KEY, -1 );
if ( -1 == ordinal ) { if ( -1 == ordinal ) {
Log.d( TAG, "handleCommand(): no ordinal!" );
// Drop it // Drop it
} else if ( null == m_sender ) { } else if ( null == m_sender ) {
Log.w( TAG, "exiting: m_queue is null" ); Log.w( TAG, "exiting: m_queue is null" );
stopSelf(); stopSelf();
} else { } else {
BTAction cmd = BTAction.values()[ordinal]; BTAction cmd = BTAction.values()[ordinal];
Log.i( TAG, "onStartCommand; cmd=%s", cmd.toString() ); Log.i( TAG, "handleCommand; cmd=%s", cmd.toString() );
switch( cmd ) { switch( cmd ) {
case START_FOREGROUND: case START_FOREGROUND:
stopForeground( true ); // Kill the notification stopForeground( true ); // Kill the notification
break; break;
case START_BACKGROUND: case START_BACKGROUND:
startListener();
noteLastUsed( this ); // prevent timer from killing immediately noteLastUsed( this ); // prevent timer from killing immediately
setTimeoutTimer(); setTimeoutTimer();
break; break;
@ -469,7 +486,7 @@ public class BTService extends XWService {
case INVITE: case INVITE:
String jsonData = intent.getStringExtra( GAMEDATA_KEY ); String jsonData = intent.getStringExtra( GAMEDATA_KEY );
NetLaunchInfo nli = NetLaunchInfo.makeFrom( this, jsonData ); NetLaunchInfo nli = NetLaunchInfo.makeFrom( this, jsonData );
Log.i( TAG, "onStartCommand: nli: %s", nli.toString() ); Log.i( TAG, "handleCommand: nli: %s", nli.toString() );
String btAddr = intent.getStringExtra( ADDR_KEY ); String btAddr = intent.getStringExtra( ADDR_KEY );
m_sender.add( new BTQueueElem( BTCmd.INVITE, nli, btAddr ) ); m_sender.add( new BTQueueElem( BTCmd.INVITE, nli, btAddr ) );
break; break;
@ -559,14 +576,115 @@ public class BTService extends XWService {
} }
private static class BTListenerThread extends Thread { private static class BTListenerThread extends Thread {
// Wrap so we can synchronize on the container
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 mService;
private volatile Thread mTimerThread;
BTListenerThread( Context context, BTService service ) private BTListenerThread( Context context, BTService service )
{ {
mContext = context; mContext = context;
mService = service; mService = service;
// Started without a BTService instance? Run for only a little
// while
if ( null == service ) {
startKillTimer();
}
}
static void startYourself( Context context, BTService service )
{
Log.d( TAG, "startYourself(%s, %s)", context, service );
DbgUtils.assertOnUIThread();
synchronized ( s_listener ) {
if ( s_listener[0] == null ) {
s_listener[0] = new BTListenerThread( context, service );
s_listener[0].start();
} else if ( null != service ) {
s_listener[0].setService( context, service );
}
}
}
static void startYourself( Context context )
{
DbgUtils.assertOnUIThread();
startYourself( context, null );
}
static void stopYourself( BTListenerThread self )
{
Log.d( TAG, "stopYourself()" );
BTListenerThread listener;
synchronized ( s_listener ) {
listener = s_listener[0];
s_listener[0] = null;
}
if ( null != listener ) {
Assert.assertTrue( self == null || self == listener );
listener.stopListening();
try {
listener.join( 100 );
} catch ( InterruptedException ie ) {
Log.ex( TAG, ie );
}
}
}
void setService( Context context, BTService service )
{
if ( null == mService ) {
Log.d( TAG, "setService(): we didn't have one before. Do something!!!" );
mService = service;
Assert.assertNotNull( context );
mContext = context; // Use Service instead of Receiver (possibly)
} else {
Assert.assertTrue( service == mService );
}
}
private void startTheService()
{
stopKillTimer();
if (! inForeground() ) {
startService( mContext,
getIntentTo( mContext,
BTAction.START_BACKGROUND ) );
} else {
// Will be a race condition maybe?
Assert.assertFalse( BuildConfig.DEBUG );
}
}
private synchronized void startKillTimer()
{
Assert.assertNull( mTimerThread );
mTimerThread = new Thread( new Runnable() {
@Override
public void run() {
try {
// Give legitimate caller 10 seconds to retry with
// a packet that we'll recognize.
Thread.sleep( 10 * 1000 );
stopYourself( BTListenerThread.this );
} catch (InterruptedException ie) {
Log.e( TAG, "timer thread interrupted; exiting" );
}
}
} );
mTimerThread.start();
}
private synchronized void stopKillTimer()
{
if ( mTimerThread != null ) {
mTimerThread.interrupt();
mTimerThread = null;
}
} }
@Override @Override
@ -593,27 +711,32 @@ 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 ) ) {
switch( cmd ) { if ( null == mService ) {
case PING: Log.d( TAG, "dropping packet; starting service" );
receivePing( socket ); startTheService();
break; } else {
case INVITE: switch( cmd ) {
receiveInvitation( proto, inStream, socket ); case PING:
break; receivePing( socket );
case MESG_SEND: break;
receiveMessage( cmd, inStream, socket ); case INVITE:
break; receiveInvitation( proto, inStream, socket );
break;
case MESG_SEND:
receiveMessage( cmd, inStream, socket );
break;
case MESG_GAMEGONE: case MESG_GAMEGONE:
receiveMessage( cmd, inStream, socket ); receiveMessage( cmd, inStream, socket );
break; break;
default: default:
Log.e( TAG, "unexpected msg %s", cmd.toString()); Log.e( TAG, "unexpected msg %s", cmd.toString());
break; break;
}
mService.updateStatusIn( true );
noteLastUsed( mContext );
} }
mService.updateStatusIn( true );
noteLastUsed( mContext );
} else { } else {
DataOutputStream os = DataOutputStream os =
new DataOutputStream( socket.getOutputStream() ); new DataOutputStream( socket.getOutputStream() );
@ -1118,9 +1241,7 @@ public class BTService extends XWService {
private void startListener() private void startListener()
{ {
m_btMsgSink = new BTMsgSink(); BTListenerThread.startYourself( this, this );
m_listener = new BTListenerThread( this, this );
m_listener.start();
} }
private void startSender() private void startSender()
@ -1131,16 +1252,7 @@ public class BTService extends XWService {
private void stopListener() private void stopListener()
{ {
BTListenerThread listener = m_listener; BTListenerThread.stopYourself( null );
if ( listener != null ) {
m_listener = null;
listener.stopListening();
try {
listener.join( 100 );
} catch ( InterruptedException ie ) {
Log.ex( TAG, ie );
}
}
} }
private void stopSender() private void stopSender()
@ -1192,12 +1304,20 @@ public class BTService extends XWService {
DataOutputStream dos; DataOutputStream dos;
try { try {
socket.connect(); for ( int ii = 0; ii < 3; ++ii ) {
try {
socket.connect();
break;
} 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 ); Log.i( TAG, "connect() to %s successful", name );
} catch ( IOException ioe ) { } catch ( IOException | InterruptedException ioe ) {
dos = null; dos = null;
Log.w( TAG, "BTService.connect() to %s failed", name ); Log.w( TAG, "BTService.connect() to %s failed", name );
// logIOE( ioe ); // logIOE( ioe );