Merge branch 'android_branch' into android_translate

This commit is contained in:
Eric House 2018-01-17 20:54:49 -08:00
commit efec6aba66
28 changed files with 686 additions and 497 deletions

View file

@ -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.

View file

@ -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;
}

View file

@ -13,9 +13,9 @@
</style>
</head>
<body>
<h2>CrossWords 4.4.131 release</h2>
<h2>CrossWords 4.4.132 release</h2>
<p>An F-Droid-only release meeting new requirements</p>
<p>This release makes communication with the relay more robust.</p>
<div id="survey">
<p>Please <a href="https://www.surveymonkey.com/s/GX3XLHR">take
@ -25,10 +25,14 @@
<h3>New with this release</h3>
<ul>
<li>F-Droid has stiffened their prohibition against including
proprietary Google components. This release complies by removing
&quot;Google Cloud Messaging&quot; which never worked on
f-droid installs anyway.</li>
<li>Communicate with relay via http when the custom protocol
isn't working (e.g. when a wifi router or firewall blocks
it.)</li>
<li>Improved translations for French, Japanese and Norwegian
(by Weblate volunteers)</li>
<li>Add a few menubar icons (thanks to The Noun Project)</li>
<li>Fix taps on left end of items in the Games List not always
selecting/unselecting them</li>
</ul>
<p>(The full changelog

View file

@ -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 )
{

View file

@ -482,14 +482,12 @@ public class GameUtils {
if ( force ) {
HashMap<Long,CommsConnTypeSet> 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<Long,CommsConnTypeSet> m_games;
private ResendDoneProc m_doneProc;
private CommsConnType m_filter;
private MultiMsgSink m_sink;
private int m_nSent = 0;
public ResendTask( Context context, HashMap<Long,CommsConnTypeSet> 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 );
}
}
}

View file

@ -119,10 +119,6 @@ public class MultiMsgSink implements TransportProcs {
return nSent;
}
public void relayStatus( CommsRelayState newState )
{
}
public void relayErrorProc( XWRELAY_ERROR relayErr )
{
}

View file

@ -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<Integer, PacketData> s_packetsSentUDP = new HashMap<>();
private static Map<Integer, PacketData> s_packetsSentWeb = new HashMap<>();
private static List<PacketData> s_packetsSentUDP = new ArrayList<>();
private static List<PacketData> 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<PacketData> m_queue =
new LinkedBlockingQueue<PacketData>();
private Thread m_fetchThread = null; // no longer used
private AtomicReference<UDPThreads> 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<PacketData> dataListUDP = new ArrayList<>();
List<PacketData> 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<PacketData> 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<PacketData> list = fromUDP ? s_packetsSentUDP : s_packetsSentWeb;
synchronized( list ) {
list.add(packet );
}
}
return sentLen;
}
private int sendViaUDP( List<PacketData> packets )
private void noteSent( List<PacketData> packets, boolean fromUDP )
{
int sentLen = 0;
long nowMS = System.currentTimeMillis();
List<PacketData> 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<PacketData> packets )
{
Runnable ackTimer = new Runnable() {
@Override
public void run() {
List<PacketData> 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<Integer, PacketData> 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<PacketData> packets, Map<Integer, PacketData> 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<PacketData> m_queue =
new LinkedBlockingQueue<PacketData>();
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<PacketData> dataListUDP = new ArrayList<>();
List<PacketData> 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<PacketData> 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<PacketData> 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<PacketData> forResend = new ArrayList<>();
boolean foundNonAck = false;
synchronized ( s_packetsSentUDP ) {
Iterator<PacketData> 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<Void, Void, Void> {
private Context m_context;
private HashMap<String,ArrayList<byte[]>> 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<Integer, PacketData> map = fromUDP ? s_packetsSentUDP : s_packetsSentWeb;
Assert.assertTrue( packetID != 0 );
List<PacketData> map = fromUDP ? s_packetsSentUDP : s_packetsSentWeb;
synchronized( map ) {
packet = map.remove( packetID );
PacketData packet = null;
Iterator<PacketData> 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<String> 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 {}
}

View file

@ -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;
}

View file

@ -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;

View file

@ -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 );

View file

@ -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<QueueElem> 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;
}

View file

@ -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 );

View file

@ -32,7 +32,7 @@
<item android:id="@+id/board_menu_trade_cancel"
android:title="@string/button_trade_cancel"
android:showAsAction="ifRoom"
android:icon="@drawable/back__gen"
android:icon="@drawable/untrade__gen"
/>
<item android:id="@+id/board_menu_trade_commit"

View file

@ -126,6 +126,8 @@
<string name="key_enable_nfc_toself">key_enable_nfc_toself</string>
<string name="key_enable_sms_toself">key_enable_sms_toself</string>
<string name="key_enable_relay_toself">key_enable_relay_toself</string>
<string name="key_ignore_gcm">key_ignore_gcm</string>
<string name="key_show_gcm">key_show_gcm</string>
<string name="key_nag_intervals">key_nag_intervals</string>
<string name="key_download_path">key_download_path</string>
<string name="key_got_langdict">key_got_langdict</string>

View file

@ -1692,7 +1692,7 @@
<string name="about_vers_fmt">CrossWords for Android, Version %1$s,
rev %2$s, built on %3$s.</string>
<!-- copyright info -->
<string name="about_copyright">Copyright (C) 1998-2017 by Eric
<string name="about_copyright">Copyright (C) 1998-2018 by Eric
House. This free/open source software is released under the GNU Public
License.</string>
@ -2601,6 +2601,12 @@
<string name="enable_relay_toself_title">Enable relay invites to self</string>
<string name="enable_relay_toself_summary">(To aid testing and debugging)</string>
<string name="ignore_gcm_title">Ignore incoming GCM messages</string>
<string name="ignore_gcm_summary">Mimic life without a google account</string>
<string name="show_sms_title">Show SMS sends, receives</string>
<string name="show_gcm_title">Show GCM receives</string>
<!-- Shown after "resend messages" menuitem chosen -->
<plurals name="resent_msgs_fmt">
<item quantity="one">One move sent</item>

View file

@ -390,28 +390,6 @@
android:defaultValue="false"
/>
<PreferenceScreen android:title="@string/pref_group_sms_title"
android:summary="@string/pref_group_sms_summary"
>
<CheckBoxPreference android:key="@string/key_enable_sms_toself"
android:title="@string/enable_sms_toself_title"
android:summary="@string/enable_sms_toself_summary"
android:defaultValue="false"
/>
<org.eehouse.android.xw4.XWListPreference
android:key="@string/key_force_radio"
android:title="@string/force_radio_title"
android:entries="@array/force_radio_names"
android:entryValues="@array/force_radio_names"
android:defaultValue="@string/radio_name_real"
/>
<CheckBoxPreference android:key="@string/key_show_sms"
android:title="Show SMS sends, receives"
android:defaultValue="false"
/>
</PreferenceScreen>
<PreferenceScreen android:title="@string/pref_group_relay_title"
android:summary="@string/pref_group_relay_summary"
>
@ -420,6 +398,15 @@
android:summary="@string/enable_relay_toself_summary"
android:defaultValue="false"
/>
<CheckBoxPreference android:key="@string/key_ignore_gcm"
android:title="@string/ignore_gcm_title"
android:summary="@string/ignore_gcm_summary"
android:defaultValue="false"
/>
<CheckBoxPreference android:key="@string/key_show_gcm"
android:title="@string/show_gcm_title"
android:defaultValue="false"
/>
<org.eehouse.android.xw4.XWEditTextPreference
android:key="@string/key_relay_host"
android:title="@string/relay_host"
@ -452,6 +439,28 @@
/>
</PreferenceScreen>
<PreferenceScreen android:title="@string/pref_group_sms_title"
android:summary="@string/pref_group_sms_summary"
>
<CheckBoxPreference android:key="@string/key_enable_sms_toself"
android:title="@string/enable_sms_toself_title"
android:summary="@string/enable_sms_toself_summary"
android:defaultValue="false"
/>
<org.eehouse.android.xw4.XWListPreference
android:key="@string/key_force_radio"
android:title="@string/force_radio_title"
android:entries="@array/force_radio_names"
android:entryValues="@array/force_radio_names"
android:defaultValue="@string/radio_name_real"
/>
<CheckBoxPreference android:key="@string/key_show_sms"
android:title="@string/show_sms_title"
android:defaultValue="false"
/>
</PreferenceScreen>
<PreferenceScreen android:title="@string/pref_group_l10n_title"
android:summary="@string/pref_group_l10n_summary"
>

View file

@ -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 );
}
}

View file

@ -0,0 +1 @@
strings.xml

View file

@ -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">
<metadata
id="metadata3416">
<rdf:RDF>
@ -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">
<inkscape:grid
type="xygrid"
id="grid3510" />
id="grid3510"
originx="-17"
originy="-14" />
</sodipodi:namedview>
<title
id="title3398">01</title>
<path
d="m 83,26 -66,0 0,60 66,0 z m -6,54 -54,0 0,-48 54,0 z"
d="M 66,22 0,22 0,82 66,82 Z M 60,76 6,76 6,28 60,28 Z"
id="path3400"
inkscape:connector-curvature="0" />
<polygon
points="41.7,46.7 35.7,46.7 35.7,58.3 64.3,58.3 64.3,46.7 64.3,46.7 58.3,46.7 58.3,52.3 41.7,52.3 "
points="64.3,58.3 64.3,46.7 64.3,46.7 58.3,46.7 58.3,52.3 41.7,52.3 41.7,46.7 35.7,46.7 35.7,58.3 "
id="polygon3402"
transform="translate(0,-5)" />
transform="translate(-17,-9)" />
<rect
x="25"
y="15"
x="8"
y="11"
width="50"
height="6"
id="rect3404" />
<rect
x="30"
y="4"
x="13"
y="0"
width="40"
height="6"
id="rect3406" />

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="120"
height="120" xml:space="preserve">
<g
id="g12"
transform="matrix(1.25,0,0,-1.25,0,120)">
<g transform='translate(46.03,16.24)' id='g1584'>
<path style='fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none' d='M 0,0 5.5,5.502 -16.87,27.87 35.7,27.87 35.7,35.65 -16.87,35.65 5.5,58.02 0,63.52 -31.76,31.76 0,0 z' id='path1586'/>
</g></g>
</svg>

Before

Width:  |  Height:  |  Size: 609 B

View file

@ -1,9 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="120"
height="120" xml:space="preserve">
<g
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="75.012497"
height="90.025002"
xml:space="preserve"
id="svg2"
inkscape:version="0.91 r13725"
sodipodi:docname="content_copy.svg"><metadata
id="metadata10"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs8" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="787"
inkscape:window-height="480"
id="namedview6"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:zoom="1.9666667"
inkscape:cx="37.5125"
inkscape:cy="45.0125"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" /><g
id="g12"
transform="matrix(1.25,0,0,-1.25,0,120)">
<path style='fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none' d='M 29.99,72.01 78,72.01 78,11.99 29.99,11.99 29.99,72.01 z M 72,66 36,66 36,17.99 72,17.99 72,66 z M 55.34,53 42,53 42,55 55.34,55 55.34,53 z M 66,47 41.99,47 41.99,49 66,49 66,47 z M 59.34,41 42,41 42,43 59.34,43 59.34,41 z M 55.34,35 42,35 42,37 55.34,37 55.34,35 z M 64.67,28.99 41.99,28.99 41.99,30.99 64.67,30.99 64.67,28.99 z M 60.01,78.01 23.99,78.01 23.99,29.99 26.99,29.99 26.99,23.99 23.99,23.99 17.99,23.99 17.99,84.01 66,84.01 66,78.01 66,75.01 60.01,75.01 60.01,78.01 z' id='path1220'/></g>
</svg>
transform="matrix(1.25,0,0,-1.25,-22.4875,105.0125)"><path
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 29.99,72.01 48.01,0 0,-60.02 -48.01,0 0,60.02 z M 72,66 36,66 36,17.99 72,17.99 72,66 Z M 55.34,53 42,53 l 0,2 13.34,0 0,-2 z M 66,47 l -24.01,0 0,2 24.01,0 0,-2 z M 59.34,41 42,41 l 0,2 17.34,0 0,-2 z m -4,-6 -13.34,0 0,2 13.34,0 0,-2 z m 9.33,-6.01 -22.68,0 0,2 22.68,0 0,-2 z m -4.66,49.02 -36.02,0 0,-48.02 3,0 0,-6 -3,0 -6,0 0,60.02 48.01,0 0,-6 0,-3 -5.99,0 0,3 z"
id="path1220"
inkscape:connector-curvature="0" /></g></svg>

Before

Width:  |  Height:  |  Size: 933 B

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -1,18 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="120"
height="120" xml:space="preserve">
<g
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="59.5"
height="85.212502"
xml:space="preserve"
id="svg2"
inkscape:version="0.91 r13725"
sodipodi:docname="content_discard.svg"><metadata
id="metadata15"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs13" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1594"
inkscape:window-height="887"
id="namedview11"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:zoom="1.9666667"
inkscape:cx="18.055085"
inkscape:cy="42.6"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" /><g
id="g12"
transform="matrix(1.25,0,0,-1.25,0,120)">
<g id='g1254'>
<g id='g1256'>
<g transform='translate(68.92,54.16)' id='g1262'>
<path style='fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none' d='M 0,0 C -0.046,0.281 -0.218,0.557 -0.476,0.82 -2.555,-1.292 -10.92,-1.642 -20.92,-1.642 -30.92,-1.642 -39.28,-1.292 -41.37,0.82 -41.62,0.557 -41.78,0.281 -41.84,0 L -41.86,0 -41.86,-0.152 C -41.86,-0.17 -41.86,-0.188 -41.86,-0.206 -41.86,-0.247 -41.85,-0.283 -41.85,-0.322 L -40.22,-33.68 -40.21,-33.68 C -40.07,-36.29 -37.07,-40.24 -20.92,-40.24 -4.766,-40.24 -1.77,-36.29 -1.629,-33.68 L -1.617,-33.68 0.013,-0.322 C 0.016,-0.283 0.028,-0.247 0.028,-0.206 0.028,-0.188 0.016,-0.17 0.016,-0.152 L 0.028,0 0,0 z' id='path1264'/>
</g>
<g transform='translate(42.16,71.94)' id='g1266'>
<path style='fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none' d='M 0,0 -0.657,-0.041 -0.657,3.221 C -0.657,4.647 -0.458,5.815 -0.223,5.815 L 2.809,5.815 8.002,5.815 11.03,5.815 C 11.27,5.815 11.46,4.647 11.46,3.221 L 11.46,0.012 C 9.658,0.105 7.779,0.164 5.835,0.164 3.82,0.164 1.863,0.105 0,0 M 15.79,-0.326 15.79,7.549 C 15.79,8.971 14.63,10.15 13.19,10.15 L -2.392,10.15 C -3.813,10.15 -4.982,8.971 -4.982,7.549 L -4.982,-0.416 C -12.69,-1.29 -17.96,-3.06 -17.96,-5.11 L -17.96,-9.003 C -17.96,-9.802 -17.15,-10.56 -15.7,-11.25 -11.9,-13.03 -3.686,-14.27 5.835,-14.27 15.36,-14.27 23.58,-13.03 27.38,-11.25 28.82,-10.56 29.64,-9.802 29.64,-9.003 L 29.64,-5.11 C 29.64,-2.988 23.96,-1.161 15.79,-0.326' id='path1268'/>
</g>
</g>
</g></g>
</svg>
transform="matrix(1.25,0,0,-1.25,-30.25,102.6125)"><g
id="g1254"><g
id="g1256"><g
transform="translate(68.92,54.16)"
id="g1262"><path
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c -0.046,0.281 -0.218,0.557 -0.476,0.82 -2.079,-2.112 -10.444,-2.462 -20.444,-2.462 -10,0 -18.36,0.35 -20.45,2.462 C -41.62,0.557 -41.78,0.281 -41.84,0 l -0.02,0 0,-0.152 c 0,-0.018 0,-0.036 0,-0.054 0,-0.041 0.01,-0.077 0.01,-0.116 l 1.63,-33.358 0.01,0 c 0.14,-2.61 3.14,-6.56 19.29,-6.56 16.154,0 19.15,3.95 19.291,6.56 l 0.012,0 1.63,33.358 c 0.003,0.039 0.015,0.075 0.015,0.116 0,0.018 -0.012,0.036 -0.012,0.054 L 0.028,0 0,0 Z"
id="path1264"
inkscape:connector-curvature="0" /></g><g
transform="translate(42.16,71.94)"
id="g1266"><path
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 -0.657,-0.041 0,3.262 c 0,1.426 0.199,2.594 0.434,2.594 l 3.032,0 5.193,0 3.028,0 c 0.24,0 0.43,-1.168 0.43,-2.594 l 0,-3.209 C 9.658,0.105 7.779,0.164 5.835,0.164 3.82,0.164 1.863,0.105 0,0 m 15.79,-0.326 0,7.875 c 0,1.422 -1.16,2.601 -2.6,2.601 l -15.582,0 c -1.421,0 -2.59,-1.179 -2.59,-2.601 l 0,-7.965 C -12.69,-1.29 -17.96,-3.06 -17.96,-5.11 l 0,-3.893 c 0,-0.799 0.81,-1.557 2.26,-2.247 3.8,-1.78 12.014,-3.02 21.535,-3.02 9.525,0 17.745,1.24 21.545,3.02 1.44,0.69 2.26,1.448 2.26,2.247 l 0,3.893 c 0,2.122 -5.68,3.949 -13.85,4.784"
id="path1268"
inkscape:connector-curvature="0" /></g></g></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
x="0px"
y="0px"
viewBox="0 0 70 70"
enable-background="new 0 0 100 100"
xml:space="preserve"
id="svg3590"
inkscape:version="0.91 r13725"
sodipodi:docname="untrade.svg"
width="70"
height="70"><metadata
id="metadata3602"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs3600" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1163"
id="namedview3598"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:zoom="5.3400704"
inkscape:cx="-12.866321"
inkscape:cy="22.385961"
inkscape:window-x="1920"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg3590" /><path
d="m 12.159535,30.485512 0,-8.546 40.651,0 -5.652,5.652 c -0.879,0.879 -0.879,2.304 0,3.182 0.439,0.439 1.015,0.659 1.591,0.659 0.576,0 1.151,-0.22 1.591,-0.659 l 9.494,-9.493 c 0.014,-0.014 0.023,-0.031 0.037,-0.044 0.087,-0.092 0.17,-0.189 0.241,-0.295 0.024,-0.036 0.04,-0.077 0.063,-0.115 0.052,-0.088 0.104,-0.176 0.145,-0.271 0.02,-0.047 0.028,-0.097 0.045,-0.144 0.031,-0.091 0.064,-0.181 0.084,-0.276 0.029,-0.145 0.045,-0.294 0.045,-0.445 0,-0.151 -0.016,-0.3 -0.045,-0.445 -0.02,-0.097 -0.054,-0.187 -0.085,-0.278 -0.016,-0.047 -0.024,-0.096 -0.044,-0.142 -0.041,-0.1 -0.097,-0.192 -0.151,-0.284 -0.02,-0.034 -0.033,-0.07 -0.056,-0.103 -0.082,-0.123 -0.176,-0.237 -0.279,-0.34 l -9.491,-9.4909998 c -0.879,-0.879 -2.303,-0.879 -3.182,0 -0.879,0.878 -0.879,2.3029998 0,3.1819998 l 5.651,5.651 -42.9029996,0 c -1.242,0 -2.25,1.007 -2.25,2.25 l 0,10.796 c 0,1.243 1.008,2.25 2.25,2.25 1.2419996,0 2.2499996,-1.008 2.2499996,-2.251 z m 47.084,6.258 c -1.242,0 -2.25,1.007 -2.25,2.25 l 0,8.546 -40.651,0 5.652,-5.652 c 0.879,-0.879 0.879,-2.304 0,-3.182 -0.879,-0.879 -2.303,-0.878 -3.182,0 l -9.4939996,9.493 c -0.014,0.013 -0.022,0.03 -0.035,0.043 -0.088,0.092 -0.172,0.189 -0.243,0.296 -0.023,0.035 -0.038,0.074 -0.06,0.11 -0.054,0.09 -0.107,0.179 -0.147,0.276 -0.02,0.046 -0.028,0.095 -0.044,0.141 -0.031,0.092 -0.065,0.183 -0.085,0.279 -0.029,0.146 -0.045,0.294 -0.045,0.445 0,0.151 0.016,0.299 0.045,0.445 0.02,0.097 0.054,0.189 0.085,0.281 0.016,0.046 0.025,0.094 0.044,0.139 0.042,0.102 0.098,0.195 0.153,0.289 0.02,0.032 0.033,0.067 0.054,0.098 0.082,0.123 0.175,0.237 0.279,0.341 l 9.4909996,9.491 c 0.439,0.439 1.015,0.659 1.591,0.659 0.576,0 1.151,-0.22 1.591,-0.659 0.879,-0.878 0.879,-2.303 0,-3.182 l -5.651,-5.651 42.902,0 c 1.242,0 2.25,-1.007 2.25,-2.25 l 0,-10.796 c 0,-1.243 -1.008,-2.25 -2.25,-2.25 z"
id="path3592"
inkscape:connector-curvature="0" /><circle
style="fill:none;stroke:#000000;stroke-width:3.17759514;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4159"
cx="35"
cy="35"
r="33.411201" /><path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.00030446;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 13.380987,12.086444 44.81032,48.282085"
id="path4171"
inkscape:connector-curvature="0" /></svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

@ -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

View file

@ -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 );

View file

@ -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 );
}
}

View file

@ -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 );

View file

@ -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