From 68ae9790b4e55cb867533b85a2c818835073fd94 Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 5 Dec 2018 20:14:57 -0800 Subject: [PATCH] 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. --- .../org/eehouse/android/xw4/BTReceiver.java | 2 +- .../org/eehouse/android/xw4/BTService.java | 210 ++++++++++++++---- 2 files changed, 166 insertions(+), 46 deletions(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BTReceiver.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BTReceiver.java index 3912d0c82..19b21e6a9 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BTReceiver.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BTReceiver.java @@ -38,7 +38,7 @@ public class BTReceiver extends BroadcastReceiver { action, intent.toString() ); if ( action.equals( BluetoothDevice.ACTION_ACL_CONNECTED ) ) { - BTService.startService( context ); + BTService.onACLConnected( context ); } else if ( action.equals( BluetoothAdapter.ACTION_STATE_CHANGED ) ) { int newState = intent.getIntExtra( BluetoothAdapter.EXTRA_STATE, -1 ); diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BTService.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BTService.java index ec463e3a1..6a0cb9b67 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BTService.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BTService.java @@ -79,7 +79,8 @@ public class BTService extends XWService { 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 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 int MAX_SEND_FAIL = 3; @@ -187,7 +188,6 @@ public class BTService extends XWService { private BluetoothAdapter m_adapter; private BTMsgSink m_btMsgSink; - private BTListenerThread m_listener; private BTSenderThread m_sender; private Notification m_notification; // make once use many private Handler mHandler; @@ -261,11 +261,10 @@ public class BTService extends XWService { if ( null == sInForeground || inForeground() != inForeground ) { sInForeground = inForeground; - Intent intent = - getIntentTo( context, - inForeground ? BTAction.START_FOREGROUND - : BTAction.START_BACKGROUND ); - startService( context, intent ); + startService( context, + getIntentTo( context, + inForeground ? BTAction.START_FOREGROUND + : BTAction.START_BACKGROUND ) );; } } @@ -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 ) { Intent intent = getIntentTo( context, BTAction.RADIO ); @@ -309,8 +314,7 @@ public class BTService extends XWService { public static void scan( Context context ) { - Intent intent = getIntentTo( context, BTAction.SCAN ); - startService( context, intent ); + startService( context, getIntentTo( context, BTAction.SCAN ) ); } public static void pingHost( Context context, String hostAddr, int gameID ) @@ -411,6 +415,10 @@ public class BTService extends XWService { @Override public void onCreate() { + Log.d( TAG, "%s.onCreate()", this ); + super.onCreate(); + + m_btMsgSink = new BTMsgSink(); mHandler = new Handler(); startForegroundIf(); @@ -438,24 +446,33 @@ public class BTService extends XWService { return result; } + @Override + public void onDestroy() + { + Log.d( TAG, "%s.onDestroy()", this ); + super.onDestroy(); + } + private int handleCommand( Intent intent ) { int result; if ( XWApp.BTSUPPORTED && null != intent ) { int ordinal = intent.getIntExtra( CMD_KEY, -1 ); if ( -1 == ordinal ) { + Log.d( TAG, "handleCommand(): no ordinal!" ); // Drop it } else if ( null == m_sender ) { Log.w( TAG, "exiting: m_queue is null" ); stopSelf(); } else { BTAction cmd = BTAction.values()[ordinal]; - Log.i( TAG, "onStartCommand; cmd=%s", cmd.toString() ); + Log.i( TAG, "handleCommand; cmd=%s", cmd.toString() ); switch( cmd ) { case START_FOREGROUND: stopForeground( true ); // Kill the notification break; case START_BACKGROUND: + startListener(); noteLastUsed( this ); // prevent timer from killing immediately setTimeoutTimer(); break; @@ -469,7 +486,7 @@ public class BTService extends XWService { case INVITE: String jsonData = intent.getStringExtra( GAMEDATA_KEY ); 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 ); m_sender.add( new BTQueueElem( BTCmd.INVITE, nli, btAddr ) ); break; @@ -559,14 +576,115 @@ public class BTService extends XWService { } 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 Context mContext; private BTService mService; + private volatile Thread mTimerThread; - BTListenerThread( Context context, BTService service ) + private BTListenerThread( Context context, BTService service ) { mContext = context; 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 @@ -593,27 +711,32 @@ public class BTService extends XWService { BTCmd cmd = BTCmd.values()[inStream.readByte()]; Log.d( TAG, "BTListenerThread() got %s", cmd ); if ( protoOK( proto, cmd ) ) { - switch( cmd ) { - case PING: - receivePing( socket ); - break; - case INVITE: - receiveInvitation( proto, inStream, socket ); - break; - case MESG_SEND: - receiveMessage( cmd, inStream, socket ); - break; + if ( null == mService ) { + Log.d( TAG, "dropping packet; starting service" ); + 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; + case MESG_GAMEGONE: + receiveMessage( cmd, inStream, socket ); + break; - default: - Log.e( TAG, "unexpected msg %s", cmd.toString()); - break; + default: + Log.e( TAG, "unexpected msg %s", cmd.toString()); + break; + } + mService.updateStatusIn( true ); + noteLastUsed( mContext ); } - mService.updateStatusIn( true ); - noteLastUsed( mContext ); } else { DataOutputStream os = new DataOutputStream( socket.getOutputStream() ); @@ -1118,9 +1241,7 @@ public class BTService extends XWService { private void startListener() { - m_btMsgSink = new BTMsgSink(); - m_listener = new BTListenerThread( this, this ); - m_listener.start(); + BTListenerThread.startYourself( this, this ); } private void startSender() @@ -1131,16 +1252,7 @@ public class BTService extends XWService { private void stopListener() { - BTListenerThread listener = m_listener; - if ( listener != null ) { - m_listener = null; - listener.stopListening(); - try { - listener.join( 100 ); - } catch ( InterruptedException ie ) { - Log.ex( TAG, ie ); - } - } + BTListenerThread.stopYourself( null ); } private void stopSender() @@ -1192,12 +1304,20 @@ public class BTService extends XWService { DataOutputStream dos; 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.writeByte( BT_PROTO ); dos.writeByte( cmd.ordinal() ); Log.i( TAG, "connect() to %s successful", name ); - } catch ( IOException ioe ) { + } catch ( IOException | InterruptedException ioe ) { dos = null; Log.w( TAG, "BTService.connect() to %s failed", name ); // logIOE( ioe );