diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/PrefsActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/PrefsActivity.java index 1398c5d28..ad7804597 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/PrefsActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/PrefsActivity.java @@ -41,6 +41,7 @@ public class PrefsActivity extends PreferenceActivity private String m_keyLogging; private String m_smsToasting; private String m_smsEnable; + private String m_udpEnabled; private String m_downloadPath; @Override @@ -122,6 +123,7 @@ public class PrefsActivity extends PreferenceActivity m_keyLogging = getString( R.string.key_logging_on ); m_smsToasting = getString( R.string.key_show_sms ); m_smsEnable = getString( R.string.key_enable_sms ); + m_udpEnabled = getString( R.string.key_udp_relay ); m_downloadPath = getString( R.string.key_download_path ); Button button = (Button)findViewById( R.id.revert_colors ); @@ -161,6 +163,8 @@ public class PrefsActivity extends PreferenceActivity DbgUtils.logEnable( sp.getBoolean( key, false ) ); } else if ( key.equals( m_smsToasting ) ) { SMSService.smsToastEnable( sp.getBoolean( key, false ) ); + } else if ( key.equals( m_udpEnabled ) ) { + RelayService.udpChanged( this ); } else if ( key.equals( m_smsEnable ) ) { if ( sp.getBoolean( key, true ) ) { SMSService.checkForInvites( this ); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java index bc4d2d179..b5ed85fcc 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java @@ -24,31 +24,66 @@ import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.IBinder; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; import java.io.DataOutputStream; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; import java.net.Socket; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; +import java.util.concurrent.LinkedBlockingQueue; + +import junit.framework.Assert; import org.eehouse.android.xw4.jni.GameSummary; +import org.eehouse.android.xw4.jni.UtilCtxt; public class RelayService extends Service { private static final int MAX_SEND = 1024; private static final int MAX_BUF = MAX_SEND - 2; + private static final String CMD_STR = "CMD"; + private static final int UDP_CHANGED = 1; + + private Thread m_fetchThread = null; + private Thread m_UDPReadThread = null; + private Thread m_UDPWriteThread = null; + private DatagramSocket m_UDPSocket; + private LinkedBlockingQueue m_queue = null; + + // These must match the enum XWRelayReg in xwrelay.h + private static final int XWPDEV_PROTO_VERSION = 0; + // private static final int XWPDEV_NONE = 0; + private static final int XWPDEV_ALERT = 1; + private static final int XWPDEV_REG = 2; + + public static void startService( Context context ) + { + Intent intent = getIntentTo( context, UDP_CHANGED ); + context.startService( intent ); + } + + public static void udpChanged( Context context ) + { + startService( context ); + } + + private static Intent getIntentTo( Context context, int cmd ) + { + Intent intent = new Intent( context, RelayService.class ); + intent.putExtra( CMD_STR, cmd ); + return intent; + } + @Override public void onCreate() { super.onCreate(); - - Thread thread = new Thread( null, new Runnable() { - public void run() { - fetchAndProcess(); - RelayService.this.stopSelf(); - } - }, getClass().getName() ); - thread.start(); + startFetchThreadIf(); } @Override @@ -57,6 +92,36 @@ public class RelayService extends Service { return null; } + @Override + public int onStartCommand( Intent intent, int flags, int startId ) + { + DbgUtils.logf( "RelayService::onStartCommand" ); + int result; + if ( null != intent ) { + int cmd = intent.getIntExtra( CMD_STR, -1 ); + switch( cmd ) { + case UDP_CHANGED: + DbgUtils.logf( "RelayService::onStartCommand::UDP_CHANGED" ); + if ( XWPrefs.getUDPEnabled( this ) ) { + stopFetchThreadIf(); + startUDPThreads(); + registerWithRelay(); + } else { + stopUDPThreadsIf(); + startFetchThreadIf(); + } + break; + default: + Assert.fail(); + } + + result = Service.START_STICKY; + } else { + result = Service.START_STICKY_COMPATIBILITY; + } + return result; + } + private void setupNotification( String[] relayIDs ) { for ( String relayID : relayIDs ) { @@ -75,6 +140,189 @@ public class RelayService extends Service { } } + private void startFetchThreadIf() + { + DbgUtils.logf( "startFetchThreadIf()" ); + if ( !XWPrefs.getUDPEnabled( this ) && null == m_fetchThread ) { + m_fetchThread = new Thread( null, new Runnable() { + public void run() { + fetchAndProcess(); + m_fetchThread = null; + RelayService.this.stopSelf(); + } + }, getClass().getName() ); + m_fetchThread.start(); + } + } + + private void stopFetchThreadIf() + { + if ( null != m_fetchThread ) { + DbgUtils.logf( "2: m_fetchThread NOT NULL; WHAT TO DO???" ); + } + } + + private void startUDPThreads() + { + DbgUtils.logf( "startUDPThreads" ); + Assert.assertNull( m_UDPWriteThread ); + Assert.assertNull( m_UDPReadThread ); + Assert.assertTrue( XWPrefs.getUDPEnabled( this ) ); + + int port = XWPrefs.getDefaultRelayPort( RelayService.this ); + String host = XWPrefs.getDefaultRelayHost( RelayService.this ); + try { + m_UDPSocket = new DatagramSocket(); + InetAddress addr = InetAddress.getByName( host ); + m_UDPSocket.connect( addr, port ); + } catch( java.net.SocketException se ) { + DbgUtils.loge( se ); + Assert.fail(); + } catch( java.net.UnknownHostException uhe ) { + DbgUtils.loge( uhe ); + } + + m_UDPReadThread = new Thread( null, new Runnable() { + public void run() { + byte[] buf = new byte[1024]; + for ( ; ; ) { + DatagramPacket packet = + new DatagramPacket( buf, buf.length ); + try { + DbgUtils.logf( "UPD read thread blocking on receive" ); + m_UDPSocket.receive( packet ); + DbgUtils.logf( "UPD read thread: receive returned" ); + } catch( java.io.IOException ioe ) { + DbgUtils.loge( ioe ); + break; // ??? + } + DbgUtils.logf( "received %d bytes", packet.getLength() ); + gotPacket( packet ); + } + } + }, getClass().getName() ); + m_UDPReadThread.start(); + + m_queue = new LinkedBlockingQueue(); + m_UDPWriteThread = new Thread( null, new Runnable() { + public void run() { + for ( ; ; ) { + DatagramPacket outPacket; + try { + outPacket = m_queue.take(); + } catch ( InterruptedException ie ) { + DbgUtils.logf( "RelayService; write thread killed" ); + break; + } + if ( null == outPacket || 0 == outPacket.getLength() ) { + DbgUtils.logf( "stopping write thread" ); + break; + } + DbgUtils.logf( "Sending packet of length %d", + outPacket.getLength() ); + try { + m_UDPSocket.send( outPacket ); + } catch ( java.io.IOException ioe ) { + DbgUtils.loge( ioe ); + } + } + } + }, getClass().getName() ); + m_UDPWriteThread.start(); + } + + private void stopUDPThreadsIf() + { + DbgUtils.logf( "stopUDPThreadsIf" ); + if ( null != m_queue && null != m_UDPWriteThread ) { + // can't add null + m_queue.add( new DatagramPacket( new byte[0], 0 ) ); + try { + DbgUtils.logf( "joining m_UDPWriteThread" ); + m_UDPWriteThread.join(); + DbgUtils.logf( "SUCCESSFULLY joined m_UDPWriteThread" ); + } catch( java.lang.InterruptedException ie ) { + DbgUtils.loge( ie ); + } + m_UDPWriteThread = null; + m_queue = null; + } + if ( null != m_UDPSocket && null != m_UDPReadThread ) { + m_UDPSocket.close(); + DbgUtils.logf( "waiting for read thread to exit" ); + try { + m_UDPReadThread.join(); + } catch( java.lang.InterruptedException ie ) { + DbgUtils.loge( ie ); + } + DbgUtils.logf( "read thread exited" ); + m_UDPReadThread = null; + m_UDPSocket = null; + } + DbgUtils.logf( "stopUDPThreadsIf DONE" ); + } + + private void gotPacket( DatagramPacket packet ) + { + DbgUtils.logf( "gotPacket" ); + ByteArrayInputStream bis = new ByteArrayInputStream( packet.getData() ); + DataInputStream dis = new DataInputStream( bis ); + try { + byte proto = dis.readByte(); + if ( XWPDEV_PROTO_VERSION == proto ) { + byte cmd = dis.readByte(); + switch ( cmd ) { + case XWPDEV_ALERT: + short len = dis.readShort(); + byte[] tmp = new byte[len]; + dis.read( tmp ); + String msg = new String( tmp ); + DbgUtils.logf( "got message: %s", msg ); + break; + default: + DbgUtils.logf( "RelayService: Unhandled cmd: %d", cmd ); + break; + } + } else { + DbgUtils.logf( "bad proto %d", proto ); + } + } catch ( java.io.IOException ioe ) { + DbgUtils.loge( ioe ); + } + } + + private void registerWithRelay() + { + byte typ; + String devid = XWPrefs.getRelayDevID( this ); + if ( null != devid && 0 < devid.length() ) { + typ = UtilCtxt.ID_TYPE_RELAY; + } else { + devid = XWPrefs.getGCMDevID( this ); + if ( null != devid && 0 < devid.length() ) { + typ = UtilCtxt.ID_TYPE_ANDROID_GCM; + } else { + devid = "DO NOT SHIP WITH ME"; + typ = UtilCtxt.ID_TYPE_ANDROID_OTHER; + } + } + + ByteArrayOutputStream bas = new ByteArrayOutputStream(); + DataOutputStream outBuf = new DataOutputStream( bas ); + try { + outBuf.writeByte( XWPDEV_PROTO_VERSION ); + outBuf.writeByte( XWPDEV_REG ); + outBuf.writeByte( typ ); + outBuf.writeShort( devid.length() ); + outBuf.writeBytes( devid ); + + byte[] data = bas.toByteArray(); + m_queue.add( new DatagramPacket( data, data.length ) ); + } catch ( java.io.IOException ioe ) { + DbgUtils.loge( ioe ); + } + } + private void fetchAndProcess() { long[][] rowIDss = new long[1][]; diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java index be0a95ef2..1a78e9f47 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java @@ -66,7 +66,7 @@ public class SMSService extends Service { private static final int INVITE = 2; private static final int SEND = 3; private static final int REMOVE = 4; - private static final int MESG_GAMEGONE = 5; + // private static final int MESG_GAMEGONE = 5; private static final int CHECK_MSGDB = 6; private static final int ADDED_MISSING = 7; diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java index d3d02f534..e48854788 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java @@ -58,10 +58,10 @@ public class XWApp extends Application { RelayReceiver.RestartTimer( this ); UpdateCheckReceiver.restartTimer( this ); + BTService.startService( this ); - SMSService.checkForInvites( this ); - + RelayService.startService( this ); GCMIntentService.init( this ); } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java index 1491c52ff..649059f3a 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java @@ -34,6 +34,11 @@ public class XWPrefs { return getPrefsBoolean( context, R.string.key_enable_sms, false ); } + public static boolean getUDPEnabled( Context context ) + { + return getPrefsBoolean( context, R.string.key_udp_relay, false ); + } + public static boolean getDebugEnabled( Context context ) { return getPrefsBoolean( context, R.string.key_enable_debug, false ); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java index 5302f41f7..95c18bf01 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java @@ -60,6 +60,7 @@ public interface UtilCtxt { public static final int ID_TYPE_NONE = 0; public static final int ID_TYPE_RELAY = 1; public static final int ID_TYPE_ANDROID_GCM = 3; + public static final int ID_TYPE_ANDROID_OTHER = 4; String getDevID( /*out*/ byte[] typ ); void deviceRegistered( int devIDType, String idRelay );