From 7ccb576214ab477b66d1da3733e93a045be68231 Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 8 Feb 2019 07:26:00 -0800 Subject: [PATCH] Recast BTService as a JobIntentService Got tired of the space the forground-service notification icons were taking. So now BT sends and receives are done via static threads and onHandleWork(). The send thread times itself out quickly. The receive thread doesn't yet. We'll see how long the OS lets it run and what needs to be done to deal with that. --- .../android/app/src/main/AndroidManifest.xml | 3 - .../org/eehouse/android/xw4/BTReceiver.java | 6 - .../org/eehouse/android/xw4/BTService.java | 944 ++++++++---------- .../android/xw4/GamesListDelegate.java | 16 - .../org/eehouse/android/xw4/RelayService.java | 230 ++--- .../java/org/eehouse/android/xw4/XWApp.java | 4 - .../org/eehouse/android/xw4/XWJIService.java | 77 ++ .../main/res/drawable/notify_btservice.png | Bin 308 -> 0 bytes .../app/src/main/res/values/strings.xml | 10 - .../xw4d/res/drawable/notify_btservice.png | Bin 471 -> 0 bytes 10 files changed, 577 insertions(+), 713 deletions(-) create mode 100644 xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWJIService.java delete mode 100644 xwords4/android/app/src/main/res/drawable/notify_btservice.png delete mode 100644 xwords4/android/app/src/xw4d/res/drawable/notify_btservice.png 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 c1f6aa86e2f2e66757bfcc58e70d22140d1d6869..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 308 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDD3?#L31Vw-pXMj(LD+5FN|NsAiJlpFZrGOM; zNswPKgTu2MX&_FLx4R2N2dk_HNO^%rWHAGSp&1A>&U)^o02E{|@$_|Nf6U0mq+=nI z$IAv3TH@*A7@~20?K*wm6bfL&=gC5#nEQH z$<^XOlXW7K#fq?5-|TW_`kSlvI@x}+IN!We%z#T$ecjj4C7HVvHP1_{s+xN|Z~5^? t_97Eg(&6Me0<#zOEdSX2{m1-YOwR+`q%s@cS^?e5;OXk;vd$@?2>@5ZXx0D# 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 9f97effce2aba6c588f7e666155a5af609266fa4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 471 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDD3?#L31Vw-pOS+@4BLl<6e(pbstU$g(vPY0F z14ES>14Ba#1H&(%P{RubhEkx$R|yOZRx=nF#0%!^3bX-A@C5jTxH2%L1DXH-|7Tzb zeOvZC6v$yL3GxeOaCmkj4a7vL>4nJ7@C1FH=O z_Q#A|Oh$rTb60Hz3N7<=aSYKozxR?OU$cS$Yk=3KV--S&H21Wui}rW2a5TT`zw6h` zU@^C&@0)VdmHpEb9Ok?}mU}O0)sFZLD_o7%^jvrHm^aZ^J