diff --git a/xwords4/android/XWords4/res/values/common_rsrc.xml b/xwords4/android/XWords4/res/values/common_rsrc.xml index 945cca928..9a47d4569 100644 --- a/xwords4/android/XWords4/res/values/common_rsrc.xml +++ b/xwords4/android/XWords4/res/values/common_rsrc.xml @@ -75,6 +75,7 @@ key_checked_sms key_default_group key_group_posns + key_last_packet key_notagain_sync key_notagain_chat diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java index c46331b93..aaff02f2d 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java @@ -213,7 +213,7 @@ public class DlgDelegate { if ( null == DBUtils.getRelayIDs( m_activity, null ) ) { showOKOnlyDialog( R.string.no_games_to_refresh ); } else { - RelayReceiver.RestartTimer( m_activity, true ); + RelayService.timerFired( m_activity ); Utils.showToast( m_activity, R.string.msgs_progress ); } } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java index 7eb488c08..f6de3ecfd 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java @@ -45,6 +45,7 @@ public class GCMIntentService extends GCMBaseIntentService { { DbgUtils.logf( "GCMIntentService.onRegistered(%s)", regId ); XWPrefs.setGCMDevID( context, regId ); + notifyRelayService( true ); } @Override @@ -52,6 +53,7 @@ public class GCMIntentService extends GCMBaseIntentService { { DbgUtils.logf( "GCMIntentService.onUnregistered(%s)", regId ); XWPrefs.clearGCMDevID( context ); + notifyRelayService( false ); } @Override @@ -62,6 +64,8 @@ public class GCMIntentService extends GCMBaseIntentService { if ( ignoreIt ) { DbgUtils.logf( "received GCM but ignoring it" ); } else { + notifyRelayService( true ); + value = intent.getStringExtra( "checkUpdates" ); if ( null != value && Boolean.parseBoolean( value ) ) { UpdateCheckReceiver.checkVersions( context, true ); @@ -69,7 +73,7 @@ public class GCMIntentService extends GCMBaseIntentService { value = intent.getStringExtra( "getMoves" ); if ( null != value && Boolean.parseBoolean( value ) ) { - RelayReceiver.RestartTimer( context, true ); + RelayService.timerFired( context ); } value = intent.getStringExtra( "msgs64" ); @@ -120,4 +124,12 @@ public class GCMIntentService extends GCMBaseIntentService { } } + private void notifyRelayService( boolean working ) + { + if ( working && XWPrefs.getGCMIgnored( this ) ) { + working = false; + } + RelayService.gcmConfirmed( working ); + } + } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayReceiver.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayReceiver.java index cfd239cf9..eaf50fe6f 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayReceiver.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayReceiver.java @@ -39,26 +39,19 @@ public class RelayReceiver extends BroadcastReceiver { RestartTimer( context ); } else { // DbgUtils.logf( "RelayReceiver::onReceive()" ); - // Toast.makeText(context, "RelayReceiver: fired", - // Toast.LENGTH_SHORT).show(); - Intent service = new Intent( context, RelayService.class ); - context.startService( service ); + // Toast.makeText( context, "RelayReceiver: timer fired", + // Toast.LENGTH_SHORT).show(); + RelayService.timerFired( context ); } } - public static void RestartTimer( Context context, boolean force ) - { - RestartTimer( context, - 1000 * XWPrefs.getProxyInterval( context ), force ); - } - public static void RestartTimer( Context context ) { - RestartTimer( context, false ); + RestartTimer( context, + 1000 * XWPrefs.getProxyInterval( context ) ); } - public static void RestartTimer( Context context, long interval_millis, - boolean force ) + public static void RestartTimer( Context context, long interval_millis ) { AlarmManager am = (AlarmManager)context.getSystemService( Context.ALARM_SERVICE ); @@ -66,22 +59,15 @@ public class RelayReceiver extends BroadcastReceiver { Intent intent = new Intent( context, RelayReceiver.class ); PendingIntent pi = PendingIntent.getBroadcast( context, 0, intent, 0 ); - if ( force || interval_millis > 0 ) { - long first_millis = 0; - if ( !force ) { - first_millis = SystemClock.elapsedRealtime() + interval_millis; - } + if ( interval_millis > 0 ) { + long first_millis = SystemClock.elapsedRealtime() + interval_millis; am.setInexactRepeating( AlarmManager.ELAPSED_REALTIME_WAKEUP, first_millis, // first firing interval_millis, pi ); } else { + // will happen if user's set getProxyInterval to return 0 am.cancel( pi ); } } - public static void RestartTimer( Context context, long interval_millis ) - { - RestartTimer( context, interval_millis, false ); - } - } 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 0ffa26df8..5ce22de0a 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java @@ -46,18 +46,26 @@ import org.eehouse.android.xw4.MultiService.MultiEvent; import org.eehouse.android.xw4.jni.CommsAddrRec; import org.eehouse.android.xw4.jni.GameSummary; import org.eehouse.android.xw4.jni.UtilCtxt; +import org.eehouse.android.xw4.jni.UtilCtxt.DevIDType; import org.eehouse.android.xw4.jni.XwJNI; -public class RelayService extends XWService { +public class RelayService extends XWService + implements NetStateCache.StateChangedIf { private static final int MAX_SEND = 1024; private static final int MAX_BUF = MAX_SEND - 2; + // One week, in seconds. Probably should be configurable. + private static final long MAX_KEEPALIVE_SECS = 7 * 24 * 60 * 60; + private static final String CMD_STR = "CMD"; + // These should be enums private static final int PROCESS_MSGS = 1; private static final int UDP_CHANGED = 2; private static final int SEND = 3; private static final int RECEIVE = 4; + private static final int TIMER_FIRED = 5; + private static final int RESET = 6; private static final String MSGS = "MSGS"; private static final String RELAY_ID = "RELAY_ID"; @@ -66,6 +74,7 @@ public class RelayService extends XWService { private static HashSet s_packetsSent = new HashSet(); private static int s_nextPacketID = 1; + private static boolean s_gcmWorking = false; private Thread m_fetchThread = null; private Thread m_UDPReadThread = null; @@ -74,8 +83,9 @@ public class RelayService extends XWService { private LinkedBlockingQueue m_queue = new LinkedBlockingQueue(); private Handler m_handler; - private Runnable m_killer; - private short m_maxInterval = 0; + private Runnable m_onInactivity; + private short m_maxIntervalSeconds = 5; // give time to get real value + private long m_lastPacketReceived; // These must match the enum XWRelayReg in xwrelay.h private static final int XWPDEV_PROTO_VERSION = 0; @@ -87,7 +97,7 @@ public class RelayService extends XWService { ,XWPDEV_ALERT ,XWPDEV_REG ,XWPDEV_REGRSP - ,XWPDEV_PING + ,XWPDEV_KEEPALIVE ,XWPDEV_HAVEMSGS ,XWPDEV_RQSTMSGS ,XWPDEV_MSG @@ -97,16 +107,10 @@ public class RelayService extends XWService { ,XWPDEV_ACK }; - // private static final int XWPDEV_ALERT = 1; - // private static final int XWPDEV_REG = 2; - // private static final int XWPDEV_REGRSP = 3; - // private static final int XWPDEV_PING = 4; - // private static final int XWPDEV_HAVEMSGS = 5; - // private static final int XWPDEV_RQSTMSGS = 6; - // private static final int XWPDEV_MSG = 7; - // private static final int XWPDEV_MSGNOCONN = 8; - // private static final int XWPDEV_MSGRSP = 9; - // private static final int XWPDEV_BADREG = 10; + public static void gcmConfirmed( boolean confirmed ) + { + s_gcmWorking = confirmed; + } public static void startService( Context context ) { @@ -116,16 +120,30 @@ public class RelayService extends XWService { public static void reset( Context context ) { - DbgUtils.logf( "RelayService.reset() called" ); + Intent intent = getIntentTo( context, RESET ); + context.startService( intent ); + } + + public static void timerFired( Context context ) + + { + Intent intent = getIntentTo( context, TIMER_FIRED ); + context.startService( intent ); } public static int sendPacket( Context context, long rowid, byte[] msg ) { - Intent intent = getIntentTo( context, SEND ) - .putExtra( ROWID, rowid ) - .putExtra( BINBUFFER, msg ); - context.startService( intent ); - return msg.length; + int result = -1; + if ( NetStateCache.netAvail( context ) ) { + Intent intent = getIntentTo( context, SEND ) + .putExtra( ROWID, rowid ) + .putExtra( BINBUFFER, msg ); + context.startService( intent ); + result = msg.length; + } else { + DbgUtils.logf( "RelayService.sendPacket: network down" ); + } + return result; } // Exists to get incoming data onto the main thread @@ -169,12 +187,20 @@ public class RelayService extends XWService { public void onCreate() { super.onCreate(); - startFetchThreadIf(); + m_lastPacketReceived = + XWPrefs.getPrefsLong( this, R.string.key_last_packet, 0 ); + m_handler = new Handler(); - m_killer = new Runnable() { + m_onInactivity = new Runnable() { public void run() { - // DbgUtils.logf( "RelayService: m_killer fired" ); - stopSelf(); + DbgUtils.logf( "RelayService: m_onInactivity fired" ); + if ( !shouldMaintainConnection() ) { + NetStateCache.unregister( RelayService.this, + RelayService.this ); + stopSelf(); + } else { + timerFired( RelayService.this ); + } } }; } @@ -182,10 +208,10 @@ public class RelayService extends XWService { @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 ); + DbgUtils.logf( "RelayService::onStartCommand: cmd=%d", cmd ); switch( cmd ) { case -1: break; @@ -205,15 +231,11 @@ public class RelayService extends XWService { } break; case UDP_CHANGED: - DbgUtils.logf( "RelayService::onStartCommand::UDP_CHANGED" ); - if ( XWPrefs.getUDPEnabled( this ) ) { - stopFetchThreadIf(); - startUDPThreadsIfNot(); - registerWithRelay(); - } else { - stopUDPThreadsIf(); - startFetchThreadIf(); - } + startThreads(); + break; + case RESET: + stopThreads(); + startThreads(); break; case SEND: case RECEIVE: @@ -226,6 +248,11 @@ public class RelayService extends XWService { feedMessage( rowid, msg ); } break; + case TIMER_FIRED: + if ( !startFetchThreadIf() ) { + requestMessages(); + } + break; default: Assert.fail(); } @@ -234,10 +261,32 @@ public class RelayService extends XWService { } else { result = Service.START_STICKY_COMPATIBILITY; } + + NetStateCache.register( this, this ); resetExitTimer(); return result; } + @Override + public void onDestroy() + { + DbgUtils.logf( "RelayService.onDestroy() called" ); + XWPrefs.setPrefsLong( this, R.string.key_last_packet, + m_lastPacketReceived ); + + if ( shouldMaintainConnection() ) { + long interval_millis = m_maxIntervalSeconds * 1000; + RelayReceiver.RestartTimer( this, interval_millis ); + } + super.onDestroy(); + } + + // NetStateCache.StateChangedIf interface + public void netAvail( boolean nowAvailable ) + { + startService( this ); // bad name: will *stop* threads too + } + private void setupNotification( String[] relayIDs ) { for ( String relayID : relayIDs ) { @@ -259,10 +308,11 @@ public class RelayService extends XWService { msg, (int)rowid ); } - private void startFetchThreadIf() + private boolean startFetchThreadIf() { DbgUtils.logf( "startFetchThreadIf()" ); - if ( !XWPrefs.getUDPEnabled( this ) && null == m_fetchThread ) { + boolean handled = !XWPrefs.getUDPEnabled( this ); + if ( handled && null == m_fetchThread ) { m_fetchThread = new Thread( null, new Runnable() { public void run() { fetchAndProcess(); @@ -272,12 +322,15 @@ public class RelayService extends XWService { }, getClass().getName() ); m_fetchThread.start(); } + return handled; } private void stopFetchThreadIf() { if ( null != m_fetchThread ) { DbgUtils.logf( "2: m_fetchThread NOT NULL; WHAT TO DO???" ); + m_fetchThread.stop(); + m_fetchThread = null; } } @@ -302,6 +355,7 @@ public class RelayService extends XWService { + "on receive" ); m_UDPSocket.receive( packet ); resetExitTimer(); + m_lastPacketReceived = Utils.getCurSeconds(); DbgUtils.logf( "UPD read thread: " + "receive returned" ); } catch( java.io.IOException ioe ) { @@ -371,8 +425,11 @@ public class RelayService extends XWService { try { m_UDPSocket.send( outPacket ); resetExitTimer(); + ConnStatusHandler.showSuccessOut(); } catch ( java.io.IOException ioe ) { DbgUtils.loge( ioe ); + } catch ( NullPointerException npe ) { + DbgUtils.logf( "network problem; dropping packet" ); } } DbgUtils.logf( "write thread exiting" ); @@ -419,6 +476,8 @@ public class RelayService extends XWService { // Running on reader thread private void gotPacket( DatagramPacket packet ) { + ConnStatusHandler.showSuccessIn(); + int packetLen = packet.getLength(); byte[] data = new byte[packetLen]; System.arraycopy( packet.getData(), 0, data, 0, packetLen ); @@ -443,9 +502,10 @@ public class RelayService extends XWService { break; case XWPDEV_REGRSP: str = getStringWithLength( dis ); - m_maxInterval = dis.readShort(); + m_maxIntervalSeconds = dis.readShort(); DbgUtils.logf( "got relayid %s, maxInterval %d", str, - m_maxInterval ); + m_maxIntervalSeconds ); + XWPrefs.setRelayDevID( this, str ); break; case XWPDEV_HAVEMSGS: requestMessages(); @@ -598,23 +658,23 @@ public class RelayService extends XWService { private String getDevID( byte[] typp ) { - UtilCtxt.DevIDType typ; + DevIDType typ; String devid = XWPrefs.getRelayDevID( this ); if ( null != devid && 0 < devid.length() ) { - typ = UtilCtxt.DevIDType.ID_TYPE_RELAY; + typ = DevIDType.ID_TYPE_RELAY; } else { devid = XWPrefs.getGCMDevID( this ); if ( null != devid && 0 < devid.length() ) { - typ = UtilCtxt.DevIDType.ID_TYPE_ANDROID_GCM; + typ = DevIDType.ID_TYPE_ANDROID_GCM; } else { devid = ""; - typ = UtilCtxt.DevIDType.ID_TYPE_ANON; + typ = DevIDType.ID_TYPE_ANON; } } if ( null != typp ) { typp[0] = (byte)typ.ordinal(); } else { - Assert.assertTrue( typ == UtilCtxt.DevIDType.ID_TYPE_RELAY ); + Assert.assertTrue( typ == DevIDType.ID_TYPE_RELAY ); } return devid; } @@ -633,7 +693,7 @@ public class RelayService extends XWService { sink ) ) { setupNotification( rowid ); } else { - DbgUtils.logf( "feedMessage: background dropped it" ); + DbgUtils.logf( "feedMessage(): background dropped it" ); } } } @@ -832,7 +892,8 @@ public class RelayService extends XWService { { synchronized( s_packetsSent ) { s_packetsSent.remove( packetID ); - DbgUtils.logf( "Got ack for %d; there are %d unacked packets", + DbgUtils.logf( "RelayService.noteAck(): Got ack for %d; " + + "there are %d unacked packets", packetID, s_packetsSent.size() ); } } @@ -841,11 +902,60 @@ public class RelayService extends XWService { private void resetExitTimer() { // DbgUtils.logf( "RelayService.resetExitTimer()" ); - m_handler.removeCallbacks( m_killer ); + m_handler.removeCallbacks( m_onInactivity ); - // UDP socket's no good as a return address after 2 minutes of - // in activity, so take down the service after that time. - m_handler.postDelayed( m_killer, 3 * 60 * 1000 ); + // UDP socket's no good as a return address after several + // minutes of inactivity, so do something after that time. + m_handler.postDelayed( m_onInactivity, + m_maxIntervalSeconds * 1000 ); + } + + private void startThreads() + { + if ( !NetStateCache.netAvail( this ) ) { + stopThreads(); + } else if ( XWPrefs.getUDPEnabled( this ) ) { + stopFetchThreadIf(); + startUDPThreadsIfNot(); + registerWithRelay(); + } else { + stopUDPThreadsIf(); + startFetchThreadIf(); + } + } + + private void stopThreads() + { + stopFetchThreadIf(); + stopUDPThreadsIf(); + } + + /* Timers: + * + * Two goals: simulate the GCM experience for those who don't have + * it (e.g. Kindle); and stop this service when it's not needed. + * For now, we'll try to be immediately reachable from the relay + * if: 1) the device doesn't have GCM; and 2) it's been less than + * a week since we last received a packet from the relay. We'll + * do this even if there are no relay games left on the device in + * order to support the rematch feature. + * + * Goal: maintain connection by keeping this service alive with + * its periodic pings to relay. When it dies or is killed, + * notice, and use RelayReceiver's timer to get it restarted a bit + * later. But note: s_gcmWorking will not be set when the app is + * relaunched. + */ + + private boolean shouldMaintainConnection() + { + boolean result = !s_gcmWorking; + if ( result ) { + long interval = Utils.getCurSeconds() - m_lastPacketReceived; + result = interval < MAX_KEEPALIVE_SECS; + } + DbgUtils.logf( "RelayService.shouldMaintainConnection=>%b", result ); + return result; } private class PacketHeader {