diff --git a/xwords4/android/INSTALL.txt b/xwords4/android/INSTALL.txt index 23fda0b62..4139d59fe 100644 --- a/xwords4/android/INSTALL.txt +++ b/xwords4/android/INSTALL.txt @@ -1,29 +1,23 @@ -Here's how I'm building crosswords for Android. +(Updated Dec 2017) -First, cd into the directory xwords4/android/XWords4. Everything +Here's how I'm building CrossWords for Android. + +First, cd into the directory xwords4/android. Everything happens there. -IF (and it's a big if) you have all the necessary tools installed (the -Android SDK and NDK, apache ant, and probably other stuff I've -forgotten), two commands will build it, the first a one-time deal and -the second repeated every time you made a change. +To build and install the debug version of CrossWords: -The build process requires a file called local.properties that must be -generated locally. Generate it before your first build by running +# ./gradlew clean insXw4Deb -# ../scripts/setup_local_props.sh +To build and install the debug version of CrossDbg (a variant meant +for development that can co-exist with CrossWords): -Then build using this command: +# ./gradlew -PuseCrashlytics insXw4dDeb -# ant debug - -Or, if you have a device or emulator attached (listed by 'adb -devices'), try - -# ant debug install - -Making a release build requires that you've set up your keys. (I did -this too long ago to remember how but the info's easy to find). Once -that's done, just tether your device and type - -# ant release install +I do all development on Debian and Ubuntu Linux systems. I have built +on MacOS, where once you get all the necessary tools installed via +homebrew there's only one problem I'm aware of: the parameter 'white' +that's passed to convert by android/scripts/images.mk on Linux systems +needs to be 'black' on MacOS. I have no clue why. If you don't make +this change the subset of actionbar icons that are generated from .svg +files will be black-on-black. diff --git a/xwords4/android/app/build.gradle b/xwords4/android/app/build.gradle index b170c5e9a..9a07b0b08 100644 --- a/xwords4/android/app/build.gradle +++ b/xwords4/android/app/build.gradle @@ -1,6 +1,6 @@ def INITIAL_CLIENT_VERS = 8 -def VERSION_CODE_BASE = 127 -def VERSION_NAME = '4.4.131' +def VERSION_CODE_BASE = 128 +def VERSION_NAME = '4.4.132' def FABRIC_API_KEY = System.getenv("FABRIC_API_KEY") def GCM_SENDER_ID = System.getenv("GCM_SENDER_ID") def BUILD_INFO_NAME = "build-info.txt" @@ -201,6 +201,8 @@ dependencies { compile 'com.android.support:support-v4:23.4.0' + // 2.6.8 is probably as far forward as I can go without upping my + // min-supported SDK version xw4dCompile('com.crashlytics.sdk.android:crashlytics:2.6.8@aar') { transitive = true; } diff --git a/xwords4/android/app/src/main/assets/changes.html b/xwords4/android/app/src/main/assets/changes.html index 5ee93b9c0..a09afa66a 100644 --- a/xwords4/android/app/src/main/assets/changes.html +++ b/xwords4/android/app/src/main/assets/changes.html @@ -13,9 +13,9 @@ -

CrossWords 4.4.131 release

+

CrossWords 4.4.132 release

-

An F-Droid-only release meeting new requirements

+

This release makes communication with the relay more robust.

Please take @@ -25,10 +25,14 @@

New with this release

(The full changelog diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/CommsTransport.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/CommsTransport.java index 59830c635..4c51b9af5 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/CommsTransport.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/CommsTransport.java @@ -387,32 +387,6 @@ public class CommsTransport implements TransportProcs, return nSent; } - public void relayStatus( CommsRelayState newState ) - { - Log.i( TAG, "relayStatus called; state=%s", newState.toString() ); - - switch( newState ) { - case COMMS_RELAYSTATE_UNCONNECTED: - case COMMS_RELAYSTATE_DENIED: - case COMMS_RELAYSTATE_CONNECT_PENDING: - ConnStatusHandler.updateStatus( m_context, null, - CommsConnType.COMMS_CONN_RELAY, - false ); - break; - case COMMS_RELAYSTATE_CONNECTED: - case COMMS_RELAYSTATE_RECONNECTED: - ConnStatusHandler.updateStatusOut( m_context, null, - CommsConnType.COMMS_CONN_RELAY, - true ); - break; - case COMMS_RELAYSTATE_ALLCONNECTED: - ConnStatusHandler.updateStatusIn( m_context, null, - CommsConnType.COMMS_CONN_RELAY, - true ); - break; - } - } - public void relayConnd( String room, int devOrder, boolean allHere, int nMissing ) { diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameUtils.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameUtils.java index 92c476792..7bb484826 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameUtils.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameUtils.java @@ -482,14 +482,12 @@ public class GameUtils { if ( force ) { HashMap games = DBUtils.getGamesWithSendsPending( context ); - if ( 0 < games.size() ) { - new ResendTask( context, games, filter, proc ).execute(); + new ResendTask( context, games, filter, proc ).execute(); - System.arraycopy( sendTimes, 0, /* src */ - sendTimes, 1, /* dest */ - sendTimes.length - 1 ); - sendTimes[0] = now; - } + System.arraycopy( sendTimes, 0, /* src */ + sendTimes, 1, /* dest */ + sendTimes.length - 1 ); + sendTimes[0] = now; } } @@ -1259,7 +1257,7 @@ public class GameUtils { private HashMap m_games; private ResendDoneProc m_doneProc; private CommsConnType m_filter; - private MultiMsgSink m_sink; + private int m_nSent = 0; public ResendTask( Context context, HashMap games, CommsConnType filter, ResendDoneProc proc ) @@ -1288,14 +1286,15 @@ public class GameUtils { GameLock lock = new GameLock( rowid, false ); if ( lock.tryLock() ) { CurGameInfo gi = new CurGameInfo( m_context ); - m_sink = new MultiMsgSink( m_context, rowid ); - GamePtr gamePtr = loadMakeGame( m_context, gi, m_sink, lock ); + MultiMsgSink sink = new MultiMsgSink( m_context, rowid ); + GamePtr gamePtr = loadMakeGame( m_context, gi, sink, lock ); if ( null != gamePtr ) { int nSent = XwJNI.comms_resendAll( gamePtr, true, m_filter, false ); gamePtr.release(); Log.d( TAG, "ResendTask.doInBackground(): sent %d " + "messages for rowid %d", nSent, rowid ); + m_nSent += sink.numSent(); } else { Log.d( TAG, "ResendTask.doInBackground(): loadMakeGame()" + " failed for rowid %d", rowid ); @@ -1320,8 +1319,7 @@ public class GameUtils { protected void onPostExecute( Void unused ) { if ( null != m_doneProc ) { - int nSent = null == m_sink ? 0 : m_sink.numSent(); - m_doneProc.onResendDone( m_context, nSent ); + m_doneProc.onResendDone( m_context, m_nSent ); } } } diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/MultiMsgSink.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/MultiMsgSink.java index 01b6ed72c..0a08833e2 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/MultiMsgSink.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/MultiMsgSink.java @@ -119,10 +119,6 @@ public class MultiMsgSink implements TransportProcs { return nSent; } - public void relayStatus( CommsRelayState newState ) - { - } - public void relayErrorProc( XWRELAY_ERROR relayErr ) { } 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 984e57840..8f4a1870e 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 @@ -60,7 +60,9 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; public class RelayService extends XWService implements NetStateCache.StateChangedIf { @@ -69,6 +71,7 @@ public class RelayService extends XWService private static final int MAX_BUF = MAX_SEND - 2; private static final int REG_WAIT_INTERVAL = 10; private static final int INITIAL_BACKOFF = 5; + private static final int UDP_FAIL_LIMIT = 5; // One day, in seconds. Probably should be configurable. private static final long MAX_KEEPALIVE_SECS = 24 * 60 * 60; @@ -99,8 +102,8 @@ public class RelayService extends XWService private static final String ROWID = "ROWID"; private static final String BINBUFFER = "BINBUFFER"; - private static Map s_packetsSentUDP = new HashMap<>(); - private static Map s_packetsSentWeb = new HashMap<>(); + private static List s_packetsSentUDP = new ArrayList<>(); + private static List s_packetsSentWeb = new ArrayList<>(); private static AtomicInteger s_nextPacketID = new AtomicInteger(); private static boolean s_gcmWorking = false; private static boolean s_registered = false; @@ -110,18 +113,14 @@ public class RelayService extends XWService private static long s_curNextTimer; static { resetBackoffTimer(); } - private Thread m_fetchThread = null; - private Thread m_UDPReadThread = null; - private Thread m_UDPWriteThread = null; - private DatagramSocket m_UDPSocket; - private LinkedBlockingQueue m_queue = - new LinkedBlockingQueue(); + private Thread m_fetchThread = null; // no longer used + private AtomicReference m_UDPThreadsRef = new AtomicReference<>(); private Handler m_handler; private Runnable m_onInactivity; private int m_maxIntervalSeconds = 0; private long m_lastGamePacketReceived; - // m_nativeNotWorking: set to true if too many acks missed? - private boolean m_nativeNotWorking = false; + private int m_nativeFailScore; + private boolean m_skipUPDSet; private static DevIDType s_curType = DevIDType.ID_TYPE_NONE; private static long s_regStartTime = 0; @@ -318,7 +317,7 @@ public class RelayService extends XWService // Exists to get incoming data onto the main thread private static void postData( Context context, long rowid, byte[] msg ) { - Log.d( TAG, "postData(): packet of length %d for token %d", + Log.d( TAG, "postData(): packet of length %d for token (rowid) %d", msg.length, rowid ); if ( DBUtils.haveGame( context, rowid ) ) { Intent intent = getIntentTo( context, MsgCmds.RECEIVE ) @@ -386,6 +385,8 @@ public class RelayService extends XWService } } }; + + m_skipUPDSet = XWPrefs.getSkipToWebAPI( this ); } @Override @@ -400,7 +401,7 @@ public class RelayService extends XWService cmd = null; } if ( null != cmd ) { - Log.d( TAG, "onStartCommand(): cmd=%s", cmd.toString() ); + // Log.d( TAG, "onStartCommand(): cmd=%s", cmd.toString() ); switch( cmd ) { case PROCESS_GAME_MSGS: String[] relayIDs = new String[1]; @@ -554,278 +555,64 @@ public class RelayService extends XWService private void startUDPThreadsIfNot() { if ( XWApp.UDP_ENABLED && relayEnabled( this ) ) { - if ( null == m_UDPReadThread ) { - m_UDPReadThread = new Thread( null, new Runnable() { - public void run() { - - connectSocket(); // block until this is done - startWriteThread(); - - Log.i( TAG, "read thread running" ); - byte[] buf = new byte[1024]; - for ( ; ; ) { - DatagramPacket packet = - new DatagramPacket( buf, buf.length ); - try { - m_UDPSocket.receive( packet ); - resetExitTimer(); - gotPacket( packet ); - } catch ( java.io.InterruptedIOException iioe ) { - // DbgUtils.logf( "FYI: udp receive timeout" ); - } catch( java.io.IOException ioe ) { - break; - } - } - Log.i( TAG, "read thread exiting" ); - } - }, getClass().getName() ); - m_UDPReadThread.start(); - } else { - Log.i( TAG, "m_UDPReadThread not null and assumed to be running" ); + synchronized ( m_UDPThreadsRef ) { + if ( null == m_UDPThreadsRef.get() ) { + UDPThreads threads = new UDPThreads(); + m_UDPThreadsRef.set( threads ); + threads.start(); + } } } else { Log.i( TAG, "startUDPThreadsIfNot(): UDP disabled" ); } } // startUDPThreadsIfNot - private void connectSocket() - { - if ( null == m_UDPSocket ) { - int port = XWPrefs.getDefaultRelayPort( this ); - String host = XWPrefs.getDefaultRelayHost( this ); - try { - m_UDPSocket = new DatagramSocket(); - m_UDPSocket.setSoTimeout(30 * 1000); // timeout so we can log - - InetAddress addr = InetAddress.getByName( host ); - m_UDPSocket.connect( addr, port ); // remember this address - Log.d( TAG, "connectSocket(%s:%d): m_UDPSocket now %H", - host, port, m_UDPSocket ); - } catch( java.net.SocketException se ) { - Log.ex( TAG, se ); - Assert.fail(); - } catch( java.net.UnknownHostException uhe ) { - Log.ex( TAG, uhe ); - } - } else { - Assert.assertTrue( m_UDPSocket.isConnected() ); - Log.i( TAG, "m_UDPSocket not null" ); - } - } - private boolean skipNativeSend() { - boolean skip = m_nativeNotWorking; - if ( ! skip ) { - skip = XWPrefs.getSkipToWebAPI( RelayService.this ); - } + boolean skip = m_nativeFailScore > UDP_FAIL_LIMIT || m_skipUPDSet; + // Log.d( TAG, "skipNativeSend(score=%d)) => %b", m_nativeFailScore, skip ); return skip; } - private void startWriteThread() + // So it's a map. The timer iterates over the whole map, which should + // never be *that* big, and pulls everything older than 10 seconds. If + // anything in that list isn't an ACK (since ACKs will always be there + // because they're not ACK'd) then the whole thing gets resent. + + private void noteSent( PacketData packet, boolean fromUDP ) { - if ( null == m_UDPWriteThread ) { - m_UDPWriteThread = new Thread( null, new Runnable() { - public void run() { - Log.i( TAG, "write thread starting" ); - for ( ; ; ) { - boolean exitNow = false; - boolean useWeb = skipNativeSend(); - List dataListUDP = new ArrayList<>(); - List dataListWeb = new ArrayList<>(); - try { - for ( PacketData outData = m_queue.take(); // blocks - null != outData; - outData = m_queue.poll() ) { // doesn't block - if ( outData.isEOQ() ) { - exitNow = true; - break; - } - if ( useWeb || outData.getForWeb() ) { - dataListWeb.add(outData); - } else { - dataListUDP.add(outData); - } - } - } catch ( InterruptedException ie ) { - Log.w( TAG, "write thread killed" ); - break; - } - if ( exitNow ) { - Log.i( TAG, "stopping write thread" ); - break; - } - - sendViaWeb( dataListWeb ); - sendViaUDP( dataListUDP ); - - resetExitTimer(); - ConnStatusHandler.showSuccessOut(); - } - Log.i( TAG, "write thread exiting" ); - } - }, getClass().getName() ); - m_UDPWriteThread.start(); - } else { - Log.i( TAG, "m_UDPWriteThread not null and assumed to " - + "be running" ); - } - } - - private int sendViaWeb( List packets ) - { - Log.d( TAG, "sendViaWeb(): sending %d at once", packets.size() ); - int sentLen = 0; - if ( packets.size() > 0 ) { - HttpURLConnection conn = NetUtils.makeHttpRelayConn( this, "post" ); - if ( null == conn ) { - Log.e( TAG, "sendViaWeb(): null conn for POST" ); - } else { - try { - JSONArray dataArray = new JSONArray(); - for ( PacketData packet : packets ) { - Assert.assertFalse( packet.isEOQ() ); - byte[] datum = packet.assemble(); - dataArray.put( Utils.base64Encode(datum) ); - sentLen += datum.length; - } - JSONObject params = new JSONObject(); - params.put( "data", dataArray ); - - String result = NetUtils.runConn(conn, params); - if ( null != result ) { - Log.d( TAG, "sendViaWeb(): POST(%s) => %s", params, result ); - JSONObject resultObj = new JSONObject( result ); - JSONArray resData = resultObj.getJSONArray( "data" ); - int nReplies = resData.length(); - Log.d( TAG, "sendViaWeb(): got %d replies", nReplies ); - - noteSent( packets, s_packetsSentWeb ); // before we process the acks below :-) - - for ( int ii = 0; ii < nReplies; ++ii ) { - byte[] datum = Utils.base64Decode( resData.getString( ii ) ); - // PENDING: skip ack or not - gotPacket( datum, false, false ); - } - } else { - Log.e( TAG, "sendViaWeb(): failed result for POST" ); - } - } catch ( JSONException ex ) { - Assert.assertFalse( BuildConfig.DEBUG ); - } + Log.d( TAG, "Sent (fromUDP=%b) packet: cmd=%s, id=%d", + fromUDP, packet.m_cmd.toString(), packet.m_packetID ); + if ( fromUDP || packet.m_cmd != XWRelayReg.XWPDEV_ACK ) { + List list = fromUDP ? s_packetsSentUDP : s_packetsSentWeb; + synchronized( list ) { + list.add(packet ); } } - return sentLen; } - private int sendViaUDP( List packets ) + private void noteSent( List packets, boolean fromUDP ) { - int sentLen = 0; + long nowMS = System.currentTimeMillis(); + List map = fromUDP ? s_packetsSentUDP : s_packetsSentWeb; + Log.d( TAG, "noteSent(fromUDP=%b): adding %d; size before: %d", + fromUDP, packets.size(), map.size() ); for ( PacketData packet : packets ) { - boolean getOut = true; - byte[] data = packet.assemble(); - try { - DatagramPacket udpPacket = new DatagramPacket( data, data.length ); - m_UDPSocket.send( udpPacket ); - - sentLen += udpPacket.getLength(); - noteSent( packet, s_packetsSentUDP ); - getOut = false; - } catch ( java.net.SocketException se ) { - Log.ex( TAG, se ); - Log.i( TAG, "Restarting threads to force" - + " new socket" ); - m_handler.post( new Runnable() { - public void run() { - stopUDPThreadsIf(); - } - } ); - } catch ( java.io.IOException ioe ) { - Log.ex( TAG, ioe ); - } catch ( NullPointerException npe ) { - Log.w( TAG, "network problem; dropping packet" ); - } - if ( getOut ) { - break; + if ( fromUDP ) { + packet.setSentMS( nowMS ); } + noteSent( packet, fromUDP ); } - - if ( sentLen > 0 ) { - startAckTimer( packets ); - } - - return sentLen; - } - - private void startAckTimer( final List packets ) - { - Runnable ackTimer = new Runnable() { - @Override - public void run() { - List forResend = new ArrayList<>(); - Log.d( TAG, "ackTimer.run() called" ); - synchronized ( s_packetsSentUDP ) { - for ( PacketData packet : packets ) { - PacketData stillThere = s_packetsSentUDP.remove(packet.m_packetID); - if ( stillThere != null ) { - Log.d( TAG, "packed %d not yet acked; resending", - stillThere.m_packetID ); - stillThere.setForWeb(); - forResend.add( stillThere ); - } - } - } - m_queue.addAll( forResend ); - } - }; - m_handler.postDelayed( ackTimer, 10 * 1000 ); - } - - private void noteSent( PacketData packet, Map map ) - { - int pid = packet.m_packetID; - Log.d( TAG, "Sent [udp?] packet: cmd=%s, id=%d", - packet.m_cmd.toString(), pid ); - if ( packet.m_cmd != XWRelayReg.XWPDEV_ACK ) { - synchronized( map ) { - map.put( pid, packet ); - } - } - } - - private void noteSent( List packets, Map map ) - { - for ( PacketData packet : packets ) { - noteSent( packet, map ); - } + Log.d( TAG, "noteSent(fromUDP=%b): size after: %d", fromUDP, map.size() ); } private void stopUDPThreadsIf() { DbgUtils.assertOnUIThread(); - if ( null != m_UDPWriteThread ) { - // can't add null - m_queue.add( new PacketData() ); - try { - Log.d( TAG, "joining m_UDPWriteThread" ); - m_UDPWriteThread.join(); - Log.d( TAG, "SUCCESSFULLY joined m_UDPWriteThread" ); - } catch( java.lang.InterruptedException ie ) { - Log.ex( TAG, ie ); - } - m_UDPWriteThread = null; - m_queue.clear(); - } - if ( null != m_UDPSocket && null != m_UDPReadThread ) { - m_UDPSocket.close(); - try { - m_UDPReadThread.join(); - } catch( java.lang.InterruptedException ie ) { - Log.ex( TAG, ie ); - } - m_UDPReadThread = null; - m_UDPSocket = null; + UDPThreads threads = m_UDPThreadsRef.getAndSet( null ); + if ( null != threads ) { + threads.stop(); } } @@ -928,7 +715,7 @@ public class RelayService extends XWService if ( resetBackoff ) { resetBackoffTimer(); } - } + } // gotPacket() private void gotPacket( DatagramPacket packet ) { @@ -1014,14 +801,14 @@ public class RelayService extends XWService private void requestMessagesImpl( XWRelayReg reg ) { - ByteArrayOutputStream bas = new ByteArrayOutputStream(); try { DevIDType[] typp = new DevIDType[1]; String devid = getDevID( typp ); if ( null != devid ) { + ByteArrayOutputStream bas = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream( bas ); writeVLIString( out, devid ); - Log.d(TAG, "requestMessagesImpl(): devid: %s; type: " + typp[0], devid ); + // Log.d( TAG, "requestMessagesImpl(): devid: %s; type: " + typp[0], devid ); postPacket( bas, reg ); } else { Log.d(TAG, "requestMessagesImpl(): devid is null" ); @@ -1149,7 +936,11 @@ public class RelayService extends XWService private void postPacket( ByteArrayOutputStream bas, XWRelayReg cmd ) { - m_queue.add( new PacketData( bas, cmd ) ); + startUDPThreadsIfNot(); + UDPThreads threads = m_UDPThreadsRef.get(); + if ( threads != null ) { + threads.add( new PacketData( bas, cmd ) ); + } // 0 ok; thread will often have sent already! // DbgUtils.logf( "postPacket() done; %d in queue", m_queue.size() ); } @@ -1213,6 +1004,277 @@ public class RelayService extends XWService } } + private class UDPThreads { + private DatagramSocket m_UDPSocket; + private LinkedBlockingQueue m_queue = + new LinkedBlockingQueue(); + private Thread m_UDPReadThread; + private Thread m_UDPWriteThread; + + UDPThreads() {} + + void start() + { + m_UDPReadThread = new Thread( null, new Runnable() { + public void run() { + + connectSocket(); // block until this is done + startWriteThread(); + + Log.i( TAG, "read thread running" ); + byte[] buf = new byte[1024]; + for ( ; ; ) { + DatagramPacket packet = + new DatagramPacket( buf, buf.length ); + try { + m_UDPSocket.receive( packet ); + resetExitTimer(); + gotPacket( packet ); + } catch ( java.io.InterruptedIOException iioe ) { + // DbgUtils.logf( "FYI: udp receive timeout" ); + } catch( java.io.IOException ioe ) { + break; + } + } + Log.i( TAG, "read thread exiting" ); + } + }, getClass().getName() ); + m_UDPReadThread.start(); + } + + void stop() + { + m_queue.add( new EOQPacketData() ); // will kill the writer thread + } + + void add( PacketData packet ) + { + m_queue.add( packet ); + } + + private void connectSocket() + { + if ( null == m_UDPSocket ) { + int port = XWPrefs.getDefaultRelayPort( RelayService.this ); + String host = XWPrefs.getDefaultRelayHost( RelayService.this ); + try { + m_UDPSocket = new DatagramSocket(); + m_UDPSocket.setSoTimeout(30 * 1000); // timeout so we can log + + InetAddress addr = InetAddress.getByName( host ); + m_UDPSocket.connect( addr, port ); // remember this address + Log.d( TAG, "connectSocket(%s:%d): m_UDPSocket now %H", + host, port, m_UDPSocket ); + } catch( java.net.SocketException se ) { + Log.ex( TAG, se ); + Assert.fail(); + } catch( java.net.UnknownHostException uhe ) { + Log.ex( TAG, uhe ); + } + } else { + Assert.assertTrue( m_UDPSocket.isConnected() ); + Log.i( TAG, "m_UDPSocket not null" ); + } + } + + private void startWriteThread() + { + Assert.assertNull( m_UDPWriteThread ); + + m_UDPWriteThread = new Thread( null, new Runnable() { + public void run() { + Log.i( TAG, "write thread starting" ); + for ( boolean gotEOQ = false; !gotEOQ; ) { + List dataListUDP = new ArrayList<>(); + List dataListWeb = new ArrayList<>(); + PacketData outData; + try { + long ts = s_packetsSentUDP.size() > 0 ? 10 : 3600; + Log.d( TAG, "blocking %d sec on poll()", ts ); + for ( outData = m_queue.poll(ts, TimeUnit.SECONDS); + null != outData; + outData = m_queue.poll() ) { // doesn't block + if ( outData instanceof EOQPacketData ) { + gotEOQ = true; + break; + } else if ( skipNativeSend() || outData.getForWeb() ) { + dataListWeb.add (outData ); + } else { + dataListUDP.add( outData ); + } + } + } catch ( InterruptedException ie ) { + Log.w( TAG, "write thread killed" ); + break; + } + + sendViaWeb( dataListWeb ); + sendViaUDP( dataListUDP ); + + resetExitTimer(); + runUDPAckTimer(); + + ConnStatusHandler.showSuccessOut(); + } + + Log.i( TAG, "write thread killing read thread" ); + + // now kill the read thread + m_UDPSocket.close(); + try { + m_UDPReadThread.join(); + } catch( java.lang.InterruptedException ie ) { + Log.ex( TAG, ie ); + } + + Log.i( TAG, "write thread exiting" ); + } + }, getClass().getName() ); + m_UDPWriteThread.start(); + } + + private int sendViaWeb( List packets ) + { + Log.d( TAG, "sendViaWeb(): sending %d at once", packets.size() ); + int sentLen = 0; + if ( packets.size() > 0 ) { + HttpURLConnection conn = NetUtils.makeHttpRelayConn( RelayService.this, "post" ); + if ( null == conn ) { + Log.e( TAG, "sendViaWeb(): null conn for POST" ); + } else { + try { + JSONArray dataArray = new JSONArray(); + for ( PacketData packet : packets ) { + Assert.assertFalse( packet instanceof EOQPacketData ); + byte[] datum = packet.assemble(); + dataArray.put( Utils.base64Encode(datum) ); + sentLen += datum.length; + } + JSONObject params = new JSONObject(); + params.put( "data", dataArray ); + + String result = NetUtils.runConn( conn, params ); + boolean succeeded = null != result; + if ( succeeded ) { + Log.d( TAG, "sendViaWeb(): POST(%s) => %s", params, result ); + JSONObject resultObj = new JSONObject( result ); + JSONArray resData = resultObj.getJSONArray( "data" ); + int nReplies = resData.length(); + // Log.d( TAG, "sendViaWeb(): got %d replies", nReplies ); + + noteSent( packets, false ); // before we process the acks below :-) + + for ( int ii = 0; ii < nReplies; ++ii ) { + byte[] datum = Utils.base64Decode( resData.getString( ii ) ); + // PENDING: skip ack or not + gotPacket( datum, false, false ); + } + } else { + Log.e( TAG, "sendViaWeb(): failed result for POST" ); + + } + + ConnStatusHandler.updateStatus( RelayService.this, null, + CommsConnType.COMMS_CONN_RELAY, + succeeded ); + } catch ( JSONException ex ) { + Assert.assertFalse( BuildConfig.DEBUG ); + } + } + } + return sentLen; + } + + private int sendViaUDP( List packets ) + { + int sentLen = 0; + + if ( packets.size() > 0 ) { + noteSent( packets, true ); + for ( PacketData packet : packets ) { + boolean getOut = true; + byte[] data = packet.assemble(); + try { + DatagramPacket udpPacket = new DatagramPacket( data, data.length ); + m_UDPSocket.send( udpPacket ); + + sentLen += udpPacket.getLength(); + // packet.setSentMS( nowMS ); + getOut = false; + } catch ( java.net.SocketException se ) { + Log.ex( TAG, se ); + Log.i( TAG, "Restarting threads to force new socket" ); + ConnStatusHandler.updateStatusOut( RelayService.this, null, + CommsConnType.COMMS_CONN_RELAY, + true ); + + m_handler.post( new Runnable() { + public void run() { + stopUDPThreadsIf(); + } + } ); + break; + } catch ( java.io.IOException ioe ) { + Log.ex( TAG, ioe ); + } catch ( NullPointerException npe ) { + Log.w( TAG, "network problem; dropping packet" ); + } + if ( getOut ) { + break; + } + } + + ConnStatusHandler.updateStatus( RelayService.this, null, + CommsConnType.COMMS_CONN_RELAY, + sentLen > 0 ); + } + + return sentLen; + } + + private long m_lastRunMS = 0; + private void runUDPAckTimer() + { + long nowMS = System.currentTimeMillis(); + if ( m_lastRunMS + 3000 > nowMS ) { // never more frequently than 3 sec. + // Log.d( TAG, "runUDPAckTimer(): too soon, so skipping" ); + } else { + m_lastRunMS = nowMS; + + long minSentMS = nowMS - 10000; // 10 seconds ago + long prevSentMS = 0; + List forResend = new ArrayList<>(); + boolean foundNonAck = false; + synchronized ( s_packetsSentUDP ) { + Iterator iter; + for ( iter = s_packetsSentUDP.iterator(); iter.hasNext(); ) { + PacketData packet = iter.next(); + long sentMS = packet.getSentMS(); + Assert.assertTrue( prevSentMS <= sentMS ); + prevSentMS = sentMS; + if ( sentMS > minSentMS ) { + break; + } + + forResend.add( packet ); + if ( packet.m_cmd != XWRelayReg.XWPDEV_ACK ) { + foundNonAck = true; + ++m_nativeFailScore; + } + iter.remove(); + } + Log.d( TAG, "runUDPAckTimer(): %d too-new packets remaining", + s_packetsSentUDP.size() ); + } + if ( foundNonAck ) { + Log.d( TAG, "runUDPAckTimer(): reposting %d packets", forResend.size() ); + m_queue.addAll( forResend ); + } + } + } + + } + private static class AsyncSender extends AsyncTask { private Context m_context; private HashMap> m_msgHash; @@ -1227,7 +1289,6 @@ public class RelayService extends XWService @Override protected Void doInBackground( Void... ignored ) { - Assert.assertFalse( XWPrefs.getSkipToWebAPI( m_context ) ); // format: total msg lenth: 2 // number-of-relayIDs: 2 // for-each-relayid: relayid + '\n': varies @@ -1276,7 +1337,6 @@ public class RelayService extends XWService // Now open a real socket, write size and proto, and // copy in the formatted buffer - Assert.assertFalse( XWPrefs.getSkipToWebAPI( m_context ) ); Socket socket = NetUtils.makeProxySocket( m_context, 8000 ); if ( null != socket ) { DataOutputStream outStream = @@ -1358,27 +1418,47 @@ public class RelayService extends XWService return nextPacketID; } - private static void noteAck( int packetID, boolean fromUDP ) + private void noteAck( int packetID, boolean fromUDP ) { - PacketData packet; - Map map = fromUDP ? s_packetsSentUDP : s_packetsSentWeb; + Assert.assertTrue( packetID != 0 ); + List map = fromUDP ? s_packetsSentUDP : s_packetsSentWeb; synchronized( map ) { - packet = map.remove( packetID ); + PacketData packet = null; + Iterator iter = map.iterator(); + for ( iter = map.iterator(); iter.hasNext(); ) { + PacketData next = iter.next(); + if ( next.m_packetID == packetID ) { + packet = next; + iter.remove(); + break; + } + } + if ( packet != null ) { - Log.d( TAG, "noteAck(fromUDP=%b): removed for id %d: %s", - fromUDP, packetID, packet ); + // Log.d( TAG, "noteAck(fromUDP=%b): removed for id %d: %s", + // fromUDP, packetID, packet ); + if ( fromUDP ) { + --m_nativeFailScore; + } } else { Log.w( TAG, "Weird: got ack %d but never sent", packetID ); } if ( BuildConfig.DEBUG ) { ArrayList pstrs = new ArrayList<>(); - for ( Integer pkid : map.keySet() ) { - pstrs.add( map.get(pkid).toString() ); + for ( PacketData datum : map ) { + pstrs.add( String.format("%d", datum.m_packetID ) ); } Log.d( TAG, "noteAck(fromUDP=%b): Got ack for %d; there are %d unacked packets: %s", fromUDP, packetID, map.size(), TextUtils.join( ",", pstrs ) ); } } + + // If we get an ACK, things are working, even if it's not found above + // (which would be the case for an ACK sent via web, which we don't + // save.) + ConnStatusHandler.updateStatus( this, null, + CommsConnType.COMMS_CONN_RELAY, + true ); } // Called from any thread @@ -1490,7 +1570,7 @@ public class RelayService extends XWService result = figureBackoffSeconds(); } - Log.d( TAG, "getMaxIntervalSeconds() => %d", result ); + Log.d( TAG, "getMaxIntervalSeconds() => %d", result ); // WFT? went from 40 to 1000 return result; } @@ -1526,7 +1606,8 @@ public class RelayService extends XWService private boolean shouldMaintainConnection() { boolean result = relayEnabled( this ) - && (XWApp.GCM_IGNORED || !s_gcmWorking); + && (!s_gcmWorking || XWPrefs.getIgnoreGCM( this )); + if ( result ) { long interval = Utils.getCurSeconds() - m_lastGamePacketReceived; result = interval < MAX_KEEPALIVE_SECS; @@ -1564,7 +1645,7 @@ public class RelayService extends XWService Assert.assertTrue( diff < Integer.MAX_VALUE ); result = (int)diff; } - Log.d( TAG, "figureBackoffSeconds() => %d", result ); + // Log.d( TAG, "figureBackoffSeconds() => %d", result ); return result; } @@ -1583,16 +1664,12 @@ public class RelayService extends XWService public byte[] m_header; public int m_packetID; private long m_created; - private boolean m_useWeb; + private long m_sentUDP; - public PacketData() { - m_bas = null; - m_created = System.currentTimeMillis(); - } + private PacketData() {} public PacketData( ByteArrayOutputStream bas, XWRelayReg cmd ) { - this(); m_bas = bas; m_cmd = cmd; } @@ -1604,10 +1681,9 @@ public class RelayService extends XWService System.currentTimeMillis() - m_created ); } - void setForWeb() { m_useWeb = true; } - boolean getForWeb() { return m_useWeb; } - - public boolean isEOQ() { return 0 == getLength(); } + void setSentMS( long ms ) { m_sentUDP = ms; } + long getSentMS() { return m_sentUDP; } + boolean getForWeb() { return m_sentUDP != 0; } public int getLength() { @@ -1647,4 +1723,7 @@ public class RelayService extends XWService } } } + + // Exits only to exist, so instanceof can distinguish + private class EOQPacketData extends PacketData {} } diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Utils.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Utils.java index de49a2c2f..4c073a829 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Utils.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Utils.java @@ -443,7 +443,7 @@ public class Utils { { // Note: an int is big enough for *seconds* (not milliseconds) since 1970 // until 2038 - long millis = new Date().getTime(); + long millis = System.currentTimeMillis(); int result = (int)(millis / 1000); return result; } 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 dbf740b99..b76ed4bfb 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 @@ -39,7 +39,6 @@ public class XWApp extends Application { public static final boolean ATTACH_SUPPORTED = false; public static final boolean LOG_LIFECYLE = false; public static final boolean DEBUG_EXP_TIMERS = false; - public static final boolean GCM_IGNORED = false; public static final boolean UDP_ENABLED = true; public static final boolean SMS_INVITE_ENABLED = true; public static final boolean LOCUTILS_ENABLED = false; diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWPrefs.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWPrefs.java index 6747f05a2..ccf1779b7 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWPrefs.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWPrefs.java @@ -67,6 +67,16 @@ public class XWPrefs { return getPrefsBoolean( context, R.string.key_enable_nfc_toself, false ); } + public static boolean getIgnoreGCM( Context context ) + { + return getPrefsBoolean( context, R.string.key_ignore_gcm, false ); + } + + public static boolean getToastGCM( Context context ) + { + return getPrefsBoolean( context, R.string.key_show_gcm, false ); + } + public static boolean getRelayInviteToSelfEnabled( Context context ) { return getPrefsBoolean( context, R.string.key_enable_relay_toself, false ); diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/JNIThread.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/JNIThread.java index 49a83d74d..28bf81107 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/JNIThread.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/JNIThread.java @@ -266,15 +266,19 @@ public class JNIThread extends Thread { } public boolean busy() - { // synchronize this!!! + { boolean result = false; + + // Docs: The returned iterator is a "weakly consistent" iterator that + // will never throw ConcurrentModificationException, and guarantees to + // traverse elements as they existed upon construction of the + // iterator, and may (but is not guaranteed to) reflect any + // modifications subsequent to construction. Iterator iter = m_queue.iterator(); - while ( iter.hasNext() ) { - if ( iter.next().m_isUIEvent ) { - result = true; - break; - } + while ( iter.hasNext() && !result ) { + result = iter.next().m_isUIEvent; } + return result; } diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/TransportProcs.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/TransportProcs.java index 5d3f381a4..ce1a31ea8 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/TransportProcs.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/TransportProcs.java @@ -38,7 +38,6 @@ public interface TransportProcs { , COMMS_RELAYSTATE_RECONNECTED , COMMS_RELAYSTATE_ALLCONNECTED }; - void relayStatus( CommsRelayState newState ); void relayConnd( String room, int devOrder, boolean allHere, int nMissing ); diff --git a/xwords4/android/app/src/main/res/menu/board_menu.xml b/xwords4/android/app/src/main/res/menu/board_menu.xml index a027b9aaf..4814192e3 100644 --- a/xwords4/android/app/src/main/res/menu/board_menu.xml +++ b/xwords4/android/app/src/main/res/menu/board_menu.xml @@ -32,7 +32,7 @@ key_enable_nfc_toself key_enable_sms_toself key_enable_relay_toself + key_ignore_gcm + key_show_gcm key_nag_intervals key_download_path key_got_langdict diff --git a/xwords4/android/app/src/main/res/values/strings.xml b/xwords4/android/app/src/main/res/values/strings.xml index 57aa7015b..f6616d84e 100644 --- a/xwords4/android/app/src/main/res/values/strings.xml +++ b/xwords4/android/app/src/main/res/values/strings.xml @@ -1692,7 +1692,7 @@ CrossWords for Android, Version %1$s, rev %2$s, built on %3$s. - Copyright (C) 1998-2017 by Eric + Copyright (C) 1998-2018 by Eric House. This free/open source software is released under the GNU Public License. @@ -2601,6 +2601,12 @@ Enable relay invites to self (To aid testing and debugging) + Ignore incoming GCM messages + Mimic life without a google account + + Show SMS sends, receives + Show GCM receives + One move sent diff --git a/xwords4/android/app/src/main/res/xml/xwprefs.xml b/xwords4/android/app/src/main/res/xml/xwprefs.xml index 43dc96048..67963ed6e 100644 --- a/xwords4/android/app/src/main/res/xml/xwprefs.xml +++ b/xwords4/android/app/src/main/res/xml/xwprefs.xml @@ -390,28 +390,6 @@ android:defaultValue="false" /> - - - - - - - @@ -420,6 +398,15 @@ android:summary="@string/enable_relay_toself_summary" android:defaultValue="false" /> + + + + + + + + + diff --git a/xwords4/android/app/src/xw4/java/org/eehouse/android/xw4/GCMIntentService.java b/xwords4/android/app/src/xw4/java/org/eehouse/android/xw4/GCMIntentService.java index 7175e56d9..5069dea57 100644 --- a/xwords4/android/app/src/xw4/java/org/eehouse/android/xw4/GCMIntentService.java +++ b/xwords4/android/app/src/xw4/java/org/eehouse/android/xw4/GCMIntentService.java @@ -34,6 +34,8 @@ import junit.framework.Assert; public class GCMIntentService extends GCMBaseIntentService { private static final String TAG = GCMIntentService.class.getSimpleName(); + private Boolean m_toastGCM; + public GCMIntentService() { super( BuildConfig.GCM_SENDER_ID ); @@ -67,14 +69,19 @@ public class GCMIntentService extends GCMBaseIntentService { protected void onMessage( Context context, Intent intent ) { Log.d( TAG, "onMessage()" ); - notifyRelayService( context, true ); - String value; - boolean ignoreIt = XWApp.GCM_IGNORED; - if ( ignoreIt ) { - Log.d( TAG, "received GCM but ignoring it" ); + if ( null == m_toastGCM ) { + m_toastGCM = new Boolean( XWPrefs.getToastGCM( context ) ); + } + + if ( XWPrefs.getIgnoreGCM( context ) ) { + String logMsg = "received GCM but ignoring it"; + Log.d( TAG, logMsg ); + DbgUtils.showf( context, logMsg ); } else { - value = intent.getStringExtra( "checkUpdates" ); + notifyRelayService( context, true ); + + String value = intent.getStringExtra( "checkUpdates" ); if ( null != value && Boolean.parseBoolean( value ) ) { UpdateCheckReceiver.checkVersions( context, true ); } @@ -82,6 +89,9 @@ public class GCMIntentService extends GCMBaseIntentService { value = intent.getStringExtra( "getMoves" ); if ( null != value && Boolean.parseBoolean( value ) ) { RelayService.timerFired( context ); + if ( m_toastGCM ) { + DbgUtils.showf( context, "onMessage(): got 'getMoves'" ); + } } value = intent.getStringExtra( "msgs64" ); @@ -90,6 +100,11 @@ public class GCMIntentService extends GCMBaseIntentService { try { JSONArray msgs64 = new JSONArray( value ); String[] strs64 = new String[msgs64.length()]; + if ( m_toastGCM ) { + DbgUtils.showf( context, "onMessage(): got %d msgs", + strs64.length ); + } + for ( int ii = 0; ii < strs64.length; ++ii ) { strs64[ii] = msgs64.optString(ii); } @@ -100,6 +115,7 @@ public class GCMIntentService extends GCMBaseIntentService { } } catch (org.json.JSONException jse ) { Log.ex( TAG, jse ); + Assert.assertFalse( BuildConfig.DEBUG ); } } @@ -145,10 +161,8 @@ public class GCMIntentService extends GCMBaseIntentService { private void notifyRelayService( Context context, boolean working ) { - if ( working && XWApp.GCM_IGNORED ) { - working = false; + if ( !XWPrefs.getIgnoreGCM( context ) ) { + RelayService.gcmConfirmed( context, working ); } - RelayService.gcmConfirmed( context, working ); } - } diff --git a/xwords4/android/app/src/xw4d/res/values/.gitignore b/xwords4/android/app/src/xw4d/res/values/.gitignore new file mode 100644 index 000000000..be5186f01 --- /dev/null +++ b/xwords4/android/app/src/xw4d/res/values/.gitignore @@ -0,0 +1 @@ +strings.xml diff --git a/xwords4/android/img_src/archive.svg b/xwords4/android/img_src/archive.svg index c12a564e8..737fc926e 100644 --- a/xwords4/android/img_src/archive.svg +++ b/xwords4/android/img_src/archive.svg @@ -8,15 +8,15 @@ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" data-name="Layer 1" - viewBox="0 0 100 100" + viewBox="0 0 66 82" x="0px" y="0px" id="svg3396" version="1.1" inkscape:version="0.91 r13725" sodipodi:docname="archive.svg" - width="100" - height="100"> + width="66" + height="82"> @@ -41,40 +41,46 @@ inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="1920" - inkscape:window-height="1016" + inkscape:window-height="1163" id="namedview3412" showgrid="true" showborder="false" inkscape:zoom="6.616" - inkscape:cx="26.571947" - inkscape:cy="80.637848" - inkscape:window-x="0" - inkscape:window-y="27" + inkscape:cx="-17.332527" + inkscape:cy="66.335551" + inkscape:window-x="1920" + inkscape:window-y="0" inkscape:window-maximized="1" - inkscape:current-layer="svg3396"> + inkscape:current-layer="svg3396" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0"> + id="grid3510" + originx="-17" + originy="-14" /> 01 + transform="translate(-17,-9)" /> diff --git a/xwords4/android/img_src/back.svg b/xwords4/android/img_src/back.svg deleted file mode 100644 index d7f1cce27..000000000 --- a/xwords4/android/img_src/back.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - diff --git a/xwords4/android/img_src/content_copy.svg b/xwords4/android/img_src/content_copy.svg index 9992ca6ef..61b7bdc71 100644 --- a/xwords4/android/img_src/content_copy.svg +++ b/xwords4/android/img_src/content_copy.svg @@ -1,9 +1,49 @@ - - - - +image/svg+xml - - + transform="matrix(1.25,0,0,-1.25,-22.4875,105.0125)"> \ No newline at end of file diff --git a/xwords4/android/img_src/content_discard.svg b/xwords4/android/img_src/content_discard.svg index bb781e9bd..2465727ae 100644 --- a/xwords4/android/img_src/content_discard.svg +++ b/xwords4/android/img_src/content_discard.svg @@ -1,18 +1,59 @@ - - - - +image/svg+xml - - - - - - - - - - - + transform="matrix(1.25,0,0,-1.25,-30.25,102.6125)"> \ No newline at end of file diff --git a/xwords4/android/img_src/untrade.svg b/xwords4/android/img_src/untrade.svg new file mode 100644 index 000000000..c4d051c1f --- /dev/null +++ b/xwords4/android/img_src/untrade.svg @@ -0,0 +1,59 @@ + +image/svg+xml \ No newline at end of file diff --git a/xwords4/android/jni/xportwrapper.c b/xwords4/android/jni/xportwrapper.c index 375069949..8071aead2 100644 --- a/xwords4/android/jni/xportwrapper.c +++ b/xwords4/android/jni/xportwrapper.c @@ -95,19 +95,8 @@ and_xport_send( const XP_U8* buf, XP_U16 len, const XP_UCHAR* msgNo, } static void -and_xport_relayStatus( void* closure, CommsRelayState newState ) +and_xport_relayStatus( void* XP_UNUSED(closure), CommsRelayState XP_UNUSED(newState) ) { - AndTransportProcs* aprocs = (AndTransportProcs*)closure; - if ( NULL != aprocs->jxport ) { - JNIEnv* env = ENVFORME( aprocs->ti ); - const char* sig = "(L" PKG_PATH("jni/TransportProcs$CommsRelayState") ";)V"; - jmethodID mid = getMethodID( env, aprocs->jxport, "relayStatus", sig ); - - jobject jenum = intToJEnum( env, newState, - PKG_PATH("jni/TransportProcs$CommsRelayState") ); - (*env)->CallVoidMethod( env, aprocs->jxport, mid, jenum ); - deleteLocalRef( env, jenum ); - } } static void diff --git a/xwords4/android/jni/xwjni.c b/xwords4/android/jni/xwjni.c index 9d09568a9..fb5996244 100644 --- a/xwords4/android/jni/xwjni.c +++ b/xwords4/android/jni/xwjni.c @@ -1673,8 +1673,6 @@ Java_org_eehouse_android_xw4_jni_XwJNI_game_1receiveMessage { jboolean result; XWJNI_START_GLOBALS(); - XP_ASSERT( state->game.comms ); - XP_ASSERT( state->game.server ); XWStreamCtxt* stream = streamFromJStream( MPPARM(mpool) env, globals->vtMgr, jstream ); @@ -1686,31 +1684,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_game_1receiveMessage addrp = &addr; } - /* pthread_mutex_lock( &state->msgMutex ); */ - - ServerCtxt* server = state->game.server; - CommsMsgState commsState; - result = comms_checkIncomingStream( state->game.comms, stream, addrp, - &commsState ); - if ( result ) { - (void)server_do( server ); - - result = server_receiveMessage( server, stream ); - } - comms_msgProcessed( state->game.comms, &commsState, !result ); - - /* pthread_mutex_unlock( &state->msgMutex ); */ - - if ( result ) { - /* in case MORE work's pending. Multiple calls are required in at - least one case, where I'm a host handling client registration *AND* - I'm a robot. Only one server_do and I'll never make that first - robot move. That's because comms can't detect a duplicate initial - packet (in validateInitialMessage()). */ - for ( int ii = 0; ii < 5; ++ii ) { - (void)server_do( server ); - } - } + result = game_receiveMessage( &state->game, stream, addrp ); stream_destroy( stream ); diff --git a/xwords4/common/board.c b/xwords4/common/board.c index 6e6285d1b..f697d3eb3 100644 --- a/xwords4/common/board.c +++ b/xwords4/common/board.c @@ -2128,13 +2128,13 @@ board_requestHint( BoardCtxt* board, result = nTiles > 0; } + XP_Bool canMove = XP_FALSE; if ( result ) { #ifdef XWFEATURE_SEARCHLIMIT BdHintLimits limits; BdHintLimits* lp = NULL; #endif XP_Bool wasVisible; - XP_Bool canMove; wasVisible = setArrowVisible( board, XP_FALSE ); @@ -2194,11 +2194,9 @@ board_requestHint( BoardCtxt* board, } setArrowVisible( board, wasVisible ); } - } else { - util_userError( board->util, ERR_NO_HINT_FOUND ); } - if ( !result ) { + if ( !canMove ) { util_userError( board->util, ERR_NO_HINT_FOUND ); } } diff --git a/xwords4/common/engine.c b/xwords4/common/engine.c index b1595acac..c1e037fbe 100644 --- a/xwords4/common/engine.c +++ b/xwords4/common/engine.c @@ -528,7 +528,7 @@ engine_findMove( EngineCtxt* engine, const ModelCtxt* model, newMove->nTiles = 0; canMove = XP_FALSE; } - result = XP_TRUE; + XP_ASSERT( result ); } util_engineStopping( engine->util ); diff --git a/xwords4/linux/Makefile b/xwords4/linux/Makefile index 30d0c59dc..801a2e8e2 100644 --- a/xwords4/linux/Makefile +++ b/xwords4/linux/Makefile @@ -265,6 +265,8 @@ REQUIRED_DEBS = gcc libgtk-3-dev \ libncursesw5-dev \ uuid-dev \ libsqlite3-dev \ + libcurl4-openssl-dev \ + libjson-c-dev \ .PHONY: debcheck debs_install