diff --git a/xwords4/android/app/src/main/AndroidManifest.xml b/xwords4/android/app/src/main/AndroidManifest.xml
index 41c34dec1..88c6786c6 100644
--- a/xwords4/android/app/src/main/AndroidManifest.xml
+++ b/xwords4/android/app/src/main/AndroidManifest.xml
@@ -212,9 +212,6 @@
-
-
-
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 c010b62ad..14b8fc61a 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
@@ -29,9 +29,6 @@ import android.content.Intent;
public class BTReceiver extends BroadcastReceiver {
private static final String TAG = BTReceiver.class.getSimpleName();
- // This string is also used in AndroidManifest (as a string literal)
- static final String ACTION_STOP_BT = "org.eehouse.android.ACTION_STOP_BT";
-
@Override
public void onReceive( Context context, Intent intent )
{
@@ -41,9 +38,6 @@ public class BTReceiver extends BroadcastReceiver {
action, intent.toString() );
switch (action ) {
- case ACTION_STOP_BT:
- BTService.stopBackground( context );
- break;
case BluetoothDevice.ACTION_ACL_CONNECTED:
BTService.onACLConnected( context );
break;
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 94fdfd4dc..74e6e3aea 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
@@ -76,10 +76,11 @@ import java.util.concurrent.TimeUnit;
// startForegroundService() until AFTER calling startForeground(). Doing so
// will cause a crash.
-public class BTService extends XWService {
+public class BTService extends XWJIService {
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 final static int sJobID = 218719979;
// half minute for testing; maybe 15 on ship? Or make it a debug config.
private static int DEFAULT_KEEPALIVE_SECONDS = 15 * 60;
private static int CONNECT_SLEEP_MS = 1000;
@@ -93,25 +94,29 @@ public class BTService extends XWService {
private static final int BT_PROTO_NLI = 2; // using binary/common form of NLI
private static final int BT_PROTO = BT_PROTO_JSONS; // change in a release or two
- private enum BTAction { _NONE,
- CANCEL_SERVICE,
- START_FOREGROUND,
- START_BACKGROUND,
- SCAN,
- INVITE,
- SEND,
- RADIO,
- REMOVE,
- NFCINVITE,
- PINGHOST,
+ private enum BTAction implements XWJICmds { _NONE,
+ ACL_CONN,
+ START_BACKGROUND,
+ SCAN,
+ INVITE,
+ SEND,
+ RADIO,
+ REMOVE,
+ PINGHOST,
+
+ // Pass to main work thread
+ MAKE_OR_NOTIFY,
+ RECEIVE_MSG,
+ POST_GAME_GONE,
};
- private static final String CMD_KEY = "CMD";
private static final String MSG_KEY = "MSG";
private static final String GAMENAME_KEY = "NAM";
private static final String ADDR_KEY = "ADR";
private static final String SCAN_TIMEOUT_KEY = "SCAN_TIMEOUT";
private static final String RADIO_KEY = "RDO";
+ private static final String SOCKET_REF = "SOCKET";
+ private static final String NLI_KEY = "NLI";
private static final String GAMEID_KEY = "GMI";
private static final String GAMEDATA_KEY = "GD";
@@ -123,8 +128,6 @@ public class BTService extends XWService {
private static final String BT_NAME_KEY = "BT_NAME";
private static final String BT_ADDRESS_KEY = "BT_ADDRESS";
- private static Boolean sInForeground;
-
private enum BTCmd {
BAD_PROTO,
PING,
@@ -197,7 +200,6 @@ public class BTService extends XWService {
private BluetoothAdapter m_adapter;
private BTMsgSink m_btMsgSink;
- private BTSenderThread m_sender;
private Notification m_notification; // make once use many
private Handler mHandler;
private BTServiceHelper mHelper;
@@ -265,67 +267,50 @@ public class BTService extends XWService {
return result;
}
- private static void onAppStateChange( Context context, boolean inForeground )
+ private static void writeBack( BluetoothSocket socket, BTCmd result )
{
- Log.d( TAG, "onAppStateChange(inForeground=%b)", inForeground );
- if ( null == sInForeground || inForeground() != inForeground ) {
- sInForeground = inForeground;
-
- startService( context,
- getIntentTo( context,
- inForeground ? BTAction.START_FOREGROUND
- : BTAction.START_BACKGROUND ) );
+ try {
+ DataOutputStream os = new DataOutputStream( socket.getOutputStream() );
+ os.writeByte( result.ordinal() );
+ os.flush();
+ } catch ( IOException ex ) {
+ Log.ex( TAG, ex );
}
}
- private static boolean inForeground()
+ private static void enqueueWork( Intent intent )
{
- boolean result = sInForeground != null && sInForeground;
- // Log.d( TAG, "inForeground() => %b", result );
- return result;
+ Context context = XWApp.getContext();
+ enqueueWork( context, intent );
}
- static void onAppToForeground( Context context )
+ private static void enqueueWork( Context context, Intent intent )
{
- onAppStateChange( context, true );
- }
-
- static void onAppToBackground( Context context )
- {
- onAppStateChange( context, false );
- }
-
- public static void startService( Context context )
- {
- if ( XWApp.BTSUPPORTED ) {
- startService( context, new Intent( context, BTService.class ) );
- }
+ enqueueWork( context, BTService.class, sJobID, intent );
+ Log.d( TAG, "called enqueueWork(cmd=%s)",
+ cmdFrom( intent, BTAction.values() ) );
}
public static void onACLConnected( Context context )
{
Log.d( TAG, "onACLConnected()" );
- BTListenerThread.startYourself( context );
- }
+ enqueueWork( context,
+ getIntentTo( context, BTAction.ACL_CONN ) );
- public static void stopBackground( Context context )
- {
- startService( context,
- getIntentTo( context, BTAction.CANCEL_SERVICE ) );
}
public static void radioChanged( Context context, boolean cameOn )
{
Intent intent = getIntentTo( context, BTAction.RADIO )
.putExtra( RADIO_KEY, cameOn );
- startService( context, intent );
+ enqueueWork( context, intent );
}
public static void scan( Context context, int timeoutMS )
{
Intent intenet = getIntentTo( context, BTAction.SCAN )
.putExtra( SCAN_TIMEOUT_KEY, timeoutMS );
- startService( context, intenet );
+ enqueueWork( context, intenet );
}
public static void pingHost( Context context, String hostAddr, int gameID )
@@ -334,7 +319,7 @@ public class BTService extends XWService {
Intent intent = getIntentTo( context, BTAction.PINGHOST )
.putExtra( ADDR_KEY, hostAddr )
.putExtra( GAMEID_KEY, gameID );
- startService( context, intent );
+ enqueueWork( context, intent );
}
public static void inviteRemote( Context context, String btAddr,
@@ -344,20 +329,7 @@ public class BTService extends XWService {
Intent intent = getIntentTo( context, BTAction.INVITE )
.putExtra( GAMEDATA_KEY, nli.toString() )
.putExtra( ADDR_KEY, btAddr );
- startService( context, intent );
- }
-
- public static void gotGameViaNFC( Context context, NetLaunchInfo bli )
- {
- Intent intent = getIntentTo( context, BTAction.NFCINVITE )
- .putExtra( GAMEID_KEY, bli.gameID() )
- .putExtra( DICT_KEY, bli.dict )
- .putExtra( LANG_KEY, bli.lang )
- .putExtra( NTO_KEY, bli.nPlayersT )
- .putExtra( BT_NAME_KEY, bli.btName )
- .putExtra( BT_ADDRESS_KEY, bli.btAddress )
- ;
- startService( context, intent );
+ enqueueWork( context, intent );
}
public static int enqueueFor( Context context, byte[] buf,
@@ -371,7 +343,7 @@ public class BTService extends XWService {
intent.putExtra( MSG_KEY, buf );
intent.putExtra( ADDR_KEY, btAddr );
intent.putExtra( GAMEID_KEY, gameID );
- startService( context, intent );
+ enqueueWork( context, intent );
nSent = buf.length;
}
@@ -384,39 +356,22 @@ public class BTService extends XWService {
public static void gameDied( Context context, String btAddr, int gameID )
{
- Intent intent = getIntentTo( context, BTAction.REMOVE );
- intent.putExtra( GAMEID_KEY, gameID );
- intent.putExtra( ADDR_KEY, btAddr );
- startService( context, intent );
- }
-
- private static void startService( Context context, Intent intent )
- {
- boolean inForeground = inForeground();
- Log.d( TAG, "startService(%s); inForeground = %b", intent, inForeground );
-
- if ( ! inForeground && canRunForegroundService() ) {
- context.startForegroundService( intent );
- } else if ( inForeground || Build.VERSION.SDK_INT < Build.VERSION_CODES.O ) {
- context.startService( intent );
- } else {
- Log.d( TAG, "startService(); not starting" );
- }
- }
-
- // We can run a foreground service IIF the OS version is recent enough AND
- // user hasn't said not to do it.
- private static boolean canRunForegroundService()
- {
- // added in API level 26
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
+ Assert.assertNotNull( btAddr );
+ Intent intent = getIntentTo( context, BTAction.REMOVE )
+ .putExtra( GAMEID_KEY, gameID )
+ .putExtra( ADDR_KEY, btAddr );
+ enqueueWork( context, intent );
}
private static Intent getIntentTo( Context context, BTAction cmd )
{
- Intent intent = new Intent( context, BTService.class );
- intent.putExtra( CMD_KEY, cmd.ordinal() );
- return intent;
+ return getIntentTo( context, BTService.class, cmd );
+ }
+
+ private static Intent getIntentTo( BTAction cmd )
+ {
+ Context context = XWApp.getContext();
+ return getIntentTo( context, cmd );
}
@Override
@@ -429,7 +384,6 @@ public class BTService extends XWService {
m_btMsgSink = new BTMsgSink();
mHandler = new Handler();
- startForegroundIf();
BluetoothAdapter adapter = XWApp.BTSUPPORTED
? BluetoothAdapter.getDefaultAdapter() : null;
@@ -438,7 +392,6 @@ public class BTService extends XWService {
Log.i( TAG, "onCreate(); bt name = %s; bt addr = %s",
adapter.getName(), adapter.getAddress() );
startListener();
- startSender();
} else {
Log.w( TAG, "not starting threads: BT not available" );
stopSelf();
@@ -446,202 +399,156 @@ public class BTService extends XWService {
}
@Override
- public int onStartCommand( Intent intent, int flags, int startId )
+ public void onDestroy()
{
- startForegroundIf();
-
- int result = handleCommand( intent );
-
- return result;
+ super.onDestroy();
+ releaseSender( this );
}
- private int handleCommand( Intent intent )
+ @Override
+ XWJICmds[] getCmds() { return BTAction.values(); }
+
+ @Override
+ void onHandleWorkImpl( Intent intent, XWJICmds jicmd, long timestamp )
{
- 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();
+ BTAction cmd = (BTAction)jicmd;
+ switch( cmd ) {
+ case ACL_CONN: // just forces onCreate to run
+ break;
+
+ case START_BACKGROUND:
+ startListener();
+ noteLastUsed( this ); // prevent timer from killing immediately
+ setTimeoutTimer();
+ break;
+
+ case SCAN:
+ int timeout = intent.getIntExtra( SCAN_TIMEOUT_KEY, -1 );
+ add( new BTQueueElem( BTCmd.SCAN, timeout ) );
+ break;
+ case INVITE:
+ String jsonData = intent.getStringExtra( GAMEDATA_KEY );
+ NetLaunchInfo nli = NetLaunchInfo.makeFrom( this, jsonData );
+ Log.i( TAG, "handleCommand: nli: %s", nli );
+ String btAddr = intent.getStringExtra( ADDR_KEY );
+ add( new BTQueueElem( BTCmd.INVITE, nli, btAddr ) );
+ break;
+
+ case PINGHOST:
+ btAddr = intent.getStringExtra( ADDR_KEY );
+ int gameID = intent.getIntExtra( GAMEID_KEY, 0 );
+ add( new BTQueueElem( BTCmd.PING, btAddr, gameID ) );
+ break;
+
+ case SEND:
+ byte[] buf = intent.getByteArrayExtra( MSG_KEY );
+ btAddr = intent.getStringExtra( ADDR_KEY );
+ gameID = intent.getIntExtra( GAMEID_KEY, -1 );
+ if ( -1 != gameID ) {
+ add( new BTQueueElem( BTCmd.MESG_SEND, buf,
+ btAddr, gameID ) );
+ }
+ break;
+ case RADIO:
+ boolean cameOn = intent.getBooleanExtra( RADIO_KEY, false );
+ MultiEvent evt = cameOn? MultiEvent.BT_ENABLED
+ : MultiEvent.BT_DISABLED;
+ mHelper.postEvent( evt );
+ if ( cameOn ) {
+ GameUtils.resendAllIf( this, CommsConnType.COMMS_CONN_BT );
} else {
- BTAction cmd = BTAction.values()[ordinal];
- Log.i( TAG, "handleCommand; cmd=%s", cmd.toString() );
- switch( cmd ) {
- case CANCEL_SERVICE:
- case START_FOREGROUND:
- stopForeground( true ); // Kill the notification
- break;
- case START_BACKGROUND:
- startListener();
- noteLastUsed( this ); // prevent timer from killing immediately
- setTimeoutTimer();
- break;
-
- case SCAN:
- int timeout = intent.getIntExtra( SCAN_TIMEOUT_KEY, -1 );
- m_sender.add( new BTQueueElem( BTCmd.SCAN, timeout ) );
- break;
- case INVITE:
- String jsonData = intent.getStringExtra( GAMEDATA_KEY );
- NetLaunchInfo nli = NetLaunchInfo.makeFrom( this, jsonData );
- Log.i( TAG, "handleCommand: nli: %s", nli );
- String btAddr = intent.getStringExtra( ADDR_KEY );
- m_sender.add( new BTQueueElem( BTCmd.INVITE, nli, btAddr ) );
- break;
-
- case PINGHOST:
- btAddr = intent.getStringExtra( ADDR_KEY );
- int gameID = intent.getIntExtra( GAMEID_KEY, 0 );
- m_sender.add( new BTQueueElem( BTCmd.PING, btAddr, gameID ) );
- break;
-
- case NFCINVITE:
- gameID = intent.getIntExtra( GAMEID_KEY, -1 );
- int lang = intent.getIntExtra( LANG_KEY, -1 );
- String dict = intent.getStringExtra( DICT_KEY );
- int nPlayersT = intent.getIntExtra( NTO_KEY, -1 );
- String btName = intent.getStringExtra( BT_NAME_KEY );
- btAddr = intent.getStringExtra( BT_ADDRESS_KEY );
- // /*(void)*/makeOrNotify( this, gameID, null, lang, dict,
- // nPlayersT, 1, btName, btAddr );
- Assert.fail();
- break;
-
- case SEND:
- byte[] buf = intent.getByteArrayExtra( MSG_KEY );
- btAddr = intent.getStringExtra( ADDR_KEY );
- gameID = intent.getIntExtra( GAMEID_KEY, -1 );
- if ( -1 != gameID ) {
- m_sender.add( new BTQueueElem( BTCmd.MESG_SEND, buf,
- btAddr, gameID ) );
- }
- break;
- case RADIO:
- boolean cameOn = intent.getBooleanExtra( RADIO_KEY, false );
- MultiEvent evt = cameOn? MultiEvent.BT_ENABLED
- : MultiEvent.BT_DISABLED;
- mHelper.postEvent( evt );
- if ( cameOn ) {
- GameUtils.resendAllIf( this, CommsConnType.COMMS_CONN_BT );
- } else {
- ConnStatusHandler.updateStatus( this, null,
- CommsConnType.COMMS_CONN_BT,
- false );
- stopListener();
- stopSender();
- stopSelf();
- }
- break;
- case REMOVE:
- gameID = intent.getIntExtra( GAMEID_KEY, -1 );
- btAddr = intent.getStringExtra( ADDR_KEY );
- m_sender.add( new BTQueueElem( BTCmd.MESG_GAMEGONE, btAddr, gameID ) );
- break;
- default:
- Assert.fail();
- }
+ ConnStatusHandler.updateStatus( this, null,
+ CommsConnType.COMMS_CONN_BT,
+ false );
+ stopListener();
+ // stopSender();
+ stopSelf();
}
- result = Service.START_STICKY;
- } else {
- result = Service.START_STICKY_COMPATIBILITY;
- }
- return result;
- } // handleCommand()
+ break;
+ case REMOVE:
+ gameID = intent.getIntExtra( GAMEID_KEY, -1 );
+ btAddr = intent.getStringExtra( ADDR_KEY );
+ add( new BTQueueElem( BTCmd.MESG_GAMEGONE, btAddr, gameID ) );
+ break;
- private void startForegroundIf()
+ case MAKE_OR_NOTIFY:
+ int socketRef = intent.getIntExtra( SOCKET_REF, -1 );
+ BluetoothSocket socket = socketForRef( socketRef );
+ if ( null == socket ) {
+ Log.e( TAG, "socket didn't survive into onHandleWork()" );
+ } else {
+ nli = (NetLaunchInfo)intent.getSerializableExtra( NLI_KEY );
+ BluetoothDevice host = socket.getRemoteDevice();
+ BTCmd response = makeOrNotify( nli, host.getName(), host.getAddress() );
+
+ writeBack( socket, response );
+
+ closeForRef( socketRef );
+ }
+ break;
+
+ case RECEIVE_MSG:
+ socketRef = intent.getIntExtra( SOCKET_REF, -1 );
+ socket = socketForRef( socketRef );
+ if ( null != socket ) {
+ gameID = intent.getIntExtra( GAMEID_KEY, -1 );
+ buf = intent.getByteArrayExtra( MSG_KEY );
+ BluetoothDevice host = socket.getRemoteDevice();
+ CommsAddrRec addr = new CommsAddrRec( host.getName(),
+ host.getAddress() );
+ XWServiceHelper.ReceiveResult rslt
+ = mHelper.receiveMessage( this, gameID,
+ m_btMsgSink,
+ buf, addr );
+
+ BTCmd response = rslt == XWServiceHelper.ReceiveResult.GAME_GONE ?
+ BTCmd.MESG_GAMEGONE : BTCmd.MESG_ACCPT;
+ writeBack( socket, response );
+ closeForRef( socketRef );
+ }
+ break;
+
+ case POST_GAME_GONE:
+ socketRef = intent.getIntExtra( SOCKET_REF, -1 );
+ socket = socketForRef( socketRef );
+ if ( null != socket ) {
+ gameID = intent.getIntExtra( GAMEID_KEY, -1 );
+ mHelper.postEvent( MultiEvent.MESSAGE_NOGAME, gameID );
+ writeBack( socket, BTCmd.MESG_ACCPT );
+ closeForRef( socketRef );
+ }
+ break;
+
+ default:
+ Assert.fail();
+ }
+ } // onHandleWorkImpl()
+
+ private void add( BTQueueElem elem )
{
- if ( ! inForeground() ) {
- Log.d( TAG, "startForegroundIf(): starting" );
-
- if ( null == m_notification ) {
- Intent notifIntent = GamesListDelegate.makeBackgroundIntent( this );
- PendingIntent pendIntent = PendingIntent
- .getActivity( this, Utils.nextRandomInt(), notifIntent,
- PendingIntent.FLAG_ONE_SHOT );
-
- String channelID = Channels.getChannelID( this, Channels.ID.FOREGROUND );
- NotificationCompat.Builder builder =
- new NotificationCompat.Builder( this, channelID )
- .setSmallIcon( R.drawable.notify_btservice )
- .setContentText( getString(R.string.bkng_notify_text) )
- .setContentIntent( pendIntent )
- ;
-
- // Add button to kill the foreground service
- Intent stopIntent = new Intent( this, BTReceiver.class );
- stopIntent.setAction( BTReceiver.ACTION_STOP_BT );
- PendingIntent stopPI =
- PendingIntent.getBroadcast(this, 0, stopIntent, 0);
- builder.addAction( R.drawable.notify_btservice,
- getString(R.string.bkng_stop_text), stopPI );
-
- // If supported, button to take user to settings (to make
- // notification less annoying perhaps)
- if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ) {
- Intent hideIntent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
- .putExtra( Settings.EXTRA_APP_PACKAGE, getPackageName() )
- .putExtra( Settings.EXTRA_CHANNEL_ID, channelID )
- ;
- PendingIntent hidePI =
- PendingIntent.getActivity( this, 0, hideIntent, 0 );
- builder.addAction( R.drawable.notify_btservice,
- getString(R.string.bkng_settings_text),
- hidePI );
- }
-
- m_notification = builder.build();
- }
-
- Log.d( TAG, "calling service.startForeground()" );
- startForeground( R.string.app_name, m_notification );
- } else {
- Log.d( TAG, "startForegroundIf(): NOT starting" );
- }
+ senderFor( this ).add( elem );
}
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[] mServiceHolder = { null };
private volatile Thread mTimerThread;
- private BTListenerThread( Context context, BTService service )
- {
- mContext = context;
- mServiceHolder[0] = service;
+ private BTListenerThread() {}
- // Started without a BTService instance? Run for only a little
- // while
- if ( null == service ) {
- startKillTimer();
- }
- }
-
- static void startYourself( Context context, BTService service )
+ static void startYourself()
{
- Log.d( TAG, "startYourself(%s, %s)", context, service );
- DbgUtils.assertOnUIThread();
+ Log.d( TAG, "startYourself()" );
+ // DbgUtils.assertOnUIThread();
synchronized ( s_listener ) {
if ( s_listener[0] == null ) {
- s_listener[0] = new BTListenerThread( context, service );
+ s_listener[0] = new BTListenerThread();
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()" );
@@ -661,71 +568,10 @@ public class BTService extends XWService {
}
}
- void setService( Context context, BTService service )
- {
- synchronized ( mServiceHolder ) {
- if ( service != mServiceHolder[0] ) {
- Assert.assertNotNull( context );
- mContext = context; // Use Service instead of Receiver (possibly)
- mServiceHolder[0] = service;
- if ( service != null ) {
- Log.d( TAG, "setService(): setting. notifying all!!!" );
- mServiceHolder.notifyAll();
- }
- }
- }
- }
-
- private void startTheService()
- {
- stopKillTimer();
-
- startService( mContext,
- getIntentTo( mContext,
- BTAction.START_BACKGROUND ) );
- }
-
- private BTService blockForService() throws InterruptedException
- {
- synchronized( mServiceHolder ) {
- while ( mServiceHolder[0] == null ) {
- mServiceHolder.wait();
- }
- return mServiceHolder[0];
- }
- }
-
- 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( 20 * 1000 );
- stopYourself( BTListenerThread.this );
- } catch (InterruptedException ie) {
- Log.e( TAG, "kill timer thread exiting; we're live!" );
- }
- }
- } );
- mTimerThread.start();
- }
-
- private synchronized void stopKillTimer()
- {
- if ( mTimerThread != null ) {
- mTimerThread.interrupt();
- mTimerThread = null;
- }
- }
-
@Override
public void run() { // receive thread
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- String appName = XWApp.getAppName( mContext );
+ String appName = XWApp.getAppName( XWApp.getContext() );
try {
m_serverSocket = adapter.
@@ -738,7 +584,9 @@ public class BTService extends XWService {
while ( null != m_serverSocket && adapter.isEnabled() ) {
try {
+ Log.d( TAG, "%s.run() calling accept()", this );
BluetoothSocket socket = m_serverSocket.accept(); // blocks
+ Log.d( TAG, "accept() => %s", socket );
DataInputStream inStream =
new DataInputStream( socket.getInputStream() );
@@ -746,16 +594,9 @@ public class BTService extends XWService {
BTCmd cmd = BTCmd.values()[inStream.readByte()];
Log.d( TAG, "BTListenerThread() got %s", cmd );
if ( protoOK( proto, cmd ) ) {
- if ( null == mServiceHolder[0] ) {
- startTheService();
- }
- processWhenReady( socket, proto, inStream, cmd );
+ process( socket, proto, inStream, cmd );
} else {
- DataOutputStream os =
- new DataOutputStream( socket.getOutputStream() );
- os.writeByte( BTCmd.BAD_PROTO.ordinal() );
- os.flush();
- socket.close();
+ writeBack( socket, BTCmd.BAD_PROTO );
}
} catch ( IOException ioe ) {
Log.w( TAG, "trying again..." );
@@ -767,75 +608,32 @@ public class BTService extends XWService {
}
closeServerSocket();
+ Log.d( TAG, "BTListenerThread.run() exiting" );
} // 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 mInQueue = new LinkedBlockingQueue<>();
-
- private Thread mIgnored;
- private void processWhenReady( BluetoothSocket socket, byte proto,
- DataInputStream inStream, BTCmd cmd )
+ private void process( BluetoothSocket socket, byte proto,
+ DataInputStream inStream, BTCmd cmd )
+ throws IOException
{
- 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;
+ 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( service, cmd, elem.mInStream,
- elem.mSocket );
- break;
+ case MESG_GAMEGONE:
+ receiveMessage( cmd, inStream, socket );
+ 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();
+ default:
+ Log.e( TAG, "unexpected msg %s", cmd.toString());
+ break;
}
-
- mInQueue.add( new InPacket( socket, proto, inStream, cmd ) );
}
public void stopListening()
@@ -862,11 +660,12 @@ public class BTService extends XWService {
return ok;
}
- private void receivePing( BTService service, BluetoothSocket socket ) throws IOException
+ private void receivePing( BluetoothSocket socket ) throws IOException
{
DataInputStream inStream = new DataInputStream( socket.getInputStream() );
int gameID = inStream.readInt();
- boolean deleted = 0 != gameID && !DBUtils.haveGame( mContext, gameID );
+ boolean deleted = 0 != gameID && !DBUtils
+ .haveGame( XWApp.getContext(), gameID );
DataOutputStream os = new DataOutputStream( socket.getOutputStream() );
os.writeByte( BTCmd.PONG.ordinal() );
@@ -874,18 +673,18 @@ public class BTService extends XWService {
os.flush();
socket.close();
- service.updateStatusOut( true );
+ // service.updateStatusOut( true );
}
- private void receiveInvitation( BTService service, byte proto,
- DataInputStream is, BluetoothSocket socket )
+ private void receiveInvitation( byte proto, DataInputStream is,
+ BluetoothSocket socket )
throws IOException
{
BTCmd result;
NetLaunchInfo nli;
if ( BT_PROTO_JSONS == proto ) {
String asJson = is.readUTF();
- nli = NetLaunchInfo.makeFrom( mContext, asJson );
+ nli = NetLaunchInfo.makeFrom( XWApp.getContext(), asJson );
} else {
short len = is.readShort();
byte[] nliData = new byte[len];
@@ -893,19 +692,15 @@ public class BTService extends XWService {
nli = XwJNI.nliFromStream( nliData );
}
- BluetoothDevice host = socket.getRemoteDevice();
- result = service
- .makeOrNotify( nli, host.getName(), host.getAddress() );
-
- DataOutputStream os = new DataOutputStream( socket.getOutputStream() );
- os.writeByte( result.ordinal() );
- os.flush();
-
- socket.close();
+ Intent intent = getIntentTo( BTAction.MAKE_OR_NOTIFY )
+ .putExtra( SOCKET_REF, makeRefFor( socket ) )
+ .putExtra( NLI_KEY, nli )
+ ;
+ enqueueWork( intent );
} // receiveInvitation
- private void receiveMessage( BTService service, BTCmd cmd,
- DataInputStream dis, BluetoothSocket socket )
+ private void receiveMessage( BTCmd cmd, DataInputStream dis,
+ BluetoothSocket socket )
{
try {
BTCmd result = null;
@@ -914,32 +709,28 @@ public class BTService extends XWService {
case MESG_SEND:
byte[] buffer = new byte[dis.readShort()];
dis.readFully( buffer );
- BluetoothDevice host = socket.getRemoteDevice();
- CommsAddrRec addr = new CommsAddrRec( host.getName(),
- host.getAddress() );
- XWServiceHelper.ReceiveResult rslt
- = service.mHelper.receiveMessage( mContext, gameID,
- service.m_btMsgSink,
- buffer, addr );
-
- result = rslt == XWServiceHelper.ReceiveResult.GAME_GONE ?
- BTCmd.MESG_GAMEGONE : BTCmd.MESG_ACCPT;
+ enqueueWork( getIntentTo( BTAction.RECEIVE_MSG )
+ .putExtra( SOCKET_REF, makeRefFor( socket ) )
+ .putExtra( GAMEID_KEY, gameID )
+ .putExtra( MSG_KEY, buffer ) );
+ socket = null;
break;
case MESG_GAMEGONE:
- service.mHelper.postEvent( MultiEvent.MESSAGE_NOGAME, gameID );
- result = BTCmd.MESG_ACCPT;
+ enqueueWork( getIntentTo( BTAction.POST_GAME_GONE )
+ .putExtra( SOCKET_REF, makeRefFor( socket ) )
+ .putExtra( GAMEID_KEY, gameID ) );
+ socket = null;
break;
default:
result = BTCmd.BAD_PROTO;
break;
}
- DataOutputStream os =
- new DataOutputStream( socket.getOutputStream() );
- os.writeByte( result.ordinal() );
- os.flush();
- socket.close();
+ if ( null != socket ) {
+ writeBack( socket, result );
+ socket.close();
+ }
} catch ( IOException ioe ) {
logIOE( ioe );
}
@@ -972,38 +763,68 @@ public class BTService extends XWService {
return btAddr;
}
+ // sender thread
+ //
+ // Thing wants to outlive an instance of BTService, but not live
+ // forever. Ideally it exists long enough to send the elems posted by one
+ // instance then dies.
+
+ private static Map sMap = new HashMap<>();
+ private synchronized BTSenderThread senderFor( BTService bts )
+ {
+ BTSenderThread result = sMap.get( bts );
+ if ( null == result ) {
+ result = new BTSenderThread();
+ result.start();
+ sMap.put( bts, result );
+ }
+ return result;
+ }
+
+ private synchronized void releaseSender( BTService bts )
+ {
+ BTSenderThread self = senderFor( bts );
+ self.mFinishing = true;
+ sMap.remove( bts );
+ }
+
private class BTSenderThread extends Thread {
private LinkedBlockingQueue m_queue;
private HashMap > m_resends;
- // private HashSet m_deadGames;
+ private volatile boolean mFinishing = false;
- public BTSenderThread()
+ private BTSenderThread()
{
m_queue = new LinkedBlockingQueue();
m_resends = new HashMap >();
- // m_deadGames = new HashSet();
}
public void add( BTQueueElem elem )
{
+ Assert.assertFalse( mFinishing );
m_queue.add( elem );
}
@Override
- public void run() // send thread
+ public void run()
{
+ Log.d( TAG, "BTListenerThread.run() starting" );
for ( ; ; ) {
BTQueueElem elem;
- long timeout = haveResends() ? RESEND_TIMEOUT : Long.MAX_VALUE;
+ // onTheWayOut: mFinishing can change while we're in poll()
+ boolean onTheWayOut = mFinishing;
try {
- elem = m_queue.poll( timeout, TimeUnit.SECONDS );
+ elem = m_queue.poll( RESEND_TIMEOUT, TimeUnit.SECONDS );
} catch ( InterruptedException ie ) {
Log.w( TAG, "interrupted; killing thread" );
break;
}
- if ( null == elem ) {
- doAnyResends();
+ if ( null == elem ) { // timed out
+ if ( doAnyResends() && onTheWayOut ) {
+ // nothing to send AND nothing to resend: outta here!
+ break;
+ }
} else {
// DbgUtils.logf( "run: got %s from queue", elem.m_cmd.toString() );
@@ -1041,6 +862,8 @@ public class BTService extends XWService {
}
}
}
+ Log.d( TAG, "BTListenerThread.run() exiting (owner was %s)",
+ BTService.this );
} // run
private void sendPings( MultiEvent event, int timeout )
@@ -1274,7 +1097,7 @@ public class BTService extends XWService {
if ( ! success ) {
int failCount = elem.incrFailCount();
mHelper.postEvent( MultiEvent.MESSAGE_RESEND, btName,
- RESEND_TIMEOUT, failCount );
+ RESEND_TIMEOUT, failCount );
}
}
return success;
@@ -1282,8 +1105,10 @@ public class BTService extends XWService {
private boolean doAnyResends( LinkedList resends )
{
- boolean success = null == resends || 0 == resends.size();
+ int count = 0;
+ boolean success = null == resends || 0 < resends.size();
if ( !success ) {
+ count = resends.size();
success = true;
ListIterator iter = resends.listIterator();
while ( iter.hasNext() && success ) {
@@ -1299,6 +1124,9 @@ public class BTService extends XWService {
}
}
+ if ( 0 < count ) {
+ Log.d( TAG, "doAnyResends(size=%d) => %b", count, success );
+ }
return success;
}
@@ -1307,14 +1135,16 @@ public class BTService extends XWService {
return doAnyResends( m_resends.get( btAddr ) );
}
- private void doAnyResends()
+ private boolean doAnyResends()
{
+ boolean success = true;
Iterator> iter =
m_resends.values().iterator();
while ( iter.hasNext() ) {
LinkedList list = iter.next();
- doAnyResends( list );
+ success = doAnyResends( list ) && success;
}
+ return success;
}
private void addToResends( BTQueueElem elem )
@@ -1339,36 +1169,62 @@ public class BTService extends XWService {
}
return found;
}
+
+ private DataOutputStream connect( BluetoothSocket socket, BTCmd cmd )
+ {
+ return connect( socket, cmd, 20000 );
+ }
+
+ private DataOutputStream connect( BluetoothSocket socket, BTCmd cmd,
+ int timeout )
+ {
+ String name = socket.getRemoteDevice().getName();
+ String addr = socket.getRemoteDevice().getAddress();
+ Log.w( TAG, "connect(%s/%s, timeout=%d) starting", name, addr, timeout );
+ // DbgUtils.logf( "connecting to %s to send cmd %s", name, cmd.toString() );
+ // Docs say always call cancelDiscovery before trying to connect
+ m_adapter.cancelDiscovery();
+
+ DataOutputStream dos = null;
+
+ // Retry for some time. Some devices take a long time to generate and
+ // broadcast ACL conn ACTION
+ for ( long end = timeout + System.currentTimeMillis(); ; ) {
+ try {
+ Log.d( TAG, "trying connect(%s/%s) ", name, addr );
+ socket.connect();
+ Log.i( TAG, "connect(%s/%s) succeeded", name, addr );
+ dos = new DataOutputStream( socket.getOutputStream() );
+ dos.writeByte( BT_PROTO );
+ dos.writeByte( cmd.ordinal() );
+ break; // success!!!
+ } catch (IOException ioe) {
+ if ( CONNECT_SLEEP_MS + System.currentTimeMillis() > end ) {
+ break;
+ }
+ try {
+ Thread.sleep( CONNECT_SLEEP_MS );
+ } catch ( InterruptedException ex ) {
+ break;
+ }
+ }
+ }
+ return dos;
+ }
} // class BTSenderThread
private void startListener()
{
Assert.assertNotNull( mHelper );
- BTListenerThread.startYourself( this, this );
- }
-
- private void startSender()
- {
- m_sender = new BTSenderThread();
- m_sender.start();
+ BTListenerThread.startYourself();
}
private void stopListener()
{
+ Log.d( TAG, "stopListener()" );
BTListenerThread.stopYourself( null );
}
- private void stopSender()
- {
- m_sender.interrupt();
- try {
- m_sender.join( 100 );
- } catch ( InterruptedException ie ) {
- Log.ex( TAG, ie );
- }
- m_sender = null;
- }
-
private BTCmd makeOrNotify( NetLaunchInfo nli, String btName,
String btAddr )
{
@@ -1382,86 +1238,40 @@ public class BTService extends XWService {
return result;
}
- private DataOutputStream connect( BluetoothSocket socket, BTCmd cmd )
- {
- return connect( socket, cmd, 20000 );
- }
-
- private DataOutputStream connect( BluetoothSocket socket, BTCmd cmd,
- int timeout )
- {
- String name = socket.getRemoteDevice().getName();
- Log.w( TAG, "connect(%s, timeout=%d) starting", name, timeout );
- // DbgUtils.logf( "connecting to %s to send cmd %s", name, cmd.toString() );
- // Docs say always call cancelDiscovery before trying to connect
- m_adapter.cancelDiscovery();
-
- DataOutputStream dos = null;
-
- // Retry for some time. Some devices take a long time to generate and
- // broadcast ACL conn ACTION
- for ( long end = timeout + System.currentTimeMillis(); ; ) {
- try {
- Log.d( TAG, "trying connect(%s) ", name );
- socket.connect();
- Log.i( TAG, "connect(%s) succeeded", name );
- dos = new DataOutputStream( socket.getOutputStream() );
- dos.writeByte( BT_PROTO );
- dos.writeByte( cmd.ordinal() );
- break; // success!!!
- } catch (IOException ioe) {
- if ( CONNECT_SLEEP_MS + System.currentTimeMillis() > end ) {
- break;
- }
- try {
- Thread.sleep( CONNECT_SLEEP_MS );
- } catch ( InterruptedException ex ) {
- break;
- }
- }
- }
- return dos;
- }
-
private static void noteLastUsed( Context context )
{
Log.d( TAG, "noteLastUsed(" + context + ")" );
- synchronized (BTService.class) {
- int nowSecs = (int)(SystemClock.uptimeMillis() / 1000);
- int newKeepSecs = nowSecs + DEFAULT_KEEPALIVE_SECONDS;
- DBUtils.setIntFor( context, KEY_KEEPALIVE_UNTIL_SECS, newKeepSecs );
- }
+ // synchronized (BTService.class) {
+ // int nowSecs = (int)(SystemClock.uptimeMillis() / 1000);
+ // int newKeepSecs = nowSecs + DEFAULT_KEEPALIVE_SECONDS;
+ // DBUtils.setIntFor( context, KEY_KEEPALIVE_UNTIL_SECS, newKeepSecs );
+ // }
}
private void setTimeoutTimer()
{
- DbgUtils.assertOnUIThread();
+ // // DbgUtils.assertOnUIThread();
- if ( inForeground() ) {
- // Let the timer die if we're in the foreground!
- Log.d( TAG, "setTimeoutTimer(): in foreground; not resetting timer" );
- } else {
- long dieTimeMillis;
- synchronized (BTService.class) {
- dieTimeMillis = 1000 * DBUtils.getIntFor( this, KEY_KEEPALIVE_UNTIL_SECS, 0 );
- }
- long nowMillis = SystemClock.uptimeMillis();
+ // long dieTimeMillis;
+ // synchronized (BTService.class) {
+ // dieTimeMillis = 1000 * DBUtils.getIntFor( this, KEY_KEEPALIVE_UNTIL_SECS, 0 );
+ // }
+ // long nowMillis = SystemClock.uptimeMillis();
- if ( dieTimeMillis <= nowMillis ) {
- Log.d( TAG, "setTimeoutTimer(): killing the thing" );
- stopListener();
- stopForeground(true);
- } else {
- mHandler.removeCallbacksAndMessages( this );
- mHandler.postAtTime( new Runnable() {
- @Override
- public void run() {
- setTimeoutTimer();
- }
- }, this, dieTimeMillis );
- Log.d( TAG, "setTimeoutTimer(): set for %dms from now", dieTimeMillis - nowMillis );
- }
- }
+ // if ( dieTimeMillis <= nowMillis ) {
+ // Log.d( TAG, "setTimeoutTimer(): killing the thing" );
+ // stopListener();
+ // stopForeground(true);
+ // } else {
+ // mHandler.removeCallbacksAndMessages( this );
+ // mHandler.postAtTime( new Runnable() {
+ // @Override
+ // public void run() {
+ // setTimeoutTimer();
+ // }
+ // }, this, dieTimeMillis );
+ // Log.d( TAG, "setTimeoutTimer(): set for %dms from now", dieTimeMillis - nowMillis );
+ // }
}
private static void logIOE( IOException ioe )
@@ -1524,18 +1334,14 @@ public class BTService extends XWService {
public int sendViaBluetooth( byte[] buf, int gameID, CommsAddrRec addr )
{
int nSent = -1;
- if ( null == m_sender ) {
- Log.w( TAG, "sendViaBluetooth(): no send thread" );
+ String btAddr = getSafeAddr( addr );
+ if ( null != btAddr && 0 < btAddr.length() ) {
+ add( new BTQueueElem( BTCmd.MESG_SEND, buf, btAddr,
+ gameID ) );
+ nSent = buf.length;
} else {
- String btAddr = getSafeAddr( addr );
- if ( null != btAddr && 0 < btAddr.length() ) {
- m_sender.add( new BTQueueElem( BTCmd.MESG_SEND, buf, btAddr,
- gameID ) );
- nSent = buf.length;
- } else {
- Log.i( TAG, "sendViaBluetooth(): no addr for dev %s",
- addr.bt_hostName );
- }
+ Log.i( TAG, "sendViaBluetooth(): no addr for dev %s",
+ addr.bt_hostName );
}
return nSent;
}
@@ -1566,4 +1372,44 @@ public class BTService extends XWService {
postEvent( MultiEvent.BT_GAME_CREATED, rowid );
}
}
+
+ private static Map s_sockets = new HashMap<>();
+ private static int makeRefFor( BluetoothSocket socket )
+ {
+ int code = socket.hashCode();
+ synchronized ( s_sockets ) {
+ Assert.assertTrue( !s_sockets.containsKey(code) || !BuildConfig.DEBUG );
+ s_sockets.put( code, socket );
+ }
+ Log.d( TAG, "makeRefFor(%s) => %d (size: %d)", socket, code, s_sockets.size() );
+ return code;
+ }
+
+ private static BluetoothSocket socketForRef( int ref )
+ {
+ BluetoothSocket result = null;
+ synchronized ( s_sockets ) {
+ if ( s_sockets.containsKey( ref ) ) {
+ result = s_sockets.get( ref );
+ Assert.assertTrue( null != result || !BuildConfig.DEBUG );
+ }
+ }
+ // Log.d( TAG, "socketForRef(%d) => %s", ref, result );
+ return result;
+ }
+
+ private static void closeForRef( int ref )
+ {
+ synchronized ( s_sockets ) {
+ BluetoothSocket socket = socketForRef( ref );
+ if ( null != socket ) {
+ try {
+ socket.close();
+ } catch ( IOException ex ) {
+ Log.ex( TAG, ex );
+ }
+ }
+ }
+ Log.d( TAG, "closeForRef(%d) (size: %d)", ref, s_sockets.size() );
+ }
}
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java
index cdec67cce..6735fb858 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java
@@ -85,7 +85,6 @@ public class GamesListDelegate extends ListDelegateBase
private static final String RELAYIDS_EXTRA = "relayids";
private static final String ROWID_EXTRA = "rowid";
- private static final String BACKGROUND_EXTRA = "bkgrnd";
private static final String GAMEID_EXTRA = "gameid";
private static final String REMATCH_ROWID_EXTRA = "rm_rowid";
private static final String REMATCH_DICT_EXTRA = "rm_dict";
@@ -2289,14 +2288,6 @@ public class GamesListDelegate extends ListDelegateBase
}
}
- private void tryBackgroundIntent( Intent intent )
- {
- if ( intent.getBooleanExtra( BACKGROUND_EXTRA, false ) ) {
- makeOkOnlyBuilder( R.string.btservice_expl )
- .show();
- }
- }
-
private void askDefaultName()
{
String name = CommonPrefs.getDefaultPlayerName( m_activity, 0, true );
@@ -2511,7 +2502,6 @@ public class GamesListDelegate extends ListDelegateBase
startRematch( intent );
tryAlert( intent );
tryNFCIntent( intent );
- tryBackgroundIntent( intent );
}
private void doOpenGame( Object[] params )
@@ -2704,12 +2694,6 @@ public class GamesListDelegate extends ListDelegateBase
;
}
- public static Intent makeBackgroundIntent( Context context )
- {
- return makeSelfIntent( context )
- .putExtra( BACKGROUND_EXTRA, true );
- }
-
public static Intent makeRowidIntent( Context context, long rowid )
{
Intent intent = makeSelfIntent( context )
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RelayService.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RelayService.java
index 5f20b5557..5d1bd1ad3 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RelayService.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RelayService.java
@@ -26,7 +26,6 @@ import android.content.Intent;
import android.os.Build;
import android.os.Handler;
import android.support.annotation.Nullable;
-import android.support.v4.app.JobIntentService;
import android.text.TextUtils;
import org.eehouse.android.xw4.FBMService;
@@ -64,7 +63,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.HttpsURLConnection;
-public class RelayService extends JobIntentService
+public class RelayService extends XWJIService
implements NetStateCache.StateChangedIf {
private static final String TAG = RelayService.class.getSimpleName();
private static final int MAX_SEND = 1024;
@@ -82,24 +81,21 @@ public class RelayService extends JobIntentService
// One day, in seconds. Probably should be configurable.
private static final long MAX_KEEPALIVE_SECS = 24 * 60 * 60;
- private static final String CMD_KEY = "CMD";
- private static final String TIMESTAMP = "TIMESTAMP";
-
- private static enum MsgCmds { INVALID,
- DO_WORK,
- PROCESS_GAME_MSGS,
- PROCESS_DEV_MSGS,
- UDP_CHANGED,
- SEND,
- SENDNOCONN,
- RECEIVE,
- TIMER_FIRED,
- RESET,
- UPGRADE,
- INVITE,
- GOT_INVITE,
- GOT_PACKET,
- STOP,
+ private static enum MsgCmds implements XWJICmds { INVALID,
+ DO_WORK,
+ PROCESS_GAME_MSGS,
+ PROCESS_DEV_MSGS,
+ UDP_CHANGED,
+ SEND,
+ SENDNOCONN,
+ RECEIVE,
+ TIMER_FIRED,
+ RESET,
+ UPGRADE,
+ INVITE,
+ GOT_INVITE,
+ GOT_PACKET,
+ STOP,
}
private static final String MSGS_ARR = "MSGS_ARR";
@@ -222,18 +218,7 @@ public class RelayService extends JobIntentService
private static void enqueueWork( Context context, Intent intent )
{
enqueueWork( context, RelayService.class, sJobID, intent );
- Log.d( TAG, "called enqueueWork(cmd=%s)", cmdFrom( intent ) );
- }
-
- private static MsgCmds cmdFrom( Intent intent )
- {
- MsgCmds cmd;
- try {
- cmd = MsgCmds.values()[intent.getIntExtra( CMD_KEY, -1 )];
- } catch (Exception ex) { // OOB most likely
- cmd = null;
- }
- return cmd;
+ Log.d( TAG, "called enqueueWork(cmd=%s)", cmdFrom( intent, MsgCmds.values() ) );
}
private static void stopService( Context context )
@@ -353,10 +338,7 @@ public class RelayService extends JobIntentService
private static Intent getIntentTo( Context context, MsgCmds cmd )
{
- Intent intent = new Intent( context, RelayService.class )
- .putExtra( CMD_KEY, cmd.ordinal() )
- .putExtra( TIMESTAMP, System.currentTimeMillis() );
- return intent;
+ return getIntentTo( context, RelayService.class, cmd );
}
@Override
@@ -391,15 +373,15 @@ public class RelayService extends JobIntentService
}
@Override
- public void onHandleWork( Intent intent )
+ void onHandleWorkImpl( Intent intent, XWJICmds jicmd, long timestamp )
{
DbgUtils.assertOnUIThread( false );
- Log.d( TAG, "%s.onHandleWork(cmd=%s)", this, cmdFrom( intent ) );
+ // Log.d( TAG, "%s.onHandleWork(cmd=%s)", this, cmdFrom( intent ) );
try {
connectSocketOnce(); // must not be on UI thread
- handleCommand( intent );
+ handleCommand( intent, jicmd, timestamp );
boolean goOn = serviceQueue();
if ( !goOn ) {
@@ -438,6 +420,9 @@ public class RelayService extends JobIntentService
Log.d( TAG, "%s.onDestroy() DONE", this );
}
+ @Override
+ XWJICmds[] getCmds() { return MsgCmds.values(); }
+
// NetStateCache.StateChangedIf interface
@Override
public void onNetAvail( boolean nowAvailable )
@@ -445,94 +430,89 @@ public class RelayService extends JobIntentService
startService( this ); // bad name: will *stop* threads too
}
- private void handleCommand( Intent intent )
+ private void handleCommand( Intent intent, XWJICmds jicmd, long timestamp )
{
- MsgCmds cmd = cmdFrom( intent );
- if ( null != cmd ) {
- long timestamp = intent.getLongExtra( TIMESTAMP, 0 );
- Log.d( TAG, "handleCommand(): cmd=%s (age=%dms)", cmd.toString(),
- System.currentTimeMillis() - timestamp );
- switch( cmd ) {
- case DO_WORK: // exists only to launch service
- break;
- case PROCESS_GAME_MSGS:
- String[] relayIDs = new String[1];
- relayIDs[0] = intent.getStringExtra( RELAY_ID );
- long[] rowIDs = DBUtils.getRowIDsFor( this, relayIDs[0] );
- if ( 0 < rowIDs.length ) {
- byte[][][] msgs = expandMsgsArray( intent );
- process( msgs, rowIDs, relayIDs );
- }
- break;
- case PROCESS_DEV_MSGS:
- byte[][][] msgss = expandMsgsArray( intent );
- for ( byte[][] msgs : msgss ) {
- for ( byte[] msg : msgs ) {
- gotPacket( msg, true, false, timestamp );
- }
- }
- break;
- case UDP_CHANGED:
- startThreads();
- break;
- case RESET:
- stopThreads();
- startThreads();
- break;
- case UPGRADE:
- UpdateCheckReceiver.checkVersions( this, false );
- break;
- case GOT_INVITE:
- int srcDevID = intent.getIntExtra( INVITE_FROM, 0 );
- NetLaunchInfo nli
- = NetLaunchInfo.makeFrom( this, intent.getStringExtra(NLI_DATA) );
- receiveInvitation( srcDevID, nli );
- break;
- case GOT_PACKET:
- byte[] msg = intent.getByteArrayExtra( BINBUFFER );
- gotPacket( msg, false, true );
- break;
- case SEND:
- case RECEIVE:
- case SENDNOCONN:
- startUDPReadThreadOnce();
- long rowid = intent.getLongExtra( ROWID, -1 );
- msg = intent.getByteArrayExtra( BINBUFFER );
- if ( MsgCmds.SEND == cmd ) {
- sendMessage( rowid, msg, timestamp );
- } else if ( MsgCmds.SENDNOCONN == cmd ) {
- String relayID = intent.getStringExtra( RELAY_ID );
- String msgNo = intent.getStringExtra( MSGNUM );
- sendNoConnMessage( rowid, relayID, msg, msgNo, timestamp );
- } else {
- mHelper.receiveMessage( this, rowid, null, msg, s_addr );
- }
- break;
- case INVITE:
- startUDPReadThreadOnce();
- srcDevID = intent.getIntExtra( DEV_ID_SRC, 0 );
- int destDevID = intent.getIntExtra( DEV_ID_DEST, 0 );
- String relayID = intent.getStringExtra( RELAY_ID );
- String nliData = intent.getStringExtra( NLI_DATA );
- sendInvitation( srcDevID, destDevID, relayID, nliData, timestamp );
- break;
- case TIMER_FIRED:
- if ( !NetStateCache.netAvail( this ) ) {
- Log.w( TAG, "not connecting: no network" );
- } else if ( startFetchThreadIfNotUDP() ) {
- // do nothing
- } else if ( registerWithRelayIfNot( timestamp ) ) {
- requestMessages( timestamp );
- }
- RelayReceiver.setTimer( this );
- break;
- case STOP:
- stopThreads();
- stopSelf();
- break;
- default:
- Assert.assertFalse( BuildConfig.DEBUG );
+ MsgCmds cmd = (MsgCmds)jicmd;
+ switch( cmd ) {
+ case DO_WORK: // exists only to launch service
+ break;
+ case PROCESS_GAME_MSGS:
+ String[] relayIDs = new String[1];
+ relayIDs[0] = intent.getStringExtra( RELAY_ID );
+ long[] rowIDs = DBUtils.getRowIDsFor( this, relayIDs[0] );
+ if ( 0 < rowIDs.length ) {
+ byte[][][] msgs = expandMsgsArray( intent );
+ process( msgs, rowIDs, relayIDs );
}
+ break;
+ case PROCESS_DEV_MSGS:
+ byte[][][] msgss = expandMsgsArray( intent );
+ for ( byte[][] msgs : msgss ) {
+ for ( byte[] msg : msgs ) {
+ gotPacket( msg, true, false, timestamp );
+ }
+ }
+ break;
+ case UDP_CHANGED:
+ startThreads();
+ break;
+ case RESET:
+ stopThreads();
+ startThreads();
+ break;
+ case UPGRADE:
+ UpdateCheckReceiver.checkVersions( this, false );
+ break;
+ case GOT_INVITE:
+ int srcDevID = intent.getIntExtra( INVITE_FROM, 0 );
+ NetLaunchInfo nli
+ = NetLaunchInfo.makeFrom( this, intent.getStringExtra(NLI_DATA) );
+ receiveInvitation( srcDevID, nli );
+ break;
+ case GOT_PACKET:
+ byte[] msg = intent.getByteArrayExtra( BINBUFFER );
+ gotPacket( msg, false, true );
+ break;
+ case SEND:
+ case RECEIVE:
+ case SENDNOCONN:
+ startUDPReadThreadOnce();
+ long rowid = intent.getLongExtra( ROWID, -1 );
+ msg = intent.getByteArrayExtra( BINBUFFER );
+ if ( MsgCmds.SEND == cmd ) {
+ sendMessage( rowid, msg, timestamp );
+ } else if ( MsgCmds.SENDNOCONN == cmd ) {
+ String relayID = intent.getStringExtra( RELAY_ID );
+ String msgNo = intent.getStringExtra( MSGNUM );
+ sendNoConnMessage( rowid, relayID, msg, msgNo, timestamp );
+ } else {
+ mHelper.receiveMessage( this, rowid, null, msg, s_addr );
+ }
+ break;
+ case INVITE:
+ startUDPReadThreadOnce();
+ srcDevID = intent.getIntExtra( DEV_ID_SRC, 0 );
+ int destDevID = intent.getIntExtra( DEV_ID_DEST, 0 );
+ String relayID = intent.getStringExtra( RELAY_ID );
+ String nliData = intent.getStringExtra( NLI_DATA );
+ sendInvitation( srcDevID, destDevID, relayID, nliData, timestamp );
+ break;
+ case TIMER_FIRED:
+ if ( !NetStateCache.netAvail( this ) ) {
+ Log.w( TAG, "not connecting: no network" );
+ } else if ( startFetchThreadIfNotUDP() ) {
+ // do nothing
+ } else if ( registerWithRelayIfNot( timestamp ) ) {
+ requestMessages( timestamp );
+ }
+ RelayReceiver.setTimer( this );
+ break;
+ case STOP:
+ stopThreads();
+ stopSelf();
+ break;
+ default:
+ Assert.assertFalse( BuildConfig.DEBUG );
}
}
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWApp.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWApp.java
index 7b39e09d4..1a8791805 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWApp.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWApp.java
@@ -99,16 +99,12 @@ public class XWApp extends Application implements LifecycleObserver {
Log.d( TAG, "onAny(%s)", event );
switch( event ) {
case ON_RESUME:
- BTService.onAppToForeground( this );
// Do here what checkForMoves does
if ( null != DBUtils.getRelayIDs( this, null ) ) {
RelayService.timerFired( this );
}
GameUtils.resendAllIf( this, null );
break;
- case ON_STOP:
- BTService.onAppToBackground( this );
- break;
}
}
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWJIService.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWJIService.java
new file mode 100644
index 000000000..152ad8fce
--- /dev/null
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWJIService.java
@@ -0,0 +1,77 @@
+/* -*- compile-command: "find-and-gradle.sh insXw4Deb"; -*- */
+/*
+ * Copyright 2010 - 2015 by Eric House (xwords@eehouse.org). All
+ * rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+package org.eehouse.android.xw4;
+
+
+import android.support.v4.app.JobIntentService;
+import android.content.Context;
+import android.content.Intent;
+
+abstract class XWJIService extends JobIntentService {
+ static final String CMD_KEY = "CMD";
+ private static final String TIMESTAMP = "TIMESTAMP";
+
+ public interface XWJICmds {
+ public int ordinal();
+ }
+
+ abstract void onHandleWorkImpl( Intent intent, XWJICmds cmd, long timestamp );
+ abstract XWJICmds[] getCmds();
+
+ @Override
+ public final void onHandleWork( Intent intent )
+ {
+ long timestamp = getTimestamp(intent);
+ XWJICmds cmd = cmdFrom( intent );
+ Log.d( getClass().getSimpleName(),
+ "onHandleWork(): cmd=%s; age=%dms; threadCount: %d)",
+ cmd, System.currentTimeMillis() - timestamp,
+ Thread.activeCount() );
+
+ onHandleWorkImpl( intent, cmd, timestamp );
+ }
+
+ static XWJICmds cmdFrom( Intent intent, XWJICmds[] values )
+ {
+ int ord = intent.getIntExtra( CMD_KEY, -1 );
+ return values[ord];
+ }
+
+ XWJICmds cmdFrom( Intent intent )
+ {
+ int ord = intent.getIntExtra( CMD_KEY, -1 );
+ return getCmds()[ord];
+ }
+
+ long getTimestamp( Intent intent )
+ {
+ long result = intent.getLongExtra( TIMESTAMP, 0 );
+ return result;
+ }
+
+ static Intent getIntentTo( Context context, Class clazz, XWJICmds cmd )
+ {
+ Intent intent = new Intent( context, clazz )
+ .putExtra( CMD_KEY, cmd.ordinal() )
+ .putExtra( TIMESTAMP, System.currentTimeMillis() );
+ return intent;
+ }
+}
diff --git a/xwords4/android/app/src/main/res/drawable/notify_btservice.png b/xwords4/android/app/src/main/res/drawable/notify_btservice.png
deleted file mode 100644
index c1f6aa86e..000000000
Binary files a/xwords4/android/app/src/main/res/drawable/notify_btservice.png and /dev/null differ
diff --git a/xwords4/android/app/src/main/res/values/strings.xml b/xwords4/android/app/src/main/res/values/strings.xml
index afc1449f4..730fab04e 100644
--- a/xwords4/android/app/src/main/res/values/strings.xml
+++ b/xwords4/android/app/src/main/res/values/strings.xml
@@ -2056,10 +2056,6 @@
Enable WiFi Direct
Experimental, uses lots of battery
- Accepting Bluetooth messages…
- Stop
- Settings
-
Confirm your SMS plan
@@ -2678,12 +2674,6 @@
player name \"%1$s\". Would you like to personalize with your own
name before you create this game?
- This notification is present whenever
- CrossWords is running in the background to accept Bluetooth
- messages. It usually runs for about 15 minutes after CrossWords
- starts or a Bluetooth message is received.
-
-
This game has sent no invitations
Disable side-by-side
diff --git a/xwords4/android/app/src/xw4d/res/drawable/notify_btservice.png b/xwords4/android/app/src/xw4d/res/drawable/notify_btservice.png
deleted file mode 100644
index 9f97effce..000000000
Binary files a/xwords4/android/app/src/xw4d/res/drawable/notify_btservice.png and /dev/null differ