From 283045c64972c8aeb5ff1edb1aa7e2f2e73035f0 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 8 Nov 2012 19:23:52 -0800 Subject: [PATCH 001/146] revert previous trivial change --- .../src/org/eehouse/android/xw4/ConnStatusHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/ConnStatusHandler.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/ConnStatusHandler.java index 023b0266d..13c973646 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/ConnStatusHandler.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/ConnStatusHandler.java @@ -333,8 +333,8 @@ public class ConnStatusHandler { try { ObjectInputStream ois = new ObjectInputStream( new ByteArrayInputStream(bytes) ); - Object obj = ois.readObject(); - s_records = (HashMap)obj; + s_records = + (HashMap)ois.readObject(); // } catch ( java.io.StreamCorruptedException sce ) { // DbgUtils.logf( "loadState: %s", sce.toString() ); // } catch ( java.io.OptionalDataException ode ) { From 5cbafb9f5661316b8203fb4bab70cf6f0a1dad42 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 8 Nov 2012 19:49:45 -0800 Subject: [PATCH 002/146] up version strings and code --- xwords4/android/XWords4/AndroidManifest.xml | 2 +- xwords4/android/XWords4/res/raw/changes | 8 +++++--- xwords4/android/XWords4/res/values/app_name.xml | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/xwords4/android/XWords4/AndroidManifest.xml b/xwords4/android/XWords4/AndroidManifest.xml index f8a817f36..d9bb9afd2 100644 --- a/xwords4/android/XWords4/AndroidManifest.xml +++ b/xwords4/android/XWords4/AndroidManifest.xml @@ -22,7 +22,7 @@ to come from a domain that you own or have control over. --> diff --git a/xwords4/android/XWords4/res/raw/changes b/xwords4/android/XWords4/res/raw/changes index 509bcac36..8eba69951 100644 --- a/xwords4/android/XWords4/res/raw/changes +++ b/xwords4/android/XWords4/res/raw/changes @@ -5,11 +5,13 @@ -Crosswords 4.4 beta 54 release +Crosswords 4.4 beta 55 release
    -
  • Don't try to access directory OS says is for downloads when it - doesn't actually exist
  • +
  • Use "Google Cloud Messaging" for much faster relay move + notifications
  • + +
  • Fix crashes on Kindle Fire
diff --git a/xwords4/android/XWords4/res/values/app_name.xml b/xwords4/android/XWords4/res/values/app_name.xml index 1735f8137..e3a0a8b7a 100644 --- a/xwords4/android/XWords4/res/values/app_name.xml +++ b/xwords4/android/XWords4/res/values/app_name.xml @@ -1,5 +1,5 @@ - 4.4 beta 54 + 4.4 beta 55 From 07123ac823f3a5d5c6281b059d6cbc64e4deccd1 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 8 Nov 2012 19:54:09 -0800 Subject: [PATCH 003/146] cleanup: remove logging and only check for messages when the json data holds that command. --- .../eehouse/android/xw4/GCMIntentService.java | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java index e9eb0e995..9d78c37b6 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java @@ -37,12 +37,11 @@ public class GCMIntentService extends GCMBaseIntentService { @Override protected void onRegistered( Context context, String regId ) { - DbgUtils.logf("GCMIntentService.onRegistered(%s)", regId ); XWPrefs.setGCMDevID( context, regId ); } @Override - protected void onUnregistered( Context context, String regId ) + protected void onUnregistered( Context context, String regId ) { DbgUtils.logf( "GCMIntentService.onUnregistered(%s)", regId ); XWPrefs.clearGCMDevID( context ); @@ -51,22 +50,17 @@ public class GCMIntentService extends GCMBaseIntentService { @Override protected void onMessage( Context context, Intent intent ) { - DbgUtils.logf( "GCMIntentService.onMessage(%s)", intent.toString() ); - boolean doRestartTimer = true; // keep a few days... String value = intent.getStringExtra( "msg" ); if ( null != value ) { - doRestartTimer = false; // expected key means new format - String title = intent.getStringExtra( "title" ); - Utils.postNotification( context, null, title, value, 100000 ); + if ( null != title ) { + int code = value.hashCode() ^ title.hashCode(); + Utils.postNotification( context, null, title, value, code ); + } } value = intent.getStringExtra( "getMoves" ); if ( null != value && Boolean.parseBoolean( value ) ) { - doRestartTimer = true; - } - - if ( doRestartTimer ) { RelayReceiver.RestartTimer( context, true ); } } From 0ef7c8bf46229909f48ce66e15f56ffaba10f209 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 8 Nov 2012 19:58:03 -0800 Subject: [PATCH 004/146] return TYPE_NONE on devices like Kindle that don't do GCM instead of an empty string claiming to be a GCM ID. --- .../XWords4/src/org/eehouse/android/xw4/XWPrefs.java | 6 +++++- .../src/org/eehouse/android/xw4/jni/UtilCtxt.java | 1 + .../src/org/eehouse/android/xw4/jni/UtilCtxtImpl.java | 10 +++++++--- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java index dac4ac1a0..b8a9df7ce 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java @@ -191,7 +191,11 @@ public class XWPrefs { public static String getGCMDevID( Context context ) { - return getPrefsString( context, R.string.key_gcm_regid ); + String result = getPrefsString( context, R.string.key_gcm_regid ); + if ( result.equals("") ) { + result = null; + } + return result; } public static void clearGCMDevID( Context context ) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java index 113cb8f86..28fa12f7d 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java @@ -57,6 +57,7 @@ public interface UtilCtxt { void setIsServer( boolean isServer ); // Possible values for typ[0], these must match enum in xwrelay.sh + public static final int ID_TYPE_NONE = 0; public static final int ID_TYPE_RELAY = 1; public static final int ID_TYPE_ANDROID_GCM = 3; diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxtImpl.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxtImpl.java index e1410c915..fdac77a8f 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxtImpl.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxtImpl.java @@ -94,15 +94,19 @@ public class UtilCtxtImpl implements UtilCtxt { subclassOverride( "setIsServer" ); } - public String getDevID( /*out*/ byte[] typ ) + public String getDevID( /*out*/ byte[] typa ) { + byte typ = UtilCtxt.ID_TYPE_NONE; String result = XWPrefs.getRelayDevID( m_context ); if ( null != result ) { - typ[0] = UtilCtxt.ID_TYPE_RELAY; + typ = UtilCtxt.ID_TYPE_RELAY; } else { result = XWPrefs.getGCMDevID( m_context ); - typ[0] = UtilCtxt.ID_TYPE_ANDROID_GCM; + if ( null != result ) { + typ = UtilCtxt.ID_TYPE_ANDROID_GCM; + } } + typa[0] = typ; return result; } From 0f21f849d90fed88eb0766e428b637c62b177d4f Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 8 Nov 2012 20:08:20 -0800 Subject: [PATCH 005/146] fix crash attempting to download dict when there's no network, e.g. in airplane mode. --- .../XWords4/src/org/eehouse/android/xw4/NetUtils.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetUtils.java index 288a935e6..b076879c7 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetUtils.java @@ -317,6 +317,9 @@ public class NetUtils { DbgUtils.loge( mue ); } catch ( java.io.IOException ioe ) { DbgUtils.loge( ioe ); + } catch ( Exception ce ) { + // E.g. java.net.ConnectException; we failed + // to download, ok. } finally { if ( null != urlConn ) { urlConn.disconnect(); @@ -324,7 +327,10 @@ public class NetUtils { } sno.close(); - DictLangCache.inval( context, name, loc, true ); + + if ( success ) { + DictLangCache.inval( context, name, loc, true ); + } if ( null != lstnr ) { lstnr.downloadFinished( name, success ); } From ff16db5c1092feb68965210cdbe0dd956e160a09 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 8 Nov 2012 20:09:16 -0800 Subject: [PATCH 006/146] remove logging --- .../android/XWords4/src/org/eehouse/android/xw4/NetUtils.java | 1 - 1 file changed, 1 deletion(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetUtils.java index b076879c7..64c12f3d7 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetUtils.java @@ -311,7 +311,6 @@ public class NetUtils { 1024*8 ); success = DictUtils.saveDict( context, in, name, loc ); - DbgUtils.logf( "saveDict returned %b", success ); } catch ( java.net.MalformedURLException mue ) { DbgUtils.loge( mue ); From f1ee77882b7b35a7bdb530fa6efdf09ef29ab2cf Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 9 Nov 2012 06:54:12 -0800 Subject: [PATCH 007/146] Send devid with RECONN as well as CONN so games present when device upgrades can also use GCM. Tested on relay but not device. --- xwords4/common/comms.c | 13 ++++++------- xwords4/relay/cref.cpp | 40 +++++++++++++++++++-------------------- xwords4/relay/crefmgr.cpp | 6 +++--- xwords4/relay/crefmgr.h | 4 ++-- xwords4/relay/xwrelay.cpp | 33 ++++++++++++++++++++------------ 5 files changed, 51 insertions(+), 45 deletions(-) diff --git a/xwords4/common/comms.c b/xwords4/common/comms.c index 498e38c04..bf37b3f51 100644 --- a/xwords4/common/comms.c +++ b/xwords4/common/comms.c @@ -1,6 +1,6 @@ /* -*- compile-command: "cd ../linux && make MEMDEBUG=TRUE -j3"; -*- */ /* - * Copyright 2001-2011 by Eric House (xwords@eehouse.org). All rights + * Copyright 2001 - 2012 by Eric House (xwords@eehouse.org). All rights * reserved. * * This program is free software; you can redistribute it and/or @@ -1331,12 +1331,10 @@ got_connect_cmd( CommsCtxt* comms, XWStreamCtxt* stream, #endif #ifdef XWFEATURE_DEVID - if ( !reconnected ) { - XP_UCHAR devID[MAX_DEVID_LEN + 1]; - stringFromStreamHere( stream, devID, sizeof(devID) ); - if ( devID[0] != '\0' ) { - util_deviceRegistered( comms->util, devID ); - } + XP_UCHAR devID[MAX_DEVID_LEN + 1]; + stringFromStreamHere( stream, devID, sizeof(devID) ); + if ( devID[0] != '\0' ) { + util_deviceRegistered( comms->util, devID ); } #endif @@ -2157,6 +2155,7 @@ msg_to_stream( CommsCtxt* comms, XWRELAY_Cmd cmd, XWHostID destID, stream_putU16( stream, comms_getChannelSeed(comms) ); stream_putU8( stream, comms->util->gameInfo->dictLang ); stringToStream( stream, comms->r.connName ); + putDevID( comms, stream ); set_relay_state( comms, COMMS_RELAYSTATE_CONNECT_PENDING ); break; diff --git a/xwords4/relay/cref.cpp b/xwords4/relay/cref.cpp index a825557b3..5f66a6c9c 100644 --- a/xwords4/relay/cref.cpp +++ b/xwords4/relay/cref.cpp @@ -627,6 +627,7 @@ CookieRef::handleEvents() /* Assumption: has mutex!!!! */ while ( m_eventQueue.size () > 0 ) { XW_RELAY_STATE nextState; + DBMgr::DevIDRelay devID; CRefEvent evt = m_eventQueue.front(); m_eventQueue.pop_front(); @@ -642,7 +643,6 @@ CookieRef::handleEvents() case XWA_SEND_CONNRSP: { HostID hid; - DBMgr::DevIDRelay devID; if ( increasePlayerCounts( &evt, false, &hid, &devID ) ) { setAllConnectedTimer(); sendResponse( &evt, true, &devID ); @@ -668,8 +668,8 @@ CookieRef::handleEvents() /* break; */ case XWA_SEND_RERSP: - increasePlayerCounts( &evt, true, NULL, NULL ); - sendResponse( &evt, false, NULL ); + increasePlayerCounts( &evt, true, NULL, &devID ); + sendResponse( &evt, false, &devID ); sendAnyStored( &evt ); postCheckAllHere(); break; @@ -894,7 +894,7 @@ CookieRef::increasePlayerCounts( CRefEvent* evt, bool reconn, HostID* hidp, // have we not already converted it? if ( ID_TYPE_RELAY == devIDType ) { devID = (DBMgr::DevIDRelay)strtoul( evt->u.con.devID->m_devIDString.c_str(), - NULL, 16 ); + NULL, 16 ); } else { devID = DBMgr::Get()->RegisterDevice( evt->u.con.devID ); } @@ -1070,25 +1070,23 @@ CookieRef::sendResponse( const CRefEvent* evt, bool initial, memcpy( bufp, connName, len ); bufp += len; - if ( initial ) { - // we always write at least empty string - char idbuf[MAX_DEVID_LEN + 1] = {0}; + // we always write at least empty string + char idbuf[MAX_DEVID_LEN + 1] = {0}; - // If client supports devid, and we have one (response case), write it as - // 8-byte hex string plus a length byte -- but only if we didn't already - // receive it. - if ( !!devID && ID_TYPE_RELAY < evt->u.con.devID->m_devIDType ) { - len = snprintf( idbuf, sizeof(idbuf), "%.8X", *devID ); - assert( len < sizeof(idbuf) ); - } + // If client supports devid, and we have one (response case), write it as + // 8-byte hex string plus a length byte -- but only if we didn't already + // receive it. + if ( !!devID && ID_TYPE_RELAY < evt->u.con.devID->m_devIDType ) { + len = snprintf( idbuf, sizeof(idbuf), "%.8X", *devID ); + assert( len < sizeof(idbuf) ); + } - len = strlen( idbuf ); - assert( len <= MAX_DEVID_LEN ); - *bufp++ = (char)len; - if ( 0 < len ) { - memcpy( bufp, idbuf, len ); - bufp += len; - } + len = strlen( idbuf ); + assert( len <= MAX_DEVID_LEN ); + *bufp++ = (char)len; + if ( 0 < len ) { + memcpy( bufp, idbuf, len ); + bufp += len; } send_with_length( socket, buf, bufp - buf, true ); diff --git a/xwords4/relay/crefmgr.cpp b/xwords4/relay/crefmgr.cpp index f86288d93..9adb5df07 100644 --- a/xwords4/relay/crefmgr.cpp +++ b/xwords4/relay/crefmgr.cpp @@ -616,13 +616,13 @@ SafeCref::SafeCref( const char* cookie, int socket, int clientVersion, /* REconnect case */ SafeCref::SafeCref( const char* connName, const char* cookie, HostID hid, - int socket, int clientVersion, int nPlayersH, int nPlayersS, - unsigned short gameSeed, int langCode, + int socket, int clientVersion, DevID* devID, int nPlayersH, + int nPlayersS, unsigned short gameSeed, int langCode, bool wantsPublic, bool makePublic ) : m_cinfo( NULL ) , m_mgr( CRefMgr::Get() ) , m_clientVersion( clientVersion ) - , m_devID( NULL ) + , m_devID( devID ) , m_isValid( false ) { CidInfo* cinfo; diff --git a/xwords4/relay/crefmgr.h b/xwords4/relay/crefmgr.h index e605b88f0..63e061f28 100644 --- a/xwords4/relay/crefmgr.h +++ b/xwords4/relay/crefmgr.h @@ -177,8 +177,8 @@ class SafeCref { bool makePublic ); /* for reconnect */ SafeCref( const char* connName, const char* cookie, HostID hid, - int socket, int clientVersion, int nPlayersH, int nPlayersS, - unsigned short gameSeed, int langCode, + int socket, int clientVersion, DevID* devID, int nPlayersH, + int nPlayersS, unsigned short gameSeed, int langCode, bool wantsPublic, bool makePublic ); SafeCref( const char* const connName ); SafeCref( CookieID cid, bool failOk = false ); diff --git a/xwords4/relay/xwrelay.cpp b/xwords4/relay/xwrelay.cpp index 3105992f7..b01bd0c5c 100644 --- a/xwords4/relay/xwrelay.cpp +++ b/xwords4/relay/xwrelay.cpp @@ -242,6 +242,21 @@ getNetString( unsigned char** bufpp, const unsigned char* end, string& out ) return success; } +static void +getDevID( unsigned char** bufpp, const unsigned char* end, + unsigned short flags, DevID* devID ) +{ + if ( XWRELAY_PROTO_VERSION_CLIENTID <= flags ) { + unsigned char devIDType = 0; + if ( getNetByte( bufpp, end, &devIDType ) && 0 != devIDType ) { + if ( getNetString( bufpp, end, devID->m_devIDString ) + && 0 < devID->m_devIDString.length() ) { + devID->m_devIDType = (DevIDType)devIDType; + } + } + } +} + #ifdef RELAY_HEARTBEAT static bool processHeartbeat( unsigned char* buf, int bufLen, int socket ) @@ -381,16 +396,7 @@ processConnect( unsigned char* bufp, int bufLen, int socket, in_addr& addr ) && getNetByte( &bufp, end, &langCode ) ) { DevID devID; - if ( XWRELAY_PROTO_VERSION_CLIENTID <= flags ) { - unsigned char devIDType = 0; - if ( getNetByte( &bufp, end, &devIDType ) - && 0 != devIDType ) { - if ( getNetString( &bufp, end, devID.m_devIDString ) - && 0 < devID.m_devIDString.length() ) { - devID.m_devIDType = (DevIDType)devIDType; - } - } - } + getDevID( &bufp, end, flags, &devID ); logf( XW_LOGINFO, "%s(): langCode=%d; nPlayersT=%d; " "wantsPublic=%d; seed=%.4X", @@ -449,9 +455,12 @@ processReconnect( unsigned char* bufp, int bufLen, int socket, in_addr& addr ) && getNetByte( &bufp, end, &langCode ) && readStr( &bufp, end, connName, sizeof(connName) ) ) { + DevID devID; + getDevID( &bufp, end, flags, &devID ); + SafeCref scr( connName[0]? connName : NULL, - cookie, srcID, socket, clientVersion, nPlayersH, - nPlayersT, gameSeed, langCode, + cookie, srcID, socket, clientVersion, &devID, + nPlayersH, nPlayersT, gameSeed, langCode, wantsPublic, makePublic ); success = scr.Reconnect( socket, srcID, nPlayersH, nPlayersT, gameSeed, addr, &err ); From b0f85e02dffbac8ecf49d1420c8085d5e9cb4210 Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 9 Nov 2012 06:55:38 -0800 Subject: [PATCH 008/146] use grep of app's help message to determine whether a param is supported so can test with older clients and not crash. --- xwords4/linux/scripts/discon_ok2.sh | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/xwords4/linux/scripts/discon_ok2.sh b/xwords4/linux/scripts/discon_ok2.sh index fbaa29f2d..c8b475339 100755 --- a/xwords4/linux/scripts/discon_ok2.sh +++ b/xwords4/linux/scripts/discon_ok2.sh @@ -182,6 +182,9 @@ build_cmds() { fi fi + # rememeber what the app can do + HELP="$(APPS[$COUNTER] --help 2>&1 || true)" + PARAMS="$(player_params $NLOCALS $NPLAYERS $DEV)" PARAMS="$PARAMS $BOARD_SIZE --room $ROOM --trade-pct 20 --sort-tiles " [ $UNDO_PCT -gt 0 ] && PARAMS="$PARAMS --undo-pct $UNDO_PCT " @@ -190,7 +193,9 @@ build_cmds() { PARAMS="$PARAMS --drop-nth-packet $DROP_N $PLAT_PARMS" # PARAMS="$PARAMS --savefail-pct 10" [ -n "$SEED" ] && PARAMS="$PARAMS --seed $RANDOM" - # PARAMS="$PARAMS --devid LINUX_TEST_$(printf %.5d ${COUNTER})" + if echo $HELP | grep -q '\-\-devid'; then + PARAMS="$PARAMS --devid LINUX_TEST_$(printf %.5d ${COUNTER})" + fi PARAMS="$PARAMS $PUBLIC" ARGS[$COUNTER]=$PARAMS ROOMS[$COUNTER]=$ROOM @@ -383,13 +388,16 @@ increment_drop() { set_relay_devid() { KEY=$1 - CMD=${ARGS[$KEY]} - if [ "$CMD" != "${CMD/--devid //}" ]; then - RELAY_ID=$(grep 'deviceRegistered: new id: ' ${LOGS[$KEY]} | tail -n 1) - if [ -n "$RELAY_ID" ]; then - RELAY_ID=$(echo $RELAY_ID | sed 's,^.*new id: ,,') + HELP="$(${APPS[$KEY]} --help 2>&1 || true)" + if echo $HELP | grep -q '\-\-devid'; then + CMD=${ARGS[$KEY]} + if [ "$CMD" != "${CMD/--devid //}" ]; then + RELAY_ID=$(grep 'deviceRegistered: new id: ' ${LOGS[$KEY]} | tail -n 1) + if [ -n "$RELAY_ID" ]; then + RELAY_ID=$(echo $RELAY_ID | sed 's,^.*new id: ,,') # turn --devid into --rdevid $RELAY_ID - ARGS[$KEY]=$(echo $CMD | sed 's,^\(.*\)--devid[ ]\+[^ ]\+\(.*\)$,\1--rdevid $RELAY_ID\2,') + ARGS[$KEY]=$(echo $CMD | sed 's,^\(.*\)--devid[ ]\+[^ ]\+\(.*\)$,\1--rdevid $RELAY_ID\2,') + fi fi fi } @@ -423,7 +431,7 @@ run_cmds() { PIDS[$KEY]=0 ROOM_PIDS[$ROOM]=0 [ "$DROP_N" -ge 0 ] && increment_drop $KEY - # set_relay_devid $KEY + set_relay_devid $KEY check_game $KEY fi done From ba733634cb2a6fe0cb9b173e33109848846409b4 Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 9 Nov 2012 06:57:37 -0800 Subject: [PATCH 009/146] NPE: don't crash on first install --- .../XWords4/src/org/eehouse/android/xw4/GCMIntentService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java index 9d78c37b6..12625d11c 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java @@ -78,7 +78,7 @@ public class GCMIntentService extends GCMBaseIntentService { } String curID = XWPrefs.getGCMDevID( app ); - if ( ! curID.equals( regId ) ) { + if ( null == curID || ! curID.equals( regId ) ) { XWPrefs.setGCMDevID( app, regId ); } } catch ( UnsupportedOperationException uoe ) { From b53412d98d1f458cd3c9924204b939874ceb060f Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 10 Nov 2012 14:52:10 -0800 Subject: [PATCH 010/146] Fix to actually work. Grrr. --- xwords4/linux/scripts/discon_ok2.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xwords4/linux/scripts/discon_ok2.sh b/xwords4/linux/scripts/discon_ok2.sh index c8b475339..e2c2a889f 100755 --- a/xwords4/linux/scripts/discon_ok2.sh +++ b/xwords4/linux/scripts/discon_ok2.sh @@ -183,7 +183,7 @@ build_cmds() { fi # rememeber what the app can do - HELP="$(APPS[$COUNTER] --help 2>&1 || true)" + HELP="$(${APPS[$COUNTER]} --help 2>&1 || true)" PARAMS="$(player_params $NLOCALS $NPLAYERS $DEV)" PARAMS="$PARAMS $BOARD_SIZE --room $ROOM --trade-pct 20 --sort-tiles " @@ -396,7 +396,7 @@ set_relay_devid() { if [ -n "$RELAY_ID" ]; then RELAY_ID=$(echo $RELAY_ID | sed 's,^.*new id: ,,') # turn --devid into --rdevid $RELAY_ID - ARGS[$KEY]=$(echo $CMD | sed 's,^\(.*\)--devid[ ]\+[^ ]\+\(.*\)$,\1--rdevid $RELAY_ID\2,') + ARGS[$KEY]=$(echo $CMD | sed "s,^\(.*\)--devid[ ]\+[^ ]\+\(.*\)$,\1--rdevid $RELAY_ID\2,") fi fi fi From 1316ae4b6722e51e51c6ecbca04517150a199ac1 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 10 Nov 2012 15:05:44 -0800 Subject: [PATCH 011/146] Fix relay devid protocol to deal with case where client submits a ID_TYPE_RELAY id that's not in the devices table (as has happened when a device switches relay URLs during testing, but might also happen if I have to delete an entry from the devices table.) In that case, return ID_TYPE_NONE to the client, which will be its clue to delete its ID_TYPE_RELAY id and submit the platform-specific id again. Note: android won't compile this revision thanks to util.h change --- xwords4/common/comms.c | 12 ++++++---- xwords4/common/util.h | 9 ++++---- xwords4/linux/linuxmain.c | 3 --- xwords4/linux/linuxutl.c | 7 +++++- xwords4/relay/cref.cpp | 48 +++++++++++++++++++++++---------------- xwords4/relay/dbmgr.cpp | 20 ++++++++++++---- 6 files changed, 63 insertions(+), 36 deletions(-) diff --git a/xwords4/common/comms.c b/xwords4/common/comms.c index bf37b3f51..600dc88a4 100644 --- a/xwords4/common/comms.c +++ b/xwords4/common/comms.c @@ -1331,10 +1331,14 @@ got_connect_cmd( CommsCtxt* comms, XWStreamCtxt* stream, #endif #ifdef XWFEATURE_DEVID - XP_UCHAR devID[MAX_DEVID_LEN + 1]; - stringFromStreamHere( stream, devID, sizeof(devID) ); - if ( devID[0] != '\0' ) { - util_deviceRegistered( comms->util, devID ); + DevIDType typ = stream_getU8( stream ); + XP_UCHAR devID[MAX_DEVID_LEN + 1] = {0}; + if ( ID_TYPE_NONE != typ ) { + stringFromStreamHere( stream, devID, sizeof(devID) ); + } + if ( ID_TYPE_NONE == typ /* error case */ + || '\0' != devID[0] ) /* new info case */ { + util_deviceRegistered( comms->util, typ, devID ); } #endif diff --git a/xwords4/common/util.h b/xwords4/common/util.h index b114efdc6..29c64fd3d 100644 --- a/xwords4/common/util.h +++ b/xwords4/common/util.h @@ -154,7 +154,8 @@ typedef struct UtilVtable { XP_U32 (*m_util_getCurSeconds)( XW_UtilCtxt* uc ); #ifdef XWFEATURE_DEVID const XP_UCHAR* (*m_util_getDevID)( XW_UtilCtxt* uc, DevIDType* typ ); - void (*m_util_deviceRegistered)( XW_UtilCtxt* uc, const XP_UCHAR* idRelay ); + void (*m_util_deviceRegistered)( XW_UtilCtxt* uc, DevIDType typ, + const XP_UCHAR* idRelay ); #endif DictionaryCtxt* (*m_util_makeEmptyDict)( XW_UtilCtxt* uc ); @@ -284,10 +285,10 @@ struct XW_UtilCtxt { (uc)->vtable->m_util_getCurSeconds((uc)) #ifdef XWFEATURE_DEVID -# define util_getDevID( uc, t ) \ +# define util_getDevID( uc, t ) \ (uc)->vtable->m_util_getDevID((uc),(t)) -# define util_deviceRegistered( uc, id ) \ - (uc)->vtable->m_util_deviceRegistered( (uc), (id) ) +# define util_deviceRegistered( uc, typ, id ) \ + (uc)->vtable->m_util_deviceRegistered( (uc), (typ), (id) ) #endif #define util_makeEmptyDict( uc ) \ diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index 0b4b6d7e1..1dd97388d 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -1593,9 +1593,6 @@ main( int argc, char** argv ) mainParams.allowPeek = XP_TRUE; mainParams.showRobotScores = XP_FALSE; mainParams.useMmap = XP_TRUE; -#ifdef XWFEATURE_DEVID - mainParams.devID = ""; -#endif char* envDictPath = getenv( "XW_DICTSPATH" ); if ( !!envDictPath ) { diff --git a/xwords4/linux/linuxutl.c b/xwords4/linux/linuxutl.c index 196551219..bbeda7081 100644 --- a/xwords4/linux/linuxutl.c +++ b/xwords4/linux/linuxutl.c @@ -353,19 +353,24 @@ linux_util_getDevID( XW_UtilCtxt* uc, DevIDType* typ ) if ( !!cGlobals->params->rDevID ) { *typ = ID_TYPE_RELAY; result = cGlobals->params->rDevID; - } else { + } else if ( !!cGlobals->params->devID ) { *typ = ID_TYPE_LINUX; result = cGlobals->params->devID; + } else { + *typ = ID_TYPE_NONE; + result = NULL; } return result; } static void linux_util_deviceRegistered( XW_UtilCtxt* XP_UNUSED(uc), + DevIDType XP_UNUSED_DBG(typ), const XP_UCHAR* idRelay ) { /* Script discon_ok2.sh is grepping for this in logs, so don't change it! */ + XP_ASSERT( ID_TYPE_RELAY == typ ); /* all the linux client deals with */ XP_LOGF( "%s: new id: %s", __func__, idRelay ); } #endif diff --git a/xwords4/relay/cref.cpp b/xwords4/relay/cref.cpp index 5f66a6c9c..0ec0b64d3 100644 --- a/xwords4/relay/cref.cpp +++ b/xwords4/relay/cref.cpp @@ -891,13 +891,7 @@ CookieRef::increasePlayerCounts( CRefEvent* evt, bool reconn, HostID* hidp, DevIDType devIDType = evt->u.con.devID->m_devIDType; // does client support devID if ( ID_TYPE_NONE != devIDType ) { - // have we not already converted it? - if ( ID_TYPE_RELAY == devIDType ) { - devID = (DBMgr::DevIDRelay)strtoul( evt->u.con.devID->m_devIDString.c_str(), - NULL, 16 ); - } else { - devID = DBMgr::Get()->RegisterDevice( evt->u.con.devID ); - } + devID = DBMgr::Get()->RegisterDevice( evt->u.con.devID ); } *devIDp = devID; } @@ -1071,22 +1065,38 @@ CookieRef::sendResponse( const CRefEvent* evt, bool initial, bufp += len; // we always write at least empty string - char idbuf[MAX_DEVID_LEN + 1] = {0}; // If client supports devid, and we have one (response case), write it as // 8-byte hex string plus a length byte -- but only if we didn't already // receive it. - if ( !!devID && ID_TYPE_RELAY < evt->u.con.devID->m_devIDType ) { - len = snprintf( idbuf, sizeof(idbuf), "%.8X", *devID ); - assert( len < sizeof(idbuf) ); - } - - len = strlen( idbuf ); - assert( len <= MAX_DEVID_LEN ); - *bufp++ = (char)len; - if ( 0 < len ) { - memcpy( bufp, idbuf, len ); - bufp += len; + + // there are three possibilities: it sent us a platform-specific ID and we + // need to return the relay version; or it sent us a valid relay version; + // or it sent us an invalid one (for whatever reason, e.g. we've wiped the + // devices table entry for a problematic GCM id to force reregistration.) + // In the first case, we return the new relay version. In the second, we + // return that the type is ID_TYPE_RELAY but don't bother with the version + // string; and in the third, we return ID_TYPE_NONE. + + if ( DBMgr::DEVID_NONE == *devID ) { // first case + *bufp++ = ID_TYPE_NONE; + } else { + *bufp++ = ID_TYPE_RELAY; + + // Write an empty string if the client passed the ID to us, or the id + // if it's new to the client. + char idbuf[MAX_DEVID_LEN + 1]; + if ( !!ID_TYPE_RELAY < evt->u.con.devID->m_devIDType ) { + len = snprintf( idbuf, sizeof(idbuf), "%.8X", *devID ); + assert( len < sizeof(idbuf) ); + } else { + len = 0; + } + *bufp++ = (char)len; + if ( 0 < len ) { + memcpy( bufp, idbuf, len ); + bufp += len; + } } send_with_length( socket, buf, bufp - buf, true ); diff --git a/xwords4/relay/dbmgr.cpp b/xwords4/relay/dbmgr.cpp index 600d320e8..0d6e02531 100644 --- a/xwords4/relay/dbmgr.cpp +++ b/xwords4/relay/dbmgr.cpp @@ -253,13 +253,16 @@ DBMgr::DevIDRelay DBMgr::RegisterDevice( const DevID* host ) { DBMgr::DevIDRelay devID; - assert( host->m_devIDType != ID_TYPE_RELAY ); + assert( host->m_devIDType != ID_TYPE_NONE ); int ii; bool success; // if it's already present, just return devID = getDevID( host ); - if ( DEVID_NONE == devID ) { + + // If it's not present *and* of type ID_TYPE_RELAY, we can do nothing. + // Fail. + if ( DEVID_NONE == devID && ID_TYPE_RELAY < host->m_devIDType ) { // loop until we're successful inserting the unique key. Ship with this // coming from random, but test with increasing values initially to make // sure duplicates are detected. @@ -641,16 +644,23 @@ DBMgr::getDevID( const DevID* devID ) { DBMgr::DevIDRelay rDevID = DEVID_NONE; DevIDType devIDType = devID->m_devIDType; + char query[512] = {0}; assert( ID_TYPE_NONE < devIDType ); const char* asStr = devID->m_devIDString.c_str(); if ( ID_TYPE_RELAY == devIDType ) { - rDevID = strtoul( asStr, NULL, 16 ); + // confirm it's there + DBMgr::DevIDRelay cur = strtoul( asStr, NULL, 16 ); + if ( DEVID_NONE != cur ) { + const char* fmt = "SELECT id FROM " DEVICES_TABLE " WHERE id=%d"; + snprintf( query, sizeof(query), fmt, cur ); + } } else { const char* fmt = "SELECT id FROM " DEVICES_TABLE " WHERE devtype=%d and devid = '%s'"; - char query[512]; snprintf( query, sizeof(query), fmt, devIDType, asStr ); - logf( XW_LOGINFO, "%s: query: %s", __func__, query ); + } + if ( '\0' != query[0] ) { + logf( XW_LOGINFO, "%s: query: %s", __func__, query ); PGresult* result = PQexec( getThreadConn(), query ); assert( 1 >= PQntuples( result ) ); if ( 1 == PQntuples( result ) ) { From 6a856610954745aaae8d4b0d7ba729e963d4d6d8 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 10 Nov 2012 19:31:22 -0800 Subject: [PATCH 012/146] lots of improvements: make useful for testing with non-android clients; shelve backoff data, etc. --- xwords4/relay/scripts/gcm_loop.py | 135 ++++++++++++++++++------------ 1 file changed, 82 insertions(+), 53 deletions(-) diff --git a/xwords4/relay/scripts/gcm_loop.py b/xwords4/relay/scripts/gcm_loop.py index f7c524053..61c5fe253 100755 --- a/xwords4/relay/scripts/gcm_loop.py +++ b/xwords4/relay/scripts/gcm_loop.py @@ -6,7 +6,7 @@ # # Depends on the gcm module -import getpass, sys, gcm, psycopg2, time, signal +import getpass, sys, gcm, psycopg2, time, signal, shelve from time import gmtime, strftime # I'm not checking my key in... @@ -25,50 +25,65 @@ import mykey # contact list if it is the target of at least one message in the msgs # table. +k_shelfFile = "gcm_loop.shelf" +k_SENT = 'SENT' g_con = None +g_sent = None g_debug = False g_skipSend = False # for debugging -DEVTYPE = 3 # 3 == GCM +DEVTYPE_GCM = 3 # 3 == GCM LINE_LEN = 76 def init(): + global g_sent try: con = psycopg2.connect(database='xwgames', user=getpass.getuser()) except psycopg2.DatabaseError, e: print 'Error %s' % e sys.exit(1) + + shelf = shelve.open( k_shelfFile ) + if k_SENT in shelf: g_sent = shelf[k_SENT] + else: g_sent = {} + shelf.close(); + if g_debug: print 'g_sent:', g_sent + return con -def getPendingMsgs( con ): +def getPendingMsgs( con, typ ): cur = con.cursor() - cur.execute("SELECT id, devid FROM msgs WHERE devid IN (SELECT id FROM devices WHERE devtype=%d)" % DEVTYPE) + query = """SELECT id, devid FROM msgs WHERE + devid IN (SELECT id FROM devices WHERE devtype=%d) + AND NOT connname IN (SELECT connname FROM games WHERE dead); """ + cur.execute(query % typ) result = cur.fetchall() if g_debug: print "getPendingMsgs=>", result return result -def asGCMIds(con, devids): +def asGCMIds(con, devids, typ): cur = con.cursor() query = "SELECT devid FROM devices WHERE devtype = %d AND id IN (%s)" \ - % (DEVTYPE, ",".join([str(y) for y in devids])) + % (typ, ",".join([str(y) for y in devids])) cur.execute( query ) return [elem[0] for elem in cur.fetchall()] -def notifyGCM( devids ): - instance = gcm.GCM( mykey.myKey ) - data = { 'getMoves': True, - # 'title' : 'Msg from Darth', - # 'msg' : "I am your father, Luke.", - } - # JSON request +def notifyGCM( devids, typ ): + if typ == DEVTYPE_GCM: + instance = gcm.GCM( mykey.myKey ) + data = { 'getMoves': True, + # 'title' : 'Msg from Darth', + # 'msg' : "I am your father, Luke.", + } + response = instance.json_request( registration_ids = devids, + data = data ) - response = instance.json_request( registration_ids = devids, - data = data ) - - if 'errors' in response: - for error, reg_ids in response.items(): - print error + if 'errors' in response: + for error, reg_ids in response.items(): + print error + else: + print 'no errors' else: - print 'no errors' + print "not sending to", len(devids), "devices because typ ==", typ def shouldSend(val): pow = 1 @@ -80,53 +95,68 @@ def shouldSend(val): # be sent/resent now and mark them as sent. Backoff is based on # msgids: if the only messages a device has pending have been seen # before, backoff applies. -def targetsAfterBackoff( msgs, sent ): - targets = [] +def targetsAfterBackoff( msgs ): + global g_sent + print 'sent:', g_sent + targets = {} for row in msgs: msgid = row[0] - if not msgid in sent: - sent[msgid] = 0 - sent[msgid] += 1 - if shouldSend( sent[msgid] ): - targets.append( row[1] ) - return targets + devid = row[1] + if not msgid in g_sent: + g_sent[msgid] = 0 + g_sent[msgid] += 1 + if shouldSend( g_sent[msgid] ): + targets[devid] = True + return targets.keys() # devids is an array of (msgid, devid) tuples -def pruneSent( devids, sent ): - if g_debug: print "pruneSent: before:", sent - lenBefore = len(sent) +def pruneSent( devids ): + global g_sent + if g_debug: print "pruneSent: before:", g_sent + lenBefore = len(g_sent) msgids = [] for row in devids: msgids.append(row[0]) - for msgid in sent.keys(): + for msgid in g_sent.keys(): if not msgid in msgids: - del sent[msgid] - if g_debug: print "pruneSent: after:", sent - return sent + del g_sent[msgid] + if g_debug: print "pruneSent: after:", g_sent + +def cleanup(): + global g_con, g_sent + if g_con: + g_con.close() + g_con = None + shelf = shelve.open( k_shelfFile ) + shelf[k_SENT] = g_sent + shelf.close(); def handleSigTERM( one, two ): print 'handleSigTERM called: ', one, two - global g_con - if g_con: - g_con.close() - g_con = None + cleanup() def usage(): - print "usage:", sys.argv[0], "[--loop]" + print "usage:", sys.argv[0], "[--loop ] [--type typ] [--verbose]" sys.exit(); def main(): - global g_con + global g_con, g_sent, g_debug loopInterval = 0 g_con = init() emptyCount = 0 + typ = DEVTYPE_GCM ii = 1 while ii < len(sys.argv): arg = sys.argv[ii] if arg == '--loop': - ii = ii + 1 + ii += 1 loopInterval = float(sys.argv[ii]) + elif arg == '--type': + ii += 1 + typ = int(sys.argv[ii]) + elif arg == '--verbose': + g_debug = True else: usage() ii = ii + 1 @@ -134,30 +164,29 @@ def main(): signal.signal( signal.SIGTERM, handleSigTERM ) signal.signal( signal.SIGINT, handleSigTERM ) - sent = {} while g_con: - devids = getPendingMsgs( g_con ) + if g_debug: print + devids = getPendingMsgs( g_con, typ ) if 0 < len(devids): - targets = targetsAfterBackoff( devids, sent ) + targets = targetsAfterBackoff( devids ) if 0 < len(targets): if 0 < emptyCount: print "" emptyCount = 0 print strftime("%Y-%m-%d %H:%M:%S", gmtime()), print "devices needing notification:", targets - if not g_skipSend: - notifyGCM( asGCMIds( g_con, targets ) ) - pruneSent( devids, sent ) + notifyGCM( asGCMIds( g_con, targets, typ ), typ ) + pruneSent( devids ) else: - sys.stdout.write('.') - sys.stdout.flush() + if not g_debug: + sys.stdout.write('.') + sys.stdout.flush() emptyCount = emptyCount + 1 if 0 == (emptyCount % LINE_LEN): print "" if 0 == loopInterval: break time.sleep( loopInterval ) - if g_debug: print + if not g_debug: print - if g_con: - g_con.close() + cleanup() ############################################################################## if __name__ == '__main__': From 6ca966f22ae267733a293a0c1bed712372f2ce8a Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 12 Nov 2012 07:27:20 -0800 Subject: [PATCH 013/146] make test script and linux client mimic case where relay regid is rejected by relay. Client re-registers and a new relay regid is issued. --- xwords4/linux/linuxutl.c | 24 +++++++++---- xwords4/linux/scripts/discon_ok2.sh | 53 ++++++++++++++++++++--------- 2 files changed, 55 insertions(+), 22 deletions(-) diff --git a/xwords4/linux/linuxutl.c b/xwords4/linux/linuxutl.c index bbeda7081..650c0c892 100644 --- a/xwords4/linux/linuxutl.c +++ b/xwords4/linux/linuxutl.c @@ -364,14 +364,26 @@ linux_util_getDevID( XW_UtilCtxt* uc, DevIDType* typ ) } static void -linux_util_deviceRegistered( XW_UtilCtxt* XP_UNUSED(uc), - DevIDType XP_UNUSED_DBG(typ), +linux_util_deviceRegistered( XW_UtilCtxt* uc, DevIDType typ, const XP_UCHAR* idRelay ) { - /* Script discon_ok2.sh is grepping for this in logs, so don't change - it! */ - XP_ASSERT( ID_TYPE_RELAY == typ ); /* all the linux client deals with */ - XP_LOGF( "%s: new id: %s", __func__, idRelay ); + /* Script discon_ok2.sh is grepping for these strings in logs, so don't + change them! */ + CommonGlobals* cGlobals = (CommonGlobals*)uc->closure; + switch( typ ) { + case ID_TYPE_NONE: /* error case */ + XP_LOGF( "%s: id rejected", __func__ ); + cGlobals->params->rDevID = cGlobals->params->devID = NULL; + break; + case ID_TYPE_RELAY: + if ( 0 < strlen( idRelay ) ) { + XP_LOGF( "%s: new id: %s", __func__, idRelay ); + } + break; + default: + XP_ASSERT(0); + break; + } } #endif diff --git a/xwords4/linux/scripts/discon_ok2.sh b/xwords4/linux/scripts/discon_ok2.sh index e2c2a889f..7abda93ab 100755 --- a/xwords4/linux/scripts/discon_ok2.sh +++ b/xwords4/linux/scripts/discon_ok2.sh @@ -29,6 +29,7 @@ declare -A PIDS declare -A APPS declare -A NEW_ARGS declare -A ARGS +declare -A ARGS_DEVID declare -A ROOMS declare -A FILES declare -A LOGS @@ -182,9 +183,6 @@ build_cmds() { fi fi - # rememeber what the app can do - HELP="$(${APPS[$COUNTER]} --help 2>&1 || true)" - PARAMS="$(player_params $NLOCALS $NPLAYERS $DEV)" PARAMS="$PARAMS $BOARD_SIZE --room $ROOM --trade-pct 20 --sort-tiles " [ $UNDO_PCT -gt 0 ] && PARAMS="$PARAMS --undo-pct $UNDO_PCT " @@ -193,15 +191,14 @@ build_cmds() { PARAMS="$PARAMS --drop-nth-packet $DROP_N $PLAT_PARMS" # PARAMS="$PARAMS --savefail-pct 10" [ -n "$SEED" ] && PARAMS="$PARAMS --seed $RANDOM" - if echo $HELP | grep -q '\-\-devid'; then - PARAMS="$PARAMS --devid LINUX_TEST_$(printf %.5d ${COUNTER})" - fi PARAMS="$PARAMS $PUBLIC" ARGS[$COUNTER]=$PARAMS ROOMS[$COUNTER]=$ROOM FILES[$COUNTER]=$FILE LOGS[$COUNTER]=$LOG PIDS[$COUNTER]=0 + ARGS_DEVID[$COUNTER]="" + update_devid_cmd $COUNTER print_cmdline $COUNTER @@ -244,7 +241,7 @@ read_resume_cmds() { launch() { LOG=${LOGS[$1]} APP="${APPS[$1]}" - PARAMS="${NEW_ARGS[$1]} ${ARGS[$1]}" + PARAMS="${NEW_ARGS[$1]} ${ARGS[$1]} ${ARGS_DEVID[$1]}" exec $APP $PARAMS >/dev/null 2>>$LOG } @@ -282,6 +279,7 @@ close_device() { unset LOGS[$ID] unset ROOMS[$ID] unset APPS[$ID] + unset ARGS_DEVID[$ID] } OBITS="" @@ -386,19 +384,42 @@ increment_drop() { fi } -set_relay_devid() { +get_relayid() { + KEY=$1 + RELAY_ID=$(grep 'deviceRegistered: new id: ' ${LOGS[$KEY]} | tail -n 1) + if [ -n "$RELAY_ID" ]; then + RELAY_ID=$(echo $RELAY_ID | sed 's,^.*new id: ,,') + else + usage "new id string not in $LOG" + fi + echo $RELAY_ID +} + +update_devid_cmd() { KEY=$1 HELP="$(${APPS[$KEY]} --help 2>&1 || true)" if echo $HELP | grep -q '\-\-devid'; then - CMD=${ARGS[$KEY]} - if [ "$CMD" != "${CMD/--devid //}" ]; then - RELAY_ID=$(grep 'deviceRegistered: new id: ' ${LOGS[$KEY]} | tail -n 1) - if [ -n "$RELAY_ID" ]; then - RELAY_ID=$(echo $RELAY_ID | sed 's,^.*new id: ,,') - # turn --devid into --rdevid $RELAY_ID - ARGS[$KEY]=$(echo $CMD | sed "s,^\(.*\)--devid[ ]\+[^ ]\+\(.*\)$,\1--rdevid $RELAY_ID\2,") + CMD="--devid LINUX_TEST_$(printf %.5d ${KEY})" + LOG=${LOGS[$KEY]} + if [ -z "${ARGS_DEVID[$KEY]}" -o ! -e $LOG ]; then # upgrade or first run + : + else + # otherwise, we should have successfully registered. If + # we have AND the reg has been rejected, make without + # --rdevid so will reregister. + LAST_GOOD=$(grep -h -n 'linux_util_deviceRegistered: new id' $LOG | tail -n 1 | sed 's,:.*$,,') + LAST_REJ=$(grep -h -n 'linux_util_deviceRegistered: id rejected' $LOG | tail -n 1 | sed 's,:.*$,,') + # echo "LAST_GOOD: $LAST_GOOD; LAST_REJ: $LAST_REJ" + if [ -z "$LAST_GOOD" ]; then # not yet registered + : + elif [ -z "$LAST_REJ" ]; then + CMD="$CMD --rdevid $(get_relayid $KEY)" + elif [ "$LAST_REJ" -lt "$LAST_GOOD" ]; then # registered and not more recently rejected + CMD="$CMD --rdevid $(get_relayid $KEY)" fi fi + # echo $CMD + ARGS_DEVID[$KEY]=$CMD fi } @@ -431,7 +452,7 @@ run_cmds() { PIDS[$KEY]=0 ROOM_PIDS[$ROOM]=0 [ "$DROP_N" -ge 0 ] && increment_drop $KEY - set_relay_devid $KEY + update_devid_cmd $KEY check_game $KEY fi done From 4f3c81417d4b4ed4c63ecbeec5a6eb380cbefacb Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 12 Nov 2012 08:01:24 -0800 Subject: [PATCH 014/146] go back to logging devids for now --- .../XWords4/src/org/eehouse/android/xw4/GCMIntentService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java index 12625d11c..8a37f9698 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java @@ -37,6 +37,7 @@ public class GCMIntentService extends GCMBaseIntentService { @Override protected void onRegistered( Context context, String regId ) { + DbgUtils.logf( "GCMIntentService.onRegistered(%s)", regId ); XWPrefs.setGCMDevID( context, regId ); } From 76c938cc672fef2af1449b607529a08c0f61a6ac Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 12 Nov 2012 08:03:25 -0800 Subject: [PATCH 015/146] fix to compile with API change; to drop relayid when it's rejected and to use register with the GCM id on next [re]connect; and to not leave variable unintialized when unable to call into java world due to being called without context. --- xwords4/android/XWords4/jni/utilwrapper.c | 8 +++++--- .../src/org/eehouse/android/xw4/XWPrefs.java | 7 +++++++ .../src/org/eehouse/android/xw4/jni/UtilCtxt.java | 2 +- .../org/eehouse/android/xw4/jni/UtilCtxtImpl.java | 14 ++++++++++++-- 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/xwords4/android/XWords4/jni/utilwrapper.c b/xwords4/android/XWords4/jni/utilwrapper.c index 7d6a24e48..d29d610c0 100644 --- a/xwords4/android/XWords4/jni/utilwrapper.c +++ b/xwords4/android/XWords4/jni/utilwrapper.c @@ -552,6 +552,7 @@ static const XP_UCHAR* and_util_getDevID( XW_UtilCtxt* uc, DevIDType* typ ) { const XP_UCHAR* result = NULL; + *typ = ID_TYPE_NONE; UTIL_CBK_HEADER( "getDevID", "([B)Ljava/lang/String;" ); jbyteArray jbarr = makeByteArray( env, 1, NULL ); jstring jresult = (*env)->CallObjectMethod( env, util->jutil, mid, jbarr ); @@ -581,11 +582,12 @@ and_util_getDevID( XW_UtilCtxt* uc, DevIDType* typ ) } static void -and_util_deviceRegistered( XW_UtilCtxt* uc, const XP_UCHAR* idRelay ) +and_util_deviceRegistered( XW_UtilCtxt* uc, DevIDType typ, + const XP_UCHAR* idRelay ) { - UTIL_CBK_HEADER( "deviceRegistered", "(Ljava/lang/String;)V" ); + UTIL_CBK_HEADER( "deviceRegistered", "(ILjava/lang/String;)V" ); jstring jstr = (*env)->NewStringUTF( env, idRelay ); - (*env)->CallVoidMethod( env, util->jutil, mid, jstr ); + (*env)->CallVoidMethod( env, util->jutil, mid, typ, jstr ); deleteLocalRef( env, jstr ); UTIL_CBK_TAIL(); } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java index b8a9df7ce..24ebbe8eb 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java @@ -187,6 +187,7 @@ public class XWPrefs { public static void setGCMDevID( Context context, String devID ) { setPrefsString( context, R.string.key_gcm_regid, devID ); + clearPrefsKey( context, R.string.key_relay_regid ); } public static String getGCMDevID( Context context ) @@ -201,6 +202,7 @@ public class XWPrefs { public static void clearGCMDevID( Context context ) { clearPrefsKey( context, R.string.key_gcm_regid ); + clearRelayDevID( context ); } public static String getRelayDevID( Context context ) @@ -217,6 +219,11 @@ public class XWPrefs { setPrefsString( context, R.string.key_relay_regid, idRelay ); } + public static void clearRelayDevID( Context context ) + { + clearPrefsKey( context, R.string.key_relay_regid ); + } + public static boolean getHaveCheckedSMS( Context context ) { return getPrefsBoolean( context, R.string.key_checked_sms, false ); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java index 28fa12f7d..5302f41f7 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java @@ -62,7 +62,7 @@ public interface UtilCtxt { public static final int ID_TYPE_ANDROID_GCM = 3; String getDevID( /*out*/ byte[] typ ); - void deviceRegistered( String idRelay ); + void deviceRegistered( int devIDType, String idRelay ); void bonusSquareHeld( int bonus ); void playerScoreHeld( int player ); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxtImpl.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxtImpl.java index fdac77a8f..f39ad5e1a 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxtImpl.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxtImpl.java @@ -110,9 +110,19 @@ public class UtilCtxtImpl implements UtilCtxt { return result; } - public void deviceRegistered( String idRelay ) + public void deviceRegistered( int devIDType, String idRelay ) { - XWPrefs.setRelayDevID( m_context, idRelay ); + switch ( devIDType ) { + case UtilCtxt.ID_TYPE_RELAY: + XWPrefs.setRelayDevID( m_context, idRelay ); + break; + case UtilCtxt.ID_TYPE_NONE: + XWPrefs.clearRelayDevID( m_context ); + break; + default: + Assert.fail(); + break; + } } public void bonusSquareHeld( int bonus ) From 60814264e7ab2a4499dbf62876f98f6cfab2e06c Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 12 Nov 2012 20:56:50 -0800 Subject: [PATCH 016/146] compile with XWFEATURE_COMMSACK turned off --- xwords4/android/XWords4/jni/xwjni.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xwords4/android/XWords4/jni/xwjni.c b/xwords4/android/XWords4/jni/xwjni.c index c74f1d402..01a951d1d 100644 --- a/xwords4/android/XWords4/jni/xwjni.c +++ b/xwords4/android/XWords4/jni/xwjni.c @@ -1314,7 +1314,9 @@ Java_org_eehouse_android_xw4_jni_XwJNI_comms_1resendAll XP_ASSERT( !!comms ); (void)comms_resendAll( comms ); if ( thenAck ) { +#ifdef XWFEATURE_COMMSACK comms_ackAny( comms ); +#endif } XWJNI_END(); } From 082e6a61f70dd13f27e993ba32294b52b6a9fbde Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 12 Nov 2012 20:57:39 -0800 Subject: [PATCH 017/146] comment out unused command --- .../src/org/eehouse/android/xw4/jni/JNIThread.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java index 74128edab..b4c48d7fa 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java @@ -77,7 +77,7 @@ public class JNIThread extends Thread { CMD_COUNTS_VALUES, CMD_REMAINING, CMD_RESEND, - CMD_ACKANY, + // CMD_ACKANY, CMD_HISTORY, CMD_FINAL, CMD_ENDGAME, @@ -497,9 +497,9 @@ public class JNIThread extends Thread { XwJNI.comms_resendAll( m_jniGamePtr, ((Boolean)args[0]).booleanValue() ); break; - case CMD_ACKANY: - XwJNI.comms_ackAny( m_jniGamePtr ); - break; + // case CMD_ACKANY: + // XwJNI.comms_ackAny( m_jniGamePtr ); + // break; case CMD_HISTORY: boolean gameOver = XwJNI.server_getGameIsOver( m_jniGamePtr ); From 43b1e219f386139e6646a12353680e7a64916daf Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 13 Nov 2012 06:14:36 -0800 Subject: [PATCH 018/146] add a bit more logging to comms --- xwords4/common/comms.c | 28 +++++++++++++++++++++------- xwords4/common/movestak.c | 2 +- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/xwords4/common/comms.c b/xwords4/common/comms.c index 600dc88a4..2aa3a33e5 100644 --- a/xwords4/common/comms.c +++ b/xwords4/common/comms.c @@ -785,9 +785,11 @@ comms_saveSucceeded( CommsCtxt* comms, XP_U16 saveToken ) XP_LOGF( "%s(saveToken=%d)", __func__, saveToken ); XP_ASSERT( !!comms ); if ( saveToken == comms->lastSaveToken ) { - XP_LOGF( "%s: lastSave matches", __func__ ); AddressRecord* rec; for ( rec = comms->recs; !!rec; rec = rec->next ) { + XP_LOGF( "%s: lastSave matches; updating lastMsgSaved %ld to " + "lastMsgRcd %ld", __func__, rec->lastMsgSaved, + rec->lastMsgRcd ); rec->lastMsgSaved = rec->lastMsgRcd; } #ifdef XWFEATURE_COMMSACK @@ -1037,9 +1039,10 @@ addToQueue( CommsCtxt* comms, MsgQueueElem* newMsgElem ) XP_ASSERT( comms->queueLen > 0 ); } ++comms->queueLen; - XP_LOGF( "%s: queueLen now %d after channelNo: %d; msgID: " XP_LD, - __func__, comms->queueLen, - newMsgElem->channelNo & CHANNEL_MASK, newMsgElem->msgID ); + XP_LOGF( "%s: queueLen now %d after channelNo: %d; msgID: " XP_LD + "; len: %d", __func__, comms->queueLen, + newMsgElem->channelNo & CHANNEL_MASK, newMsgElem->msgID, + newMsgElem->len ); } /* addToQueue */ #ifdef DEBUG @@ -1244,14 +1247,25 @@ comms_resendAll( CommsCtxt* comms ) void comms_ackAny( CommsCtxt* comms ) { +#ifdef DEBUG + XP_Bool noneSent = XP_TRUE; +#endif AddressRecord* rec; for ( rec = comms->recs; !!rec; rec = rec->next ) { if ( rec->lastMsgAckd < rec->lastMsgRcd ) { - XP_LOGF( "%s: %ld < %ld: rec needs ack", __func__, - rec->lastMsgAckd, rec->lastMsgRcd ); +#ifdef DEBUG + noneSent = XP_FALSE; +#endif + XP_LOGF( "%s: channel %x; %ld < %ld: rec needs ack", __func__, + rec->channelNo, rec->lastMsgAckd, rec->lastMsgRcd ); sendEmptyMsg( comms, rec ); } } +#ifdef DEBUG + if ( noneSent ) { + XP_LOGF( "%s: nothing to send", __func__ ); + } +#endif } #endif @@ -1845,7 +1859,7 @@ sendEmptyMsg( CommsCtxt* comms, AddressRecord* rec ) 0 /*rec? rec->lastMsgRcd : 0*/, rec, rec? rec->channelNo : 0, NULL ); - sendMsg( comms, elem ); + (void)sendMsg( comms, elem ); freeElem( comms, elem ); } /* sendEmptyMsg */ #endif diff --git a/xwords4/common/movestak.c b/xwords4/common/movestak.c index 140da1fb4..984d43795 100644 --- a/xwords4/common/movestak.c +++ b/xwords4/common/movestak.c @@ -147,7 +147,7 @@ stack_getHash( const StackCtxt* stack ) stream_copyBits( stack->data, 0, stack->top, buf, &len ); // LOG_HEX( buf, len, __func__ ); hash = finishHash( augmentHash( 0L, buf, len ) ); - LOG_RETURNF( "%.8X", (unsigned int)hash ); + // LOG_RETURNF( "%.8X", (unsigned int)hash ); return hash; } /* stack_getHash */ #endif From 2908802017d789cf08e4aff45c8997013ee082ed Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 13 Nov 2012 06:15:36 -0800 Subject: [PATCH 019/146] try to make linux client mimic Android a bit better in saving after any network activity so messages get acked more quickly. --- xwords4/linux/Makefile | 1 + xwords4/linux/cursesmain.c | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/xwords4/linux/Makefile b/xwords4/linux/Makefile index fe0af91a2..05543eeee 100644 --- a/xwords4/linux/Makefile +++ b/xwords4/linux/Makefile @@ -111,6 +111,7 @@ DEFINES += -DXWFEATURE_HILITECELL # allow change dict inside running game DEFINES += -DXWFEATURE_CHANGEDICT DEFINES += -DXWFEATURE_DEVID +DEFINES += -DXWFEATURE_COMMSACK # MAX_ROWS controls STREAM_VERS_BIGBOARD and with it move hashing DEFINES += -DMAX_ROWS=32 diff --git a/xwords4/linux/cursesmain.c b/xwords4/linux/cursesmain.c index 42529981c..945719aa0 100644 --- a/xwords4/linux/cursesmain.c +++ b/xwords4/linux/cursesmain.c @@ -474,6 +474,7 @@ onetime_idle( gpointer data ) if ( !!globals->cGlobals.game.board ) { board_draw( globals->cGlobals.game.board ); } + saveGame( &globals->cGlobals ); } return FALSE; } @@ -1219,7 +1220,7 @@ static XP_Bool blocking_gotEvent( CursesAppGlobals* globals, int* ch ) { XP_Bool result = XP_FALSE; - int numEvents; + int numEvents, ii; short fdIndex; XP_Bool redraw = XP_FALSE; @@ -1334,12 +1335,15 @@ blocking_gotEvent( CursesAppGlobals* globals, int* ch ) } } - redraw = server_do( globals->cGlobals.game.server, NULL ) || redraw; + for ( ii = 0; ii < 5; ++ii ) { + redraw = server_do( globals->cGlobals.game.server, NULL ) || redraw; + } if ( redraw ) { /* messages change a lot */ board_invalAll( globals->cGlobals.game.board ); board_draw( globals->cGlobals.game.board ); } + saveGame( globals->cGlobals ); } return result; } /* blocking_gotEvent */ From c4f5b4522ecf72c6621c5bb14a1aaa95eae437a7 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 13 Nov 2012 07:17:56 -0800 Subject: [PATCH 020/146] minor printing tweaks --- xwords4/relay/scripts/gcm_loop.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/xwords4/relay/scripts/gcm_loop.py b/xwords4/relay/scripts/gcm_loop.py index 61c5fe253..d1877bc67 100755 --- a/xwords4/relay/scripts/gcm_loop.py +++ b/xwords4/relay/scripts/gcm_loop.py @@ -81,7 +81,9 @@ def notifyGCM( devids, typ ): for error, reg_ids in response.items(): print error else: - print 'no errors' + print 'no errors', + if g_debug: print ':', response + else: print else: print "not sending to", len(devids), "devices because typ ==", typ @@ -176,15 +178,14 @@ def main(): print "devices needing notification:", targets notifyGCM( asGCMIds( g_con, targets, typ ), typ ) pruneSent( devids ) - else: - if not g_debug: - sys.stdout.write('.') - sys.stdout.flush() - emptyCount = emptyCount + 1 - if 0 == (emptyCount % LINE_LEN): print "" + else: + emptyCount += 1 + if not g_debug: + sys.stdout.write('.') + sys.stdout.flush() + if 0 == (emptyCount % LINE_LEN): print "" if 0 == loopInterval: break time.sleep( loopInterval ) - if not g_debug: print cleanup() From 57ea768aaf34dd7000df2748536bb17b5870aaad Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 13 Nov 2012 07:22:48 -0800 Subject: [PATCH 021/146] refuse to send 0-length messages. That may happen only when the stupid curses client calls server_initClientConnection over and over, but reduces unnecessary messaging in that case at least. --- xwords4/common/comms.c | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/xwords4/common/comms.c b/xwords4/common/comms.c index 2aa3a33e5..319a5e1fc 100644 --- a/xwords4/common/comms.c +++ b/xwords4/common/comms.c @@ -998,24 +998,28 @@ comms_getChannelSeed( CommsCtxt* comms ) XP_S16 comms_send( CommsCtxt* comms, XWStreamCtxt* stream ) { - XP_PlayerAddr channelNo = stream_getAddress( stream ); - XP_LOGF( "%s: channelNo=%x", __func__, channelNo ); - AddressRecord* rec = getRecordFor( comms, NULL, channelNo, XP_FALSE ); - MsgID msgID = (!!rec)? ++rec->nextMsgID : 0; - MsgQueueElem* elem; XP_S16 result = -1; + if ( 0 == stream_getSize(stream) ) { + XP_LOGF( "%s: dropping 0-len message", __func__ ); + } else { + XP_PlayerAddr channelNo = stream_getAddress( stream ); + XP_LOGF( "%s: channelNo=%x", __func__, channelNo ); + AddressRecord* rec = getRecordFor( comms, NULL, channelNo, XP_FALSE ); + MsgID msgID = (!!rec)? ++rec->nextMsgID : 0; + MsgQueueElem* elem; - if ( 0 == channelNo ) { - channelNo = comms_getChannelSeed(comms) & ~CHANNEL_MASK; - } + if ( 0 == channelNo ) { + channelNo = comms_getChannelSeed(comms) & ~CHANNEL_MASK; + } - XP_DEBUGF( "%s: assigning msgID=" XP_LD " on chnl %x", __func__, - msgID, channelNo ); + XP_DEBUGF( "%s: assigning msgID=" XP_LD " on chnl %x", __func__, + msgID, channelNo ); - elem = makeElemWithID( comms, msgID, rec, channelNo, stream ); - if ( NULL != elem ) { - addToQueue( comms, elem ); - result = sendMsg( comms, elem ); + elem = makeElemWithID( comms, msgID, rec, channelNo, stream ); + if ( NULL != elem ) { + addToQueue( comms, elem ); + result = sendMsg( comms, elem ); + } } return result; } /* comms_send */ From 093fc4d1faae01e759a7da13e75b2da8a4b0e63d Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 13 Nov 2012 07:23:07 -0800 Subject: [PATCH 022/146] cleanup --- xwords4/linux/cursesmain.c | 29 ++++------------------------- xwords4/linux/gtkmain.c | 28 +++++++--------------------- xwords4/linux/linuxmain.c | 8 ++++++++ xwords4/linux/linuxmain.h | 2 ++ 4 files changed, 21 insertions(+), 46 deletions(-) diff --git a/xwords4/linux/cursesmain.c b/xwords4/linux/cursesmain.c index 945719aa0..1cebe705d 100644 --- a/xwords4/linux/cursesmain.c +++ b/xwords4/linux/cursesmain.c @@ -1490,25 +1490,15 @@ curses_util_remSelected( XW_UtilCtxt* uc ) } #ifndef XWFEATURE_STANDALONE_ONLY -static void -cursesSendOnClose( XWStreamCtxt* stream, void* closure ) -{ - CursesAppGlobals* globals = (CursesAppGlobals*)closure; - - XP_LOGF( "cursesSendOnClose called" ); - (void)comms_send( globals->cGlobals.game.comms, stream ); -} /* cursesSendOnClose */ - static XWStreamCtxt* curses_util_makeStreamFromAddr(XW_UtilCtxt* uc, XP_PlayerAddr channelNo ) { CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; LaunchParams* params = globals->cGlobals.params; - XWStreamCtxt* stream = mem_stream_make( MPPARM(uc->mpool) - params->vtMgr, - uc->closure, channelNo, - cursesSendOnClose ); + XWStreamCtxt* stream = mem_stream_make( MPPARM(uc->mpool) params->vtMgr, + &globals->cGlobals, channelNo, + sendOnClose ); return stream; } /* curses_util_makeStreamFromAddr */ #endif @@ -1548,17 +1538,6 @@ setupCursesUtilCallbacks( CursesAppGlobals* globals, XW_UtilCtxt* util ) util->closure = globals; } /* setupCursesUtilCallbacks */ -#ifndef XWFEATURE_STANDALONE_ONLY -static void -sendOnClose( XWStreamCtxt* stream, void* closure ) -{ - CursesAppGlobals* globals = closure; - XP_LOGF( "curses sendOnClose called" ); - XP_ASSERT( !!globals->cGlobals.game.comms ); - comms_send( globals->cGlobals.game.comms, stream ); -} /* sendOnClose */ -#endif - static CursesMenuHandler getHandlerForKey( const MenuList* list, char ch ) { @@ -1875,7 +1854,7 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) server_initClientConnection( g_globals.cGlobals.game.server, mem_stream_make( MEMPOOL params->vtMgr, - &g_globals, + &g_globals.cGlobals, (XP_PlayerAddr)0, sendOnClose ) ); } else { diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index 0567410f7..6b7278f50 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -65,9 +65,6 @@ #include "filestream.h" /* static guint gtkSetupClientSocket( GtkAppGlobals* globals, int sock ); */ -#ifndef XWFEATURE_STANDALONE_ONLY -static void sendOnCloseGTK( XWStreamCtxt* stream, void* closure ); -#endif static void setCtrlsForTray( GtkAppGlobals* globals ); static void new_game( GtkWidget* widget, GtkAppGlobals* globals ); static void new_game_impl( GtkAppGlobals* globals, XP_Bool fireConnDlg ); @@ -508,8 +505,8 @@ createOrLoadObjects( GtkAppGlobals* globals ) #ifndef XWFEATURE_STANDALONE_ONLY } else if ( !isServer ) { XWStreamCtxt* stream = - mem_stream_make( MEMPOOL params->vtMgr, globals, CHANNEL_NONE, - sendOnCloseGTK ); + mem_stream_make( MEMPOOL params->vtMgr, &globals->cGlobals, CHANNEL_NONE, + sendOnClose ); server_initClientConnection( globals->cGlobals.game.server, stream ); #endif @@ -814,11 +811,9 @@ new_game_impl( GtkAppGlobals* globals, XP_Bool fireConnDlg ) if ( isClient ) { XWStreamCtxt* stream = - mem_stream_make( MEMPOOL - globals->cGlobals.params->vtMgr, - globals, - CHANNEL_NONE, - sendOnCloseGTK ); + mem_stream_make( MEMPOOL globals->cGlobals.params->vtMgr, + &globals->cGlobals, CHANNEL_NONE, + sendOnClose ); server_initClientConnection( globals->cGlobals.game.server, stream ); } @@ -1747,8 +1742,8 @@ gtk_util_makeStreamFromAddr(XW_UtilCtxt* uc, XP_PlayerAddr channelNo ) XWStreamCtxt* stream = mem_stream_make( MEMPOOL globals->cGlobals.params->vtMgr, - uc->closure, channelNo, - sendOnCloseGTK ); + &globals->cGlobals, channelNo, + sendOnClose ); return stream; } /* gtk_util_makeStreamFromAddr */ @@ -2270,15 +2265,6 @@ gtk_socket_acceptor( int listener, Acceptor func, CommonGlobals* globals, } } /* gtk_socket_acceptor */ -static void -sendOnCloseGTK( XWStreamCtxt* stream, void* closure ) -{ - GtkAppGlobals* globals = closure; - - XP_LOGF( "sendOnClose called" ); - (void)comms_send( globals->cGlobals.game.comms, stream ); -} /* sendOnClose */ - static void drop_msg_toggle( GtkWidget* toggle, GtkAppGlobals* globals ) { diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index 1dd97388d..82b7d5c04 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -196,6 +196,14 @@ catOnClose( XWStreamCtxt* stream, void* XP_UNUSED(closure) ) free( buffer ); } /* catOnClose */ +void +sendOnClose( XWStreamCtxt* stream, void* closure ) +{ + CommonGlobals* cGlobals = (CommonGlobals*)closure; + XP_LOGF( "%s called with msg of len %d", __func__, stream_getSize(stream) ); + (void)comms_send( cGlobals->game.comms, stream ); +} + void catGameHistory( CommonGlobals* cGlobals ) { diff --git a/xwords4/linux/linuxmain.h b/xwords4/linux/linuxmain.h index bae629d47..44cf263e5 100644 --- a/xwords4/linux/linuxmain.h +++ b/xwords4/linux/linuxmain.h @@ -60,6 +60,8 @@ XP_UCHAR* strFromStream( XWStreamCtxt* stream ); void catGameHistory( CommonGlobals* cGlobals ); void catOnClose( XWStreamCtxt* stream, void* closure ); +void sendOnClose( XWStreamCtxt* stream, void* closure ); + void catFinalScores( const CommonGlobals* cGlobals, XP_S16 quitter ); XP_Bool file_exists( const char* fileName ); XWStreamCtxt* streamFromFile( CommonGlobals* cGlobals, char* name, From ba11fcf8df8fcb690d27e98f94360b8d7778384c Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 13 Nov 2012 21:01:25 -0800 Subject: [PATCH 023/146] don't bother checking for stream size of 0 in jni since comms does that now. --- xwords4/android/XWords4/jni/xwjni.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/xwords4/android/XWords4/jni/xwjni.c b/xwords4/android/XWords4/jni/xwjni.c index 01a951d1d..cf45f5594 100644 --- a/xwords4/android/XWords4/jni/xwjni.c +++ b/xwords4/android/XWords4/jni/xwjni.c @@ -974,9 +974,7 @@ and_send_on_close( XWStreamCtxt* stream, void* closure ) JNIState* state = (JNIState*)globals->state; XP_ASSERT( !!state->game.comms ); - if ( stream_getSize( stream ) > 0 ) { - comms_send( state->game.comms, stream ); - } + comms_send( state->game.comms, stream ); } JNIEXPORT void JNICALL From cf8d30ce5dc3c576ef6aae9edd31752cf0b33a07 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 13 Nov 2012 21:18:52 -0800 Subject: [PATCH 024/146] tweak logging; fix uninitialized debug-only variable --- xwords4/common/comms.c | 18 ++++++++++++------ xwords4/common/game.c | 1 + 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/xwords4/common/comms.c b/xwords4/common/comms.c index 319a5e1fc..e603aa8ea 100644 --- a/xwords4/common/comms.c +++ b/xwords4/common/comms.c @@ -55,7 +55,9 @@ typedef struct MsgQueueElem { XP_U8* msg; XP_U16 len; XP_PlayerAddr channelNo; +#ifdef DEBUG XP_U16 sendCount; /* how many times sent? */ +#endif MsgID msgID; /* saved for ease of deletion */ #ifdef COMMS_CHECKSUM gchar* checksum; @@ -607,7 +609,7 @@ comms_makeFromStream( MPFORMAL XWStreamCtxt* stream, XW_UtilCtxt* util, msg->channelNo = stream_getU16( stream ); msg->msgID = stream_getU32( stream ); -#ifdef COMMS_HEARTBEAT +#ifdef DEBUG msg->sendCount = 0; #endif msg->len = stream_getU16( stream ); @@ -787,8 +789,8 @@ comms_saveSucceeded( CommsCtxt* comms, XP_U16 saveToken ) if ( saveToken == comms->lastSaveToken ) { AddressRecord* rec; for ( rec = comms->recs; !!rec; rec = rec->next ) { - XP_LOGF( "%s: lastSave matches; updating lastMsgSaved %ld to " - "lastMsgRcd %ld", __func__, rec->lastMsgSaved, + XP_LOGF( "%s: lastSave matches; updating lastMsgSaved (%ld) to " + "lastMsgRcd (%ld)", __func__, rec->lastMsgSaved, rec->lastMsgRcd ); rec->lastMsgSaved = rec->lastMsgRcd; } @@ -944,7 +946,7 @@ makeElemWithID( CommsCtxt* comms, MsgID msgID, AddressRecord* rec, sizeof( *newMsgElem ) ); newMsgElem->channelNo = channelNo; newMsgElem->msgID = msgID; -#ifdef COMMS_HEARTBEAT +#ifdef DEBUG newMsgElem->sendCount = 0; #endif @@ -1214,8 +1216,11 @@ sendMsg( CommsCtxt* comms, MsgQueueElem* elem ) } if ( result == elem->len ) { +#ifdef DEBUG ++elem->sendCount; - XP_LOGF( "%s: elem's sendCount now %d", __func__, elem->sendCount ); +#endif + XP_LOGF( "%s: elem's sendCount since load: %d", __func__, + elem->sendCount ); } XP_LOGF( "%s(channelNo=%d;msgID=" XP_LD ")=>%d", __func__, @@ -1701,6 +1706,7 @@ validateInitialMessage( CommsCtxt* comms, rec = getRecordFor( comms, addr, *channelNo, XP_TRUE ); if ( !!rec ) { /* reject: we've already seen init message on channel */ + XP_LOGF( "%s: rejecting duplicate INIT message", __func__ ); rec = NULL; } else { if ( comms->isServer ) { @@ -2261,7 +2267,7 @@ sendNoConn( CommsCtxt* comms, const MsgQueueElem* elem, XWHostID destID ) } } - LOG_RETURNF( "%d", success ); + LOG_RETURNF( "%s", success?"TRUE":"FALSE" ); return success; } diff --git a/xwords4/common/game.c b/xwords4/common/game.c index 6507ba204..f61e25ab2 100644 --- a/xwords4/common/game.c +++ b/xwords4/common/game.c @@ -473,6 +473,7 @@ gi_readFromStream( MPFORMAL XWStreamCtxt* stream, CurGameInfo* gi ) gi->nPlayers = (XP_U8)stream_getBits( stream, NPLAYERS_NBITS ); gi->boardSize = (XP_U8)stream_getBits( stream, nColsNBits ); gi->serverRole = (DeviceRole)stream_getBits( stream, 2 ); + XP_LOGF( "%s: read role of %d", __func__, gi->serverRole ); gi->hintsNotAllowed = stream_getBits( stream, 1 ); if ( strVersion < STREAM_VERS_ROBOTIQ ) { (void)stream_getBits( stream, 2 ); From c4df31892ebfdb60f2c74fb9bff6ad80080c4dc6 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 13 Nov 2012 21:25:03 -0800 Subject: [PATCH 025/146] Fix occasional infinite loop of devices sending and receiving the same message over and over when getting updated by GCM. The problem occurred when one device had an un-ACKable initial message still in its queue. I call resendAll a lot, which caused that message to get resent to the other game which then replied without being able to ACK it so it remained to be sent again. This would continue until users moved forward in the game. The fix is to add a backoff timer to resendAll() so that it can't loop. The timer is reset when an ackable and new message is received, meaning there's been a change in what's available to resend. And since users calling resendAll manually expect it to do something, add a force param that ignores the backoff. seems to fix the problem (but needs a lot of testing.) --- xwords4/android/XWords4/jni/xwjni.c | 4 +- .../eehouse/android/xw4/BoardActivity.java | 2 +- .../eehouse/android/xw4/CommsTransport.java | 32 +++++------ .../org/eehouse/android/xw4/GameUtils.java | 2 +- .../eehouse/android/xw4/jni/JNIThread.java | 3 +- .../org/eehouse/android/xw4/jni/XwJNI.java | 3 +- xwords4/common/comms.c | 55 +++++++++++++++---- xwords4/common/comms.h | 2 +- xwords4/common/comtypes.h | 3 +- xwords4/linux/cursesmain.c | 2 +- 10 files changed, 71 insertions(+), 37 deletions(-) diff --git a/xwords4/android/XWords4/jni/xwjni.c b/xwords4/android/XWords4/jni/xwjni.c index cf45f5594..535405ece 100644 --- a/xwords4/android/XWords4/jni/xwjni.c +++ b/xwords4/android/XWords4/jni/xwjni.c @@ -1305,12 +1305,12 @@ Java_org_eehouse_android_xw4_jni_XwJNI_game_1changeDict JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_comms_1resendAll -( JNIEnv* env, jclass C, jint gamePtr, jboolean thenAck ) +( JNIEnv* env, jclass C, jint gamePtr, jboolean force, jboolean thenAck ) { XWJNI_START(); CommsCtxt* comms = state->game.comms; XP_ASSERT( !!comms ); - (void)comms_resendAll( comms ); + (void)comms_resendAll( comms, force ); if ( thenAck ) { #ifdef XWFEATURE_COMMSACK comms_ackAny( comms ); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java index db252b137..9f9f5089a 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java @@ -777,7 +777,7 @@ public class BoardActivity extends XWActivity break; case R.id.board_menu_game_resend: - m_jniThread.handle( JNICmd.CMD_RESEND, false ); + m_jniThread.handle( JNICmd.CMD_RESEND, true, false ); break; case R.id.gamel_menu_checkmoves: diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/CommsTransport.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/CommsTransport.java index b6da68897..ba89de474 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/CommsTransport.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/CommsTransport.java @@ -233,23 +233,21 @@ public class CommsTransport implements TransportProcs, public void tickle( CommsConnType connType ) { - m_jniThread.handle( JNIThread.JNICmd.CMD_RESEND, true ); - // CommsAddrRec addr = new CommsAddrRec( m_context ); - // XwJNI.comms_getAddr( m_jniGamePtr, addr ); - // switch( addr.conType ) { - // case COMMS_CONN_RELAY: - // // do nothing - // break; - // case COMMS_CONN_BT: - // // Let other know I'm here - // m_jniThread.handle( JNIThread.JNICmd.CMD_RESEND ); - // break; - // case COMMS_CONN_SMS: - // default: - // DbgUtils.logf( "tickle: unexpected type %s", - // addr.conType.toString() ); - // Assert.fail(); - // } + switch( connType ) { + case COMMS_CONN_RELAY: + // do nothing + // break; // Try skipping the resend -- later + case COMMS_CONN_BT: + case COMMS_CONN_SMS: + // Let other know I'm here + DbgUtils.logf( "tickle calling comms_resendAll" ); + m_jniThread.handle( JNIThread.JNICmd.CMD_RESEND, false, true ); + break; + default: + DbgUtils.logf( "tickle: unexpected type %s", + connType.toString() ); + Assert.fail(); + } } private synchronized void putOut( final byte[] buf ) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java index f89c3a99b..c8ec34cc8 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java @@ -708,7 +708,7 @@ public class GameUtils { FeedUtilsImpl feedImpl = new FeedUtilsImpl( context, rowid ); int gamePtr = loadMakeGame( context, gi, feedImpl, sink, lock ); - XwJNI.comms_resendAll( gamePtr, false ); + XwJNI.comms_resendAll( gamePtr, false, false ); if ( null != msgs ) { for ( byte[] msg : msgs ) { diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java index b4c48d7fa..24b82e392 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java @@ -495,7 +495,8 @@ public class JNIThread extends Thread { case CMD_RESEND: XwJNI.comms_resendAll( m_jniGamePtr, - ((Boolean)args[0]).booleanValue() ); + ((Boolean)args[0]).booleanValue(), + ((Boolean)args[1]).booleanValue() ); break; // case CMD_ACKANY: // XwJNI.comms_ackAny( m_jniGamePtr ); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/XwJNI.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/XwJNI.java index 43809fada..0d869ab06 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/XwJNI.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/XwJNI.java @@ -238,7 +238,8 @@ public class XwJNI { public static native void comms_getAddr( int gamePtr, CommsAddrRec addr ); public static native CommsAddrRec[] comms_getAddrs( int gamePtr ); public static native void comms_setAddr( int gamePtr, CommsAddrRec addr ); - public static native void comms_resendAll( int gamePtr, boolean andAck ); + public static native void comms_resendAll( int gamePtr, boolean force, + boolean andAck ); public static native void comms_ackAny( int gamePtr ); public static native void comms_transportFailed( int gamePtr ); public static native boolean comms_isConnected( int gamePtr ); diff --git a/xwords4/common/comms.c b/xwords4/common/comms.c index e603aa8ea..e43c0c191 100644 --- a/xwords4/common/comms.c +++ b/xwords4/common/comms.c @@ -110,6 +110,9 @@ struct CommsCtxt { XP_U16 queueLen; XP_U16 channelSeed; /* tries to be unique per device to aid dupe elimination at start */ + XP_U32 nextResend; + XP_U16 resendBackoff; + #ifdef COMMS_HEARTBEAT XP_Bool doHeartbeat; XP_U32 lastMsgRcvdTime; @@ -574,6 +577,10 @@ comms_makeFromStream( MPFORMAL XWStreamCtxt* stream, XW_UtilCtxt* util, comms->channelSeed = stream_getU16( stream ); XP_LOGF( "%s: loaded seed: %.4X", __func__, comms->channelSeed ); } + if ( STREAM_VERS_COMMSBACKOFF <= version ) { + comms->resendBackoff = stream_getU16( stream ); + comms->nextResend = stream_getU32( stream ); + } if ( addr.conType == COMMS_CONN_RELAY ) { comms->r.myHostID = stream_getU8( stream ); stringFromStreamHere( stream, comms->r.connName, @@ -674,7 +681,7 @@ sendConnect( CommsCtxt* comms, XP_Bool breakExisting ) case COMMS_CONN_IP_DIRECT: /* This will only work on host side when there's a single guest! */ (void)send_via_bt_or_ip( comms, BTIPMSG_RESET, CHANNEL_NONE, NULL, 0 ); - (void)comms_resendAll( comms ); + (void)comms_resendAll( comms, XP_FALSE ); break; #endif default: @@ -745,6 +752,8 @@ comms_writeToStream( CommsCtxt* comms, XWStreamCtxt* stream, stream_putU32( stream, comms->connID ); stream_putU16( stream, comms->nextChannelNo ); stream_putU16( stream, comms->channelSeed ); + stream_putU16( stream, comms->resendBackoff ); + stream_putU32( stream, comms->nextResend ); if ( comms->addr.conType == COMMS_CONN_RELAY ) { stream_putU8( stream, comms->r.myHostID ); stringToStream( stream, comms->r.connName ); @@ -781,6 +790,14 @@ comms_writeToStream( CommsCtxt* comms, XWStreamCtxt* stream, comms->lastSaveToken = saveToken; } /* comms_writeToStream */ +static void +resetBackoff( CommsCtxt* comms ) +{ + XP_LOGF( "%s: resetting backoff", __func__ ); + comms->resendBackoff = 0; + comms->nextResend = 0; +} + void comms_saveSucceeded( CommsCtxt* comms, XP_U16 saveToken ) { @@ -1236,17 +1253,32 @@ send_ack( CommsCtxt* comms ) } XP_Bool -comms_resendAll( CommsCtxt* comms ) +comms_resendAll( CommsCtxt* comms, XP_Bool force ) { XP_Bool success = XP_TRUE; - MsgQueueElem* msg; - XP_ASSERT( !!comms ); - for ( msg = comms->msgQueueHead; !!msg; msg = msg->next ) { - if ( 0 > sendMsg( comms, msg ) ) { - success = XP_FALSE; - break; + XP_U32 now = util_getCurSeconds( comms->util ); + if ( !force && (now < comms->nextResend) ) { + XP_LOGF( "%s: aborting: %ld seconds left in backoff", __func__, + comms->nextResend - now ); + success = XP_FALSE; + } else { + MsgQueueElem* msg; + + + for ( msg = comms->msgQueueHead; !!msg; msg = msg->next ) { + if ( 0 > sendMsg( comms, msg ) ) { + success = XP_FALSE; + break; + } + } + + /* Now set resend values */ + if ( success && !force ) { + comms->resendBackoff = 2 * (1 + comms->resendBackoff); + XP_LOGF( "%s: backoff now %d", __func__, comms->resendBackoff ); + comms->nextResend = now + comms->resendBackoff; } } return success; @@ -1391,7 +1423,7 @@ relayPreProcess( CommsCtxt* comms, XWStreamCtxt* stream, XWHostID* senderID ) break; case XWRELAY_RECONNECT_RESP: got_connect_cmd( comms, stream, XP_TRUE ); - comms_resendAll( comms ); + comms_resendAll( comms, XP_FALSE ); break; case XWRELAY_ALLHERE: @@ -1429,7 +1461,7 @@ relayPreProcess( CommsCtxt* comms, XWStreamCtxt* stream, XWHostID* senderID ) on RECONNECTED, so removing the test for now to fix recon problems on android. */ /* if ( COMMS_RELAYSTATE_RECONNECTED != comms->r.relayState ) { */ - comms_resendAll( comms ); + comms_resendAll( comms, XP_FALSE ); /* } */ if ( XWRELAY_ALLHERE == cmd ) { /* initial connect? */ (*comms->procs.rconnd)( comms->procs.closure, @@ -1538,7 +1570,7 @@ btIpPreProcess( CommsCtxt* comms, XWStreamCtxt* stream ) if ( consumed ) { /* This is all there is so far */ if ( typ == BTIPMSG_RESET ) { - (void)comms_resendAll( comms ); + (void)comms_resendAll( comms, XP_FALSE ); } else if ( typ == BTIPMSG_HB ) { /* noteHBReceived( comms, addr ); */ } else { @@ -1805,6 +1837,7 @@ comms_checkIncomingStream( CommsCtxt* comms, XWStreamCtxt* stream, comms->lastSaveToken = 0; /* lastMsgRcd no longer valid */ stream_setAddress( stream, channelNo ); messageValid = payloadSize > 0; + resetBackoff( comms ); } } else { XP_LOGF( "%s: message too small", __func__ ); diff --git a/xwords4/common/comms.h b/xwords4/common/comms.h index 7907be490..04234c995 100644 --- a/xwords4/common/comms.h +++ b/xwords4/common/comms.h @@ -203,7 +203,7 @@ void comms_writeToStream( CommsCtxt* comms, XWStreamCtxt* stream, void comms_saveSucceeded( CommsCtxt* comms, XP_U16 saveToken ); XP_S16 comms_send( CommsCtxt* comms, XWStreamCtxt* stream ); -XP_Bool comms_resendAll( CommsCtxt* comms ); +XP_Bool comms_resendAll( CommsCtxt* comms, XP_Bool force ); XP_U16 comms_getChannelSeed( CommsCtxt* comms ); #ifdef XWFEATURE_COMMSACK diff --git a/xwords4/common/comtypes.h b/xwords4/common/comtypes.h index b806fe124..805694f1e 100644 --- a/xwords4/common/comtypes.h +++ b/xwords4/common/comtypes.h @@ -47,6 +47,7 @@ #endif #define MAX_COLS MAX_ROWS +#define STREAM_VERS_COMMSBACKOFF 0x16 #define STREAM_VERS_DICTNAME 0x15 #ifdef HASH_STREAM # define STREAM_VERS_HASHSTREAM 0x14 @@ -82,7 +83,7 @@ #define STREAM_VERS_41B4 0x02 #define STREAM_VERS_405 0x01 -#define CUR_STREAM_VERS STREAM_VERS_DICTNAME +#define CUR_STREAM_VERS STREAM_VERS_COMMSBACKOFF typedef struct XP_Rect { XP_S16 left; diff --git a/xwords4/linux/cursesmain.c b/xwords4/linux/cursesmain.c index 1cebe705d..bd442f884 100644 --- a/xwords4/linux/cursesmain.c +++ b/xwords4/linux/cursesmain.c @@ -580,7 +580,7 @@ static XP_Bool handleResend( CursesAppGlobals* globals ) { if ( !!globals->cGlobals.game.comms ) { - comms_resendAll( globals->cGlobals.game.comms ); + comms_resendAll( globals->cGlobals.game.comms, XP_TRUE ); } return XP_TRUE; } From 9598ea3585d44e74a5bbf94fcde25b477f2ba7d5 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 13 Nov 2012 21:32:42 -0800 Subject: [PATCH 026/146] fix to compile post API change. --- xwords4/linux/gtkmain.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index 6b7278f50..af2ee6cbc 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -921,7 +921,7 @@ handle_resend( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) { CommsCtxt* comms = globals->cGlobals.game.comms; if ( comms != NULL ) { - comms_resendAll( comms ); + comms_resendAll( comms, XP_TRUE ); } } /* handle_resend */ @@ -2199,7 +2199,7 @@ gtk_socket_changed( void* closure, int oldSock, int newSock, void** storage ) /* A hack for the bluetooth case. */ CommsCtxt* comms = globals->cGlobals.game.comms; if ( (comms != NULL) && (comms_getConType(comms) == COMMS_CONN_BT) ) { - comms_resendAll( comms ); + comms_resendAll( comms, XP_FALSE ); } LOG_RETURN_VOID(); } /* gtk_socket_changed */ From 546353526553873f9093ba838e74df181ce03be0 Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 14 Nov 2012 05:42:47 -0800 Subject: [PATCH 027/146] don't increase backoff if there wasn't actually anything to send. --- xwords4/common/comms.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xwords4/common/comms.c b/xwords4/common/comms.c index e43c0c191..4e7bcb387 100644 --- a/xwords4/common/comms.c +++ b/xwords4/common/comms.c @@ -1263,9 +1263,9 @@ comms_resendAll( CommsCtxt* comms, XP_Bool force ) XP_LOGF( "%s: aborting: %ld seconds left in backoff", __func__, comms->nextResend - now ); success = XP_FALSE; - } else { - MsgQueueElem* msg; + } else if ( !!comms->msgQueueHead ) { + MsgQueueElem* msg; for ( msg = comms->msgQueueHead; !!msg; msg = msg->next ) { if ( 0 > sendMsg( comms, msg ) ) { From c847ec127edee41b62254a615bac0b09b91ea562 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 15 Nov 2012 06:15:44 -0800 Subject: [PATCH 028/146] add and use function that does formatted printing to a std::string so query buffers can no longer overflow. --- xwords4/relay/dbmgr.cpp | 247 ++++++++++++++++++++++------------------ xwords4/relay/dbmgr.h | 1 + 2 files changed, 137 insertions(+), 111 deletions(-) diff --git a/xwords4/relay/dbmgr.cpp b/xwords4/relay/dbmgr.cpp index 0d6e02531..934cf4c0f 100644 --- a/xwords4/relay/dbmgr.cpp +++ b/xwords4/relay/dbmgr.cpp @@ -47,6 +47,7 @@ static void formatParams( char* paramValues[], int nParams, const char* fmt, static int here_less_seed( const char* seeds, int perDeviceSum, unsigned short seed ); static void destr_function( void* conn ); +static void string_printf( string& str, const char* fmt, ... ); /* static */ DBMgr* DBMgr::Get() @@ -127,11 +128,11 @@ DBMgr::FindGame( const char* connName, char* cookieBuf, int bufLen, const char* fmt = "SELECT cid, room, lang, nTotal, nPerDevice, dead FROM " GAMES_TABLE " WHERE connName = '%s'" " LIMIT 1"; - char query[256]; - snprintf( query, sizeof(query), fmt, connName ); - logf( XW_LOGINFO, "query: %s", query ); + string query; + string_printf( query, fmt, connName ); + logf( XW_LOGINFO, "query: %s", query.c_str() ); - PGresult* result = PQexec( getThreadConn(), query ); + PGresult* result = PQexec( getThreadConn(), query.c_str() ); if ( 1 == PQntuples( result ) ) { cid = atoi( PQgetvalue( result, 0, 0 ) ); snprintf( cookieBuf, bufLen, "%s", PQgetvalue( result, 0, 1 ) ); @@ -234,11 +235,11 @@ DBMgr::AllDevsAckd( const char* const connName ) { const char* cmd = "SELECT ntotal=sum_array(nperdevice) AND 'A'=ALL(ack) from " GAMES_TABLE " WHERE connName='%s'"; - char query[256]; - snprintf( query, sizeof(query), cmd, connName ); - logf( XW_LOGINFO, "query: %s", query ); + string query; + string_printf( query, cmd, connName ); + logf( XW_LOGINFO, "query: %s", query.c_str() ); - PGresult* result = PQexec( getThreadConn(), query ); + PGresult* result = PQexec( getThreadConn(), query.c_str() ); int nTuples = PQntuples( result ); assert( nTuples <= 1 ); bool full = nTuples == 1 && 't' == PQgetvalue( result, 0, 0 )[0]; @@ -317,10 +318,11 @@ DBMgr::AddDevice( const char* connName, HostID curID, int clientVersion, } assert( newID <= 4 ); - char devIDBuf[512] = {0}; + string devIDBuf; if ( DEVID_NONE != devID ) { - snprintf( devIDBuf, sizeof(devIDBuf), - "devids[%d] = %d, ", newID, devID ); + string_printf( devIDBuf, "devids[%d] = %d, ", newID, devID ); + } else { + assert( 0 == strlen(devIDBuf.c_str()) ); } const char* fmt = "UPDATE " GAMES_TABLE " SET nPerDevice[%d] = %d," @@ -328,11 +330,11 @@ DBMgr::AddDevice( const char* connName, HostID curID, int clientVersion, " seeds[%d] = %d, addrs[%d] = \'%s\', %s" " mtimes[%d]='now', ack[%d]=\'%c\'" " WHERE connName = '%s'"; - char query[1024]; - snprintf( query, sizeof(query), fmt, newID, nToAdd, newID, clientVersion, - newID, seed, newID, inet_ntoa(addr), devIDBuf, - newID, newID, ackd?'A':'a', connName ); - logf( XW_LOGINFO, "%s: query: %s", __func__, query ); + string query; + string_printf( query, fmt, newID, nToAdd, newID, clientVersion, + newID, seed, newID, inet_ntoa(addr), devIDBuf.c_str(), + newID, newID, ackd?'A':'a', connName ); + logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() ); execSql( query ); @@ -342,11 +344,11 @@ DBMgr::AddDevice( const char* connName, HostID curID, int clientVersion, void DBMgr::NoteAckd( const char* const connName, HostID id ) { - char query[256]; const char* fmt = "UPDATE " GAMES_TABLE " SET ack[%d]='A'" " WHERE connName = '%s'"; - snprintf( query, sizeof(query), fmt, id, connName ); - logf( XW_LOGINFO, "%s: query: %s", __func__, query ); + string query; + string_printf( query, fmt, id, connName ); + logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() ); execSql( query ); } @@ -356,9 +358,9 @@ DBMgr::RmDeviceByHid( const char* connName, HostID hid ) { const char* fmt = "UPDATE " GAMES_TABLE " SET nPerDevice[%d] = 0, " "seeds[%d] = 0, ack[%d]='-', mtimes[%d]='now' WHERE connName = '%s'"; - char query[256]; - snprintf( query, sizeof(query), fmt, hid, hid, hid, hid, connName ); - logf( XW_LOGINFO, "%s: query: %s", __func__, query ); + string query; + string_printf( query, fmt, hid, hid, hid, hid, connName ); + logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() ); return execSql( query ); } @@ -371,10 +373,10 @@ DBMgr::HIDForSeed( const char* const connName, unsigned short seed ) const char* fmt = "SELECT seeds FROM " GAMES_TABLE " WHERE connName = '%s'" " AND %d = ANY(seeds)"; - char query[256]; - snprintf( query, sizeof(query), fmt, connName, seed ); - logf( XW_LOGINFO, "%s: query: %s", __func__, query ); - PGresult* result = PQexec( getThreadConn(), query ); + string query; + string_printf( query, fmt, connName, seed ); + logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() ); + PGresult* result = PQexec( getThreadConn(), query.c_str() ); if ( 1 == PQntuples( result ) ) { snprintf( seeds, sizeof(seeds), "%s", PQgetvalue( result, 0, 0 ) ); } @@ -418,10 +420,10 @@ DBMgr::HaveDevice( const char* connName, HostID hid, int seed ) bool found = false; const char* fmt = "SELECT * from " GAMES_TABLE " WHERE connName = '%s' AND seeds[%d] = %d"; - char query[256]; - snprintf( query, sizeof(query), fmt, connName, hid, seed ); - logf( XW_LOGINFO, "%s: query: %s", __func__, query ); - PGresult* result = PQexec( getThreadConn(), query ); + string query; + string_printf( query, fmt, connName, hid, seed ); + logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() ); + PGresult* result = PQexec( getThreadConn(), query.c_str() ); found = 1 == PQntuples( result ); PQclear( result ); return found; @@ -432,9 +434,9 @@ DBMgr::AddCID( const char* const connName, CookieID cid ) { const char* fmt = "UPDATE " GAMES_TABLE " SET cid = %d " " WHERE connName = '%s' AND cid IS NULL"; - char query[256]; - snprintf( query, sizeof(query), fmt, cid, connName ); - logf( XW_LOGINFO, "%s: query: %s", __func__, query ); + string query; + string_printf( query, fmt, cid, connName ); + logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() ); bool result = execSql( query ); logf( XW_LOGINFO, "%s(cid=%d)=>%d", __func__, cid, result ); @@ -446,9 +448,9 @@ DBMgr::ClearCID( const char* connName ) { const char* fmt = "UPDATE " GAMES_TABLE " SET cid = null " "WHERE connName = '%s'"; - char query[256]; - snprintf( query, sizeof(query), fmt, connName ); - logf( XW_LOGINFO, "%s: query: %s", __func__, query ); + string query; + string_printf( query, fmt, connName ); + logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() ); execSql( query ); } @@ -460,9 +462,9 @@ DBMgr::RecordSent( const char* const connName, HostID hid, int nBytes ) const char* fmt = "UPDATE " GAMES_TABLE " SET" " nsent = nsent + %d, mtimes[%d] = 'now'" " WHERE connName = '%s'"; - char query[256]; - snprintf( query, sizeof(query), fmt, nBytes, hid, connName ); - logf( XW_LOGINFO, "%s: query: %s", __func__, query ); + string query; + string_printf( query, fmt, nBytes, hid, connName ); + logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() ); execSql( query ); } @@ -471,23 +473,19 @@ void DBMgr::RecordSent( const int* msgIDs, int nMsgIDs ) { if ( nMsgIDs > 0 ) { - char buf[1024]; - unsigned int offset = 0; - offset = snprintf( buf, sizeof(buf), "SELECT connname,hid,sum(msglen)" - " FROM " MSGS_TABLE " WHERE id IN (" ); + string query( "SELECT connname,hid,sum(msglen)" + " FROM " MSGS_TABLE " WHERE id IN (" ); for ( int ii = 0; ; ) { - offset += snprintf( &buf[offset], sizeof(buf) - offset, "%d,", - msgIDs[ii] ); - assert( offset < sizeof(buf) ); + string_printf( query, "%d", msgIDs[ii] ); if ( ++ii == nMsgIDs ) { - --offset; /* back over comma */ break; + } else { + query.append( "," ); } } - offset += snprintf( &buf[offset], sizeof(buf) - offset, - ") GROUP BY connname,hid" ); + query.append( ") GROUP BY connname,hid" ); - PGresult* result = PQexec( getThreadConn(), buf ); + PGresult* result = PQexec( getThreadConn(), query.c_str() ); if ( PGRES_TUPLES_OK == PQresultStatus( result ) ) { int ntuples = PQntuples( result ); for ( int ii = 0; ii < ntuples; ++ii ) { @@ -507,9 +505,9 @@ DBMgr::RecordAddress( const char* const connName, HostID hid, assert( hid >= 0 && hid <= 4 ); const char* fmt = "UPDATE " GAMES_TABLE " SET addrs[%d] = \'%s\'" " WHERE connName = '%s'"; - char query[256]; - snprintf( query, sizeof(query), fmt, hid, inet_ntoa(addr), connName ); - logf( XW_LOGINFO, "%s: query: %s", __func__, query ); + string query; + string_printf( query, fmt, hid, inet_ntoa(addr), connName ); + logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() ); execSql( query ); } @@ -519,11 +517,11 @@ DBMgr::GetPlayerCounts( const char* const connName, int* nTotal, int* nHere ) { const char* fmt = "SELECT ntotal, sum_array(nperdevice) FROM " GAMES_TABLE " WHERE connName = '%s'"; - char query[256]; - snprintf( query, sizeof(query), fmt, connName ); - logf( XW_LOGINFO, "%s: query: %s", __func__, query ); + string query; + string_printf( query, fmt, connName ); + logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() ); - PGresult* result = PQexec( getThreadConn(), query ); + PGresult* result = PQexec( getThreadConn(), query.c_str() ); assert( 1 == PQntuples( result ) ); *nTotal = atoi( PQgetvalue( result, 0, 0 ) ); *nHere = atoi( PQgetvalue( result, 0, 1 ) ); @@ -533,11 +531,11 @@ DBMgr::GetPlayerCounts( const char* const connName, int* nTotal, int* nHere ) void DBMgr::KillGame( const char* const connName, int hid ) { - const char* fmt = "UPDATE " GAMES_TABLE " SET dead = TRUE," - " nperdevice[%d] = - nperdevice[%d]" - " WHERE connName = '%s'"; - char query[256]; - snprintf( query, sizeof(query), fmt, hid, hid, connName ); + const char* fmt = "UPDATE " GAMES_TABLE " SET dead = TRUE," + " nperdevice[%d] = - nperdevice[%d]" + " WHERE connName = '%s'"; + string query; + string_printf( query, fmt, hid, hid, connName ); execSql( query ); } @@ -559,11 +557,11 @@ DBMgr::PublicRooms( int lang, int nPlayers, int* nNames, string& names ) " AND nTotal>sum_array(nPerDevice)" " AND nTotal = %d"; - char query[256]; - snprintf( query, sizeof(query), fmt, lang, nPlayers ); - logf( XW_LOGINFO, "%s: query: %s", __func__, query ); + string query; + string_printf( query, fmt, lang, nPlayers ); + logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() ); - PGresult* result = PQexec( getThreadConn(), query ); + PGresult* result = PQexec( getThreadConn(), query.c_str() ); int nTuples = PQntuples( result ); for ( int ii = 0; ii < nTuples; ++ii ) { names.append( PQgetvalue( result, ii, 0 ) ); @@ -582,12 +580,12 @@ DBMgr::PendingMsgCount( const char* connName, int hid ) { int count = 0; const char* fmt = "SELECT COUNT(*) FROM " MSGS_TABLE - " WHERE connName = '%s' AND hid = %d"; - char query[256]; - snprintf( query, sizeof(query), fmt, connName, hid ); - logf( XW_LOGINFO, "%s: query: %s", __func__, query ); + " WHERE connName = '%s' AND hid = %d "; + string query; + string_printf( query, fmt, connName, hid ); + logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() ); - PGresult* result = PQexec( getThreadConn(), query ); + PGresult* result = PQexec( getThreadConn(), query.c_str() ); if ( 1 == PQntuples( result ) ) { count = atoi( PQgetvalue( result, 0, 0 ) ); } @@ -595,6 +593,12 @@ DBMgr::PendingMsgCount( const char* connName, int hid ) return count; } +bool +DBMgr::execSql( const string& query ) +{ + return execSql( query.c_str() ); +} + bool DBMgr::execSql( const char* const query ) { @@ -612,11 +616,11 @@ DBMgr::readArray( const char* const connName, int arr[] ) /* len 4 */ { const char* fmt = "SELECT nPerDevice FROM " GAMES_TABLE " WHERE connName='%s'"; - char query[256]; - snprintf( query, sizeof(query), fmt, connName ); - logf( XW_LOGINFO, "%s: query: %s", __func__, query ); + string query; + string_printf( query, fmt, connName ); + logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() ); - PGresult* result = PQexec( getThreadConn(), query ); + PGresult* result = PQexec( getThreadConn(), query.c_str() ); assert( 1 == PQntuples( result ) ); const char* arrStr = PQgetvalue( result, 0, 0 ); sscanf( arrStr, "{%d,%d,%d,%d}", &arr[0], &arr[1], &arr[2], &arr[3] ); @@ -628,11 +632,11 @@ DBMgr::getDevID( const char* connName, int hid ) { DBMgr::DevIDRelay devID; const char* fmt = "SELECT devids[%d] FROM " GAMES_TABLE " WHERE connName='%s'"; - char query[256]; - snprintf( query, sizeof(query), fmt, hid, connName ); - logf( XW_LOGINFO, "%s: query: %s", __func__, query ); + string query; + string_printf( query, fmt, hid, connName ); + logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() ); - PGresult* result = PQexec( getThreadConn(), query ); + PGresult* result = PQexec( getThreadConn(), query.c_str() ); assert( 1 == PQntuples( result ) ); devID = (DBMgr::DevIDRelay)strtoul( PQgetvalue( result, 0, 0 ), NULL, 10 ); PQclear( result ); @@ -644,7 +648,7 @@ DBMgr::getDevID( const DevID* devID ) { DBMgr::DevIDRelay rDevID = DEVID_NONE; DevIDType devIDType = devID->m_devIDType; - char query[512] = {0}; + string query; assert( ID_TYPE_NONE < devIDType ); const char* asStr = devID->m_devIDString.c_str(); if ( ID_TYPE_RELAY == devIDType ) { @@ -652,16 +656,16 @@ DBMgr::getDevID( const DevID* devID ) DBMgr::DevIDRelay cur = strtoul( asStr, NULL, 16 ); if ( DEVID_NONE != cur ) { const char* fmt = "SELECT id FROM " DEVICES_TABLE " WHERE id=%d"; - snprintf( query, sizeof(query), fmt, cur ); + string_printf( query, fmt, cur ); } } else { const char* fmt = "SELECT id FROM " DEVICES_TABLE " WHERE devtype=%d and devid = '%s'"; - snprintf( query, sizeof(query), fmt, devIDType, asStr ); + string_printf( query, fmt, devIDType, asStr ); } - if ( '\0' != query[0] ) { - logf( XW_LOGINFO, "%s: query: %s", __func__, query ); - PGresult* result = PQexec( getThreadConn(), query ); + if ( 0 < query.size() ) { + logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() ); + PGresult* result = PQexec( getThreadConn(), query.c_str() ); assert( 1 >= PQntuples( result ) ); if ( 1 == PQntuples( result ) ) { rDevID = (DBMgr::DevIDRelay)strtoul( PQgetvalue( result, 0, 0 ), NULL, 10 ); @@ -685,16 +689,14 @@ DBMgr::CountStoredMessages( const char* const connName, int hid ) const char* fmt = "SELECT count(*) FROM " MSGS_TABLE " WHERE connname = '%s' "; - char query[256]; - int len = snprintf( query, sizeof(query), fmt, connName ); - logf( XW_LOGINFO, "%s: query: %s", __func__, query ); + string query; + string_printf( query, fmt, connName ); if ( hid != -1 ) { - snprintf( &query[len], sizeof(query)-len, "AND hid = %d", - hid ); + string_printf( query, "AND hid = %d", hid ); } - PGresult* result = PQexec( getThreadConn(), query ); + PGresult* result = PQexec( getThreadConn(), query.c_str() ); assert( 1 == PQntuples( result ) ); int count = atoi( PQgetvalue( result, 0, 0 ) ); PQclear( result ); @@ -722,18 +724,13 @@ DBMgr::StoreMessage( const char* const connName, int hid, len, &newLen ); assert( NULL != bytes ); - char query[1024]; - size_t siz = snprintf( query, sizeof(query), fmt, connName, hid, - devID, bytes, len ); + string query; + string_printf( query, fmt, connName, hid, devID, bytes, len ); PQfreemem( bytes ); - if ( siz < sizeof(query) ) { - logf( XW_LOGINFO, "%s: query: %s", __func__, query ); - execSql( query ); - } else { - logf( XW_LOGERROR, "%s: buffer too small", __func__ ); - } + logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() ); + execSql( query ); } bool @@ -742,12 +739,13 @@ DBMgr::GetNthStoredMessage( const char* const connName, int hid, int* msgID ) { const char* fmt = "SELECT id, msg, msglen FROM " MSGS_TABLE - " WHERE connName = '%s' AND hid = %d ORDER BY id LIMIT 1 OFFSET %d"; - char query[256]; - snprintf( query, sizeof(query), fmt, connName, hid, nn ); - logf( XW_LOGINFO, "%s: query: %s", __func__, query ); + " WHERE connName = '%s' AND hid = %d " + "ORDER BY id LIMIT 1 OFFSET %d"; + string query; + string_printf( query, fmt, connName, hid, nn ); + logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() ); - PGresult* result = PQexec( getThreadConn(), query ); + PGresult* result = PQexec( getThreadConn(), query.c_str() ); int nTuples = PQntuples( result ); assert( nTuples <= 1 ); @@ -784,22 +782,23 @@ void DBMgr::RemoveStoredMessages( const int* msgIDs, int nMsgIDs ) { if ( nMsgIDs > 0 ) { - char ids[1024]; + string ids; size_t len = 0; int ii; for ( ii = 0; ; ) { - len += snprintf( ids + len, sizeof(ids) - len, "%d,", msgIDs[ii] ); + string_printf( ids, "%d", msgIDs[ii] ); assert( len < sizeof(ids) ); if ( ++ii == nMsgIDs ) { - ids[len-1] = '\0'; /* overwrite last comma */ break; + } else { + ids.append( "," ); } } const char* fmt = "DELETE from " MSGS_TABLE " WHERE id in (%s)"; - char query[1024]; - snprintf( query, sizeof(query), fmt, ids ); - logf( XW_LOGINFO, "%s: query: %s", __func__, query ); + string query; + string_printf( query, fmt, ids.c_str() ); + logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() ); execSql( query ); } } @@ -862,3 +861,29 @@ DBMgr::getThreadConn( void ) } return conn; } + +/* From stack overflow, toward a snprintf with an expanding buffer. + */ +static void +string_printf( string& str, const char* fmt, ... ) +{ + const int origsiz = str.size(); + int newsiz = 100; + va_list ap; + for ( ; ; ) { + str.resize( origsiz + newsiz ); + + va_start( ap, fmt ); + int len = vsnprintf( (char *)str.c_str() + origsiz, newsiz, fmt, ap ); + va_end( ap ); + + if ( len > newsiz ) { // needs more space + newsiz = len + 1; + } else if ( -1 == len ) { + assert(0); // should be impossible + } else { + str.resize( origsiz + len ); + break; + } + } +} diff --git a/xwords4/relay/dbmgr.h b/xwords4/relay/dbmgr.h index 46af13f02..bc4918dc7 100644 --- a/xwords4/relay/dbmgr.h +++ b/xwords4/relay/dbmgr.h @@ -105,6 +105,7 @@ class DBMgr { private: DBMgr(); + bool execSql( const string& query ); bool execSql( const char* const query ); /* no-results query */ void readArray( const char* const connName, int arr[] ); DevIDRelay getDevID( const char* connName, int hid ); From 4dbc76006c3b0fedce233a06c399ef45e705ce81 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 15 Nov 2012 06:16:03 -0800 Subject: [PATCH 029/146] ignore python shelf file --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e4fc03066..444620918 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ TAGS core *.apk xwords_4.4.0.0* +gcm_loop.shelf From 694857dd4e2a1aba43cd004acfa1dc2652a64a33 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 15 Nov 2012 06:25:22 -0800 Subject: [PATCH 030/146] add, but disable with compile flags, ability to mark sent messages with a timestamp rather than delete them. Plan is to experiement with this as a way of keeping devices from sending too many identical messages when GCM's in use -- since constraints on the msgs table will not allow identical messages to be added. --- xwords4/relay/Makefile | 1 + xwords4/relay/dbmgr.cpp | 23 ++++++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/xwords4/relay/Makefile b/xwords4/relay/Makefile index 0c5b4e18e..0e03488fe 100644 --- a/xwords4/relay/Makefile +++ b/xwords4/relay/Makefile @@ -45,6 +45,7 @@ CPPFLAGS += -DSPAWN_SELF -g -Wall \ -I $(shell pg_config --includedir) \ -DSVN_REV=\"$(shell cat $(GITINFO) 2>/dev/null || echo -n $(HASH) )\" # CPPFLAGS += -DDO_HTTP +# CPPFLAGS += -DHAVE_SENDTIME # turn on semaphore debugging # CPPFLAGS += -DDEBUG_LOCKS diff --git a/xwords4/relay/dbmgr.cpp b/xwords4/relay/dbmgr.cpp index 934cf4c0f..44401410c 100644 --- a/xwords4/relay/dbmgr.cpp +++ b/xwords4/relay/dbmgr.cpp @@ -580,7 +580,11 @@ DBMgr::PendingMsgCount( const char* connName, int hid ) { int count = 0; const char* fmt = "SELECT COUNT(*) FROM " MSGS_TABLE - " WHERE connName = '%s' AND hid = %d "; + " WHERE connName = '%s' AND hid = %d " +#ifdef HAVE_SENDTIME + "AND sendtime IS NULL" +#endif + ; string query; string_printf( query, fmt, connName, hid ); logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() ); @@ -687,7 +691,11 @@ int DBMgr::CountStoredMessages( const char* const connName, int hid ) { const char* fmt = "SELECT count(*) FROM " MSGS_TABLE - " WHERE connname = '%s' "; + " WHERE connname = '%s' " +#ifdef HAVE_SENDTIME + "AND sendtime IS NULL" +#endif + ; string query; string_printf( query, fmt, connName ); @@ -740,6 +748,9 @@ DBMgr::GetNthStoredMessage( const char* const connName, int hid, { const char* fmt = "SELECT id, msg, msglen FROM " MSGS_TABLE " WHERE connName = '%s' AND hid = %d " +#ifdef HAVE_SENDTIME + "AND sendtime IS NULL " +#endif "ORDER BY id LIMIT 1 OFFSET %d"; string query; string_printf( query, fmt, connName, hid, nn ); @@ -795,7 +806,13 @@ DBMgr::RemoveStoredMessages( const int* msgIDs, int nMsgIDs ) } } - const char* fmt = "DELETE from " MSGS_TABLE " WHERE id in (%s)"; + const char* fmt = +#ifdef HAVE_SENDTIME + "UPDATE " MSGS_TABLE " SET sendtime='now' " +#else + "DELETE FROM " MSGS_TABLE +#endif + " WHERE id IN (%s)"; string query; string_printf( query, fmt, ids.c_str() ); logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() ); From 5afd93c5f5dcb6b602a9b43a7bcfeb32c64fdbc2 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 15 Nov 2012 06:42:27 -0800 Subject: [PATCH 031/146] fix NPE on Kindle --- .../src/org/eehouse/android/xw4/UpdateCheckReceiver.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/UpdateCheckReceiver.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/UpdateCheckReceiver.java index a87f77537..6334230e4 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/UpdateCheckReceiver.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/UpdateCheckReceiver.java @@ -155,7 +155,10 @@ public class UpdateCheckReceiver extends BroadcastReceiver { if ( 0 < params.length() ) { HttpPost post = makePost( context, "getUpdates" ); String json = runPost( post, params ); - makeNotificationsIf( context, fromUI, json, pm, packageName, dals ); + if ( null != json ) { + makeNotificationsIf( context, fromUI, json, pm, packageName, + dals ); + } } } From f64de16ce395c9e5e7b5a36b8a936e7f81b252b8 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 15 Nov 2012 07:03:32 -0800 Subject: [PATCH 032/146] catch and discard all exceptions that come when initing GCM. Fringe devices may do anthing at that point. --- .../XWords4/src/org/eehouse/android/xw4/GCMIntentService.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java index 8a37f9698..9b3a80e9e 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java @@ -84,6 +84,9 @@ public class GCMIntentService extends GCMBaseIntentService { } } catch ( UnsupportedOperationException uoe ) { DbgUtils.logf( "Device can't do GCM." ); + } catch ( Exception whatever ) { + // funky devices could do anything + DbgUtils.loge( whatever ); } } } From bda193029ef6601c8775d9638ee9027b6fff2f3d Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 15 Nov 2012 21:14:45 -0800 Subject: [PATCH 033/146] add constructor in order to pass SENDER_ID to parent constructor, as docs say to do --- .../src/org/eehouse/android/xw4/GCMIntentService.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java index 9b3a80e9e..9bfeca058 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java @@ -28,6 +28,11 @@ import com.google.android.gcm.GCMRegistrar; public class GCMIntentService extends GCMBaseIntentService { + public GCMIntentService() + { + super( GCMConsts.SENDER_ID ); + } + @Override protected void onError( Context context, String error ) { From e1bc9876dd810640664b92844c77c14fac90e3f6 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 15 Nov 2012 21:23:31 -0800 Subject: [PATCH 034/146] add to-do --- xwords4/android/XWords4/res/raw/changes | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/xwords4/android/XWords4/res/raw/changes b/xwords4/android/XWords4/res/raw/changes index 8eba69951..1dd978cad 100644 --- a/xwords4/android/XWords4/res/raw/changes +++ b/xwords4/android/XWords4/res/raw/changes @@ -6,7 +6,7 @@ Crosswords 4.4 beta 55 release -
    +
      New with this release
    • Use "Google Cloud Messaging" for much faster relay move notifications
    • @@ -15,6 +15,11 @@
    +
      Next up +
    • Make invitations more reliable
    • +
    • Upgrade app without involving the browser
    • +
    +

    (The full changelog is here.)

    From f70a5bc6ee154bb1f890133f0aeb8a2a75d6c7cf Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 15 Nov 2012 21:24:54 -0800 Subject: [PATCH 035/146] add getAppVersion() --- .../src/org/eehouse/android/xw4/Utils.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java index 6fd3d0afd..02824fdbf 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java @@ -403,17 +403,24 @@ public class Utils { return dict_url; } - private static void setFirstBootStatics( Context context ) + public static int getAppVersion( Context context ) { - int thisVersion = 0; - int prevVersion = 0; + int version; try { - thisVersion = context.getPackageManager() + version = context.getPackageManager() .getPackageInfo(context.getPackageName(), 0) .versionCode; } catch ( Exception e ) { + version = 0; } + return version; + } + + private static void setFirstBootStatics( Context context ) + { + int thisVersion = getAppVersion( context ); + int prevVersion = 0; SharedPreferences prefs = null; if ( 0 < thisVersion ) { From 9a4a8c7b2613c73a228312ef79519cb38734267b Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 15 Nov 2012 21:41:51 -0800 Subject: [PATCH 036/146] use static to avoid querying OS for version over and over. --- .../src/org/eehouse/android/xw4/Utils.java | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java index 02824fdbf..9ef369196 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java @@ -60,6 +60,7 @@ public class Utils { private static Boolean s_isFirstBootThisVersion = null; private static Boolean s_deviceSupportSMS = null; private static Boolean s_isFirstBootEver = null; + private static Integer s_appVersion = null; private static HashMap s_phonesHash = new HashMap(); private static int s_nextCode = 0; // keep PendingIntents unique @@ -405,16 +406,17 @@ public class Utils { public static int getAppVersion( Context context ) { - int version; - - try { - version = context.getPackageManager() - .getPackageInfo(context.getPackageName(), 0) - .versionCode; - } catch ( Exception e ) { - version = 0; + if ( null == s_appVersion ) { + try { + int version = context.getPackageManager() + .getPackageInfo(context.getPackageName(), 0) + .versionCode; + s_appVersion = new Integer( version ); + } catch ( Exception e ) { + DbgUtils.loge( e ); + } } - return version; + return null == s_appVersion? 0 : s_appVersion; } private static void setFirstBootStatics( Context context ) From cefc13a2cdf813843fcde7d9802da55ad90aacc9 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 15 Nov 2012 21:44:56 -0800 Subject: [PATCH 037/146] Docs say should allow GCM to reregister on app upgrade. And that the registrar takes care of storing devid. So app version when registration arrives, and get from registrar except after an upgrade. --- .../XWords4/res/values/common_rsrc.xml | 2 +- .../eehouse/android/xw4/GCMIntentService.java | 7 +--- .../src/org/eehouse/android/xw4/XWPrefs.java | 33 ++++++++++++++++--- .../eehouse/android/xw4/jni/UtilCtxtImpl.java | 4 ++- 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/xwords4/android/XWords4/res/values/common_rsrc.xml b/xwords4/android/XWords4/res/values/common_rsrc.xml index e09033bba..e354646b6 100644 --- a/xwords4/android/XWords4/res/values/common_rsrc.xml +++ b/xwords4/android/XWords4/res/values/common_rsrc.xml @@ -68,7 +68,7 @@ key_sms_phones key_connstat_data key_dev_id - key_gcm_regid + key_gcmvers_regid key_relay_regid key_checked_sms diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java index 9bfeca058..2ab40f4bc 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java @@ -78,15 +78,10 @@ public class GCMIntentService extends GCMBaseIntentService { try { GCMRegistrar.checkDevice( app ); // GCMRegistrar.checkManifest( app ); - final String regId = GCMRegistrar.getRegistrationId( app ); + String regId = XWPrefs.getGCMDevID( app ); if (regId.equals("")) { GCMRegistrar.register( app, GCMConsts.SENDER_ID ); } - - String curID = XWPrefs.getGCMDevID( app ); - if ( null == curID || ! curID.equals( regId ) ) { - XWPrefs.setGCMDevID( app, regId ); - } } catch ( UnsupportedOperationException uoe ) { DbgUtils.logf( "Device can't do GCM." ); } catch ( Exception whatever ) { diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java index 24ebbe8eb..4ac4561c0 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java @@ -24,6 +24,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.text.TextUtils; +import com.google.android.gcm.GCMRegistrar; import java.util.ArrayList; import java.util.ArrayList; @@ -112,6 +113,24 @@ public class XWPrefs { return result; } + public static int getPrefsInt( Context context, int keyID, int defaultValue ) + { + String key = context.getString( keyID ); + SharedPreferences sp = PreferenceManager + .getDefaultSharedPreferences( context ); + return sp.getInt( key, defaultValue ); + } + + public static void setPrefsInt( Context context, int keyID, int newValue ) + { + SharedPreferences sp = PreferenceManager + .getDefaultSharedPreferences( context ); + SharedPreferences.Editor editor = sp.edit(); + String key = context.getString( keyID ); + editor.putInt( key, newValue ); + editor.commit(); + } + public static boolean getPrefsBoolean( Context context, int keyID, boolean defaultValue ) { @@ -186,22 +205,26 @@ public class XWPrefs { public static void setGCMDevID( Context context, String devID ) { - setPrefsString( context, R.string.key_gcm_regid, devID ); + int curVers = Utils.getAppVersion( context ); + setPrefsInt( context, R.string.key_gcmvers_regid, curVers ); clearPrefsKey( context, R.string.key_relay_regid ); } public static String getGCMDevID( Context context ) { - String result = getPrefsString( context, R.string.key_gcm_regid ); - if ( result.equals("") ) { - result = null; + int curVers = Utils.getAppVersion( context ); + int storedVers = getPrefsInt( context, R.string.key_gcmvers_regid, 0 ); + String result; + if ( 0 != storedVers && storedVers < curVers ) { + result = ""; // Don't trust what registrar has + } else { + result = GCMRegistrar.getRegistrationId( context ); } return result; } public static void clearGCMDevID( Context context ) { - clearPrefsKey( context, R.string.key_gcm_regid ); clearRelayDevID( context ); } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxtImpl.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxtImpl.java index f39ad5e1a..b4ad0eb6a 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxtImpl.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxtImpl.java @@ -102,7 +102,9 @@ public class UtilCtxtImpl implements UtilCtxt { typ = UtilCtxt.ID_TYPE_RELAY; } else { result = XWPrefs.getGCMDevID( m_context ); - if ( null != result ) { + if ( result.equals("") ) { + result = null; + } else { typ = UtilCtxt.ID_TYPE_ANDROID_GCM; } } From 416db91312b32b1a4748414164842741bde7db0d Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 16 Nov 2012 07:31:52 -0800 Subject: [PATCH 038/146] experimental: always post notification on new moves received rather than giving GamesList a chance to launch game directly. --- .../XWords4/src/org/eehouse/android/xw4/RelayService.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java index 76fc3c1f8..a744ba8b9 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java @@ -112,9 +112,9 @@ public class RelayService extends Service { if ( 0 < idsWMsgs.size() ) { String[] relayIDs = new String[idsWMsgs.size()]; idsWMsgs.toArray( relayIDs ); - if ( !DispatchNotify.tryHandle( relayIDs ) ) { - setupNotification( relayIDs ); - } + // if ( !DispatchNotify.tryHandle( relayIDs ) ) { + setupNotification( relayIDs ); + // } } sink.send( this ); } From 9ad41d5a3a69b9ea0e9e626592850c3174c28c15 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 17 Nov 2012 10:14:55 -0800 Subject: [PATCH 039/146] use String[] data to build table-creation queries rather than huge inline string concatenations. --- .../src/org/eehouse/android/xw4/DBHelper.java | 147 +++++++++--------- 1 file changed, 72 insertions(+), 75 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java index 2b905c6b7..e8ab0960e 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java @@ -45,9 +45,6 @@ public class DBHelper extends SQLiteOpenHelper { public static final String IN_USE = "IN_USE"; public static final String SCORES = "SCORES"; public static final String CHAT_HISTORY = "CHAT_HISTORY"; - // GAMEID: this isn't used yet but we'll want it to look up games - // for which messages arrive. Add now while changing the DB - // format public static final String GAMEID = "GAMEID"; public static final String REMOTEDEVS = "REMOTEDEVS"; public static final String DICTLANG = "DICTLANG"; @@ -76,12 +73,63 @@ public class DBHelper extends SQLiteOpenHelper { public static final String ITERPOS = "ITERPOS"; public static final String ITERTOP = "ITERTOP"; public static final String ITERPREFIX = "ITERPREFIX"; - - // not used yet public static final String CREATE_TIME = "CREATE_TIME"; - // not used yet public static final String LASTPLAY_TIME = "LASTPLAY_TIME"; + private static final String[] s_summaryColsAndTypes = { + GAME_NAME, "TEXT" + ,NUM_MOVES, "INTEGER" + ,TURN, "INTEGER" + ,GIFLAGS, "INTEGER" + ,NUM_PLAYERS, "INTEGER" + ,MISSINGPLYRS,"INTEGER" + ,PLAYERS, "TEXT" + ,GAME_OVER, "INTEGER" + ,SERVERROLE, "INTEGER" + ,CONTYPE, "INTEGER" + ,ROOMNAME, "TEXT" + ,INVITEID, "TEXT" + ,RELAYID, "TEXT" + ,SEED, "INTEGER" + ,DICTLANG, "INTEGER" + ,DICTLIST, "TEXT" + ,SMSPHONE, "TEXT" + ,SCORES, "TEXT" + ,CHAT_HISTORY, "TEXT" + ,GAMEID, "INTEGER" + ,REMOTEDEVS, "TEXT" + ,LASTMOVE, "INTEGER DEFAULT 0" + // HASMSGS: sqlite doesn't have bool; use 0 and 1 + ,HASMSGS, "INTEGER DEFAULT 0" + ,CONTRACTED, "INTEGER DEFAULT 0" + ,CREATE_TIME, "INTEGER" + ,LASTPLAY_TIME,"INTEGER" + ,SNAPSHOT, "BLOB" + }; + + private static final String[] s_obitsColsAndTypes = { + RELAYID, "TEXT" + ,SEED, "INTEGER" + }; + + private static final String[] s_dictInfoColsAndTypes = { + DICTNAME, "TEXT" + ,LOC, "UNSIGNED INTEGER(1)" + ,MD5SUM, "TEXT(32)" + ,WORDCOUNT,"INTEGER" + ,LANGCODE, "INTEGER" + }; + + private static final String[] s_dictBrowseColsAndTypes = { + DICTNAME, "TEXT" + ,LOC, "UNSIGNED INTEGER(1)" + ,WORDCOUNTS, "TEXT" + ,ITERMIN, "INTEGRE(4)" + ,ITERMAX, "INTEGER(4)" + ,ITERPOS, "INTEGER" + ,ITERTOP, "INTEGER" + ,ITERPREFIX, "TEXT" + }; public DBHelper( Context context ) { @@ -93,81 +141,28 @@ public class DBHelper extends SQLiteOpenHelper { return DB_NAME; } - private void onCreateSum( SQLiteDatabase db ) + private void createTable( SQLiteDatabase db, String name, String[] data ) { - db.execSQL( "CREATE TABLE " + TABLE_NAME_SUM + " (" - + GAME_NAME + " TEXT," - + NUM_MOVES + " INTEGER," - + TURN + " INTEGER," - + GIFLAGS + " INTEGER," + StringBuilder query = + new StringBuilder( String.format("CREATE TABLE %s (", name ) ); - + NUM_PLAYERS + " INTEGER," - + MISSINGPLYRS + " INTEGER," - + PLAYERS + " TEXT," - + GAME_OVER + " INTEGER," + for ( int ii = 0; ii < data.length; ii += 2 ) { + String col = String.format( " %s %s,", data[ii], data[ii+1] ); + query.append( col ); + } + query.setLength(query.length() - 1); // nuke the last comma + query.append( ");" ); - + SERVERROLE + " INTEGER," - + CONTYPE + " INTEGER," - + ROOMNAME + " TEXT," - + INVITEID + " TEXT," - + RELAYID + " TEXT," - + SEED + " INTEGER," - + DICTLANG + " INTEGER," - + DICTLIST + " TEXT," - - + SMSPHONE + " TEXT," - + SCORES + " TEXT," - + CHAT_HISTORY + " TEXT," - + GAMEID + " INTEGER," - + REMOTEDEVS + " TEXT," - + LASTMOVE + " INTEGER DEFAULT 0," - // HASMSGS: sqlite doesn't have bool; use 0 and 1 - + HASMSGS + " INTEGER DEFAULT 0," - + CONTRACTED + " INTEGER DEFAULT 0," - - + CREATE_TIME + " INTEGER," - + LASTPLAY_TIME + " INTEGER," - - + SNAPSHOT + " BLOB);" - ); - } - - private void onCreateObits( SQLiteDatabase db ) - { - db.execSQL( "CREATE TABLE " + TABLE_NAME_OBITS + " (" - + RELAYID + " TEXT," - + SEED + " INTEGER);" - ); - } - - private void onCreateDictsDB( SQLiteDatabase db ) - { - db.execSQL( "CREATE TABLE " + TABLE_NAME_DICTINFO + "(" - + DICTNAME + " TEXT," - + LOC + " UNSIGNED INTEGER(1)," - + MD5SUM + " TEXT(32)," - + WORDCOUNT + " INTEGER," - + LANGCODE + " INTEGER);" - ); - - db.execSQL( "CREATE TABLE " + TABLE_NAME_DICTBROWSE + "(" - + DICTNAME + " TEXT," - + LOC + " UNSIGNED INTEGER(1)," - + WORDCOUNTS + " TEXT," - + ITERMIN + " INTEGER(4)," - + ITERMAX + " INTEGER(4)," - + ITERPOS + " INTEGER," - + ITERTOP + " INTEGER," - + ITERPREFIX + " TEXT);" - ); + db.execSQL( query.toString() ); } @Override public void onCreate( SQLiteDatabase db ) { - onCreateSum( db ); - onCreateObits( db ); - onCreateDictsDB( db ); + createTable( db, TABLE_NAME_SUM, s_summaryColsAndTypes ); + createTable( db, TABLE_NAME_OBITS, s_obitsColsAndTypes ); + createTable( db, TABLE_NAME_DICTINFO, s_dictInfoColsAndTypes ); + createTable( db, TABLE_NAME_DICTBROWSE, s_dictBrowseColsAndTypes ); } @Override @@ -178,7 +173,7 @@ public class DBHelper extends SQLiteOpenHelper { switch( oldVersion ) { case 5: - onCreateObits(db); + createTable( db, TABLE_NAME_OBITS, s_obitsColsAndTypes ); case 6: addColumn( db, TURN, "INTEGER" ); addColumn( db, GIFLAGS, "INTEGER" ); @@ -195,7 +190,9 @@ public class DBHelper extends SQLiteOpenHelper { case 11: addColumn( db, REMOTEDEVS, "TEXT" ); case 12: - onCreateDictsDB( db ); + createTable( db, TABLE_NAME_DICTINFO, s_dictInfoColsAndTypes ); + createTable( db, TABLE_NAME_DICTBROWSE, s_dictBrowseColsAndTypes ); + case 13: addColumn( db, LASTMOVE, "INTEGER" ); // nothing yet From 2779139e758fa594df675b3f81cd19f75991551f Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 17 Nov 2012 10:20:14 -0800 Subject: [PATCH 040/146] add columns using data as well. Like pref commit, this does not change behavior. --- .../src/org/eehouse/android/xw4/DBHelper.java | 61 +++++++++++-------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java index e8ab0960e..24e406b87 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java @@ -141,21 +141,6 @@ public class DBHelper extends SQLiteOpenHelper { return DB_NAME; } - private void createTable( SQLiteDatabase db, String name, String[] data ) - { - StringBuilder query = - new StringBuilder( String.format("CREATE TABLE %s (", name ) ); - - for ( int ii = 0; ii < data.length; ii += 2 ) { - String col = String.format( " %s %s,", data[ii], data[ii+1] ); - query.append( col ); - } - query.setLength(query.length() - 1); // nuke the last comma - query.append( ");" ); - - db.execSQL( query.toString() ); - } - @Override public void onCreate( SQLiteDatabase db ) { @@ -175,26 +160,26 @@ public class DBHelper extends SQLiteOpenHelper { case 5: createTable( db, TABLE_NAME_OBITS, s_obitsColsAndTypes ); case 6: - addColumn( db, TURN, "INTEGER" ); - addColumn( db, GIFLAGS, "INTEGER" ); - addColumn( db, CHAT_HISTORY, "TEXT" ); + addSumColumn( db, TURN ); + addSumColumn( db, GIFLAGS ); + addSumColumn( db, CHAT_HISTORY ); case 7: - addColumn( db, MISSINGPLYRS, "INTEGER" ); + addSumColumn( db, MISSINGPLYRS ); case 8: - addColumn( db, GAME_NAME, "TEXT" ); - addColumn( db, CONTRACTED, "INTEGER" ); + addSumColumn( db, GAME_NAME ); + addSumColumn( db, CONTRACTED ); case 9: - addColumn( db, DICTLIST, "TEXT" ); + addSumColumn( db, DICTLIST ); case 10: - addColumn( db, INVITEID, "TEXT" ); + addSumColumn( db, INVITEID ); case 11: - addColumn( db, REMOTEDEVS, "TEXT" ); + addSumColumn( db, REMOTEDEVS ); case 12: createTable( db, TABLE_NAME_DICTINFO, s_dictInfoColsAndTypes ); createTable( db, TABLE_NAME_DICTBROWSE, s_dictBrowseColsAndTypes ); case 13: - addColumn( db, LASTMOVE, "INTEGER" ); + addSumColumn( db, LASTMOVE ); // nothing yet break; default: @@ -206,10 +191,34 @@ public class DBHelper extends SQLiteOpenHelper { } } - private void addColumn( SQLiteDatabase db, String colName, String colType ) + private void addSumColumn( SQLiteDatabase db, String colName ) { + String colType = null; + for ( int ii = 0; ii < s_summaryColsAndTypes.length; ii += 2 ) { + if ( s_summaryColsAndTypes[ii].equals( colName ) ) { + colType = s_summaryColsAndTypes[ii+1]; + break; + } + } + String cmd = String.format( "ALTER TABLE %s ADD COLUMN %s %s;", TABLE_NAME_SUM, colName, colType ); db.execSQL( cmd ); } + + private void createTable( SQLiteDatabase db, String name, String[] data ) + { + StringBuilder query = + new StringBuilder( String.format("CREATE TABLE %s (", name ) ); + + for ( int ii = 0; ii < data.length; ii += 2 ) { + String col = String.format( " %s %s,", data[ii], data[ii+1] ); + query.append( col ); + } + query.setLength(query.length() - 1); // nuke the last comma + query.append( ");" ); + + db.execSQL( query.toString() ); + } + } From cb7085858302d77b29697235fa2f8d4c3a998500 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 20 Nov 2012 04:14:25 -0800 Subject: [PATCH 041/146] up strings for next release (still a while out!) --- xwords4/android/XWords4/AndroidManifest.xml | 2 +- xwords4/android/XWords4/res/raw/changes | 6 +----- xwords4/android/XWords4/res/values/app_name.xml | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/xwords4/android/XWords4/AndroidManifest.xml b/xwords4/android/XWords4/AndroidManifest.xml index d9bb9afd2..129bca3ea 100644 --- a/xwords4/android/XWords4/AndroidManifest.xml +++ b/xwords4/android/XWords4/AndroidManifest.xml @@ -22,7 +22,7 @@ to come from a domain that you own or have control over. --> diff --git a/xwords4/android/XWords4/res/raw/changes b/xwords4/android/XWords4/res/raw/changes index 1dd978cad..1c68a0ff2 100644 --- a/xwords4/android/XWords4/res/raw/changes +++ b/xwords4/android/XWords4/res/raw/changes @@ -5,13 +5,9 @@ -Crosswords 4.4 beta 55 release +Crosswords 4.4 beta 56 release
      New with this release -
    • Use "Google Cloud Messaging" for much faster relay move - notifications
    • - -
    • Fix crashes on Kindle Fire
    diff --git a/xwords4/android/XWords4/res/values/app_name.xml b/xwords4/android/XWords4/res/values/app_name.xml index e3a0a8b7a..844bb12a6 100644 --- a/xwords4/android/XWords4/res/values/app_name.xml +++ b/xwords4/android/XWords4/res/values/app_name.xml @@ -1,5 +1,5 @@ - 4.4 beta 55 + 4.4 beta 56 From face24e91567131de597a48ec60673ea67fddf9f Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 20 Nov 2012 06:10:57 -0800 Subject: [PATCH 042/146] make invite email text a bit easier to read --- .../android/XWords4/res/values/strings.xml | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/xwords4/android/XWords4/res/values/strings.xml b/xwords4/android/XWords4/res/values/strings.xml index 7a760b01e..e13204885 100644 --- a/xwords4/android/XWords4/res/values/strings.xml +++ b/xwords4/android/XWords4/res/values/strings.xml @@ -1229,11 +1229,20 @@ encodings for the greater-than and less-than symbols which are not legal in xml strings.)--> \u003ca href=\"%1$s\"\u003ETap - here\u003c/a\u003E (%1$s) to accept my invitation and join this - game.\u003cbr\u003E \u003ca - href=\"http://eehouse.org/market_redir.php\"\u003E Tap - here\u003c/a\u003E (http://eehouse.org/market_redir.php) to - install Crosswords if you haven\'t already. + here\u003c/a\u003E (or raw the link below) to accept my invitation and + join this game. + \u003cbr \\\u003E + \u003cbr \\\u003E + (raw link: %1$s) + \u003cbr \\\u003E + \u003cbr \\\u003E + \u003ca href=\"http://eehouse.org/market_redir.php\"\u003E Tap + here\u003c/a\u003E (or the raw link below) to + install Crosswords if you haven\'t already. + \u003cbr \\\u003E + \u003cbr \\\u003E + (raw link: http://eehouse.org/market_redir.php) + \u003ca href=\"%1$s\"\u003ETap - here\u003c/a\u003E (or raw the link below) to accept my invitation and + here\u003c/a\u003E (or the raw link below) to accept my invitation and join this game. \u003cbr \\\u003E \u003cbr \\\u003E From 04d839868d46c0e4a9aaa8867e8222defa908c3a Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 20 Nov 2012 06:24:23 -0800 Subject: [PATCH 045/146] on launch to handle an invite for a game that seems to already exist, instead launch that game -- for better feedback. --- .../src/org/eehouse/android/xw4/DBUtils.java | 18 ++++++++++++------ .../eehouse/android/xw4/DispatchNotify.java | 12 +++++++++++- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java index 48055402f..8403f65e5 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java @@ -551,10 +551,12 @@ public class DBUtils { synchronized( s_dbHelper ) { SQLiteDatabase db = s_dbHelper.getReadableDatabase(); String[] columns = { ROW_ID }; - String selection = DBHelper.ROOMNAME + "='" + nli.room + "' AND " - + DBHelper.INVITEID + "='" + nli.inviteID + "' AND " - + DBHelper.DICTLANG + "=" + nli.lang + " AND " - + DBHelper.NUM_PLAYERS + "=" + nli.nPlayers; + String selection = + String.format( "%s='%s' AND %s='%s' AND %s=%d AND %s=%d", + DBHelper.ROOMNAME, nli.room, + DBHelper.INVITEID, nli.inviteID, + DBHelper.DICTLANG, nli.lang, + DBHelper.NUM_PLAYERS, nli.nPlayers ); Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns, selection, null, null, null, null ); if ( 1 == cursor.getCount() && cursor.moveToFirst() ) { @@ -566,10 +568,14 @@ public class DBUtils { return result; } - public static boolean isNewInvite( Context context, Uri data ) + public static long getRowIDForOpen( Context context, Uri data ) { + long rowid = ROWID_NOTFOUND; NetLaunchInfo nli = new NetLaunchInfo( data ); - return null != nli && -1 == getRowIDForOpen( context, nli ); + if ( null != nli ) { + rowid = getRowIDForOpen( context, nli ); + } + return rowid; } public static String[] getRelayIDs( Context context, boolean noMsgs ) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DispatchNotify.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DispatchNotify.java index ab9231174..a82d1addc 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DispatchNotify.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DispatchNotify.java @@ -29,6 +29,8 @@ import android.os.Bundle; import java.util.HashSet; import junit.framework.Assert; +import org.eehouse.android.xw4.jni.GameSummary; + public class DispatchNotify extends Activity { public static final String RELAYIDS_EXTRA = "relayids"; @@ -63,12 +65,20 @@ public class DispatchNotify extends Activity { mustLaunch = true; } } else if ( null != data ) { - if ( DBUtils.isNewInvite( this, data ) ) { + long rowid = DBUtils.getRowIDForOpen( this, data ); + if ( DBUtils.ROWID_NOTFOUND == rowid ) { if ( !tryHandle( data ) ) { mustLaunch = true; } } else { DbgUtils.logf( "DispatchNotify: dropping duplicate invite" ); + GameSummary summary = DBUtils.getSummary( this, rowid ); + if ( null != summary ) { + gameID = summary.gameID; + if ( !tryHandle( gameID ) ) { + mustLaunch = true; + } + } } } From daf5a89a0eedbd02a62509c899411d9d713c0d2d Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 21 Nov 2012 21:49:42 -0800 Subject: [PATCH 046/146] remove unused imports (no code change) --- .../XWords4/src/org/eehouse/android/xw4/GameUtils.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java index c8ec34cc8..b956c73c3 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java @@ -24,15 +24,8 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; import android.net.Uri; -import android.os.Environment; import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.nio.channels.FileChannel; -import java.util.ArrayList; import java.util.Arrays; -import android.content.res.AssetManager; import java.util.concurrent.locks.Lock; import java.util.HashMap; import java.util.HashSet; From 0bea58b128829630fe7aef84ec963403d8a578b1 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 22 Nov 2012 08:13:06 -0800 Subject: [PATCH 047/146] make it possible to build without knowing the GCM sender ID. --- xwords4/android/XWords4/build.xml | 1 + .../src/org/eehouse/android/xw4/GCMIntentService.java | 2 +- xwords4/android/scripts/gen_gcmid.sh | 5 +++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/xwords4/android/XWords4/build.xml b/xwords4/android/XWords4/build.xml index b1d8ad988..d1536010c 100644 --- a/xwords4/android/XWords4/build.xml +++ b/xwords4/android/XWords4/build.xml @@ -61,6 +61,7 @@ diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java index 2ab40f4bc..1fc22e503 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java @@ -74,7 +74,7 @@ public class GCMIntentService extends GCMBaseIntentService { public static void init( Application app ) { int sdkVersion = Integer.valueOf( android.os.Build.VERSION.SDK ); - if ( 8 <= sdkVersion ) { + if ( 8 <= sdkVersion && 0 < GCMConsts.SENDER_ID.length() ) { try { GCMRegistrar.checkDevice( app ); // GCMRegistrar.checkManifest( app ); diff --git a/xwords4/android/scripts/gen_gcmid.sh b/xwords4/android/scripts/gen_gcmid.sh index 9ca58833a..75bbb2089 100755 --- a/xwords4/android/scripts/gen_gcmid.sh +++ b/xwords4/android/scripts/gen_gcmid.sh @@ -1,8 +1,9 @@ #!/bin/sh +set -e -u + if [ -z "$GCM_SENDER_ID" ]; then - echo "GCM_SENDER_ID not in env" - exit 1 + echo "GCM_SENDER_ID empty; GCM use will be disabled" >&2 fi cat < Date: Thu, 22 Nov 2012 08:20:08 -0800 Subject: [PATCH 048/146] work even if ENV variable unset --- xwords4/android/scripts/gen_gcmid.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xwords4/android/scripts/gen_gcmid.sh b/xwords4/android/scripts/gen_gcmid.sh index 75bbb2089..bf719f2ab 100755 --- a/xwords4/android/scripts/gen_gcmid.sh +++ b/xwords4/android/scripts/gen_gcmid.sh @@ -2,6 +2,8 @@ set -e -u +GCM_SENDER_ID=${GCM_SENDER_ID:-""} + if [ -z "$GCM_SENDER_ID" ]; then echo "GCM_SENDER_ID empty; GCM use will be disabled" >&2 fi From a3e0d0ca1e9d34efe7b12e9531ce5cb006a54b6a Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 22 Nov 2012 13:03:32 -0800 Subject: [PATCH 049/146] recover from situation where game is created in response to an invitation for a lang for which it has no dicts installed. Shouldn't happen, but on relay still can. --- .../org/eehouse/android/xw4/GamesList.java | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java index 7484dd696..ae2413beb 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java @@ -80,7 +80,7 @@ public class GamesList extends XWListActivity private GameListAdapter m_adapter; private String m_missingDict; - private String[] m_missingDictNames; + private String m_missingDictName; private long m_missingDictRowId; private String[] m_sameLangDicts; private int m_missingDictLang; @@ -104,10 +104,15 @@ public class GamesList extends XWListActivity lstnr = new DialogInterface.OnClickListener() { public void onClick( DialogInterface dlg, int item ) { // just do one - NetUtils.downloadDictInBack( GamesList.this, - m_missingDictLang, - m_missingDictNames[0], - GamesList.this ); + if ( null == m_missingDictName ) { + DictsActivity.launchAndDownload( GamesList.this, + m_missingDictLang ); + } else { + NetUtils.downloadDictInBack( GamesList.this, + m_missingDictLang, + m_missingDictName, + GamesList.this ); + } } }; String message; @@ -119,7 +124,7 @@ public class GamesList extends XWListActivity gameName, langName ); } else { message = getString( R.string.no_dict_substf, - gameName, m_missingDictNames[0], + gameName, m_missingDictName, langName ); } @@ -152,8 +157,10 @@ public class GamesList extends XWListActivity dict = DictLangCache.stripCount( dict ); GameUtils.replaceDicts( GamesList.this, m_missingDictRowId, - m_missingDictNames[0], + m_missingDictName, dict ); + GameUtils.launchGame( GamesList.this, + m_missingDictRowId ); } }; dialog = new AlertDialog.Builder( this ) @@ -340,7 +347,7 @@ public class GamesList extends XWListActivity { super.onSaveInstanceState( outState ); outState.putLong( SAVE_ROWID, m_rowid ); - outState.putStringArray( SAVE_DICTNAMES, m_missingDictNames ); + outState.putString( SAVE_DICTNAMES, m_missingDictName ); if ( null != m_netLaunchInfo ) { m_netLaunchInfo.putSelf( outState ); } @@ -351,7 +358,7 @@ public class GamesList extends XWListActivity if ( null != bundle ) { m_rowid = bundle.getLong( SAVE_ROWID ); m_netLaunchInfo = new NetLaunchInfo( bundle ); - m_missingDictNames = bundle.getStringArray( SAVE_DICTNAMES ); + m_missingDictName = bundle.getString( SAVE_DICTNAMES ); } } @@ -698,13 +705,21 @@ public class GamesList extends XWListActivity missingNames, missingLang ); if ( !hasDicts ) { - m_missingDictNames = missingNames[0]; m_missingDictLang = missingLang[0]; + if ( 0 < missingNames[0].length ) { + m_missingDictName = missingNames[0][0]; + } else { + m_missingDictName = null; + } m_missingDictRowId = rowid; if ( 0 == DictLangCache.getLangCount( this, m_missingDictLang ) ) { showDialog( WARN_NODICT ); - } else { + } else if ( null != m_missingDictName ) { showDialog( WARN_NODICT_SUBST ); + } else { + String dict = DictLangCache.getHaveLang( this, m_missingDictLang)[0]; + GameUtils.replaceDicts( this, m_missingDictRowId, null, dict ); + GameUtils.launchGame( this, m_missingDictRowId ); } } return hasDicts; From b2e329f670ca0e776f97e6a19ff6a14f6545ba37 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 25 Nov 2012 08:53:15 -0800 Subject: [PATCH 050/146] pass along wl (dict) param if present --- xwords4/android/scripts/newgame.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/xwords4/android/scripts/newgame.php b/xwords4/android/scripts/newgame.php index da634bb05..06388c44f 100644 --- a/xwords4/android/scripts/newgame.php +++ b/xwords4/android/scripts/newgame.php @@ -1,3 +1,4 @@ + redirecting to Crosswords....

    -

    This page is meant to be viewed (briefly) on your Android device after which Crosswords should launch. - If this fails it's probably because you don't have a new enough version of Crosswords installed. +

    This page is meant to be viewed (briefly) on your Android + device after which Crosswords should launch. +

    +

    If this fails it's probably because you don't have a new enough + version of Crosswords installed. Or because your browser does + not allow URL redirects.

    + From 2a1c02826882d1c9a25cbd0528546e3f398aed48 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 25 Nov 2012 08:54:46 -0800 Subject: [PATCH 051/146] deal with case where relay invite arrives for a language that device doesn't have support for. Dictionary name is now included in the invite, and if that's present offer to download it then launch game. For previous versions that don't include the dict name, offer to download from the language (though that isn't tested yet.) --- .../eehouse/android/xw4/BoardActivity.java | 5 +- .../src/org/eehouse/android/xw4/DBUtils.java | 4 +- .../eehouse/android/xw4/DispatchNotify.java | 27 ++++++++-- .../org/eehouse/android/xw4/GameUtils.java | 27 ++++++---- .../org/eehouse/android/xw4/GamesList.java | 22 ++++++-- .../org/eehouse/android/xw4/MultiService.java | 54 ++++++++++++++++--- .../eehouse/android/xw4/NetLaunchInfo.java | 36 ++++++++++--- .../eehouse/android/xw4/NewGameActivity.java | 6 ++- .../org/eehouse/android/xw4/SMSService.java | 19 +++---- 9 files changed, 149 insertions(+), 51 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java index 9f9f5089a..e45f64e3e 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java @@ -816,9 +816,8 @@ public class BoardActivity extends XWActivity if ( DlgDelegate.DISMISS_BUTTON != which ) { GameUtils.launchInviteActivity( BoardActivity.this, DlgDelegate.EMAIL_BTN == which, - m_room, null, - m_gi.dictLang, - m_gi.nPlayers ); + m_room, null, m_gi.dictLang, + m_gi.dictName, m_gi.nPlayers ); } } else if ( AlertDialog.BUTTON_POSITIVE == which ) { JNICmd cmd = JNICmd.CMD_NONE; diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java index 8403f65e5..8b9473d5f 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java @@ -556,7 +556,7 @@ public class DBUtils { DBHelper.ROOMNAME, nli.room, DBHelper.INVITEID, nli.inviteID, DBHelper.DICTLANG, nli.lang, - DBHelper.NUM_PLAYERS, nli.nPlayers ); + DBHelper.NUM_PLAYERS, nli.nPlayersT ); Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns, selection, null, null, null, null ); if ( 1 == cursor.getCount() && cursor.moveToFirst() ) { @@ -572,7 +572,7 @@ public class DBUtils { { long rowid = ROWID_NOTFOUND; NetLaunchInfo nli = new NetLaunchInfo( data ); - if ( null != nli ) { + if ( null != nli && nli.isValid() ) { rowid = getRowIDForOpen( context, nli ); } return rowid; diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DispatchNotify.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DispatchNotify.java index a82d1addc..57d674d62 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DispatchNotify.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DispatchNotify.java @@ -64,11 +64,30 @@ public class DispatchNotify extends Activity { if ( !tryHandle( gameID ) ) { mustLaunch = true; } - } else if ( null != data ) { - long rowid = DBUtils.getRowIDForOpen( this, data ); + } else if ( null != data ) { // relay invite redirected URL case + NetLaunchInfo nli = new NetLaunchInfo( data ); + long rowid = DBUtils.getRowIDForOpen( this, nli ); if ( DBUtils.ROWID_NOTFOUND == rowid ) { - if ( !tryHandle( data ) ) { - mustLaunch = true; + boolean haveDict; + if ( null == nli.dict ) { // can only test for language support + haveDict = + 0 < DictLangCache.getHaveLang( this, nli.lang ).length; + } else { + haveDict = DictLangCache.haveDict( this, nli.lang, nli.dict ); + } + if ( haveDict ) { + if ( !tryHandle( data ) ) { + mustLaunch = true; + } + } else { + Intent intent = MultiService.makeMissingDictIntent( this, + nli ); + intent.putExtra( MultiService.OWNER, + MultiService.OWNER_RELAY ); + // do we have gameID? + MultiService. + postMissingDictNotification( this, intent, + nli.inviteID.hashCode() ); } } else { DbgUtils.logf( "DispatchNotify: dropping duplicate invite" ); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java index b956c73c3..29414613c 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java @@ -408,7 +408,7 @@ public class GameUtils { } private static long makeNewMultiGame( Context context, CommsAddrRec addr, - int[] lang, String dict, + int[] lang, String[] dict, int nPlayersT, int nPlayersH, String inviteID, int gameID, boolean isHost ) @@ -416,8 +416,9 @@ public class GameUtils { long rowid = -1; CurGameInfo gi = new CurGameInfo( context, true ); - gi.setLang( lang[0], dict ); + gi.setLang( lang[0], dict[0] ); lang[0] = gi.dictLang; + dict[0] = gi.dictName; gi.setNPlayers( nPlayersT, nPlayersH ); gi.juggle(); if ( 0 != gameID ) { @@ -442,7 +443,8 @@ public class GameUtils { public static long makeNewNetGame( Context context, String room, String inviteID, int[] lang, - int nPlayersT, int nPlayersH ) + String[] dict, int nPlayersT, + int nPlayersH ) { long rowid = -1; String relayName = XWPrefs.getDefaultRelayHost( context ); @@ -450,21 +452,24 @@ public class GameUtils { CommsAddrRec addr = new CommsAddrRec( relayName, relayPort ); addr.ip_relay_invite = room; - return makeNewMultiGame( context, addr, lang, null, nPlayersT, + return makeNewMultiGame( context, addr, lang, dict, nPlayersT, nPlayersH, inviteID, 0, false ); } public static long makeNewNetGame( Context context, String room, - String inviteID, int lang, int nPlayers ) + String inviteID, int lang, String dict, + int nPlayers ) { int[] langarr = { lang }; - return makeNewNetGame( context, room, inviteID, langarr, nPlayers, 1 ); + String[] dictArr = { dict }; + return makeNewNetGame( context, room, inviteID, langarr, dictArr, + nPlayers, 1 ); } public static long makeNewNetGame( Context context, NetLaunchInfo info ) { return makeNewNetGame( context, info.room, info.inviteID, info.lang, - info.nPlayers ); + info.dict, info.nPlayersT ); } public static long makeNewBTGame( Context context, int gameID, @@ -488,11 +493,12 @@ public class GameUtils { { long rowid = -1; int[] langa = { lang }; + String[] dicta = { dict }; boolean isHost = null == addr; if ( isHost ) { addr = new CommsAddrRec(CommsAddrRec.CommsConnType.COMMS_CONN_SMS); } - return makeNewMultiGame( context, addr, langa, dict, nPlayersT, + return makeNewMultiGame( context, addr, langa, dicta, nPlayersT, nPlayersH, null, gameID, isHost ); } @@ -515,13 +521,14 @@ public class GameUtils { public static void launchInviteActivity( Context context, boolean choseEmail, String room, String inviteID, - int lang, int nPlayers ) + int lang, String dict, + int nPlayers ) { if ( null == inviteID ) { inviteID = makeRandomID(); } Uri gameUri = NetLaunchInfo.makeLaunchUri( context, room, inviteID, - lang, nPlayers ); + lang, dict, nPlayers ); if ( null != gameUri ) { int fmtId = choseEmail? R.string.invite_htmf : R.string.invite_txtf; diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java index ae2413beb..b9975a6e6 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java @@ -790,13 +790,18 @@ public class GamesList extends XWListActivity private void startNewNetGame( Intent intent ) { - Uri data = intent.getData(); - if ( null != data ) { - NetLaunchInfo info = new NetLaunchInfo( data ); - if ( info.isValid() ) { - startNewNetGame( info ); + NetLaunchInfo info = null; + if ( MultiService.isMissingDictIntent( intent ) ) { + info = new NetLaunchInfo( intent ); + } else { + Uri data = intent.getData(); + if ( null != data ) { + info = new NetLaunchInfo( data ); } } + if ( null != info && info.isValid() ) { + startNewNetGame( info ); + } } // startNewNetGame private void startHasGameID( int gameID ) @@ -833,4 +838,11 @@ public class GamesList extends XWListActivity onContentChanged(); } } + + public static void onGameDictDownload( Context context, Intent intent ) + { + intent.setClass( context, GamesList.class ); + context.startActivity( intent ); + } + } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiService.java index 8c06598b0..58c620d2b 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiService.java @@ -31,6 +31,8 @@ public class MultiService { public static final String LANG = "LANG"; public static final String DICT = "DICT"; public static final String GAMEID = "GAMEID"; + public static final String INVITEID = "INVITEID"; // relay only + public static final String ROOM = "ROOM"; public static final String GAMENAME = "GAMENAME"; public static final String NPLAYERST = "NPLAYERST"; public static final String NPLAYERSH = "NPLAYERSH"; @@ -38,6 +40,7 @@ public class MultiService { public static final String OWNER = "OWNER"; public static final int OWNER_SMS = 1; + public static final int OWNER_RELAY = 2; private BTEventListener m_li; @@ -84,11 +87,39 @@ public class MultiService { } } + public static void fillInviteIntent( Intent intent, String gameName, + int lang, String dict, + int nPlayersT, int nPlayersH ) + { + intent.putExtra( GAMENAME, gameName ); + intent.putExtra( LANG, lang ); + intent.putExtra( DICT, dict ); + intent.putExtra( NPLAYERST, nPlayersT ); // both of these used + intent.putExtra( NPLAYERSH, nPlayersH ); + } + + public static Intent makeMissingDictIntent( Context context, String gameName, + int lang, String dict, + int nPlayersT, int nPlayersH ) + { + Intent intent = new Intent( context, DictsActivity.class ); + fillInviteIntent( intent, gameName, lang, dict, nPlayersT, nPlayersH ); + return intent; + } + + public static Intent makeMissingDictIntent( Context context, NetLaunchInfo nli ) + { + Intent intent = makeMissingDictIntent( context, null, nli.lang, nli.dict, + nli.nPlayersT, 1 ); + intent.putExtra( ROOM, nli.room ); + return intent; + } + public static boolean isMissingDictIntent( Intent intent ) { return intent.hasExtra( LANG ) - && intent.hasExtra( DICT ) - && intent.hasExtra( GAMEID ) + // && intent.hasExtra( DICT ) + && (intent.hasExtra( GAMEID ) || intent.hasExtra( ROOM )) && intent.hasExtra( GAMENAME ) && intent.hasExtra( NPLAYERST ) && intent.hasExtra( NPLAYERSH ); @@ -112,6 +143,13 @@ public class MultiService { .create(); } + public static void postMissingDictNotification( Context content, + Intent intent, int id ) + { + Utils.postNotification( content, intent, R.string.missing_dict_title, + R.string.missing_dict_detail, id ); + } + // resend the intent, but only if the dict it names is here. (If // it's not, we may need to try again later, e.g. because our cue // was a focus gain.) @@ -123,11 +161,15 @@ public class MultiService { String dict = intent.getStringExtra( DICT ); downloaded = DictLangCache.haveDict( context, lang, dict ); if ( downloaded ) { - int owner = intent.getIntExtra( OWNER, -1 ); - if ( owner == OWNER_SMS ) { + switch ( intent.getIntExtra( OWNER, -1 ) ) { + case OWNER_SMS: SMSService.onGameDictDownload( context, intent ); - } else { - DbgUtils.logf( "unexpected OWNER: %d", owner ); + break; + case OWNER_RELAY: + GamesList.onGameDictDownload( context, intent ); + break; + default: + DbgUtils.logf( "unexpected OWNER" ); } } } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetLaunchInfo.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetLaunchInfo.java index d73370d79..53a9c61ab 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetLaunchInfo.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetLaunchInfo.java @@ -21,8 +21,9 @@ package org.eehouse.android.xw4; import android.content.Context; -import android.net.Uri; +import android.content.Intent; import android.net.Uri.Builder; +import android.net.Uri; import android.os.Bundle; import java.net.URLEncoder; @@ -30,11 +31,13 @@ import java.net.URLEncoder; public class NetLaunchInfo { public String room; public String inviteID; + public String dict; public int lang; - public int nPlayers; + public int nPlayersT; private static final String LANG = "netlaunchinfo_lang"; private static final String ROOM = "netlaunchinfo_room"; + private static final String DICT = "netlaunchinfo_dict"; private static final String INVITEID = "netlaunchinfo_inviteid"; private static final String NPLAYERS = "netlaunchinfo_nplayers"; private static final String VALID = "netlaunchinfo_valid"; @@ -46,7 +49,8 @@ public class NetLaunchInfo { bundle.putInt( LANG, lang ); bundle.putString( ROOM, room ); bundle.putString( INVITEID, inviteID ); - bundle.putInt( NPLAYERS, nPlayers ); + bundle.putString( DICT, dict ); + bundle.putInt( NPLAYERS, nPlayersT ); bundle.putBoolean( VALID, m_valid ); } @@ -54,8 +58,9 @@ public class NetLaunchInfo { { lang = bundle.getInt( LANG ); room = bundle.getString( ROOM ); + dict = bundle.getString( DICT ); inviteID = bundle.getString( INVITEID ); - nPlayers = bundle.getInt( NPLAYERS ); + nPlayersT = bundle.getInt( NPLAYERS ); m_valid = bundle.getBoolean( VALID ); } @@ -66,10 +71,11 @@ public class NetLaunchInfo { try { room = data.getQueryParameter( "room" ); inviteID = data.getQueryParameter( "id" ); + dict = data.getQueryParameter( "wl" ); String langStr = data.getQueryParameter( "lang" ); lang = Integer.decode( langStr ); String np = data.getQueryParameter( "np" ); - nPlayers = Integer.decode( np ); + nPlayersT = Integer.decode( np ); m_valid = true; } catch ( Exception e ) { DbgUtils.logf( "unable to parse \"%s\"", data.toString() ); @@ -77,8 +83,21 @@ public class NetLaunchInfo { } } + public NetLaunchInfo( Intent intent ) + { + room = intent.getStringExtra( MultiService.ROOM ); + inviteID = intent.getStringExtra( MultiService.INVITEID ); + lang = intent.getIntExtra( MultiService.LANG, -1 ); + dict = intent.getStringExtra( MultiService.DICT ); + nPlayersT = intent.getIntExtra( MultiService.NPLAYERST, -1 ); + m_valid = null != room + && -1 != lang + && -1 != nPlayersT; + } + public static Uri makeLaunchUri( Context context, String room, - String inviteID, int lang, int nPlayers ) + String inviteID, int lang, + String dict, int nPlayersT ) { Builder ub = new Builder(); ub.scheme( "http" ); @@ -86,9 +105,12 @@ public class NetLaunchInfo { XWPrefs.getDefaultRedirHost( context ) ) ); ub.appendQueryParameter( "lang", String.format("%d", lang ) ); - ub.appendQueryParameter( "np", String.format( "%d", nPlayers ) ); + ub.appendQueryParameter( "np", String.format( "%d", nPlayersT ) ); ub.appendQueryParameter( "room", room ); ub.appendQueryParameter( "id", inviteID ); + if ( null != dict ) { + ub.appendQueryParameter( "wl", dict ); + } return ub.build(); } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NewGameActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NewGameActivity.java index 3cfee37c9..039aaa23d 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NewGameActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NewGameActivity.java @@ -318,13 +318,14 @@ public class NewGameActivity extends XWActivity { String inviteID = null; long rowid; int[] lang = {0}; + String[] dict = {null}; final int nPlayers = 2; // hard-coded for no-configure case if ( networked ) { room = GameUtils.makeRandomID(); inviteID = GameUtils.makeRandomID(); rowid = GameUtils.makeNewNetGame( this, room, inviteID, lang, - nPlayers, 1 ); + dict, nPlayers, 1 ); } else { rowid = GameUtils.saveNew( this, new CurGameInfo( this ) ); } @@ -333,7 +334,8 @@ public class NewGameActivity extends XWActivity { GameUtils.launchGame( this, rowid, networked ); if ( networked ) { GameUtils.launchInviteActivity( this, choseEmail, room, - inviteID, lang[0], nPlayers ); + inviteID, lang[0], dict[0], + nPlayers ); } } else { GameUtils.doConfig( this, rowid, GameConfig.class ); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java index 6121e7196..18cbd8c88 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java @@ -424,17 +424,15 @@ public class SMSService extends Service { makeForInvite( phone, gameID, gameName, lang, dict, nPlayersT, nPlayersH ); } else { - Intent intent = new Intent( this, DictsActivity.class ); - fillInviteIntent( intent, phone, gameID, gameName, lang, dict, - nPlayersT, nPlayersH ); + Intent intent = MultiService + .makeMissingDictIntent( this, gameName, lang, dict, + nPlayersT, nPlayersH ); + intent.putExtra( PHONE, phone ); intent.putExtra( MultiService.OWNER, MultiService.OWNER_SMS ); intent.putExtra( MultiService.INVITER, Utils.phoneToContact( this, phone, true ) ); - Utils.postNotification( this, intent, - R.string.missing_dict_title, - R.string.missing_dict_detail, - gameID ); + MultiService.postMissingDictNotification( this, intent, gameID ); } break; case DATA: @@ -591,11 +589,8 @@ public class SMSService extends Service { { intent.putExtra( PHONE, phone ); intent.putExtra( MultiService.GAMEID, gameID ); - intent.putExtra( MultiService.GAMENAME, gameName ); - intent.putExtra( MultiService.LANG, lang ); - intent.putExtra( MultiService.DICT, dict ); - intent.putExtra( MultiService.NPLAYERST, nPlayersT ); - intent.putExtra( MultiService.NPLAYERSH, nPlayersH ); + MultiService.fillInviteIntent( intent, gameName, lang, dict, + nPlayersT, nPlayersH ); } private void feedMessage( int gameID, byte[] msg, CommsAddrRec addr ) From 7d6e393007daa959cd338ad102b9ada00ec05f11 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 25 Nov 2012 09:58:14 -0800 Subject: [PATCH 052/146] use a different missing-dict explanation string when the name of the inviter isn't known (relay case) --- xwords4/android/XWords4/res/values/strings.xml | 4 ++++ .../XWords4/src/org/eehouse/android/xw4/MultiService.java | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/xwords4/android/XWords4/res/values/strings.xml b/xwords4/android/XWords4/res/values/strings.xml index 317db2124..f3da657ee 100644 --- a/xwords4/android/XWords4/res/values/strings.xml +++ b/xwords4/android/XWords4/res/values/strings.xml @@ -2123,6 +2123,10 @@ play Crosswords using the wordlist %2$s (for play in %3$s), but it is not installed. Would you like to download the wordlist or decline the invitation?
    + You have been invited to + play Crosswords using the wordlist %2$s (for play in %3$s), but it + is not installed. Would you like to download the wordlist or + decline the invitation? Decline Downloading %s... diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiService.java index 58c620d2b..4e4d21686 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiService.java @@ -133,8 +133,10 @@ public class MultiService { String langStr = DictLangCache.getLangName( context, lang ); String dict = intent.getStringExtra( DICT ); String inviter = intent.getStringExtra( INVITER ); - String msg = context.getString( R.string.invite_dict_missing_bodyf, - inviter, dict, langStr ); + int msgID = (null == inviter) ? R.string.invite_dict_missing_body_nonamef + : R.string.invite_dict_missing_bodyf; + String msg = context.getString( msgID, inviter, dict, langStr ); + return new AlertDialog.Builder( context ) .setTitle( R.string.invite_dict_missing_title ) .setMessage( msg) From 5019d3960b0a641d01e22d1824fd8479017f4e51 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 25 Nov 2012 09:58:46 -0800 Subject: [PATCH 053/146] add read-only debug prefs giving GCM and relay device IDs. --- xwords4/android/XWords4/res/values/common_rsrc.xml | 2 ++ xwords4/android/XWords4/res/xml/xwprefs.xml | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/xwords4/android/XWords4/res/values/common_rsrc.xml b/xwords4/android/XWords4/res/values/common_rsrc.xml index e354646b6..4d48992a3 100644 --- a/xwords4/android/XWords4/res/values/common_rsrc.xml +++ b/xwords4/android/XWords4/res/values/common_rsrc.xml @@ -121,6 +121,8 @@ %1$s/%2$s Write DB to SD card Load DB from SD card + GCM Device ID + Relay Device ID diff --git a/xwords4/android/XWords4/res/xml/xwprefs.xml b/xwords4/android/XWords4/res/xml/xwprefs.xml index 9a5a3bf3c..5d221f820 100644 --- a/xwords4/android/XWords4/res/xml/xwprefs.xml +++ b/xwords4/android/XWords4/res/xml/xwprefs.xml @@ -290,6 +290,16 @@ android:summary="@string/git_rev" android:enabled="false" /> + + Date: Sun, 25 Nov 2012 10:39:46 -0800 Subject: [PATCH 054/146] rewrite to present a link rather than redirect, to attempt to present the link only on Android devices, and to encourage users to email me if I'm wrong about their not being on a device. --- xwords4/android/scripts/newgame.php | 75 +++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 20 deletions(-) diff --git a/xwords4/android/scripts/newgame.php b/xwords4/android/scripts/newgame.php index 06388c44f..20f8ac3f6 100644 --- a/xwords4/android/scripts/newgame.php +++ b/xwords4/android/scripts/newgame.php @@ -1,9 +1,7 @@ -Crosswords SMS redirect - +Crosswords Invite redirect
    -

    redirecting to Crosswords....

    - -

    This page is meant to be viewed (briefly) on your Android - device after which Crosswords should launch. -

    -

    If this fails it's probably because you don't have a new enough - version of Crosswords installed. Or because your browser does - not allow URL redirects. +

    Tap this link to launch Crosswords with + your new game. +

    +

    If this fails it's probably because you don't have a new enough + version of Crosswords installed.

    @@ -54,4 +55,38 @@ print << + + +Crosswords Invite redirect + + +
    + +
    +

    This page is meant to be viewed on a browser on your Android + device. Please open the email that sent you here on that device to + complete the invitation process. +

    + +

    (If you are viewing this on an Android device, you've + found a bug! Please email me (and be + sure to leave the user agent string in the email body.) +

    + + + + +EOF; +} + ?> From f82e5bd0e9825e9f530cafe56e31cd13df82a2a5 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 25 Nov 2012 12:30:25 -0800 Subject: [PATCH 055/146] add code to deal with Amazon, which I falsely identified as having broken custom schemes. I'm leaving it in, unreachable, to have a record. --- xwords4/android/scripts/newgame.php | 77 ++++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 6 deletions(-) diff --git a/xwords4/android/scripts/newgame.php b/xwords4/android/scripts/newgame.php index 20f8ac3f6..bb6852675 100644 --- a/xwords4/android/scripts/newgame.php +++ b/xwords4/android/scripts/newgame.php @@ -1,6 +1,31 @@ "; + } +} + $g_androidStrings = array( "android", ); $scheme = "newxwgame"; @@ -17,6 +42,7 @@ for ( $ii = 0; $ii < count($g_androidStrings) && !$onAndroid; ++$ii ) { $needle = $g_androidStrings[$ii]; $onAndroid = 0 != stripos( $agent, $needle ); } +$onFire = 0 != stripos( $agent, 'silk' ); $localurl = "$scheme://$host?room=$room&lang=$lang&np=$np"; if ( $id != "" ) { @@ -26,7 +52,7 @@ if ( $wl != "" ) { $localurl .= "&wl=$wl"; } -if ( $onAndroid ) { +if ( $onAndroid || $onFire ) { print << @@ -38,7 +64,7 @@ print <<
    - +

    Tap this link to launch Crosswords with your new game. @@ -47,7 +73,7 @@ print << - +

    @@ -55,6 +81,45 @@ print << + + +Crosswords Invite redirect + + + +

    It appears you're running on a Kindle Fire, whose non-standard (from +an Android perspective) OS doesn't support the custom schemes on which +Crosswords invitations depend. If you want to accept this invitation +you'll need to do it the manual way: + +

      +
    1. Open Crosswords, and navigate to the main Games List screen
    2. +
    3. Choose "Add game", either from the menu or the button at the bottom.
    4. +
    5. Under "New Networked game", choose "Configure first".
    6. +
    7. $langText
    8. +
    9. As the room name, enter "$room".
    10. +
    11. Make sure the total number of players shown is $np and that only one of them is not an "Off-device player".
    12. +
    13. Now tap the "Play game" button at the bottom (above the keyboard). Your new game should open and connect.
    14. +

    +

    I'm sorry this is so complicated. I'm trying to find a +workaround for this limitation in the Kindle Fire's operating system +but for now this is all I can offer.

    + +

    (Just in case Amazon's fixed the +problem, here is the link that should open +your new game.)

    + + +EOF; } else { $subject = "Android device not identified"; @@ -70,11 +135,11 @@ print <<
    - +

    This page is meant to be viewed on a browser on your Android - device. Please open the email that sent you here on that device to - complete the invitation process. + device. Please open the email that sent you here on that device and + revisit this link to complete the invitation process.

    (If you are viewing this on an Android device, you've From 63b29a2cb25a521eba463783026443724cc88ea2 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 25 Nov 2012 13:55:02 -0800 Subject: [PATCH 056/146] Revert "add read-only debug prefs giving GCM and relay device IDs." This reverts commit 5019d3960b0a641d01e22d1824fd8479017f4e51. --- xwords4/android/XWords4/res/values/common_rsrc.xml | 2 -- xwords4/android/XWords4/res/xml/xwprefs.xml | 10 ---------- 2 files changed, 12 deletions(-) diff --git a/xwords4/android/XWords4/res/values/common_rsrc.xml b/xwords4/android/XWords4/res/values/common_rsrc.xml index 4d48992a3..e354646b6 100644 --- a/xwords4/android/XWords4/res/values/common_rsrc.xml +++ b/xwords4/android/XWords4/res/values/common_rsrc.xml @@ -121,8 +121,6 @@ %1$s/%2$s Write DB to SD card Load DB from SD card - GCM Device ID - Relay Device ID diff --git a/xwords4/android/XWords4/res/xml/xwprefs.xml b/xwords4/android/XWords4/res/xml/xwprefs.xml index 5d221f820..9a5a3bf3c 100644 --- a/xwords4/android/XWords4/res/xml/xwprefs.xml +++ b/xwords4/android/XWords4/res/xml/xwprefs.xml @@ -290,16 +290,6 @@ android:summary="@string/git_rev" android:enabled="false" /> - - Date: Mon, 26 Nov 2012 03:08:54 -0800 Subject: [PATCH 057/146] don't crash on malformed invite URL --- .../eehouse/android/xw4/DispatchNotify.java | 61 ++++++++++--------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DispatchNotify.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DispatchNotify.java index 57d674d62..0e0d8f9ef 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DispatchNotify.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DispatchNotify.java @@ -66,36 +66,41 @@ public class DispatchNotify extends Activity { } } else if ( null != data ) { // relay invite redirected URL case NetLaunchInfo nli = new NetLaunchInfo( data ); - long rowid = DBUtils.getRowIDForOpen( this, nli ); - if ( DBUtils.ROWID_NOTFOUND == rowid ) { - boolean haveDict; - if ( null == nli.dict ) { // can only test for language support - haveDict = - 0 < DictLangCache.getHaveLang( this, nli.lang ).length; - } else { - haveDict = DictLangCache.haveDict( this, nli.lang, nli.dict ); - } - if ( haveDict ) { - if ( !tryHandle( data ) ) { - mustLaunch = true; + if ( null != nli && nli.isValid() ) { + long rowid = DBUtils.getRowIDForOpen( this, nli ); + if ( DBUtils.ROWID_NOTFOUND == rowid ) { + boolean haveDict; + if ( null == nli.dict ) { // can only test for language support + haveDict = + 0 < DictLangCache.getHaveLang( this, + nli.lang ).length; + } else { + haveDict = + DictLangCache.haveDict( this, nli.lang, nli.dict ); + } + if ( haveDict ) { + if ( !tryHandle( data ) ) { + mustLaunch = true; + } + } else { + Intent intent = + MultiService.makeMissingDictIntent( this, nli ); + intent.putExtra( MultiService.OWNER, + MultiService.OWNER_RELAY ); + // do we have gameID? + MultiService. + postMissingDictNotification( this, intent, + nli.inviteID + .hashCode() ); } } else { - Intent intent = MultiService.makeMissingDictIntent( this, - nli ); - intent.putExtra( MultiService.OWNER, - MultiService.OWNER_RELAY ); - // do we have gameID? - MultiService. - postMissingDictNotification( this, intent, - nli.inviteID.hashCode() ); - } - } else { - DbgUtils.logf( "DispatchNotify: dropping duplicate invite" ); - GameSummary summary = DBUtils.getSummary( this, rowid ); - if ( null != summary ) { - gameID = summary.gameID; - if ( !tryHandle( gameID ) ) { - mustLaunch = true; + DbgUtils.logf( "DispatchNotify: dropping duplicate invite" ); + GameSummary summary = DBUtils.getSummary( this, rowid ); + if ( null != summary ) { + gameID = summary.gameID; + if ( !tryHandle( gameID ) ) { + mustLaunch = true; + } } } } From 302ad04ef989462856cb6e53b7b414c3124ecf09 Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 26 Nov 2012 05:41:42 -0800 Subject: [PATCH 058/146] use recommended intent filtering to allow Crosswords to launch in response to http URLs, removing need for custom scheme and one step from the invitation process. --- xwords4/android/XWords4/AndroidManifest.xml | 13 ++++++++++--- xwords4/android/XWords4/res/values/common_rsrc.xml | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/xwords4/android/XWords4/AndroidManifest.xml b/xwords4/android/XWords4/AndroidManifest.xml index 129bca3ea..12f72f4b2 100644 --- a/xwords4/android/XWords4/AndroidManifest.xml +++ b/xwords4/android/XWords4/AndroidManifest.xml @@ -115,15 +115,22 @@ - + - + + + + + + + + diff --git a/xwords4/android/XWords4/res/values/common_rsrc.xml b/xwords4/android/XWords4/res/values/common_rsrc.xml index e354646b6..80c92487a 100644 --- a/xwords4/android/XWords4/res/values/common_rsrc.xml +++ b/xwords4/android/XWords4/res/values/common_rsrc.xml @@ -104,7 +104,7 @@ http://eehouse.org/and_wordlists - //%1$s/newgame.php + //%1$s/and Update checks URL http://eehouse.org/xw4/info.py From 9130c8da502d0da7579f22945035094d2f098db0 Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 26 Nov 2012 07:40:45 -0800 Subject: [PATCH 059/146] download and install upgrade .apk files where possible. Download is via the existing DictImportActivity, which should also be used for dict upgrade downloads to keep the UI consistent. --- .../android/xw4/DictImportActivity.java | 101 ++++++++++++++---- .../org/eehouse/android/xw4/DictUtils.java | 2 +- .../android/xw4/UpdateCheckReceiver.java | 29 ++++- .../src/org/eehouse/android/xw4/Utils.java | 27 +++++ .../org/eehouse/android/xw4/XWConstants.java | 3 + 5 files changed, 135 insertions(+), 27 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictImportActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictImportActivity.java index cb2cdd638..b173800f6 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictImportActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictImportActivity.java @@ -21,31 +21,43 @@ package org.eehouse.android.xw4; import android.app.Activity; -import android.os.Bundle; -import android.os.AsyncTask; import android.content.Intent; import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; import android.view.Window; import android.widget.ProgressBar; import android.widget.TextView; -import java.io.InputStream; import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; import java.net.URI; import junit.framework.Assert; public class DictImportActivity extends XWActivity { + public static final String APK_EXTRA = "APK"; + private class DownloadFilesTask extends AsyncTask { private String m_saved = null; + private boolean m_isApp = false; + private File m_appFile = null; + + public DownloadFilesTask( boolean isApp ) + { + super(); + m_isApp = isApp; + } + @Override protected Long doInBackground( Uri... uris ) { m_saved = null; + m_appFile = null; int count = uris.length; Assert.assertTrue( 1 == count ); - long totalSize = 0; for ( int ii = 0; ii < count; ii++ ) { Uri uri = uris[ii]; DbgUtils.logf( "trying %s", uri ); @@ -55,7 +67,12 @@ public class DictImportActivity extends XWActivity { uri.getSchemeSpecificPart(), uri.getFragment() ); InputStream is = jUri.toURL().openStream(); - m_saved = saveDict( is, uri.getPath() ); + String name = basename( uri.getPath() ); + if ( m_isApp ) { + m_appFile = saveToDownloads( is, name ); + } else { + m_saved = saveDict( is, name ); + } is.close(); } catch ( java.net.URISyntaxException use ) { DbgUtils.loge( use ); @@ -65,7 +82,7 @@ public class DictImportActivity extends XWActivity { DbgUtils.loge( ioe ); } } - return totalSize; + return new Long(0); } @Override @@ -77,6 +94,10 @@ public class DictImportActivity extends XWActivity { XWPrefs.getDefaultLoc( DictImportActivity.this ); DictLangCache.inval( DictImportActivity.this, m_saved, loc, true ); + } else if ( null != m_appFile ) { + // launch the installer + Intent intent = Utils.makeInstallIntent( m_appFile ); + startActivity( intent ); } finish(); } @@ -86,6 +107,7 @@ public class DictImportActivity extends XWActivity { protected void onCreate( Bundle savedInstanceState ) { super.onCreate( savedInstanceState ); + DownloadFilesTask dft = null; requestWindowFeature( Window.FEATURE_LEFT_ICON ); setContentView( R.layout.import_dict ); @@ -96,27 +118,60 @@ public class DictImportActivity extends XWActivity { Intent intent = getIntent(); Uri uri = intent.getData(); - if ( null != uri) { - if ( null != intent.getType() - && intent.getType().equals( "application/x-xwordsdict" ) ) { - DbgUtils.logf( "based on MIME type" ); - new DownloadFilesTask().execute( uri ); - } else if ( uri.toString().endsWith( XWConstants.DICT_EXTN ) ) { - String txt = getString( R.string.downloading_dictf, - basename( uri.getPath()) ); - TextView view = (TextView)findViewById( R.id.dwnld_message ); - view.setText( txt ); - new DownloadFilesTask().execute( uri ); - } else { - DbgUtils.logf( "bogus intent: %s/%s", intent.getType(), uri ); - finish(); - } + if ( null == uri ) { + String url = intent.getStringExtra( APK_EXTRA ); + if ( null != url ) { + dft = new DownloadFilesTask( true ); + uri = Uri.parse(url); + } + } else if ( null != intent.getType() + && intent.getType().equals( "application/x-xwordsdict" ) ) { + dft = new DownloadFilesTask( false ); + } else if ( uri.toString().endsWith( XWConstants.DICT_EXTN ) ) { + String txt = getString( R.string.downloading_dictf, + basename( uri.getPath()) ); + TextView view = (TextView)findViewById( R.id.dwnld_message ); + view.setText( txt ); + dft = new DownloadFilesTask( false ); + } + + if ( null == dft ) { + finish(); + } else { + dft.execute( uri ); } } - private String saveDict( InputStream inputStream, String path ) + private File saveToDownloads( InputStream is, String name ) + { + boolean success = false; + File appFile = new File( DictUtils.getDownloadDir( this ), name ); + Assert.assertNotNull( appFile ); + + byte[] buf = new byte[1024*4]; + try { + FileOutputStream fos = new FileOutputStream( appFile ); + int nRead; + while ( 0 <= (nRead = is.read( buf, 0, buf.length )) ) { + fos.write( buf, 0, nRead ); + } + fos.close(); + success = true; + } catch ( java.io.FileNotFoundException fnf ) { + DbgUtils.loge( fnf ); + } catch ( java.io.IOException ioe ) { + DbgUtils.loge( ioe ); + } + + if ( !success ) { + appFile.delete(); + appFile = null; + } + return appFile; + } + + private String saveDict( InputStream inputStream, String name ) { - String name = basename( path ); DictUtils.DictLoc loc = XWPrefs.getDefaultLoc( this ); if ( !DictUtils.saveDict( this, inputStream, name, loc ) ) { name = null; diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictUtils.java index bfef92453..126410cfd 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictUtils.java @@ -566,7 +566,7 @@ public class DictUtils { return null != getDownloadDir( context ); } - private static File getDownloadDir( Context context ) + public static File getDownloadDir( Context context ) { File result = null; if ( haveWriteableSD() ) { diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/UpdateCheckReceiver.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/UpdateCheckReceiver.java index 6334230e4..f91908d4c 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/UpdateCheckReceiver.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/UpdateCheckReceiver.java @@ -29,6 +29,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.net.Uri; import android.os.SystemClock; +import java.io.File; import java.util.ArrayList; import java.util.List; @@ -174,11 +175,33 @@ public class UpdateCheckReceiver extends BroadcastReceiver { if ( jobj.has( k_APP ) ) { JSONObject app = jobj.getJSONObject( k_APP ); if ( app.has( k_URL ) ) { - String url = app.getString( k_URL ); ApplicationInfo ai = pm.getApplicationInfo( packageName, 0); String label = pm.getApplicationLabel( ai ).toString(); - Intent intent = - new Intent( Intent.ACTION_VIEW, Uri.parse(url) ); + + // If there's a download dir AND an installer + // app, handle this ourselves. Otherwise just + // launch the browser + boolean useBrowser; + File downloads = DictUtils.getDownloadDir( context ); + if ( null == downloads ) { + useBrowser = true; + } else { + File tmp = new File( downloads, + "xx" + XWConstants.APK_EXTN ); + useBrowser = !Utils.canInstall( context, tmp ); + } + + Intent intent; + String url = app.getString( k_URL ); + if ( useBrowser ) { + intent = new Intent( Intent.ACTION_VIEW, + Uri.parse(url) ); + } else { + intent = new Intent( context, + DictImportActivity.class ); + intent.putExtra( DictImportActivity.APK_EXTRA, url ); + } + String title = Utils.format( context, R.string.new_app_availf, label ); String body = context.getString( R.string.new_app_avail ); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java index 9ef369196..bc823efc0 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java @@ -32,6 +32,8 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences.Editor; import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.database.Cursor; import android.net.Uri; @@ -43,8 +45,10 @@ import android.widget.CheckBox; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; +import java.io.File; import java.util.Date; import java.util.HashMap; +import java.util.List; import java.util.Random; import junit.framework.Assert; @@ -419,6 +423,29 @@ public class Utils { return null == s_appVersion? 0 : s_appVersion; } + public static Intent makeInstallIntent( File file ) + { + Uri uri = Uri.parse( "file:/" + file.getPath() ); + Intent intent = new Intent( Intent.ACTION_VIEW ); + intent.setDataAndType( uri, XWConstants.APK_TYPE ); + intent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK ); + return intent; + } + + // Return whether there's an app installed that can install + public static boolean canInstall( Context context, File path ) + { + boolean result = false; + PackageManager pm = context.getPackageManager(); + Intent intent = makeInstallIntent( path ); + List doers = + pm.queryIntentActivities( intent, + PackageManager.MATCH_DEFAULT_ONLY ); + result = 0 < doers.size(); + DbgUtils.logf( "canInstall()=>%b", result ); + return result; + } + private static void setFirstBootStatics( Context context ) { int thisVersion = getAppVersion( context ); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWConstants.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWConstants.java index 9dee0732b..456f8fff6 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWConstants.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWConstants.java @@ -23,4 +23,7 @@ package org.eehouse.android.xw4; public interface XWConstants { public static final String GAME_EXTN = ".xwg"; public static final String DICT_EXTN = ".xwd"; + public static final String APK_EXTN = ".apk"; + public static final String APK_TYPE = + "application/vnd.android.package-archive"; } From e590db5f3ff4cd0b2f3b609488d3400d46eb3cf0 Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 26 Nov 2012 08:05:05 -0800 Subject: [PATCH 060/146] merge in the db part of 82c39489f0b8b755b5e53f38ae1ca6b205c446fe (android_groups branch, local only right now), but not the UI part since it won't make the next release. --- .../XWords4/res/values/common_rsrc.xml | 1 + .../android/XWords4/res/values/strings.xml | 3 ++ .../src/org/eehouse/android/xw4/DBHelper.java | 47 ++++++++++++++-- .../src/org/eehouse/android/xw4/DBUtils.java | 53 +++++++++---------- .../src/org/eehouse/android/xw4/XWPrefs.java | 30 +++++++++++ 5 files changed, 103 insertions(+), 31 deletions(-) diff --git a/xwords4/android/XWords4/res/values/common_rsrc.xml b/xwords4/android/XWords4/res/values/common_rsrc.xml index 80c92487a..c7369a101 100644 --- a/xwords4/android/XWords4/res/values/common_rsrc.xml +++ b/xwords4/android/XWords4/res/values/common_rsrc.xml @@ -71,6 +71,7 @@ key_gcmvers_regid key_relay_regid key_checked_sms + key_default_group key_notagain_sync key_notagain_chat diff --git a/xwords4/android/XWords4/res/values/strings.xml b/xwords4/android/XWords4/res/values/strings.xml index f3da657ee..5dd2c83ef 100644 --- a/xwords4/android/XWords4/res/values/strings.xml +++ b/xwords4/android/XWords4/res/values/strings.xml @@ -2137,4 +2137,7 @@ (Not in external/sdcard memory) Downloads Directory + + My games + New games diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java index 24e406b87..b44021f29 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java @@ -20,9 +20,10 @@ package org.eehouse.android.xw4; +import android.content.ContentValues; import android.content.Context; -import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; public class DBHelper extends SQLiteOpenHelper { @@ -30,8 +31,9 @@ public class DBHelper extends SQLiteOpenHelper { public static final String TABLE_NAME_OBITS = "obits"; public static final String TABLE_NAME_DICTBROWSE = "dictbrowse"; public static final String TABLE_NAME_DICTINFO = "dictinfo"; + public static final String TABLE_NAME_GROUPS = "groups"; private static final String DB_NAME = "xwdb"; - private static final int DB_VERSION = 14; + private static final int DB_VERSION = 15; public static final String GAME_NAME = "GAME_NAME"; public static final String NUM_MOVES = "NUM_MOVES"; @@ -60,7 +62,7 @@ public class DBHelper extends SQLiteOpenHelper { public static final String SEED = "SEED"; public static final String SMSPHONE = "SMSPHONE"; public static final String LASTMOVE = "LASTMOVE"; - + public static final String GROUPID = "GROUPID"; public static final String DICTNAME = "DICTNAME"; public static final String MD5SUM = "MD5SUM"; @@ -76,6 +78,11 @@ public class DBHelper extends SQLiteOpenHelper { public static final String CREATE_TIME = "CREATE_TIME"; public static final String LASTPLAY_TIME = "LASTPLAY_TIME"; + public static final String GROUPNAME = "GROUPNAME"; + public static final String EXPANDED = "EXPANDED"; + + private Context m_context; + private static final String[] s_summaryColsAndTypes = { GAME_NAME, "TEXT" ,NUM_MOVES, "INTEGER" @@ -99,6 +106,7 @@ public class DBHelper extends SQLiteOpenHelper { ,GAMEID, "INTEGER" ,REMOTEDEVS, "TEXT" ,LASTMOVE, "INTEGER DEFAULT 0" + ,GROUPID, "INTEGER" // HASMSGS: sqlite doesn't have bool; use 0 and 1 ,HASMSGS, "INTEGER DEFAULT 0" ,CONTRACTED, "INTEGER DEFAULT 0" @@ -131,9 +139,15 @@ public class DBHelper extends SQLiteOpenHelper { ,ITERPREFIX, "TEXT" }; + private static final String[] s_groupsSchema = { + GROUPNAME, "TEXT" + ,EXPANDED, "INTEGER(1)" + }; + public DBHelper( Context context ) { super( context, DB_NAME, null, DB_VERSION ); + m_context = context; } public static String getDBName() @@ -148,6 +162,7 @@ public class DBHelper extends SQLiteOpenHelper { createTable( db, TABLE_NAME_OBITS, s_obitsColsAndTypes ); createTable( db, TABLE_NAME_DICTINFO, s_dictInfoColsAndTypes ); createTable( db, TABLE_NAME_DICTBROWSE, s_dictBrowseColsAndTypes ); + createGroupsTable( db ); } @Override @@ -177,9 +192,11 @@ public class DBHelper extends SQLiteOpenHelper { case 12: createTable( db, TABLE_NAME_DICTINFO, s_dictInfoColsAndTypes ); createTable( db, TABLE_NAME_DICTBROWSE, s_dictBrowseColsAndTypes ); - case 13: addSumColumn( db, LASTMOVE ); + case 14: + addSumColumn( db, GROUPID ); + createGroupsTable( db ); // nothing yet break; default: @@ -221,4 +238,26 @@ public class DBHelper extends SQLiteOpenHelper { db.execSQL( query.toString() ); } + private void createGroupsTable( SQLiteDatabase db ) + { + createTable( db, TABLE_NAME_GROUPS, s_groupsSchema ); + + // Create an empty group name + ContentValues values = new ContentValues(); + values.put( GROUPNAME, m_context.getString(R.string.group_cur_games) ); + values.put( EXPANDED, 1 ); + long curGroup = db.insert( TABLE_NAME_GROUPS, null, values ); + values = new ContentValues(); + values.put( GROUPNAME, m_context.getString(R.string.group_new_games) ); + values.put( EXPANDED, 0 ); + long newGroup = db.insert( TABLE_NAME_GROUPS, null, values ); + + // place all existing games in the initial unnamed group + values = new ContentValues(); + values.put( GROUPID, curGroup ); + db.update( DBHelper.TABLE_NAME_SUM, values, null, null ); + + XWPrefs.setDefaultNewGameGroup( m_context, newGroup ); + } + } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java index 8b9473d5f..72b8338e4 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java @@ -364,18 +364,9 @@ public class DBUtils { private static void setInt( long rowid, String column, int value ) { - synchronized( s_dbHelper ) { - SQLiteDatabase db = s_dbHelper.getWritableDatabase(); - - String selection = String.format( ROW_ID_FMT, rowid ); - ContentValues values = new ContentValues(); - values.put( column, value ); - - int result = db.update( DBHelper.TABLE_NAME_SUM, - values, selection, null ); - Assert.assertTrue( result == 1 ); - db.close(); - } + ContentValues values = new ContentValues(); + values.put( column, value ); + updateRow( null, DBHelper.TABLE_NAME_SUM, rowid, values ); } public static void setMsgFlags( long rowid, int flags ) @@ -693,6 +684,8 @@ public class DBUtils { long timestamp = new Date().getTime(); values.put( DBHelper.CREATE_TIME, timestamp ); values.put( DBHelper.LASTPLAY_TIME, timestamp ); + values.put( DBHelper.GROUPID, + XWPrefs.getDefaultNewGameGroup( context ) ); long rowid = db.insert( DBHelper.TABLE_NAME_SUM, null, values ); @@ -840,21 +833,9 @@ public class DBUtils { public static void setName( Context context, long rowid, String name ) { - initDB( context ); - synchronized( s_dbHelper ) { - SQLiteDatabase db = s_dbHelper.getWritableDatabase(); - - String selection = String.format( ROW_ID_FMT, rowid ); - ContentValues values = new ContentValues(); - values.put( DBHelper.GAME_NAME, name ); - - int result = db.update( DBHelper.TABLE_NAME_SUM, - values, selection, null ); - db.close(); - if ( 0 == result ) { - DbgUtils.logf( "setName(%d,%s) failed", rowid, name ); - } - } + ContentValues values = new ContentValues(); + values.put( DBHelper.GAME_NAME, name ); + updateRow( context, DBHelper.TABLE_NAME_SUM, rowid, values ); } public static HistoryPair[] getChatHistory( Context context, long rowid ) @@ -874,6 +855,23 @@ public class DBUtils { return result; } + private static void updateRow( Context context, String table, + long rowid, ContentValues values ) + { + initDB( context ); + synchronized( s_dbHelper ) { + SQLiteDatabase db = s_dbHelper.getWritableDatabase(); + + String selection = String.format( ROW_ID_FMT, rowid ); + + int result = db.update( table, values, selection, null ); + db.close(); + if ( 0 == result ) { + DbgUtils.logf( "updateRow failed" ); + } + } + } + private static String getChatHistoryStr( Context context, long rowid ) { String result = null; @@ -1220,6 +1218,7 @@ public class DBUtils { private static void initDB( Context context ) { if ( null == s_dbHelper ) { + Assert.assertNotNull( context ); s_dbHelper = new DBHelper( context ); // force any upgrade s_dbHelper.getWritableDatabase().close(); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java index 4ac4561c0..c6ef8481a 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java @@ -151,6 +151,25 @@ public class XWPrefs { editor.commit(); } + public static long getPrefsLong( Context context, int keyID, + long defaultValue ) + { + String key = context.getString( keyID ); + SharedPreferences sp = PreferenceManager + .getDefaultSharedPreferences( context ); + return sp.getLong( key, defaultValue ); + } + + public static void setPrefsLong( Context context, int keyID, long newVal ) + { + SharedPreferences sp = PreferenceManager + .getDefaultSharedPreferences( context ); + SharedPreferences.Editor editor = sp.edit(); + String key = context.getString( keyID ); + editor.putLong( key, newVal ); + editor.commit(); + } + public static void setClosedLangs( Context context, String[] langs ) { setPrefsString( context, R.string.key_closed_langs, @@ -275,6 +294,17 @@ public class XWPrefs { return getPrefsBoolean( context, R.string.key_default_loc, true ); } + public static long getDefaultNewGameGroup( Context context ) + { + return getPrefsLong( context, R.string.key_default_group, + DBUtils.ROWID_NOTFOUND ); + } + + public static void setDefaultNewGameGroup( Context context, long val ) + { + setPrefsLong( context, R.string.key_default_group, val ); + } + protected static String getPrefsString( Context context, int keyID ) { String key = context.getString( keyID ); From b4334380220b64fb300fd15c2154166f2f338ed8 Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 26 Nov 2012 18:47:35 -0800 Subject: [PATCH 061/146] add message via GCM to check for wordlist/app upgrades --- .../eehouse/android/xw4/GCMIntentService.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java index 1fc22e503..532e70329 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java @@ -56,7 +56,18 @@ public class GCMIntentService extends GCMBaseIntentService { @Override protected void onMessage( Context context, Intent intent ) { - String value = intent.getStringExtra( "msg" ); + String value; + + value = intent.getStringExtra( "getMoves" ); + if ( null != value && Boolean.parseBoolean( value ) ) { + RelayReceiver.RestartTimer( context, true ); + } + value = intent.getStringExtra( "checkUpdates" ); + if ( null != value && Boolean.parseBoolean( value ) ) { + UpdateCheckReceiver.checkVersions( context, true ); + } + + value = intent.getStringExtra( "msg" ); if ( null != value ) { String title = intent.getStringExtra( "title" ); if ( null != title ) { @@ -64,11 +75,6 @@ public class GCMIntentService extends GCMBaseIntentService { Utils.postNotification( context, null, title, value, code ); } } - - value = intent.getStringExtra( "getMoves" ); - if ( null != value && Boolean.parseBoolean( value ) ) { - RelayReceiver.RestartTimer( context, true ); - } } public static void init( Application app ) From b1f54a67ed441c47e8f7f2b47a676a52cdda1648 Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 26 Nov 2012 20:14:32 -0800 Subject: [PATCH 062/146] end path prefix with a /; otherwise Crosswords gets launched to browse the wordlist directory. --- xwords4/android/XWords4/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwords4/android/XWords4/AndroidManifest.xml b/xwords4/android/XWords4/AndroidManifest.xml index 12f72f4b2..c106e32f6 100644 --- a/xwords4/android/XWords4/AndroidManifest.xml +++ b/xwords4/android/XWords4/AndroidManifest.xml @@ -129,7 +129,7 @@ + android:host="eehouse.org" android:pathPrefix="/and/" /> From 30377908df000146079baa74be2a7f039559bc05 Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 26 Nov 2012 20:19:25 -0800 Subject: [PATCH 063/146] Use the downloader with an infinite progress indicator instead of the status-bar notifier (which is removed) for dict downloads. --- .../eehouse/android/xw4/BoardActivity.java | 13 +-- .../android/xw4/DictImportActivity.java | 95 +++++++++++++++++-- .../eehouse/android/xw4/DictsActivity.java | 12 +-- .../org/eehouse/android/xw4/GamesList.java | 15 +-- .../src/org/eehouse/android/xw4/NetUtils.java | 74 --------------- .../eehouse/android/xw4/StatusNotifier.java | 58 ----------- 6 files changed, 108 insertions(+), 159 deletions(-) delete mode 100644 xwords4/android/XWords4/src/org/eehouse/android/xw4/StatusNotifier.java diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java index e45f64e3e..6b2f35f51 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java @@ -57,7 +57,7 @@ import org.eehouse.android.xw4.jni.CurGameInfo.DeviceRole; public class BoardActivity extends XWActivity implements TransportProcs.TPMsgHandler, View.OnClickListener, - NetUtils.DownloadFinishedListener { + DictImportActivity.DownloadFinishedListener { public static final String INTENT_KEY_CHAT = "chat"; @@ -259,10 +259,11 @@ public class BoardActivity extends XWActivity if ( DLG_USEDICT == id ) { setGotGameDict( m_getDict ); } else { - NetUtils.downloadDictInBack( BoardActivity.this, - m_gi.dictLang, - m_getDict, - BoardActivity.this ); + DictImportActivity + .downloadDictInBack( BoardActivity.this, + m_gi.dictLang, + m_getDict, + BoardActivity.this ); } } }; @@ -1051,7 +1052,7 @@ public class BoardActivity extends XWActivity } ////////////////////////////////////////////////// - // NetUtils.DownloadFinishedListener interface + // DictImportActivity.DownloadFinishedListener interface ////////////////////////////////////////////////// public void downloadFinished( final String name, final boolean success ) { diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictImportActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictImportActivity.java index b173800f6..a01e68d43 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictImportActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictImportActivity.java @@ -21,6 +21,7 @@ package org.eehouse.android.xw4; import android.app.Activity; +import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.AsyncTask; @@ -32,15 +33,36 @@ import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.net.URI; +import java.util.HashMap; import junit.framework.Assert; public class DictImportActivity extends XWActivity { + // URIs coming in in intents public static final String APK_EXTRA = "APK"; + private static final String DICT_EXTRA = "XWD"; + + public interface DownloadFinishedListener { + void downloadFinished( String name, boolean success ); + } + + // Track callbacks for downloads. + private static class ListenerData { + public ListenerData( String dictName, DownloadFinishedListener lstnr ) + { + m_dictName = dictName; + m_lstnr = lstnr; + } + public String m_dictName; + public DownloadFinishedListener m_lstnr; + } + private static HashMap s_listeners = + new HashMap(); private class DownloadFilesTask extends AsyncTask { - private String m_saved = null; + private String m_savedDict = null; + private String m_url = null; private boolean m_isApp = false; private File m_appFile = null; @@ -50,10 +72,16 @@ public class DictImportActivity extends XWActivity { m_isApp = isApp; } + public DownloadFilesTask( String url, boolean isApp ) + { + this( isApp ); + m_url = url; + } + @Override protected Long doInBackground( Uri... uris ) { - m_saved = null; + m_savedDict = null; m_appFile = null; int count = uris.length; @@ -71,7 +99,7 @@ public class DictImportActivity extends XWActivity { if ( m_isApp ) { m_appFile = saveToDownloads( is, name ); } else { - m_saved = saveDict( is, name ); + m_savedDict = saveDict( is, name ); } is.close(); } catch ( java.net.URISyntaxException use ) { @@ -89,15 +117,19 @@ public class DictImportActivity extends XWActivity { protected void onPostExecute( Long result ) { DbgUtils.logf( "onPostExecute passed %d", result ); - if ( null != m_saved ) { + if ( null != m_savedDict ) { DictUtils.DictLoc loc = XWPrefs.getDefaultLoc( DictImportActivity.this ); - DictLangCache.inval( DictImportActivity.this, m_saved, + DictLangCache.inval( DictImportActivity.this, m_savedDict, loc, true ); + callListener( m_url, true ); } else if ( null != m_appFile ) { // launch the installer Intent intent = Utils.makeInstallIntent( m_appFile ); startActivity( intent ); + } else { + // we failed at something.... + callListener( m_url, false ); } finish(); } @@ -120,9 +152,13 @@ public class DictImportActivity extends XWActivity { Uri uri = intent.getData(); if ( null == uri ) { String url = intent.getStringExtra( APK_EXTRA ); + boolean isApp = null != url; + if ( !isApp ) { + url = intent.getStringExtra( DICT_EXTRA ); + } if ( null != url ) { - dft = new DownloadFilesTask( true ); - uri = Uri.parse(url); + dft = new DownloadFilesTask( url, isApp ); + uri = Uri.parse( url ); } } else if ( null != intent.getType() && intent.getType().equals( "application/x-xwordsdict" ) ) { @@ -132,7 +168,7 @@ public class DictImportActivity extends XWActivity { basename( uri.getPath()) ); TextView view = (TextView)findViewById( R.id.dwnld_message ); view.setText( txt ); - dft = new DownloadFilesTask( false ); + dft = new DownloadFilesTask( uri.toString(), false ); } if ( null == dft ) { @@ -183,6 +219,49 @@ public class DictImportActivity extends XWActivity { { return new File(path).getName(); } + + private static void rememberListener( String url, String name, + DownloadFinishedListener lstnr ) + { + ListenerData ld = new ListenerData( name, lstnr ); + synchronized( s_listeners ) { + s_listeners.put( url, ld ); + } + } + + private static void callListener( String url, boolean success ) + { + if ( null != url ) { + ListenerData ld; + synchronized( s_listeners ) { + ld = s_listeners.get( url ); + if ( null != ld ) { + s_listeners.remove( url ); + } + } + if ( null != ld ) { + ld.m_lstnr.downloadFinished( ld.m_dictName, success ); + } + } + } + + public static void downloadDictInBack( Context context, int lang, + String name, + DownloadFinishedListener lstnr ) + { + String url = Utils.makeDictUrl( context, lang, name ); + if ( null != lstnr ) { + rememberListener( url, name, lstnr ); + } + downloadDictInBack( context, url ); + } + + public static void downloadDictInBack( Context context, String url ) + { + Intent intent = new Intent( context, DictImportActivity.class ); + intent.putExtra( DICT_EXTRA, url ); + context.startActivity( intent ); + } } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictsActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictsActivity.java index e56b37559..b3f3331ff 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictsActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictsActivity.java @@ -61,7 +61,7 @@ import org.eehouse.android.xw4.DictUtils.DictLoc; public class DictsActivity extends ExpandableListActivity implements View.OnClickListener, XWListItem.DeleteCallback, MountEventReceiver.SDCardNotifiee, DlgDelegate.DlgClickNotify, - NetUtils.DownloadFinishedListener { + DictImportActivity.DownloadFinishedListener { private static final String DICT_DOLAUNCH = "do_launch"; private static final String DICT_LANG_EXTRA = "use_lang"; @@ -339,8 +339,9 @@ public class DictsActivity extends ExpandableListActivity String name = intent.getStringExtra( MultiService.DICT ); m_launchedForMissing = true; m_handler = new Handler(); - NetUtils.downloadDictInBack( DictsActivity.this, lang, - name, DictsActivity.this ); + DictImportActivity + .downloadDictInBack( DictsActivity.this, lang, + name, DictsActivity.this ); } }; lstnr2 = new OnClickListener() { @@ -555,10 +556,9 @@ public class DictsActivity extends ExpandableListActivity { int loci = intent.getIntExtra( UpdateCheckReceiver.NEW_DICT_LOC, 0 ); if ( 0 < loci ) { - DictLoc loc = DictLoc.values()[loci]; String url = intent.getStringExtra( UpdateCheckReceiver.NEW_DICT_URL ); - NetUtils.downloadDictInBack( this, url, loc, null ); + DictImportActivity.downloadDictInBack( this, url ); finish(); } } @@ -769,7 +769,7 @@ public class DictsActivity extends ExpandableListActivity launchAndDownload( activity, 0, null ); } - // NetUtils.DownloadFinishedListener interface + // DictImportActivity.DownloadFinishedListener interface public void downloadFinished( String name, final boolean success ) { if ( m_launchedForMissing ) { diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java index b9975a6e6..bebe25437 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java @@ -54,7 +54,7 @@ public class GamesList extends XWListActivity implements DispatchNotify.HandleRelaysIface, DBUtils.DBChangeListener, GameListAdapter.LoadItemCB, - NetUtils.DownloadFinishedListener { + DictImportActivity.DownloadFinishedListener { private static final int WARN_NODICT = DlgDelegate.DIALOG_LAST + 1; private static final int WARN_NODICT_SUBST = WARN_NODICT + 1; @@ -103,15 +103,16 @@ public class GamesList extends XWListActivity case WARN_NODICT_SUBST: lstnr = new DialogInterface.OnClickListener() { public void onClick( DialogInterface dlg, int item ) { - // just do one + // no name, so user must pick if ( null == m_missingDictName ) { DictsActivity.launchAndDownload( GamesList.this, m_missingDictLang ); } else { - NetUtils.downloadDictInBack( GamesList.this, - m_missingDictLang, - m_missingDictName, - GamesList.this ); + DictImportActivity + .downloadDictInBack( GamesList.this, + m_missingDictLang, + m_missingDictName, + GamesList.this ); } } }; @@ -623,7 +624,7 @@ public class GamesList extends XWListActivity return handled; } - // NetUtils.DownloadFinishedListener interface + // DictImportActivity.DownloadFinishedListener interface public void downloadFinished( String name, final boolean success ) { post( new Runnable() { diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetUtils.java index 64c12f3d7..4f900a76d 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetUtils.java @@ -21,17 +21,11 @@ package org.eehouse.android.xw4; import android.content.Context; -import android.os.Handler; -import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.HttpURLConnection; import java.net.InetAddress; import java.net.Socket; -import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -50,10 +44,6 @@ public class NetUtils { public static byte PRX_GET_MSGS = 4; public static byte PRX_PUT_MSGS = 5; - public interface DownloadFinishedListener { - void downloadFinished( String name, boolean success ); - } - public static Socket makeProxySocket( Context context, int timeoutMillis ) { @@ -273,68 +263,4 @@ public class NetUtils { DbgUtils.logf( "sendToRelay: null msgs" ); } } // sendToRelay - - static void downloadDictInBack( Context context, int lang, String name, - DownloadFinishedListener lstnr ) - { - DictUtils.DictLoc loc = XWPrefs.getDefaultLoc( context ); - downloadDictInBack( context, lang, name, loc, lstnr ); - } - - static void downloadDictInBack( Context context, int lang, String name, - DictUtils.DictLoc loc, - DownloadFinishedListener lstnr ) - { - String url = Utils.makeDictUrl( context, lang, name ); - downloadDictInBack( context, url, loc, lstnr ); - } - - static void downloadDictInBack( final Context context, final String urlStr, - final DictUtils.DictLoc loc, - final DownloadFinishedListener lstnr ) - { - String tmp = Utils.dictFromURL( context, urlStr ); - final String name = DictUtils.removeDictExtn( tmp ); - String msg = context.getString( R.string.downloadingf, name ); - final StatusNotifier sno = - new StatusNotifier( context, msg, R.string.download_done ); - - new Thread( new Runnable() { - public void run() { - boolean success = false; - HttpURLConnection urlConn = null; - try { - URL url = new URL( urlStr ); - urlConn = (HttpURLConnection)url.openConnection(); - InputStream in = new - BufferedInputStream( urlConn.getInputStream(), - 1024*8 ); - success = DictUtils.saveDict( context, in, - name, loc ); - - } catch ( java.net.MalformedURLException mue ) { - DbgUtils.loge( mue ); - } catch ( java.io.IOException ioe ) { - DbgUtils.loge( ioe ); - } catch ( Exception ce ) { - // E.g. java.net.ConnectException; we failed - // to download, ok. - } finally { - if ( null != urlConn ) { - urlConn.disconnect(); - } - } - - sno.close(); - - if ( success ) { - DictLangCache.inval( context, name, loc, true ); - } - if ( null != lstnr ) { - lstnr.downloadFinished( name, success ); - } - } - } ).start(); - } - } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/StatusNotifier.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/StatusNotifier.java deleted file mode 100644 index 6fa126c6e..000000000 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/StatusNotifier.java +++ /dev/null @@ -1,58 +0,0 @@ -/* -*- compile-command: "cd ../../../../../; ant debug install"; -*- */ -/* - * Copyright 2012 by Eric House (xwords@eehouse.org). All rights - * reserved. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -package org.eehouse.android.xw4; - -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; - -public class StatusNotifier { - private int m_id; - private NotificationManager m_mgr; - private Context m_context; - - public StatusNotifier( Context context, String msg, int id ) - { - m_context = context; - m_id = id; - - Notification notification = - new Notification( R.drawable.icon48x48, msg, - System.currentTimeMillis() ); - notification.flags = notification.flags |= Notification.FLAG_AUTO_CANCEL; - PendingIntent pi = PendingIntent.getActivity( context, 0, - new Intent(), 0 ); - notification.setLatestEventInfo( context, "", "", pi ); - - m_mgr = (NotificationManager) - context.getSystemService( Context.NOTIFICATION_SERVICE ); - m_mgr.notify( id, notification ); - } - - // Will likely be called from background thread - public void close() - { - m_mgr.cancel( m_id ); - } - -} From f3ecfa569a24f027a404db9594db834f76912702 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 27 Nov 2012 07:02:44 -0800 Subject: [PATCH 064/146] get rid of the second URL in invites that's meant for those without the app installed. It's confusing, especially in SMS case, and the page they see when they don't have the app installed can explain how to install it. --- xwords4/android/XWords4/res/values/strings.xml | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/xwords4/android/XWords4/res/values/strings.xml b/xwords4/android/XWords4/res/values/strings.xml index 5dd2c83ef..514732fb1 100644 --- a/xwords4/android/XWords4/res/values/strings.xml +++ b/xwords4/android/XWords4/res/values/strings.xml @@ -1229,27 +1229,18 @@ encodings for the greater-than and less-than symbols which are not legal in xml strings.)--> \u003ca href=\"%1$s\"\u003ETap - here\u003c/a\u003E (or the raw link below) to accept my invitation and + here\u003c/a\u003E (or the full link below) to accept my invitation and join this game. \u003cbr \\\u003E \u003cbr \\\u003E - (raw link: %1$s) - \u003cbr \\\u003E - \u003cbr \\\u003E - \u003ca href=\"http://eehouse.org/market_redir.php\"\u003E Tap - here\u003c/a\u003E (or the raw link below) to - install Crosswords if you haven\'t already. - \u003cbr \\\u003E - \u003cbr \\\u003E - (raw link: http://eehouse.org/market_redir.php) + (full link: %1$s) - Play Crosswords? Join this game: %1$s - . (But install Crosswords http://eehouse.org/market_redir.php - first if you haven\'t.) + Let\'s play Crosswords! Join this game: + %1$s . + eehouse.org http://eehouse.org/and_wordlists //%1$s/and Update checks URL diff --git a/xwords4/android/XWords4/res/xml/xwprefs.xml b/xwords4/android/XWords4/res/xml/xwprefs.xml index 9a5a3bf3c..bc436be4f 100644 --- a/xwords4/android/XWords4/res/xml/xwprefs.xml +++ b/xwords4/android/XWords4/res/xml/xwprefs.xml @@ -324,11 +324,6 @@ android:defaultValue="10998" android:numeric="decimal" /> - Date: Tue, 27 Nov 2012 07:22:03 -0800 Subject: [PATCH 066/146] add logging giving size and md5sum of downloaded .apk. --- .../android/xw4/DictImportActivity.java | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictImportActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictImportActivity.java index a01e68d43..51e7a1dcb 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictImportActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictImportActivity.java @@ -33,6 +33,7 @@ import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.net.URI; +import java.security.MessageDigest; import java.util.HashMap; import junit.framework.Assert; @@ -183,16 +184,34 @@ public class DictImportActivity extends XWActivity { boolean success = false; File appFile = new File( DictUtils.getDownloadDir( this ), name ); Assert.assertNotNull( appFile ); - + + // Get rid of this after debugging download/install problems + MessageDigest md; + try { + md = MessageDigest.getInstance("MD5"); + } catch( Exception ex ) { + md = null; + } + byte[] buf = new byte[1024*4]; try { + int nTotal = 0; FileOutputStream fos = new FileOutputStream( appFile ); int nRead; while ( 0 <= (nRead = is.read( buf, 0, buf.length )) ) { fos.write( buf, 0, nRead ); + if ( null != md ) { + md.update( buf, 0, nRead ); + } + nTotal += nRead; } fos.close(); success = true; + + String sum = null == md ? "" + : Utils.digestToString( md.digest() ); + DbgUtils.logf( "saveToDownloads: saved %d bytes to %s, md5sum=%s", + nTotal, appFile.getPath(), sum ); } catch ( java.io.FileNotFoundException fnf ) { DbgUtils.loge( fnf ); } catch ( java.io.IOException ioe ) { From bf970b6dd958cd8ad0fbf3e11c4e85354c0eb80e Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 27 Nov 2012 07:53:48 -0800 Subject: [PATCH 067/146] remove some logging --- .../XWords4/src/org/eehouse/android/xw4/SMSService.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java index 18cbd8c88..a78ba6a7d 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java @@ -664,11 +664,9 @@ public class SMSService extends Service { @Override public void onReceive(Context arg0, Intent arg1) { - DbgUtils.logf( "got MSG_DELIVERED" ); switch ( getResultCode() ) { case Activity.RESULT_OK: sendResult( MultiEvent.SMS_SEND_OK ); - DbgUtils.logf( "SUCCESS!!!" ); break; case SmsManager.RESULT_ERROR_RADIO_OFF: DbgUtils.showf( SMSService.this, "NO RADIO!!!" ); @@ -688,7 +686,6 @@ public class SMSService extends Service { @Override public void onReceive(Context arg0, Intent arg1) { - DbgUtils.logf( "got MSG_DELIVERED" ); if ( Activity.RESULT_OK == getResultCode() ) { DbgUtils.logf( "SUCCESS!!!" ); } else { From 7c5a59beb3147f8cad6afae4cf5920d4286ee1fe Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 27 Nov 2012 18:31:14 -0800 Subject: [PATCH 068/146] remind self of Blaze's download directory --- xwords4/android/XWords4/res/xml/xwprefs.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/xwords4/android/XWords4/res/xml/xwprefs.xml b/xwords4/android/XWords4/res/xml/xwprefs.xml index bc436be4f..86fdece79 100644 --- a/xwords4/android/XWords4/res/xml/xwprefs.xml +++ b/xwords4/android/XWords4/res/xml/xwprefs.xml @@ -299,6 +299,11 @@ android:summary="Menuitems etc." android:defaultValue="false" /> + + Date: Tue, 27 Nov 2012 18:48:05 -0800 Subject: [PATCH 069/146] fix uri format error that was causing install to fail --- xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java index bc823efc0..288566417 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java @@ -425,7 +425,8 @@ public class Utils { public static Intent makeInstallIntent( File file ) { - Uri uri = Uri.parse( "file:/" + file.getPath() ); + String withScheme = "file://" + file.getPath(); + Uri uri = Uri.parse( withScheme ); Intent intent = new Intent( Intent.ACTION_VIEW ); intent.setDataAndType( uri, XWConstants.APK_TYPE ); intent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK ); From cd197e45307d9bb4db495df2f02df4fa16f804c6 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 27 Nov 2012 18:55:08 -0800 Subject: [PATCH 070/146] use Class instance for synchronization of static methods rather than creating an object only for that purpose. --- .../XWords4/src/org/eehouse/android/xw4/BoardActivity.java | 2 +- .../XWords4/src/org/eehouse/android/xw4/ConnStatusHandler.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java index 6b2f35f51..8cd084c2e 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java @@ -167,7 +167,7 @@ public class BoardActivity extends XWActivity private boolean m_haveInvited = false; private static BoardActivity s_this = null; - private static Object s_thisLocker = new Object(); + private static Class s_thisLocker = BoardActivity.class; public static boolean feedMessage( int gameID, byte[] msg, CommsAddrRec retAddr ) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/ConnStatusHandler.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/ConnStatusHandler.java index 13c973646..8ceb78a32 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/ConnStatusHandler.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/ConnStatusHandler.java @@ -126,7 +126,7 @@ public class ConnStatusHandler { private static HashMap s_records = new HashMap(); - private static Object s_lockObj = new Object(); + private static Class s_lockObj = ConnStatusHandler.class; private static boolean s_needsSave = false; public static void setRect( int left, int top, int right, int bottom ) From baa790a8c3e6bf468455b00312f3145cd255dab5 Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 28 Nov 2012 06:06:42 -0800 Subject: [PATCH 071/146] remove debugging code; make Intent content details private to class. --- .../android/xw4/DictImportActivity.java | 33 +++++++------------ .../android/xw4/UpdateCheckReceiver.java | 5 ++- 2 files changed, 13 insertions(+), 25 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictImportActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictImportActivity.java index 51e7a1dcb..1497e10a4 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictImportActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictImportActivity.java @@ -1,7 +1,7 @@ /* -*- compile-command: "cd ../../../../../; ant debug install"; -*- */ /* - * Copyright 2009-2010 by Eric House (xwords@eehouse.org). All - * rights reserved. + * Copyright 2009-2012 by Eric House (xwords@eehouse.org). All rights + * reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -41,7 +41,7 @@ import junit.framework.Assert; public class DictImportActivity extends XWActivity { // URIs coming in in intents - public static final String APK_EXTRA = "APK"; + private static final String APK_EXTRA = "APK"; private static final String DICT_EXTRA = "XWD"; public interface DownloadFinishedListener { @@ -183,35 +183,16 @@ public class DictImportActivity extends XWActivity { { boolean success = false; File appFile = new File( DictUtils.getDownloadDir( this ), name ); - Assert.assertNotNull( appFile ); - - // Get rid of this after debugging download/install problems - MessageDigest md; - try { - md = MessageDigest.getInstance("MD5"); - } catch( Exception ex ) { - md = null; - } byte[] buf = new byte[1024*4]; try { - int nTotal = 0; FileOutputStream fos = new FileOutputStream( appFile ); int nRead; while ( 0 <= (nRead = is.read( buf, 0, buf.length )) ) { fos.write( buf, 0, nRead ); - if ( null != md ) { - md.update( buf, 0, nRead ); - } - nTotal += nRead; } fos.close(); success = true; - - String sum = null == md ? "" - : Utils.digestToString( md.digest() ); - DbgUtils.logf( "saveToDownloads: saved %d bytes to %s, md5sum=%s", - nTotal, appFile.getPath(), sum ); } catch ( java.io.FileNotFoundException fnf ) { DbgUtils.loge( fnf ); } catch ( java.io.IOException ioe ) { @@ -281,6 +262,14 @@ public class DictImportActivity extends XWActivity { intent.putExtra( DICT_EXTRA, url ); context.startActivity( intent ); } + + public static Intent makeAppDownloadIntent( Context context, String url ) + { + Intent intent = new Intent( context, DictImportActivity.class ); + intent.putExtra( APK_EXTRA, url ); + return intent; + } + } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/UpdateCheckReceiver.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/UpdateCheckReceiver.java index f91908d4c..8b69846d2 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/UpdateCheckReceiver.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/UpdateCheckReceiver.java @@ -197,9 +197,8 @@ public class UpdateCheckReceiver extends BroadcastReceiver { intent = new Intent( Intent.ACTION_VIEW, Uri.parse(url) ); } else { - intent = new Intent( context, - DictImportActivity.class ); - intent.putExtra( DictImportActivity.APK_EXTRA, url ); + intent = DictImportActivity + .makeAppDownloadIntent( context, url ); } String title = From 6f620ebc44054be5899a888f2b7beb8109ecc6c3 Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 28 Nov 2012 06:30:35 -0800 Subject: [PATCH 072/146] move prefix into resources two since it's repeated. --- xwords4/android/XWords4/AndroidManifest.xml | 4 +++- .../android/XWords4/res/values/common_rsrc.xml | 3 +-- .../org/eehouse/android/xw4/NetLaunchInfo.java | 18 +++++++++--------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/xwords4/android/XWords4/AndroidManifest.xml b/xwords4/android/XWords4/AndroidManifest.xml index 6e94f95a0..176d067a2 100644 --- a/xwords4/android/XWords4/AndroidManifest.xml +++ b/xwords4/android/XWords4/AndroidManifest.xml @@ -129,7 +129,9 @@ + android:host="@string/invite_host" + android:pathPrefix="@string/invite_prefix" + /> diff --git a/xwords4/android/XWords4/res/values/common_rsrc.xml b/xwords4/android/XWords4/res/values/common_rsrc.xml index e8928e6a9..516e9aa13 100644 --- a/xwords4/android/XWords4/res/values/common_rsrc.xml +++ b/xwords4/android/XWords4/res/values/common_rsrc.xml @@ -102,10 +102,9 @@ eehouse.org - eehouse.org + /and/ http://eehouse.org/and_wordlists - //%1$s/and Update checks URL http://eehouse.org/xw4/info.py diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetLaunchInfo.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetLaunchInfo.java index f25453dda..0fa720132 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetLaunchInfo.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetLaunchInfo.java @@ -22,7 +22,6 @@ package org.eehouse.android.xw4; import android.content.Context; import android.content.Intent; -import android.net.Uri.Builder; import android.net.Uri; import android.os.Bundle; import java.net.URLEncoder; @@ -99,14 +98,15 @@ public class NetLaunchInfo { String inviteID, int lang, String dict, int nPlayersT ) { - String host = context.getString( R.string.invite_host ); - Builder ub = new Builder(); - ub.scheme( "http" ); - ub.path( context.getString( R.string.game_url_pathf, host ) ); - ub.appendQueryParameter( "lang", String.format("%d", lang ) ); - ub.appendQueryParameter( "np", String.format( "%d", nPlayersT ) ); - ub.appendQueryParameter( "room", room ); - ub.appendQueryParameter( "id", inviteID ); + Uri.Builder ub = new Uri.Builder() + .scheme( "http" ) + .path( String.format( "//%s%s", + context.getString(R.string.invite_host), + context.getString(R.string.invite_prefix) ) ) + .appendQueryParameter( "lang", String.format("%d", lang ) ) + .appendQueryParameter( "np", String.format( "%d", nPlayersT ) ) + .appendQueryParameter( "room", room ) + .appendQueryParameter( "id", inviteID ); if ( null != dict ) { ub.appendQueryParameter( "wl", dict ); } From 19333e33acfb7d678e2907422773b0da2a5eefbd Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 28 Nov 2012 19:17:29 -0800 Subject: [PATCH 073/146] fix confirmation of duplicate game from invite. Along the way dramatically simplify how new game intents are passed around. --- .../org/eehouse/android/xw4/BTService.java | 3 +- .../eehouse/android/xw4/DispatchNotify.java | 165 +----------------- .../org/eehouse/android/xw4/GamesList.java | 157 +++++++++++------ .../org/eehouse/android/xw4/RelayService.java | 6 +- .../org/eehouse/android/xw4/SMSService.java | 3 +- .../org/eehouse/android/xw4/XWActivity.java | 2 - .../eehouse/android/xw4/XWListActivity.java | 2 - 7 files changed, 107 insertions(+), 231 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTService.java index e146983c7..29dadf1fb 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTService.java @@ -974,8 +974,7 @@ public class BTService extends Service { private void postNotification( int gameID, int title, String body ) { - Intent intent = new Intent( this, DispatchNotify.class ); - intent.putExtra( DispatchNotify.GAMEID_EXTRA, gameID ); + Intent intent = GamesList.makeGameIDIntent( this, gameID ); Utils.postNotification( this, intent, R.string.new_btmove_title, body, gameID ); } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DispatchNotify.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DispatchNotify.java index 0e0d8f9ef..c2178a99b 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DispatchNotify.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DispatchNotify.java @@ -33,175 +33,16 @@ import org.eehouse.android.xw4.jni.GameSummary; public class DispatchNotify extends Activity { - public static final String RELAYIDS_EXTRA = "relayids"; - public static final String GAMEID_EXTRA = "gameid"; - - public interface HandleRelaysIface { - void handleRelaysIDs( final String[] relayIDs ); - void handleInvite( final Uri invite ); - void handleGameID( int gameID ); - } - - private static HashSet s_running = - new HashSet(); - private static HandleRelaysIface s_handler; - @Override protected void onCreate( Bundle savedInstanceState ) { - boolean mustLaunch = false; super.onCreate( savedInstanceState ); - String[] relayIDs = getIntent().getStringArrayExtra( RELAYIDS_EXTRA ); - int gameID = getIntent().getIntExtra( GAMEID_EXTRA, -1 ); Uri data = getIntent().getData(); - - if ( null != relayIDs ) { - if ( !tryHandle( relayIDs ) ) { - mustLaunch = true; - } - } else if ( -1 != gameID ) { - if ( !tryHandle( gameID ) ) { - mustLaunch = true; - } - } else if ( null != data ) { // relay invite redirected URL case - NetLaunchInfo nli = new NetLaunchInfo( data ); - if ( null != nli && nli.isValid() ) { - long rowid = DBUtils.getRowIDForOpen( this, nli ); - if ( DBUtils.ROWID_NOTFOUND == rowid ) { - boolean haveDict; - if ( null == nli.dict ) { // can only test for language support - haveDict = - 0 < DictLangCache.getHaveLang( this, - nli.lang ).length; - } else { - haveDict = - DictLangCache.haveDict( this, nli.lang, nli.dict ); - } - if ( haveDict ) { - if ( !tryHandle( data ) ) { - mustLaunch = true; - } - } else { - Intent intent = - MultiService.makeMissingDictIntent( this, nli ); - intent.putExtra( MultiService.OWNER, - MultiService.OWNER_RELAY ); - // do we have gameID? - MultiService. - postMissingDictNotification( this, intent, - nli.inviteID - .hashCode() ); - } - } else { - DbgUtils.logf( "DispatchNotify: dropping duplicate invite" ); - GameSummary summary = DBUtils.getSummary( this, rowid ); - if ( null != summary ) { - gameID = summary.gameID; - if ( !tryHandle( gameID ) ) { - mustLaunch = true; - } - } - } - } - } - - if ( mustLaunch ) { - DbgUtils.logf( "DispatchNotify: nothing running" ); - Intent intent = new Intent( this, GamesList.class ); - - // This combination of flags will bring an existing - // GamesList instance to the front, killing any children - // it has, or create a new one if none exists. Coupled - // with a "standard" launchMode it seems to work, meaning - // both that the app preserves its stack in normal use - // (you can go to Home with a stack of activities and - // return to the top activity on that stack if you - // relaunch the app) and that when I launch from here the - // stack gets nuked and we don't get a second GamesList - // instance. - - intent.setFlags( Intent.FLAG_ACTIVITY_CLEAR_TOP - | Intent.FLAG_ACTIVITY_NEW_TASK ); - if ( null != relayIDs ) { - intent.putExtra( RELAYIDS_EXTRA, relayIDs ); - } else if ( -1 != gameID ) { - intent.putExtra( GAMEID_EXTRA, gameID ); - } else if ( null != data ) { - intent.setData( data ); - } else { - Assert.fail(); - } - startActivity( intent ); + if ( null != data ) { // relay invite redirected URL case + GamesList.openGame( this, data ); } finish(); - } - - public static void SetRunning( Activity running ) - { - if ( running instanceof HandleRelaysIface ) { - s_running.add( (HandleRelaysIface)running ); - } - } - - public static void ClearRunning( Activity running ) - { - if ( running instanceof HandleRelaysIface ) { - s_running.remove( (HandleRelaysIface)running ); - } - } - - public static void SetRelayIDsHandler( HandleRelaysIface iface ) - { - s_handler = iface; - } - - private static boolean tryHandle( Uri data ) - { - boolean handled = false; - if ( null != s_handler ) { - // This means the GamesList activity is frontmost - s_handler.handleInvite( data ); - handled = true; - } else { - for ( HandleRelaysIface iface : s_running ) { - iface.handleInvite( data ); - handled = true; - } - } - return handled; - } - - public static boolean tryHandle( String[] relayIDs ) - { - boolean handled = false; - if ( null != s_handler ) { - // This means the GamesList activity is frontmost - s_handler.handleRelaysIDs( relayIDs ); - handled = true; - } else { - for ( HandleRelaysIface iface : s_running ) { - iface.handleRelaysIDs( relayIDs ); - handled = true; - } - } - return handled; - } - - public static boolean tryHandle( int gameID ) - { - boolean handled = false; - if ( null != s_handler ) { - // This means the GamesList activity is frontmost - s_handler.handleGameID( gameID ); - handled = true; - } else { - for ( HandleRelaysIface iface : s_running ) { - iface.handleGameID( gameID ); - handled = true; - } - } - return handled; - } + } // onCreate } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java index bebe25437..43028718b 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java @@ -51,8 +51,7 @@ import junit.framework.Assert; import org.eehouse.android.xw4.jni.*; public class GamesList extends XWListActivity - implements DispatchNotify.HandleRelaysIface, - DBUtils.DBChangeListener, + implements DBUtils.DBChangeListener, GameListAdapter.LoadItemCB, DictImportActivity.DownloadFinishedListener { @@ -65,6 +64,9 @@ public class GamesList extends XWListActivity private static final String SAVE_ROWID = "SAVE_ROWID"; private static final String SAVE_DICTNAMES = "SAVE_DICTNAMES"; + private static final String RELAYIDS_EXTRA = "relayids"; + private static final String GAMEID_EXTRA = "gameid"; + private static final int NEW_NET_GAME_ACTION = 1; private static final int RESET_GAME_ACTION = 2; private static final int DELETE_GAME_ACTION = 3; @@ -293,8 +295,7 @@ public class GamesList extends XWListActivity { super.onNewIntent( intent ); Assert.assertNotNull( intent ); - invalRelayIDs( intent. - getStringArrayExtra( DispatchNotify.RELAYIDS_EXTRA ) ); + invalRelayIDs( intent.getStringArrayExtra( RELAYIDS_EXTRA ) ); startFirstHasDict( intent ); startNewNetGame( intent ); startHasGameID( intent ); @@ -304,7 +305,6 @@ public class GamesList extends XWListActivity protected void onStart() { super.onStart(); - DispatchNotify.SetRelayIDsHandler( this ); boolean hide = CommonPrefs.getHideIntro( this ); int hereOrGone = hide ? View.GONE : View.VISIBLE; @@ -331,7 +331,6 @@ public class GamesList extends XWListActivity // (TelephonyManager)getSystemService( Context.TELEPHONY_SERVICE ); // mgr.listen( m_phoneStateListener, PhoneStateListener.LISTEN_NONE ); // m_phoneStateListener = null; - DispatchNotify.SetRelayIDsHandler( null ); super.onStop(); } @@ -372,39 +371,6 @@ public class GamesList extends XWListActivity } } - // DispatchNotify.HandleRelaysIface interface - public void handleRelaysIDs( final String[] relayIDs ) - { - post( new Runnable() { - public void run() { - invalRelayIDs( relayIDs ); - startFirstHasDict( relayIDs ); - } - } ); - } - - public void handleInvite( Uri invite ) - { - final NetLaunchInfo nli = new NetLaunchInfo( invite ); - if ( nli.isValid() ) { - post( new Runnable() { - @Override - public void run() { - startNewNetGame( nli ); - } - } ); - } - } - - public void handleGameID( final int gameID ) - { - post( new Runnable() { - public void run() { - startHasGameID( gameID ); - } - } ); - } - // DBUtils.DBChangeListener interface public void gameSaved( final long rowid ) { @@ -474,8 +440,9 @@ public class GamesList extends XWListActivity if ( AlertDialog.BUTTON_POSITIVE == which ) { switch( id ) { case NEW_NET_GAME_ACTION: - long rowid = GameUtils.makeNewNetGame( this, m_netLaunchInfo ); - GameUtils.launchGame( this, rowid, true ); + if ( checkWarnNoDict( m_netLaunchInfo ) ) { + makeNewNetGameIf(); + } break; case RESET_GAME_ACTION: GameUtils.resetGame( this, m_rowid ); @@ -629,9 +596,12 @@ public class GamesList extends XWListActivity { post( new Runnable() { public void run() { - int id = success ? R.string.download_done - : R.string.download_failed; - Utils.showToast( GamesList.this, id ); + boolean madeGame = success ? makeNewNetGameIf() : false; + if ( ! madeGame ) { + int id = success ? R.string.download_done + : R.string.download_failed; + Utils.showToast( GamesList.this, id ); + } } } ); } @@ -698,6 +668,32 @@ public class GamesList extends XWListActivity return handled; } // handleMenuItem + private boolean checkWarnNoDict( NetLaunchInfo nli ) + { + // check that we have the dict required + boolean haveDict; + if ( null == nli.dict ) { // can only test for language support + String[] dicts = DictLangCache.getHaveLang( this, nli.lang ); + haveDict = 0 < dicts.length; + if ( haveDict ) { + // Just pick one -- good enough for the period when + // users aren't using new clients that include the + // dict name. + nli.dict = dicts[0]; + } + } else { + haveDict = + DictLangCache.haveDict( this, nli.lang, nli.dict ); + } + if ( !haveDict ) { + m_netLaunchInfo = nli; + m_missingDictLang = nli.lang; + m_missingDictName = nli.dict; + showDialog( WARN_NODICT ); + } + return haveDict; + } + private boolean checkWarnNoDict( long rowid ) { String[][] missingNames = new String[1][]; @@ -764,8 +760,7 @@ public class GamesList extends XWListActivity private void startFirstHasDict( Intent intent ) { if ( null != intent ) { - String[] relayIDs = - intent.getStringArrayExtra( DispatchNotify.RELAYIDS_EXTRA ); + String[] relayIDs = intent.getStringArrayExtra( RELAYIDS_EXTRA ); startFirstHasDict( relayIDs ); } } @@ -775,33 +770,34 @@ public class GamesList extends XWListActivity startActivity( new Intent( this, NewGameActivity.class ) ); } - private void startNewNetGame( NetLaunchInfo info ) + private void startNewNetGame( NetLaunchInfo nli ) { - long rowid = DBUtils.getRowIDForOpen( this, info ); + long rowid = DBUtils.getRowIDForOpen( this, nli ); if ( DBUtils.ROWID_NOTFOUND == rowid ) { - rowid = GameUtils.makeNewNetGame( this, info ); - GameUtils.launchGame( this, rowid, true ); + if ( checkWarnNoDict( nli ) ) { + makeNewNetGame( nli ); + } } else { - String msg = getString( R.string.dup_game_queryf, info.room ); - m_netLaunchInfo = info; + String msg = getString( R.string.dup_game_queryf, nli.room ); + m_netLaunchInfo = nli; showConfirmThen( msg, NEW_NET_GAME_ACTION ); } } // startNewNetGame private void startNewNetGame( Intent intent ) { - NetLaunchInfo info = null; + NetLaunchInfo nli = null; if ( MultiService.isMissingDictIntent( intent ) ) { - info = new NetLaunchInfo( intent ); + nli = new NetLaunchInfo( intent ); } else { Uri data = intent.getData(); if ( null != data ) { - info = new NetLaunchInfo( data ); + nli = new NetLaunchInfo( data ); } } - if ( null != info && info.isValid() ) { - startNewNetGame( info ); + if ( null != nli && nli.isValid() ) { + startNewNetGame( nli ); } } // startNewNetGame @@ -815,7 +811,7 @@ public class GamesList extends XWListActivity private void startHasGameID( Intent intent ) { - int gameID = intent.getIntExtra( DispatchNotify.GAMEID_EXTRA, 0 ); + int gameID = intent.getIntExtra( GAMEID_EXTRA, 0 ); if ( 0 != gameID ) { startHasGameID( gameID ); } @@ -840,10 +836,55 @@ public class GamesList extends XWListActivity } } + private boolean makeNewNetGameIf() + { + boolean madeGame = null != m_netLaunchInfo; + if ( madeGame ) { + makeNewNetGame( m_netLaunchInfo ); + m_netLaunchInfo = null; + } + return madeGame; + } + + private void makeNewNetGame( NetLaunchInfo info ) + { + long rowid = GameUtils.makeNewNetGame( this, info ); + GameUtils.launchGame( this, rowid, true ); + } + public static void onGameDictDownload( Context context, Intent intent ) { intent.setClass( context, GamesList.class ); context.startActivity( intent ); } + private static Intent makeSelfIntent( Context context ) + { + Intent intent = new Intent( context, GamesList.class ); + intent.setFlags( Intent.FLAG_ACTIVITY_CLEAR_TOP + | Intent.FLAG_ACTIVITY_NEW_TASK ); + return intent; + } + + public static Intent makeRelayIdsIntent( Context context, + String[] relayIDs ) + { + Intent intent = makeSelfIntent( context ); + intent.putExtra( RELAYIDS_EXTRA, relayIDs ); + return intent; + } + + public static Intent makeGameIDIntent( Context context, int gameID ) + { + Intent intent = makeSelfIntent( context ); + intent.putExtra( GAMEID_EXTRA, gameID ); + return intent; + } + + public static void openGame( Context context, Uri data ) + { + Intent intent = makeSelfIntent( context ); + intent.setData( data ); + context.startActivity( intent ); + } } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java index a744ba8b9..f2c33dc70 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java @@ -63,9 +63,9 @@ public class RelayService extends Service { long[] rowids = DBUtils.getRowIDsFor( this, relayID ); if ( null != rowids ) { for ( long rowid : rowids ) { - Intent intent = new Intent( this, DispatchNotify.class ); - intent.putExtra( DispatchNotify.RELAYIDS_EXTRA, - new String[] {relayID} ); + Intent intent = + GamesList.makeRelayIdsIntent( this, + new String[] {relayID} ); String msg = Utils.format( this, R.string.notify_bodyf, GameUtils.getName( this, rowid ) ); Utils.postNotification( this, intent, R.string.notify_title, diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java index a78ba6a7d..0706ab878 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java @@ -617,8 +617,7 @@ public class SMSService extends Service { private void postNotification( int gameID, int title, String body ) { - Intent intent = new Intent( this, DispatchNotify.class ); - intent.putExtra( DispatchNotify.GAMEID_EXTRA, gameID ); + Intent intent = GamesList.makeGameIDIntent( this, gameID ); Utils.postNotification( this, intent, title, body, gameID ); } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWActivity.java index b97d82ed5..18670d379 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWActivity.java @@ -48,7 +48,6 @@ public class XWActivity extends Activity { DbgUtils.logf( "%s.onStart(this=%H)", getClass().getName(), this ); super.onStart(); - DispatchNotify.SetRunning( this ); } @Override @@ -73,7 +72,6 @@ public class XWActivity extends Activity protected void onStop() { DbgUtils.logf( "%s.onStop(this=%H)", getClass().getName(), this ); - DispatchNotify.ClearRunning( this ); super.onStop(); } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListActivity.java index 397085e70..9f10da3d4 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListActivity.java @@ -45,7 +45,6 @@ public class XWListActivity extends ListActivity { DbgUtils.logf( "%s.onStart(this=%H)", getClass().getName(), this ); super.onStart(); - DispatchNotify.SetRunning( this ); } @Override @@ -70,7 +69,6 @@ public class XWListActivity extends ListActivity protected void onStop() { DbgUtils.logf( "%s.onStop(this=%H)", getClass().getName(), this ); - DispatchNotify.ClearRunning( this ); super.onStop(); } From 4bae56e6316313ddff2c6d8b329b01210ef9e4ae Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 28 Nov 2012 19:32:50 -0800 Subject: [PATCH 074/146] use rowid as notification id for all types of games, and cancel when games deleted. --- .../org/eehouse/android/xw4/BTService.java | 13 ++++++----- .../eehouse/android/xw4/BoardActivity.java | 22 +------------------ .../org/eehouse/android/xw4/GameUtils.java | 1 + .../org/eehouse/android/xw4/RelayService.java | 2 +- .../org/eehouse/android/xw4/SMSService.java | 14 +++++++----- .../src/org/eehouse/android/xw4/Utils.java | 3 ++- 6 files changed, 20 insertions(+), 35 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTService.java index 29dadf1fb..f62100c4e 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTService.java @@ -444,7 +444,7 @@ public class BTService extends Service { result = BTCmd.INVITE_ACCPT; String body = Utils.format( BTService.this, R.string.new_bt_bodyf, sender ); - postNotification( gameID, R.string.new_bt_title, body ); + postNotification( gameID, R.string.new_bt_title, body, rowid ); } } else { result = BTCmd.INVITE_DUPID; @@ -497,7 +497,7 @@ public class BTService extends Service { buffer, addr, m_btMsgSink ) ) { postNotification( gameID, R.string.new_btmove_title, - R.string.new_move_body ); + R.string.new_move_body, rowid ); // do nothing } else { DbgUtils.logf( "nobody took msg for gameID %X", @@ -967,16 +967,17 @@ public class BTService extends Service { return dos; } - private void postNotification( int gameID, int title, int body ) + private void postNotification( int gameID, int title, int body, long rowid ) { - postNotification( gameID, title, getString( body ) ); + postNotification( gameID, title, getString( body ), rowid ); } - private void postNotification( int gameID, int title, String body ) + private void postNotification( int gameID, int title, String body, + long rowid ) { Intent intent = GamesList.makeGameIDIntent( this, gameID ); Utils.postNotification( this, intent, R.string.new_btmove_title, - body, gameID ); + body, (int)rowid ); } private Thread killSocketIn( final BluetoothSocket socket ) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java index 8cd084c2e..cdbd49cab 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java @@ -1732,7 +1732,7 @@ public class BoardActivity extends XWActivity if ( null != m_xport ) { warnIfNoTransport(); trySendChats(); - removeNotifications(); + Utils.cancelNotification( this, (int)m_rowid ); m_xport.tickle( m_connType ); tryInvites(); } @@ -1923,26 +1923,6 @@ public class BoardActivity extends XWActivity } } - private void removeNotifications() - { - int id = 0; - switch( m_connType ) { - case COMMS_CONN_BT: - case COMMS_CONN_SMS: - id = m_gi.gameID; - break; - case COMMS_CONN_RELAY: - String relayID = DBUtils.getRelayID( this, m_rowid ); - if ( null != relayID ) { - id = relayID.hashCode(); - } - break; - } - if ( 0 != id ) { - Utils.cancelNotification( this, id ); - } - } - private void tryInvites() { if ( XWApp.BTSUPPORTED || XWApp.SMSSUPPORTED ) { diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java index 29414613c..7456721ad 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java @@ -311,6 +311,7 @@ public class GameUtils { GameLock lock = new GameLock( rowid, true ); if ( lock.tryLock() ) { tellDied( context, lock, informNow ); + Utils.cancelNotification( context, (int)rowid ); DBUtils.deleteGame( context, lock ); lock.unlock(); } else { diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java index f2c33dc70..19dcf3e7c 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java @@ -69,7 +69,7 @@ public class RelayService extends Service { String msg = Utils.format( this, R.string.notify_bodyf, GameUtils.getName( this, rowid ) ); Utils.postNotification( this, intent, R.string.notify_title, - msg, relayID.hashCode() ); + msg, (int)rowid ); } } } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java index 0706ab878..30bed8477 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java @@ -432,7 +432,8 @@ public class SMSService extends Service { MultiService.OWNER_SMS ); intent.putExtra( MultiService.INVITER, Utils.phoneToContact( this, phone, true ) ); - MultiService.postMissingDictNotification( this, intent, gameID ); + MultiService.postMissingDictNotification( this, intent, + gameID ); } break; case DATA: @@ -542,7 +543,7 @@ public class SMSService extends Service { String owner = Utils.phoneToContact( this, phone, true ); String body = Utils.format( this, R.string.new_name_bodyf, owner ); - postNotification( gameID, R.string.new_sms_title, body ); + postNotification( gameID, R.string.new_sms_title, body, rowid ); ackInvite( phone, gameID ); } @@ -607,18 +608,19 @@ public class SMSService extends Service { if ( GameUtils.feedMessage( this, rowid, msg, addr, sink ) ) { postNotification( gameID, R.string.new_smsmove_title, - getString(R.string.new_move_body) - ); + getString(R.string.new_move_body), + rowid ); } } } } } - private void postNotification( int gameID, int title, String body ) + private void postNotification( int gameID, int title, String body, + long rowid ) { Intent intent = GamesList.makeGameIDIntent( this, gameID ); - Utils.postNotification( this, intent, title, body, gameID ); + Utils.postNotification( this, intent, title, body, (int)rowid ); } // Runs in separate thread diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java index 288566417..5585407e9 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java @@ -174,7 +174,8 @@ public class Utils { } public static void postNotification( Context context, Intent intent, - String title, String body, int id ) + String title, String body, + int id ) { /* s_nextCode: per this link http://stackoverflow.com/questions/10561419/scheduling-more-than-one-pendingintent-to-same-activity-using-alarmmanager From 8250447eca79df8c4ef4c886a6ac5ff57f86402c Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 28 Nov 2012 20:41:08 -0800 Subject: [PATCH 075/146] launch game after successfully downloading dict it's missing --- .../org/eehouse/android/xw4/GamesList.java | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java index 43028718b..6a9c14879 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java @@ -83,7 +83,7 @@ public class GamesList extends XWListActivity private GameListAdapter m_adapter; private String m_missingDict; private String m_missingDictName; - private long m_missingDictRowId; + private long m_missingDictRowId = DBUtils.ROWID_NOTFOUND; private String[] m_sameLangDicts; private int m_missingDictLang; private long m_rowid; @@ -162,8 +162,7 @@ public class GamesList extends XWListActivity m_missingDictRowId, m_missingDictName, dict ); - GameUtils.launchGame( GamesList.this, - m_missingDictRowId ); + launchGameIf(); } }; dialog = new AlertDialog.Builder( this ) @@ -596,7 +595,10 @@ public class GamesList extends XWListActivity { post( new Runnable() { public void run() { - boolean madeGame = success ? makeNewNetGameIf() : false; + boolean madeGame = false; + if ( success ) { + madeGame = makeNewNetGameIf() || launchGameIf(); + } if ( ! madeGame ) { int id = success ? R.string.download_done : R.string.download_failed; @@ -698,9 +700,8 @@ public class GamesList extends XWListActivity { String[][] missingNames = new String[1][]; int[] missingLang = new int[1]; - boolean hasDicts = GameUtils.gameDictsHere( this, rowid, - missingNames, - missingLang ); + boolean hasDicts = + GameUtils.gameDictsHere( this, rowid, missingNames, missingLang ); if ( !hasDicts ) { m_missingDictLang = missingLang[0]; if ( 0 < missingNames[0].length ) { @@ -716,7 +717,7 @@ public class GamesList extends XWListActivity } else { String dict = DictLangCache.getHaveLang( this, m_missingDictLang)[0]; GameUtils.replaceDicts( this, m_missingDictRowId, null, dict ); - GameUtils.launchGame( this, m_missingDictRowId ); + launchGameIf(); } } return hasDicts; @@ -846,6 +847,16 @@ public class GamesList extends XWListActivity return madeGame; } + private boolean launchGameIf() + { + boolean madeGame = DBUtils.ROWID_NOTFOUND != m_missingDictRowId; + if ( madeGame ) { + GameUtils.launchGame( this, m_missingDictRowId ); + m_missingDictRowId = DBUtils.ROWID_NOTFOUND; + } + return madeGame; + } + private void makeNewNetGame( NetLaunchInfo info ) { long rowid = GameUtils.makeNewNetGame( this, info ); From bffb231d1390ebdc14747a2cdc8bc4e5ff3ca561 Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 28 Nov 2012 21:07:03 -0800 Subject: [PATCH 076/146] improve download-or-cancel messages --- xwords4/android/XWords4/res/values/strings.xml | 15 ++++++++------- .../src/org/eehouse/android/xw4/GamesList.java | 16 +++++++++++----- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/xwords4/android/XWords4/res/values/strings.xml b/xwords4/android/XWords4/res/values/strings.xml index 514732fb1..d44207c89 100644 --- a/xwords4/android/XWords4/res/values/strings.xml +++ b/xwords4/android/XWords4/res/values/strings.xml @@ -1461,9 +1461,10 @@ downloading and not opening the game. This first message takes wordlist name and language substituted in for %1$ and %2$ --> - Unable to open game \"%1$s\" because no - %2$s wordlist found. (It may have been deleted, or stored on - an external card that is no longer available.) + You need to download a replacement %2$s + wordlist before you can open game \"%1$s\". (The original may have + been deleted or stored on an external card that is no longer + available.) diff --git a/xwords4/android/XWords4/res/values/common_rsrc.xml b/xwords4/android/XWords4/res/values/common_rsrc.xml index 516e9aa13..860b43b11 100644 --- a/xwords4/android/XWords4/res/values/common_rsrc.xml +++ b/xwords4/android/XWords4/res/values/common_rsrc.xml @@ -104,6 +104,9 @@ eehouse.org /and/ + application/x-xwordsinvite + + http://eehouse.org/and_wordlists Update checks URL http://eehouse.org/xw4/info.py diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java index aa80833e4..468229df6 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java @@ -24,12 +24,15 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; import android.net.Uri; +import android.text.Html; import java.io.File; +import java.io.FileOutputStream; import java.util.Arrays; -import java.util.concurrent.locks.Lock; import java.util.HashMap; import java.util.HashSet; -import android.text.Html; +import java.util.concurrent.locks.Lock; +import org.json.JSONArray; +import org.json.JSONObject; import junit.framework.Assert; @@ -539,11 +542,25 @@ public class GameUtils { Intent intent = new Intent(); if ( choseEmail ) { intent.setAction( Intent.ACTION_SEND ); - intent.setType( "message/rfc822"); String subject = Utils.format( context, R.string.invite_subjectf, room ); intent.putExtra( Intent.EXTRA_SUBJECT, subject ); intent.putExtra( Intent.EXTRA_TEXT, Html.fromHtml(message) ); + + File tmpdir = DictUtils.getDownloadDir( context ); + if ( null == tmpdir ) { // no attachment + intent.setType( "message/rfc822"); + } else { + intent.setType( context.getString( R.string.invite_mime ) ); + + File attach = makeJsonFor( tmpdir, room, inviteID, lang, + dict, nPlayers ); + Uri uri = Uri.fromFile( attach ); + DbgUtils.logf( "using file uri for attachment: %s", + uri.toString() ); + intent.putExtra( Intent.EXTRA_STREAM, uri ); + } + choiceID = R.string.invite_chooser_email; } else { intent.setAction( Intent.ACTION_VIEW ); @@ -901,5 +918,29 @@ public class GameUtils { } } + private static File makeJsonFor( File dir, String room, String inviteID, + int lang, String dict, int nPlayers ) + { + File result = null; + JSONObject json = new JSONObject(); + try { + json.put( MultiService.ROOM, room ); + json.put( MultiService.INVITEID, inviteID ); + json.put( MultiService.LANG, lang ); + json.put( MultiService.DICT, dict ); + json.put( MultiService.NPLAYERST, nPlayers ); + byte[] data = json.toString().getBytes(); + + File file = new File( dir, + String.format("invite_%s.json", room ) ); + FileOutputStream fos = new FileOutputStream( file ); + fos.write( data, 0, data.length ); + fos.close(); + result = file; + } catch ( Exception ex ) { + DbgUtils.loge( ex ); + } + return result; + } } From ab0fb918eea802c270b80f2df985738447eab8f8 Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 30 Nov 2012 06:21:18 -0800 Subject: [PATCH 079/146] open json attachment and from it create a new game. Works, but there are enough problems with attachements I may turn them off. --- .../src/org/eehouse/android/xw4/DBUtils.java | 2 +- .../org/eehouse/android/xw4/GamesList.java | 2 +- .../eehouse/android/xw4/NetLaunchInfo.java | 37 +++++++++++++++---- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java index 72b8338e4..edea22652 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java @@ -562,7 +562,7 @@ public class DBUtils { public static long getRowIDForOpen( Context context, Uri data ) { long rowid = ROWID_NOTFOUND; - NetLaunchInfo nli = new NetLaunchInfo( data ); + NetLaunchInfo nli = new NetLaunchInfo( context, data ); if ( null != nli && nli.isValid() ) { rowid = getRowIDForOpen( context, nli ); } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java index 0ee0a06ce..0b7155f09 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java @@ -800,7 +800,7 @@ public class GamesList extends XWListActivity } else { Uri data = intent.getData(); if ( null != data ) { - nli = new NetLaunchInfo( data ); + nli = new NetLaunchInfo( this, data ); } } if ( null != nli && nli.isValid() ) { diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetLaunchInfo.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetLaunchInfo.java index 0fa720132..e2f79b4ed 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetLaunchInfo.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetLaunchInfo.java @@ -20,11 +20,15 @@ package org.eehouse.android.xw4; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import java.net.URLEncoder; +import java.io.InputStream; +import org.json.JSONObject; +import junit.framework.Assert; public class NetLaunchInfo { @@ -63,18 +67,35 @@ public class NetLaunchInfo { m_valid = bundle.getBoolean( VALID ); } - public NetLaunchInfo( Uri data ) + public NetLaunchInfo( Context context, Uri data ) { m_valid = false; if ( null != data ) { + String scheme = data.getScheme(); try { - room = data.getQueryParameter( "room" ); - inviteID = data.getQueryParameter( "id" ); - dict = data.getQueryParameter( "wl" ); - String langStr = data.getQueryParameter( "lang" ); - lang = Integer.decode( langStr ); - String np = data.getQueryParameter( "np" ); - nPlayersT = Integer.decode( np ); + if ( "content".equals(scheme) ) { + Assert.assertNotNull( context ); + ContentResolver resolver = context.getContentResolver(); + InputStream is = resolver.openInputStream( data ); + int len = is.available(); + byte[] buf = new byte[len]; + is.read( buf ); + + JSONObject json = new JSONObject( new String( buf ) ); + room = json.getString( MultiService.ROOM ); + inviteID = json.getString( MultiService.INVITEID ); + lang = json.getInt( MultiService.LANG ); + dict = json.getString( MultiService.DICT ); + nPlayersT = json.getInt( MultiService.NPLAYERST ); + } else { + room = data.getQueryParameter( "room" ); + inviteID = data.getQueryParameter( "id" ); + dict = data.getQueryParameter( "wl" ); + String langStr = data.getQueryParameter( "lang" ); + lang = Integer.decode( langStr ); + String np = data.getQueryParameter( "np" ); + nPlayersT = Integer.decode( np ); + } m_valid = true; } catch ( Exception e ) { DbgUtils.logf( "unable to parse \"%s\"", data.toString() ); From 95062fb967e7b93b1023b59a30f63ef00f4f3c76 Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 30 Nov 2012 07:12:07 -0800 Subject: [PATCH 080/146] log missing dicts --- .../XWords4/src/org/eehouse/android/xw4/GameUtils.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java index 468229df6..4c38cbcf7 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java @@ -25,6 +25,7 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; import android.text.Html; +import android.text.TextUtils; import java.io.File; import java.io.FileOutputStream; import java.util.Arrays; @@ -348,7 +349,8 @@ public class GameUtils { String[] dictNames = gi.dictNames(); DictUtils.DictPairs pairs = DictUtils.openDicts( context, dictNames ); if ( pairs.anyMissing( dictNames ) ) { - DbgUtils.logf( "loadMakeGame() failing: dict unavailable" ); + DbgUtils.logf( "loadMakeGame() failing: dicts %s unavailable", + TextUtils.join( ",", dictNames ) ); } else { gamePtr = XwJNI.initJNI(); From 32d7daf2b5af3138d0df0d13485fbf98bcf864f6 Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 30 Nov 2012 07:13:00 -0800 Subject: [PATCH 081/146] warn about duplicate games not just when there's exactly one, and include the most recent create time in the warning. --- .../android/XWords4/res/values/strings.xml | 4 +-- .../src/org/eehouse/android/xw4/DBUtils.java | 26 ++++++++++++------- .../org/eehouse/android/xw4/GamesList.java | 22 +++++++++------- 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/xwords4/android/XWords4/res/values/strings.xml b/xwords4/android/XWords4/res/values/strings.xml index d44207c89..000b4f633 100644 --- a/xwords4/android/XWords4/res/values/strings.xml +++ b/xwords4/android/XWords4/res/values/strings.xml @@ -1552,8 +1552,8 @@ the same room name over and over so they'll get this warning and it's harmless to ignore it. --> You already have a game that seems - to have been created from the same invitation. Are you sure you - want to open another? + to have been created (on %1$s) from the same invitation. Are you + sure you want to create another? FYI... diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java index edea22652..bc96d8db4 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java @@ -535,13 +535,16 @@ public class DBUtils { } } - public static long getRowIDForOpen( Context context, NetLaunchInfo nli ) + // Return creation time of newest game matching this nli, or null + // if none found. + public static Date getMostRecentCreate( Context context, + NetLaunchInfo nli ) { - long result = ROWID_NOTFOUND; + Date result = null; initDB( context ); synchronized( s_dbHelper ) { SQLiteDatabase db = s_dbHelper.getReadableDatabase(); - String[] columns = { ROW_ID }; + String[] columns = { DBHelper.CREATE_TIME }; String selection = String.format( "%s='%s' AND %s='%s' AND %s=%d AND %s=%d", DBHelper.ROOMNAME, nli.room, @@ -549,9 +552,12 @@ public class DBUtils { DBHelper.DICTLANG, nli.lang, DBHelper.NUM_PLAYERS, nli.nPlayersT ); Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns, - selection, null, null, null, null ); - if ( 1 == cursor.getCount() && cursor.moveToFirst() ) { - result = cursor.getLong( cursor.getColumnIndex(ROW_ID) ); + selection, null, null, null, + DBHelper.CREATE_TIME + " DESC" ); // order by + while ( cursor.moveToNext() ) { + int indx = cursor.getColumnIndex( DBHelper.CREATE_TIME ); + result = new Date( cursor.getLong( indx ) ); + break; } cursor.close(); db.close(); @@ -559,14 +565,14 @@ public class DBUtils { return result; } - public static long getRowIDForOpen( Context context, Uri data ) + public static Date getMostRecentCreate( Context context, Uri data ) { - long rowid = ROWID_NOTFOUND; + Date result = null; NetLaunchInfo nli = new NetLaunchInfo( context, data ); if ( null != nli && nli.isValid() ) { - rowid = getRowIDForOpen( context, nli ); + result = getMostRecentCreate( context, nli ); } - return rowid; + return result; } public static String[] getRelayIDs( Context context, boolean noMsgs ) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java index 0b7155f09..2fb7f724f 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java @@ -20,30 +20,31 @@ package org.eehouse.android.xw4; -import android.app.ListActivity; -import android.app.Dialog; import android.app.AlertDialog; +import android.app.Dialog; +import android.app.ListActivity; import android.app.PendingIntent; import android.content.Context; -import android.content.Intent; import android.content.DialogInterface; +import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; +import android.preference.PreferenceManager; +import android.view.ContextMenu.ContextMenuInfo; import android.view.ContextMenu; import android.view.Menu; +import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import android.view.ContextMenu.ContextMenuInfo; import android.widget.AdapterView; +import android.widget.Button; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.ListView; -import android.widget.Button; -import android.view.MenuInflater; import java.io.File; -import android.preference.PreferenceManager; +import java.util.Date; // import android.telephony.PhoneStateListener; // import android.telephony.TelephonyManager; import junit.framework.Assert; @@ -779,14 +780,15 @@ public class GamesList extends XWListActivity private void startNewNetGame( NetLaunchInfo nli ) { - long rowid = DBUtils.getRowIDForOpen( this, nli ); + Date create = DBUtils.getMostRecentCreate( this, nli ); - if ( DBUtils.ROWID_NOTFOUND == rowid ) { + if ( null == create ) { if ( checkWarnNoDict( nli ) ) { makeNewNetGame( nli ); } } else { - String msg = getString( R.string.dup_game_queryf, nli.room ); + String msg = getString( R.string.dup_game_queryf, + create.toString() ); m_netLaunchInfo = nli; showConfirmThen( msg, NEW_NET_GAME_ACTION ); } From 8b5ec28120111a4ebcf5fbd0995c6ddc0eee88f4 Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 30 Nov 2012 07:22:16 -0800 Subject: [PATCH 082/146] SMS play logs too much: remove non-error logging. --- .../XWords4/src/org/eehouse/android/xw4/SMSService.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java index 30bed8477..6734e1220 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java @@ -388,7 +388,6 @@ public class SMSService extends Service { int count = (msg.length() + (MAX_LEN_TEXT-1)) / MAX_LEN_TEXT; String[] result = new String[count]; int msgID = ++s_nSent % 0x000000FF; - DbgUtils.logf( "preparing %d packets for msgid %x", count, msgID ); int start = 0; int end = 0; @@ -400,7 +399,6 @@ public class SMSService extends Service { end += len; result[ii] = String.format( "0:%X:%X:%X:%s", msgID, ii, count, msg.substring( start, end ) ); - DbgUtils.logf( "fragment[%d]: %s", ii, result[ii] ); start = end; } return result; @@ -505,7 +503,6 @@ public class SMSService extends Service { private void disAssemble( String senderPhone, String fullMsg ) { - DbgUtils.logf( "disAssemble()" ); byte[] data = XwJNI.base64Decode( fullMsg ); DataInputStream dis = new DataInputStream( new ByteArrayInputStream(data) ); @@ -564,8 +561,6 @@ public class SMSService extends Service { for ( String fragment : fragments ) { String asPublic = toPublicFmt( fragment ); mgr.sendTextMessage( phone, null, asPublic, sent, delivery ); - DbgUtils.logf( "Message \"%s\" of %d bytes sent to %s.", - asPublic, asPublic.length(), phone ); } if ( s_showToasts ) { DbgUtils.showf( this, "sent %dth msg", s_nSent ); @@ -707,7 +702,6 @@ public class SMSService extends Service { public int transportSend( byte[] buf, final CommsAddrRec addr, int gameID ) { int nSent = -1; - DbgUtils.logf( "SMSMsgSink.transportSend()" ); if ( null != addr ) { nSent = sendPacket( addr.sms_phone, gameID, buf ); } else { @@ -750,7 +744,6 @@ public class SMSService extends Service { public boolean isComplete() { boolean complete = m_msgs.length == m_haveCount; - DbgUtils.logf( "isComplete(msg %d)=>%b", m_msgID, complete ); return complete; } From 74fec9d3d3041531baae7898ead8fe80bbf9ef70 Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 30 Nov 2012 07:50:10 -0800 Subject: [PATCH 083/146] disable attachments as part of invitations -- for now. --- xwords4/android/XWords4/AndroidManifest.xml | 16 ++++---- .../org/eehouse/android/xw4/GameUtils.java | 37 ++++++++++--------- .../src/org/eehouse/android/xw4/XWApp.java | 1 + 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/xwords4/android/XWords4/AndroidManifest.xml b/xwords4/android/XWords4/AndroidManifest.xml index fd9edb100..640303a36 100644 --- a/xwords4/android/XWords4/AndroidManifest.xml +++ b/xwords4/android/XWords4/AndroidManifest.xml @@ -134,14 +134,14 @@ /> - - - - - - + + + + + + + + diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java index 4c38cbcf7..19317bf77 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java @@ -549,7 +549,8 @@ public class GameUtils { intent.putExtra( Intent.EXTRA_SUBJECT, subject ); intent.putExtra( Intent.EXTRA_TEXT, Html.fromHtml(message) ); - File tmpdir = DictUtils.getDownloadDir( context ); + File tmpdir = XWApp.ATTACH_SUPPORTED ? + DictUtils.getDownloadDir( context ) : null; if ( null == tmpdir ) { // no attachment intent.setType( "message/rfc822"); } else { @@ -924,23 +925,25 @@ public class GameUtils { int lang, String dict, int nPlayers ) { File result = null; - JSONObject json = new JSONObject(); - try { - json.put( MultiService.ROOM, room ); - json.put( MultiService.INVITEID, inviteID ); - json.put( MultiService.LANG, lang ); - json.put( MultiService.DICT, dict ); - json.put( MultiService.NPLAYERST, nPlayers ); - byte[] data = json.toString().getBytes(); + if ( XWApp.ATTACH_SUPPORTED ) { + JSONObject json = new JSONObject(); + try { + json.put( MultiService.ROOM, room ); + json.put( MultiService.INVITEID, inviteID ); + json.put( MultiService.LANG, lang ); + json.put( MultiService.DICT, dict ); + json.put( MultiService.NPLAYERST, nPlayers ); + byte[] data = json.toString().getBytes(); - File file = new File( dir, - String.format("invite_%s.json", room ) ); - FileOutputStream fos = new FileOutputStream( file ); - fos.write( data, 0, data.length ); - fos.close(); - result = file; - } catch ( Exception ex ) { - DbgUtils.loge( ex ); + File file = new File( dir, + String.format("invite_%s.json", room ) ); + FileOutputStream fos = new FileOutputStream( file ); + fos.write( data, 0, data.length ); + fos.close(); + result = file; + } catch ( Exception ex ) { + DbgUtils.loge( ex ); + } } return result; } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java index 6c1623967..0525faf7d 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java @@ -32,6 +32,7 @@ public class XWApp extends Application { public static final boolean BTSUPPORTED = false; public static final boolean SMSSUPPORTED = true; public static final boolean GCMSUPPORTED = true; + public static final boolean ATTACH_SUPPORTED = false; public static final boolean DEBUG = false; public static final String SMS_PUBLIC_HEADER = "-XW4"; From 13b7877a80f252bc1a1de9c0b8c042c8d5643e82 Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 30 Nov 2012 08:08:45 -0800 Subject: [PATCH 084/146] put name of file being downloaded in progress dialog --- xwords4/android/XWords4/res/values/strings.xml | 4 +--- .../src/org/eehouse/android/xw4/DictImportActivity.java | 9 +++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/xwords4/android/XWords4/res/values/strings.xml b/xwords4/android/XWords4/res/values/strings.xml index 000b4f633..956715178 100644 --- a/xwords4/android/XWords4/res/values/strings.xml +++ b/xwords4/android/XWords4/res/values/strings.xml @@ -1427,9 +1427,7 @@ Guest wordlists; Host wins. - - Downloading Crosswords - wordlist %s... + Downloading %s... + Rematch diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java index dcbd08151..7c01d39f2 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java @@ -75,7 +75,7 @@ public class BoardActivity extends XWActivity private static final int PICK_TILE_REQUESTTRAY_BLK = DLG_OKONLY + 11; private static final int DLG_USEDICT = DLG_OKONLY + 12; private static final int DLG_GETDICT = DLG_OKONLY + 13; - + private static final int GAME_OVER = DLG_OKONLY + 14; private static final int CHAT_REQUEST = 1; private static final int BT_INVITE_RESULT = 2; @@ -235,6 +235,7 @@ public class BoardActivity extends XWActivity case DLG_OKONLY: case DLG_BADWORDS: case DLG_RETRY: + case GAME_OVER: ab = new AlertDialog.Builder( this ) .setTitle( m_dlgTitle ) .setMessage( m_dlgBytes ) @@ -247,6 +248,15 @@ public class BoardActivity extends XWActivity } }; ab.setNegativeButton( R.string.button_retry, lstnr ); + } else if ( GAME_OVER == id ) { + lstnr = new DialogInterface.OnClickListener() { + public void onClick( DialogInterface dlg, + int whichButton ) { + Utils.notImpl( BoardActivity.this ); + finish(); + } + }; + ab.setNegativeButton( R.string.button_rematch, lstnr ); } dialog = ab.create(); Utils.setRemoveOnDismiss( this, dialog, id ); @@ -1700,6 +1710,11 @@ public class BoardActivity extends XWActivity launchLookup( wordsToArray((String)msg.obj), m_gi.dictLang ); break; + case JNIThread.GAME_OVER: + m_dlgBytes = (String)msg.obj; + m_dlgTitle = msg.arg1; + showDialog( GAME_OVER ); + break; } } }; diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java index 3cc42187c..71577dc6a 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java @@ -94,6 +94,7 @@ public class JNIThread extends Thread { public static final int QUERY_ENDGAME = 4; public static final int TOOLBAR_STATES = 5; public static final int GOT_WORDS = 6; + public static final int GAME_OVER = 7; public class GameStateInfo implements Cloneable { public int visTileCount; @@ -528,8 +529,10 @@ public class JNIThread extends Thread { ((Boolean)args[0]).booleanValue(); int titleID = auto? R.string.summary_gameover : R.string.finalscores_title; - sendForDialog( titleID, - XwJNI.server_writeFinalScores( m_jniGamePtr ) ); + + String text = XwJNI.server_writeFinalScores( m_jniGamePtr ); + Message.obtain( m_handler, GAME_OVER, titleID, 0, text ) + .sendToTarget(); } break; From da9c7a1afd2a39c05e723932a5c1aacc1f0a7090 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 2 Dec 2012 21:39:33 -0800 Subject: [PATCH 099/146] comment out unused column --- .../android/XWords4/src/org/eehouse/android/xw4/DBHelper.java | 4 ++-- .../android/XWords4/src/org/eehouse/android/xw4/DBUtils.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java index b44021f29..cf6a4d6bd 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java @@ -60,7 +60,7 @@ public class DBHelper extends SQLiteOpenHelper { public static final String INVITEID = "INVITEID"; public static final String RELAYID = "RELAYID"; public static final String SEED = "SEED"; - public static final String SMSPHONE = "SMSPHONE"; + public static final String SMSPHONE = "SMSPHONE"; // unused -- so far public static final String LASTMOVE = "LASTMOVE"; public static final String GROUPID = "GROUPID"; @@ -100,7 +100,7 @@ public class DBHelper extends SQLiteOpenHelper { ,SEED, "INTEGER" ,DICTLANG, "INTEGER" ,DICTLIST, "TEXT" - ,SMSPHONE, "TEXT" + ,SMSPHONE, "TEXT" // unused ,SCORES, "TEXT" ,CHAT_HISTORY, "TEXT" ,GAMEID, "INTEGER" diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java index f3504932f..303b09af9 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java @@ -129,7 +129,7 @@ public class DBUtils { DBHelper.TURN, DBHelper.GIFLAGS, DBHelper.CONTYPE, DBHelper.SERVERROLE, DBHelper.ROOMNAME, DBHelper.RELAYID, - DBHelper.SMSPHONE, DBHelper.SEED, + /*DBHelper.SMSPHONE,*/ DBHelper.SEED, DBHelper.DICTLANG, DBHelper.GAMEID, DBHelper.SCORES, DBHelper.HASMSGS, DBHelper.LASTPLAY_TIME, DBHelper.REMOTEDEVS, From a640f9d97b816454ccb7195deb8cbfa17ecfc419 Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 3 Dec 2012 06:51:50 -0800 Subject: [PATCH 100/146] up version number and string --- xwords4/android/XWords4/AndroidManifest.xml | 2 +- xwords4/android/XWords4/res/raw/changes | 15 ++++++--------- xwords4/android/XWords4/res/values/app_name.xml | 2 +- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/xwords4/android/XWords4/AndroidManifest.xml b/xwords4/android/XWords4/AndroidManifest.xml index 640303a36..9524eab68 100644 --- a/xwords4/android/XWords4/AndroidManifest.xml +++ b/xwords4/android/XWords4/AndroidManifest.xml @@ -22,7 +22,7 @@ to come from a domain that you own or have control over. --> diff --git a/xwords4/android/XWords4/res/raw/changes b/xwords4/android/XWords4/res/raw/changes index 20c716fa5..c1165a0bd 100644 --- a/xwords4/android/XWords4/res/raw/changes +++ b/xwords4/android/XWords4/res/raw/changes @@ -5,17 +5,14 @@ -Crosswords 4.4 beta 56 release -

      New with this release -
    • Improve invitations: no more redirection through a website, and - confirm before creating duplicate games
    • -
    • (For sideloading users only) No more browser involvement in - updates: app can launch the installer directly to update - itself
    • -
    • Remove notifications when their games are deleted
    • +Crosswords 4.4 beta 57 release + +

      New with this release

      +
      -
        Next up +

        Next up

        +
        • One more idea for improving invitations
        • Allow grouping of games in collapsible user-defined categores: "Games with Kati", "Finished games", etc.
        • diff --git a/xwords4/android/XWords4/res/values/app_name.xml b/xwords4/android/XWords4/res/values/app_name.xml index 844bb12a6..095652a34 100644 --- a/xwords4/android/XWords4/res/values/app_name.xml +++ b/xwords4/android/XWords4/res/values/app_name.xml @@ -1,5 +1,5 @@ - 4.4 beta 56 + 4.4 beta 57 From ad606cdec073e6898584f115590d5f6abe7084cb Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 3 Dec 2012 06:53:15 -0800 Subject: [PATCH 101/146] trying to figure out why games list is occasionally redrawing itself all the time: add logging that activated only when XWApp's DEBUG flag is set, and turn that flag on. The latter change should be reversed before the next release. --- .../eehouse/android/xw4/ExpiringDelegate.java | 8 +++++++ .../eehouse/android/xw4/GameListAdapter.java | 23 ++++++++++++++----- .../src/org/eehouse/android/xw4/XWApp.java | 2 +- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/ExpiringDelegate.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/ExpiringDelegate.java index 7d1b13968..13083266d 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/ExpiringDelegate.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/ExpiringDelegate.java @@ -194,12 +194,20 @@ public class ExpiringDelegate { if ( null == m_runnable ) { m_runnable = new Runnable() { public void run() { + if ( XWApp.DEBUG ) { + DbgUtils.logf( "ExpiringDelegate: timer fired" + + " for %H", this ); + } if ( m_active ) { figurePct(); if ( m_haveTurnLocal ) { m_back = null; setBackground(); } + if ( XWApp.DEBUG ) { + DbgUtils.logf( "ExpiringDelegate: invalidating" + + " view %H", m_view ); + } m_view.invalidate(); } } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java index c234cb09a..ec28a52f0 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java @@ -1,7 +1,7 @@ /* -*- compile-command: "cd ../../../../../; ant debug install"; -*- */ /* - * Copyright 2009-2010 by Eric House (xwords@eehouse.org). All - * rights reserved. + * Copyright 2009-2012 by Eric House (xwords@eehouse.org). All rights + * reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -249,7 +249,9 @@ public class GameListAdapter extends XWListAdapter { ViewInfo vi = new ViewInfo( layout, m_rowid, expanded, summary.lastMoveTime, haveATurn, haveALocalTurn ); - + if ( XWApp.DEBUG ) { + DbgUtils.logf( "created new view for rowid %d", m_rowid ); + } synchronized( m_viewsCache ) { m_viewsCache.put( m_rowid, vi ); } @@ -329,7 +331,7 @@ public class GameListAdapter extends XWListAdapter { } } - public void setField( String field ) + public void setField( String fieldName ) { int[] ids = { R.string.game_summary_field_empty @@ -339,12 +341,21 @@ public class GameListAdapter extends XWListAdapter { }; int result = -1; for ( int id : ids ) { - if ( m_context.getString( id ).equals( field ) ) { + if ( m_context.getString( id ).equals( fieldName ) ) { result = id; break; } } - if ( m_fieldID != result ) { + if ( -1 == result ) { + if ( XWApp.DEBUG ) { + DbgUtils.logf( "GameListAdapter.setField(): unable to match" + + " fieldName %s", fieldName ); + } + } else if ( m_fieldID != result ) { + if ( XWApp.DEBUG ) { + DbgUtils.logf( "setField: clearing views cache for change" + + " from %d to %d", m_fieldID, result ); + } m_viewsCache.clear(); m_fieldID = result; } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java index 0525faf7d..06bec14f1 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java @@ -33,7 +33,7 @@ public class XWApp extends Application { public static final boolean SMSSUPPORTED = true; public static final boolean GCMSUPPORTED = true; public static final boolean ATTACH_SUPPORTED = false; - public static final boolean DEBUG = false; + public static final boolean DEBUG = true; // DON'T SHIP THIS WAY public static final String SMS_PUBLIC_HEADER = "-XW4"; From c041792f837f87d419632bd585241725fb04c8f6 Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 3 Dec 2012 08:03:00 -0800 Subject: [PATCH 102/146] move methods toward better encapsualtion (no behavior change) --- .../eehouse/android/xw4/BTInviteActivity.java | 8 ++++++++ .../org/eehouse/android/xw4/BoardActivity.java | 8 ++++---- .../src/org/eehouse/android/xw4/GameUtils.java | 16 ---------------- .../org/eehouse/android/xw4/InviteActivity.java | 2 +- .../org/eehouse/android/xw4/NewGameActivity.java | 4 ++-- .../eehouse/android/xw4/SMSInviteActivity.java | 10 +++++++++- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTInviteActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTInviteActivity.java index c6a3297d3..fcfcbd51a 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTInviteActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTInviteActivity.java @@ -45,6 +45,14 @@ public class BTInviteActivity extends InviteActivity private boolean m_firstScan; private int m_checkCount; + public static void launchForResult( Activity activity, int nMissing, + int requestCode ) + { + Intent intent = new Intent( activity, BTInviteActivity.class ); + intent.putExtra( INTENT_KEY_NMISSING, nMissing ); + activity.startActivityForResult( intent, requestCode ); + } + @Override protected void onCreate( Bundle savedInstanceState ) { diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java index 7c01d39f2..64d1c775e 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java @@ -847,12 +847,12 @@ public class BoardActivity extends XWActivity doSyncMenuitem(); break; case BT_PICK_ACTION: - GameUtils.launchBTInviter( this, m_nMissingPlayers, - BT_INVITE_RESULT ); + BTInviteActivity.launchForResult( this, m_nMissingPlayers, + BT_INVITE_RESULT ); break; case SMS_PICK_ACTION: - GameUtils.launchSMSInviter( this, m_nMissingPlayers, - SMS_INVITE_RESULT ); + SMSInviteActivity.launchForResult( this, m_nMissingPlayers, + SMS_INVITE_RESULT ); break; case SMS_CONFIG_ACTION: Utils.launchSettings( this ); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java index 19317bf77..9a291d1b8 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java @@ -508,22 +508,6 @@ public class GameUtils { nPlayersH, null, gameID, isHost ); } - public static void launchBTInviter( Activity activity, int nMissing, - int requestCode ) - { - Intent intent = new Intent( activity, BTInviteActivity.class ); - intent.putExtra( BTInviteActivity.INTENT_KEY_NMISSING, nMissing ); - activity.startActivityForResult( intent, requestCode ); - } - - public static void launchSMSInviter( Activity activity, int nMissing, - int requestCode ) - { - Intent intent = new Intent( activity, SMSInviteActivity.class ); - intent.putExtra( SMSInviteActivity.INTENT_KEY_NMISSING, nMissing ); - activity.startActivityForResult( intent, requestCode ); - } - public static void launchInviteActivity( Context context, boolean choseEmail, String room, String inviteID, diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/InviteActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/InviteActivity.java index 1a5c803f0..41dd2ae7c 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/InviteActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/InviteActivity.java @@ -43,7 +43,7 @@ abstract class InviteActivity extends XWListActivity implements View.OnClickListener { public static final String DEVS = "DEVS"; - public static final String INTENT_KEY_NMISSING = "NMISSING"; + protected static final String INTENT_KEY_NMISSING = "NMISSING"; protected int m_nMissing; protected Button m_okButton; diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NewGameActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NewGameActivity.java index 039aaa23d..85bbccd63 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NewGameActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NewGameActivity.java @@ -357,7 +357,7 @@ public class NewGameActivity extends XWActivity { intent.putExtra( GameUtils.INTENT_FORRESULT_ROWID, true ); startActivityForResult( intent, CONFIG_FOR_BT ); } else { - GameUtils.launchBTInviter( this, 1, INVITE_FOR_BT ); + BTInviteActivity.launchForResult( this, 1, INVITE_FOR_BT ); } } @@ -378,7 +378,7 @@ public class NewGameActivity extends XWActivity { intent.putExtra( GameUtils.INTENT_FORRESULT_ROWID, true ); startActivityForResult( intent, CONFIG_FOR_SMS ); } else { - GameUtils.launchSMSInviter( this, 1, INVITE_FOR_SMS ); + SMSInviteActivity.launchForResult( this, 1, INVITE_FOR_SMS ); } } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSInviteActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSInviteActivity.java index 60a7da3dd..aa955374b 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSInviteActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSInviteActivity.java @@ -32,9 +32,9 @@ import android.os.Bundle; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.CommonDataKinds; import android.provider.ContactsContract; -import android.text.method.DialerKeyListener; import android.text.Editable; import android.text.TextWatcher; +import android.text.method.DialerKeyListener; import android.view.View; import android.view.ViewGroup; import android.widget.CompoundButton; @@ -64,6 +64,14 @@ public class SMSInviteActivity extends InviteActivity { private String m_pendingNumber; private boolean m_immobileConfirmed; + public static void launchForResult( Activity activity, int nMissing, + int requestCode ) + { + Intent intent = new Intent( activity, SMSInviteActivity.class ); + intent.putExtra( INTENT_KEY_NMISSING, nMissing ); + activity.startActivityForResult( intent, requestCode ); + } + @Override protected void onCreate( Bundle savedInstanceState ) { From 2c71c8425fa5e2addda9b8ad4be5392413c92a4c Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 3 Dec 2012 08:30:29 -0800 Subject: [PATCH 103/146] pass the rematch request off to GamesList where it can create new games without interference from locked current game. Handle standalone case by cloning and launching: easy. Networked games will be harder. --- .../eehouse/android/xw4/BoardActivity.java | 10 +++++-- .../org/eehouse/android/xw4/GamesList.java | 27 +++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java index 64d1c775e..9998ba315 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java @@ -252,8 +252,7 @@ public class BoardActivity extends XWActivity lstnr = new DialogInterface.OnClickListener() { public void onClick( DialogInterface dlg, int whichButton ) { - Utils.notImpl( BoardActivity.this ); - finish(); + doRematch(); } }; ab.setNegativeButton( R.string.button_rematch, lstnr ); @@ -2106,4 +2105,11 @@ public class BoardActivity extends XWActivity m_passwdEdit = (EditText)m_passwdLyt.findViewById( R.id.edit ); } + private void doRematch() + { + Intent intent = GamesList.makeRematchIntent( this, m_gi, m_rowid ); + startActivity( intent ); + finish(); + } + } // class BoardActivity diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java index 2fb7f724f..4129c1cbf 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java @@ -68,6 +68,7 @@ public class GamesList extends XWListActivity private static final String RELAYIDS_EXTRA = "relayids"; private static final String GAMEID_EXTRA = "gameid"; + private static final String REMATCH_ROWID_EXTRA = "rowid"; private static final int NEW_NET_GAME_ACTION = 1; private static final int RESET_GAME_ACTION = 2; @@ -291,6 +292,7 @@ public class GamesList extends XWListActivity startFirstHasDict( intent ); startNewNetGame( intent ); startHasGameID( intent ); + startHasRowID( intent ); askDefaultNameIf(); } // onCreate @@ -305,6 +307,7 @@ public class GamesList extends XWListActivity startFirstHasDict( intent ); startNewNetGame( intent ); startHasGameID( intent ); + startHasRowID( intent ); } @Override @@ -826,6 +829,16 @@ public class GamesList extends XWListActivity } } + private void startHasRowID( Intent intent ) + { + long rowid = intent.getLongExtra( REMATCH_ROWID_EXTRA, -1 ); + if ( -1 != rowid ) { + // this will juggle if the preference is set + long newid = GameUtils.dupeGame( this, rowid ); + GameUtils.launchGame( this, newid ); + } + } + private void askDefaultNameIf() { if ( null == CommonPrefs.getDefaultPlayerName( this, 0, false ) ) { @@ -900,6 +913,20 @@ public class GamesList extends XWListActivity return intent; } + public static Intent makeRematchIntent( Context context, CurGameInfo gi, + long rowid ) + { + Intent intent = makeSelfIntent( context ); + + if ( CurGameInfo.DeviceRole.SERVER_STANDALONE == gi.serverRole ) { + intent.putExtra( REMATCH_ROWID_EXTRA, rowid ); + } else { + Utils.notImpl( context ); + } + + return intent; + } + public static void openGame( Context context, Uri data ) { Intent intent = makeSelfIntent( context ); From 922f4a22f4a4b05fdba35c2644f1e745a2f58498 Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 3 Dec 2012 23:07:14 -0800 Subject: [PATCH 104/146] remove unneeded variable --- .../src/org/eehouse/android/xw4/GameListAdapter.java | 5 ++++- .../XWords4/src/org/eehouse/android/xw4/GamesList.java | 7 ++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java index ec28a52f0..bcdfbefe3 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java @@ -331,8 +331,9 @@ public class GameListAdapter extends XWListAdapter { } } - public void setField( String fieldName ) + public boolean setField( String fieldName ) { + boolean changed = false; int[] ids = { R.string.game_summary_field_empty ,R.string.game_summary_field_language @@ -358,7 +359,9 @@ public class GameListAdapter extends XWListAdapter { } m_viewsCache.clear(); m_fieldID = result; + changed = true; } + return changed; } } \ No newline at end of file diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java index 4129c1cbf..fbed5b9d7 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java @@ -90,7 +90,6 @@ public class GamesList extends XWListActivity private String[] m_sameLangDicts; private int m_missingDictLang; private long m_rowid; - private String m_nameField; private NetLaunchInfo m_netLaunchInfo; // private String m_smsPhone; @@ -851,10 +850,8 @@ public class GamesList extends XWListActivity private void updateField() { String newField = CommonPrefs.getSummaryField( this ); - if ( ! newField.equals( m_nameField ) ) { - m_nameField = newField; - m_adapter.setField( newField ); - onContentChanged(); + if ( m_adapter.setField( newField ) ) { + onContentChanged(); } } From ac8c229e75c22fa322273ed50c6891a889866e8e Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 3 Dec 2012 23:10:31 -0800 Subject: [PATCH 105/146] Fix attachments, at least for the one phone I've tested on. Requires K-9 mail (Gmail doesn't allow opening attachments). Trick was to drop the file extension, since if present that seems to come ahead of mime type for filtering. --- xwords4/android/XWords4/AndroidManifest.xml | 14 ++++++-------- .../src/org/eehouse/android/xw4/GameUtils.java | 2 +- .../XWords4/src/org/eehouse/android/xw4/XWApp.java | 2 +- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/xwords4/android/XWords4/AndroidManifest.xml b/xwords4/android/XWords4/AndroidManifest.xml index 9524eab68..ba77b8886 100644 --- a/xwords4/android/XWords4/AndroidManifest.xml +++ b/xwords4/android/XWords4/AndroidManifest.xml @@ -134,14 +134,12 @@ /> - - - - - - - - + + + + + + diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java index 9a291d1b8..52b274823 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java @@ -920,7 +920,7 @@ public class GameUtils { byte[] data = json.toString().getBytes(); File file = new File( dir, - String.format("invite_%s.json", room ) ); + String.format("invite_%s", room ) ); FileOutputStream fos = new FileOutputStream( file ); fos.write( data, 0, data.length ); fos.close(); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java index 06bec14f1..57927412b 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java @@ -32,7 +32,7 @@ public class XWApp extends Application { public static final boolean BTSUPPORTED = false; public static final boolean SMSSUPPORTED = true; public static final boolean GCMSUPPORTED = true; - public static final boolean ATTACH_SUPPORTED = false; + public static final boolean ATTACH_SUPPORTED = true; public static final boolean DEBUG = true; // DON'T SHIP THIS WAY public static final String SMS_PUBLIC_HEADER = "-XW4"; From a035ef5623af299019c3ba9c5e6e40a423a8f52e Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 5 Dec 2012 06:52:10 -0800 Subject: [PATCH 106/146] attachment can come in as file or content scheme --- .../XWords4/src/org/eehouse/android/xw4/NetLaunchInfo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetLaunchInfo.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetLaunchInfo.java index e2f79b4ed..878316cf1 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetLaunchInfo.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetLaunchInfo.java @@ -73,7 +73,7 @@ public class NetLaunchInfo { if ( null != data ) { String scheme = data.getScheme(); try { - if ( "content".equals(scheme) ) { + if ( "content".equals(scheme) || "file".equals(scheme) ) { Assert.assertNotNull( context ); ContentResolver resolver = context.getContentResolver(); InputStream is = resolver.openInputStream( data ); From af853098f2bd8f634673c08389d63f7a4277818b Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 5 Dec 2012 07:32:11 -0800 Subject: [PATCH 107/146] use preferred SDK-8 API to look for downloads directory, and wrap it in an interface loaded only when SDK>=8 to avoid load-time crash on older devices. --- xwords4/android/XWords4/AndroidManifest.xml | 2 +- xwords4/android/XWords4/project.properties | 2 +- .../org/eehouse/android/xw4/DictUtils.java | 45 ++++++++++++++++++- 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/xwords4/android/XWords4/AndroidManifest.xml b/xwords4/android/XWords4/AndroidManifest.xml index ba77b8886..1b634d862 100644 --- a/xwords4/android/XWords4/AndroidManifest.xml +++ b/xwords4/android/XWords4/AndroidManifest.xml @@ -26,7 +26,7 @@ android:versionName="@string/app_version" > - + diff --git a/xwords4/android/XWords4/project.properties b/xwords4/android/XWords4/project.properties index 797fb4fc3..c1fd41ab1 100644 --- a/xwords4/android/XWords4/project.properties +++ b/xwords4/android/XWords4/project.properties @@ -10,4 +10,4 @@ # Indicates whether an apk should be generated for each density. split.density=false # Project target. -target=android-7 +target=android-8 diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictUtils.java index 126410cfd..6adcc5e6e 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictUtils.java @@ -47,6 +47,20 @@ import org.eehouse.android.xw4.jni.CurGameInfo.DeviceRole; public class DictUtils { + // Standard hack for using APIs from an SDK in code to ship on + // older devices that don't support it: prevent class loader from + // seeing something it'll barf on by loading it manually + private static interface SafeDirGetter { + public File getDownloadDir(); + } + private static SafeDirGetter s_dirGetter = null; + static { + int sdkVersion = Integer.valueOf( android.os.Build.VERSION.SDK ); + if ( 8 <= sdkVersion ) { + s_dirGetter = new DirGetter(); + } + } + // keep in sync with loc_names string-array public enum DictLoc { UNKNOWN, BUILT_IN, INTERNAL, EXTERNAL, DOWNLOAD }; public static final String INVITED = "invited"; @@ -566,10 +580,21 @@ public class DictUtils { return null != getDownloadDir( context ); } + // The approved way to get this is to call Environment. + // getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), + // but at least on my Samsung Galaxy Blaze 4G it returns a + // directory that does not exist! (FU, Samsung...) So in that + // case fall back to using getExternalStorageDirectory and + // appending "download" public static File getDownloadDir( Context context ) { File result = null; - if ( haveWriteableSD() ) { + if ( null != s_dirGetter ) { + result = s_dirGetter.getDownloadDir(); + } + if ( null != result ) { + // we're done + } else if ( haveWriteableSD() ) { File file = null; String myPath = XWPrefs.getMyDownloadDir( context ); if ( null != myPath && 0 < myPath.length() ) { @@ -577,7 +602,11 @@ public class DictUtils { } else { file = Environment.getExternalStorageDirectory(); if ( null != file ) { - file = new File( file, "download/" ); + File child = new File( file, "download/" ); + if ( child.exists() && child.isDirectory() + && child.canWrite() ) { + file = child; + } } } if ( null != file && file.exists() && file.isDirectory() ) { @@ -596,4 +625,16 @@ public class DictUtils { } return result; } + + private static class DirGetter implements SafeDirGetter { + public File getDownloadDir() + { + File path = Environment. + getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); + if ( null != path && !path.canWrite() ) { + path = null; + } + return path; + } + } } From 6789a64b68060bebe230334a80c0b6864225d4cf Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 5 Dec 2012 08:14:55 -0800 Subject: [PATCH 108/146] reduce redundant code (hopefully without adding too much confusion) --- .../org/eehouse/android/xw4/DictUtils.java | 63 ++++++++++--------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictUtils.java index 6adcc5e6e..05fff08d0 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictUtils.java @@ -580,37 +580,45 @@ public class DictUtils { return null != getDownloadDir( context ); } - // The approved way to get this is to call Environment. - // getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), - // but at least on my Samsung Galaxy Blaze 4G it returns a - // directory that does not exist! (FU, Samsung...) So in that - // case fall back to using getExternalStorageDirectory and - // appending "download" + // Loop through three ways of getting the directory until one + // produces a directory I can write to. public static File getDownloadDir( Context context ) { File result = null; - if ( null != s_dirGetter ) { - result = s_dirGetter.getDownloadDir(); - } - if ( null != result ) { - // we're done - } else if ( haveWriteableSD() ) { - File file = null; - String myPath = XWPrefs.getMyDownloadDir( context ); - if ( null != myPath && 0 < myPath.length() ) { - file = new File( myPath ); - } else { - file = Environment.getExternalStorageDirectory(); - if ( null != file ) { - File child = new File( file, "download/" ); - if ( child.exists() && child.isDirectory() - && child.canWrite() ) { - file = child; - } + outer: + for ( int attempt = 0; attempt < 4; ++attempt ) { + switch ( attempt ) { + case 0: + String myPath = XWPrefs.getMyDownloadDir( context ); + if ( null == myPath || 0 == myPath.length() ) { + continue; } + result = new File( myPath ); + break; + case 1: + if ( null == s_dirGetter ) { + continue; + } + result = s_dirGetter.getDownloadDir(); + break; + case 2: + case 3: + if ( !haveWriteableSD() ) { + continue; + } + result = Environment.getExternalStorageDirectory(); + if ( 2 == attempt && null != result ) { + // the old way... + result = new File( result, "download/" ); + } + break; } - if ( null != file && file.exists() && file.isDirectory() ) { - result = file; + + // Exit test for loop + if ( null != result ) { + if ( result.exists() && result.isDirectory() && result.canWrite() ) { + break outer; + } } } return result; @@ -631,9 +639,6 @@ public class DictUtils { { File path = Environment. getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); - if ( null != path && !path.canWrite() ) { - path = null; - } return path; } } From e719c6e61d0cf0b5e760d222b7b29823571d6090 Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 5 Dec 2012 08:15:48 -0800 Subject: [PATCH 109/146] don't crash if for some reason json file can't be written --- .../src/org/eehouse/android/xw4/GameUtils.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java index 52b274823..420819c46 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java @@ -533,18 +533,22 @@ public class GameUtils { intent.putExtra( Intent.EXTRA_SUBJECT, subject ); intent.putExtra( Intent.EXTRA_TEXT, Html.fromHtml(message) ); + File attach = null; File tmpdir = XWApp.ATTACH_SUPPORTED ? DictUtils.getDownloadDir( context ) : null; - if ( null == tmpdir ) { // no attachment + if ( null != tmpdir ) { // no attachment + attach = makeJsonFor( tmpdir, room, inviteID, lang, + dict, nPlayers ); + } + + if ( null == attach ) { // no attachment intent.setType( "message/rfc822"); } else { - intent.setType( context.getString( R.string.invite_mime ) ); - - File attach = makeJsonFor( tmpdir, room, inviteID, lang, - dict, nPlayers ); + String mime = context.getString( R.string.invite_mime ); + intent.setType( mime ); Uri uri = Uri.fromFile( attach ); - DbgUtils.logf( "using file uri for attachment: %s", - uri.toString() ); + DbgUtils.logf( "using file uri %s, type %s for attachment", + uri.toString(), mime ); intent.putExtra( Intent.EXTRA_STREAM, uri ); } From 93bf2a1b3ec2c7e23aa7051acc97df49699319b3 Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 5 Dec 2012 19:15:20 -0800 Subject: [PATCH 110/146] hard-code tiles drawn nearly-square, just to see what it looks like. Making them exactly square would probably take a new jni call. And if I ship this it needs to be optional, controlled by a preference. --- .../XWords4/src/org/eehouse/android/xw4/BoardView.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardView.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardView.java index eea07b086..d05909c99 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardView.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardView.java @@ -53,6 +53,8 @@ public class BoardView extends View implements DrawCtx, BoardHandler, private static final int PINCH_THRESHOLD = 40; private static final int SCORE_HT_DROP = 2; private static final boolean DEBUG_DRAWFRAMES = false; + // this can be a preference + private static final boolean MAKETILESSQUARE = true; private Context m_context; private Paint m_drawPaint; @@ -379,8 +381,12 @@ public class BoardView extends View implements DrawCtx, BoardHandler, heightLeft = cellSize * 3 / 2; } heightLeft /= 3; - trayHt += heightLeft * 2; scoreHt += heightLeft; + + trayHt += heightLeft * 2; + if ( MAKETILESSQUARE && trayHt > width / 7 ) { + trayHt = width / 7; + } heightUsed = trayHt + scoreHt + ((nCells - nToScroll) * cellSize); } From 83b1d4c364f4c76c0ececde36873d7b480c3f004 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 8 Dec 2012 06:40:21 -0800 Subject: [PATCH 111/146] fix compiler warnings --- xwords4/common/game.c | 14 +++++++++----- xwords4/common/server.c | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/xwords4/common/game.c b/xwords4/common/game.c index f61e25ab2..ae73da301 100644 --- a/xwords4/common/game.c +++ b/xwords4/common/game.c @@ -93,10 +93,12 @@ game_makeNewGame( MPFORMAL XWGame* game, CurGameInfo* gi, #endif ) { - XP_U16 nPlayersHere, nPlayersTotal; - - assertUtilOK( util ); +#ifndef XWFEATURE_STANDALONE_ONLY + XP_U16 nPlayersHere = 0; + XP_U16 nPlayersTotal = 0; checkServerRole( gi, &nPlayersHere, &nPlayersTotal ); +#endif + assertUtilOK( util ); gi->gameID = makeGameID( util ); @@ -137,15 +139,17 @@ game_reset( MPFORMAL XWGame* game, CurGameInfo* gi, CommonPrefs* cp, const TransportProcs* procs ) { XP_U16 ii; - XP_U16 nPlayersHere, nPlayersTotal; XP_ASSERT( !!game->model ); XP_ASSERT( !!gi ); - checkServerRole( gi, &nPlayersHere, &nPlayersTotal ); gi->gameID = makeGameID( util ); #ifndef XWFEATURE_STANDALONE_ONLY + XP_U16 nPlayersHere = 0; + XP_U16 nPlayersTotal = 0; + checkServerRole( gi, &nPlayersHere, &nPlayersTotal ); + if ( !!game->comms ) { if ( gi->serverRole == SERVER_STANDALONE ) { comms_destroy( game->comms ); diff --git a/xwords4/common/server.c b/xwords4/common/server.c index 95e4c0759..aebb41ab2 100644 --- a/xwords4/common/server.c +++ b/xwords4/common/server.c @@ -686,7 +686,7 @@ handleRegistrationMsg( ServerCtxt* server, XWStreamCtxt* stream ) { XP_Bool success = XP_TRUE; XP_U16 playersInMsg; - XP_S8 clientIndex; + XP_S8 clientIndex = 0; /* quiet compiler */ XP_U16 ii = 0; LOG_FUNC(); From 7efbd2697d3ab8a55e9d131d8a64c7266a98df24 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 8 Dec 2012 08:47:53 -0800 Subject: [PATCH 112/146] rewrite list item logic. Use a single custom LinearLayout subclass for both the loading and loaded phases, toggling its state once the data's available. Reuse it: pay attention to what's passed into getView and only allocate when there's no existing View to reuse. Stop caching Views, as that defeats Android list logic that might limit in-memory representation to the subset that's visible on-screen, instead tracking a set of rowids whose data is known to be good as a way of quickly drawing when there's a refresh. --- .../XWords4/res/layout/game_list_item.xml | 185 ++++---- .../eehouse/android/xw4/GameListAdapter.java | 431 ++++++++---------- .../org/eehouse/android/xw4/GameListItem.java | 115 +++++ .../org/eehouse/android/xw4/GamesList.java | 8 +- .../eehouse/android/xw4/XWListAdapter.java | 5 +- 5 files changed, 420 insertions(+), 324 deletions(-) create mode 100644 xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListItem.java diff --git a/xwords4/android/XWords4/res/layout/game_list_item.xml b/xwords4/android/XWords4/res/layout/game_list_item.xml index 1a6ab2cb1..771af6471 100644 --- a/xwords4/android/XWords4/res/layout/game_list_item.xml +++ b/xwords4/android/XWords4/res/layout/game_list_item.xml @@ -3,95 +3,114 @@ - + - + - - - - - - - - - - - - - - + android:visibility="gone" + > - - + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java index bcdfbefe3..87b65e852 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java @@ -35,7 +35,7 @@ import android.widget.TextView; import java.io.FileInputStream; import java.text.DateFormat; import java.util.Date; -import java.util.HashMap; // class is not synchronized +import java.util.HashSet; import java.util.Random; import junit.framework.Assert; @@ -46,10 +46,6 @@ import org.eehouse.android.xw4.jni.CurGameInfo.DeviceRole; import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType; public class GameListAdapter extends XWListAdapter { - private Context m_context; - private LayoutInflater m_factory; - private int m_fieldID; - private Handler m_handler; private static final boolean s_isFire; private static Random s_random; static { @@ -59,79 +55,35 @@ public class GameListAdapter extends XWListAdapter { } } - private class ViewInfo implements View.OnClickListener { - private View m_view; - private View m_hideable; - private ExpiringTextView m_name; - private boolean m_expanded, m_haveTurn, m_haveTurnLocal; - private long m_rowid; - private long m_lastMoveTime; - private ImageButton m_expandButton; - - public ViewInfo( View view, long rowid ) - { - m_view = view; - m_rowid = rowid; - m_lastMoveTime = 0; - } - - public ViewInfo( View view, long rowid, boolean expanded, - long lastMoveTime, boolean haveTurn, - boolean haveTurnLocal ) { - this( view, rowid ); - m_expanded = expanded; - m_lastMoveTime = lastMoveTime; - m_haveTurn = haveTurn; - m_haveTurnLocal = haveTurnLocal; - m_hideable = (LinearLayout)view.findViewById( R.id.hideable ); - m_name = (ExpiringTextView)m_view.findViewById( R.id.game_name ); - m_expandButton = (ImageButton)view.findViewById( R.id.expander ); - m_expandButton.setOnClickListener( this ); - showHide(); - } - - private void showHide() - { - m_expandButton.setImageResource( m_expanded ? - R.drawable.expander_ic_maximized : - R.drawable.expander_ic_minimized); - m_hideable.setVisibility( m_expanded? View.VISIBLE : View.GONE ); - - m_name.setBackgroundColor( android.R.color.transparent ); - m_name.setPct( m_handler, m_haveTurn && !m_expanded, - m_haveTurnLocal, m_lastMoveTime ); - } - - public void onClick( View view ) { - m_expanded = !m_expanded; - DBUtils.setExpanded( m_rowid, m_expanded ); - showHide(); - } - } - - private HashMap m_viewsCache; + private Context m_context; + private LayoutInflater m_factory; + private int m_fieldID; + private Handler m_handler; private DateFormat m_df; private LoadItemCB m_cb; - + // Track those rows known to be good. If a rowid is not in this + // set, assume it must be loaded. Add rowids to this set as + // they're loaded, and remove one when when it must be redrawn. + private HashSet m_loadedRows; public interface LoadItemCB { - public void itemLoaded( long rowid ); public void itemClicked( long rowid ); } - private class LoadItemTask extends AsyncTask { - private long m_rowid; + private class LoadItemTask extends AsyncTask { + private GameListItem m_view; private Context m_context; // private int m_id; - public LoadItemTask( Context context, long rowid/*, int id*/ ) + public LoadItemTask( Context context, GameListItem view ) { + DbgUtils.logf( "Creating LoadItemTask for row %d", + view.getRowID() ); m_context = context; - m_rowid = rowid; - // m_id = id; + m_view = view; } @Override - protected Void doInBackground( Void... unused ) + protected GameSummary doInBackground( Void... unused ) { // Without this, on the Fire only the last item in the // list it tappable. Likely my fault, but this seems to @@ -143,133 +95,26 @@ public class GameListAdapter extends XWListAdapter { } catch ( Exception e ) { } } - View layout = m_factory.inflate( R.layout.game_list_item, null ); - boolean hideTitle = false;//CommonPrefs.getHideTitleBar(m_context); - GameSummary summary = DBUtils.getSummary( m_context, m_rowid, 1500 ); - if ( null == summary ) { - m_rowid = -1; - } else { - String state = summary.summarizeState(); - TextView view = (TextView)layout.findViewById( R.id.game_name ); - if ( hideTitle ) { - view.setVisibility( View.GONE ); - } else { - String value = null; - switch ( m_fieldID ) { - case R.string.game_summary_field_empty: - break; - case R.string.game_summary_field_language: - value = - DictLangCache.getLangName( m_context, - summary.dictLang ); - break; - case R.string.game_summary_field_opponents: - value = summary.playerNames(); - break; - case R.string.game_summary_field_state: - value = state; - break; - } - - String name = GameUtils.getName( m_context, m_rowid ); - - if ( null != value ) { - value = m_context.getString( R.string.str_game_namef, - name, value ); - } else { - value = name; - } - - view.setText( value ); - } - - layout.setOnClickListener( new View.OnClickListener() { - @Override - public void onClick( View v ) { - m_cb.itemClicked( m_rowid ); - } - } ); - - LinearLayout list = - (LinearLayout)layout.findViewById( R.id.player_list ); - boolean haveATurn = false; - boolean haveALocalTurn = false; - boolean[] isLocal = new boolean[1]; - for ( int ii = 0; ii < summary.nPlayers; ++ii ) { - ExpiringLinearLayout tmp = (ExpiringLinearLayout) - m_factory.inflate( R.layout.player_list_elem, null ); - view = (TextView)tmp.findViewById( R.id.item_name ); - view.setText( summary.summarizePlayer( ii ) ); - view = (TextView)tmp.findViewById( R.id.item_score ); - view.setText( String.format( " %d", summary.scores[ii] ) ); - boolean thisHasTurn = summary.isNextToPlay( ii, isLocal ); - if ( thisHasTurn ) { - haveATurn = true; - if ( isLocal[0] ) { - haveALocalTurn = true; - } - } - tmp.setPct( m_handler, thisHasTurn, isLocal[0], - summary.lastMoveTime ); - list.addView( tmp, ii ); - } - - view = (TextView)layout.findViewById( R.id.state ); - view.setText( state ); - view = (TextView)layout.findViewById( R.id.modtime ); - long lastMoveTime = summary.lastMoveTime; - lastMoveTime *= 1000; - view.setText( m_df.format( new Date( lastMoveTime ) ) ); - - int iconID; - ImageView marker = - (ImageView)layout.findViewById( R.id.msg_marker ); - CommsConnType conType = summary.conType; - if ( CommsConnType.COMMS_CONN_RELAY == conType ) { - iconID = R.drawable.relaygame; - } else if ( CommsConnType.COMMS_CONN_BT == conType ) { - iconID = android.R.drawable.stat_sys_data_bluetooth; - } else if ( CommsConnType.COMMS_CONN_SMS == conType ) { - iconID = android.R.drawable.sym_action_chat; - } else { - iconID = R.drawable.sologame; - } - marker.setImageResource( iconID ); - - view = (TextView)layout.findViewById( R.id.role ); - String roleSummary = summary.summarizeRole(); - if ( null != roleSummary ) { - view.setText( roleSummary ); - } else { - view.setVisibility( View.GONE ); - } - - boolean expanded = DBUtils.getExpanded( m_context, m_rowid ); - ViewInfo vi = new ViewInfo( layout, m_rowid, expanded, - summary.lastMoveTime, haveATurn, - haveALocalTurn ); - if ( XWApp.DEBUG ) { - DbgUtils.logf( "created new view for rowid %d", m_rowid ); - } - synchronized( m_viewsCache ) { - m_viewsCache.put( m_rowid, vi ); - } - } - return null; + long rowid = m_view.getRowID(); + GameSummary summary = DBUtils.getSummary( m_context, rowid, 1500 ); + return summary; } // doInBackground @Override - protected void onPostExecute( Void unused ) + protected void onPostExecute( GameSummary summary ) { - // DbgUtils.logf( "onPostExecute(rowid=%d)", m_rowid ); - if ( -1 != m_rowid ) { - m_cb.itemLoaded( m_rowid ); - } + setData( m_view, summary ); + setLoaded( m_view.getRowID() ); + m_view.setLoaded( true ); + + DbgUtils.logf( "LoadItemTask for row %d finished", + m_view.getRowID() ); } } // class LoadItemTask - public GameListAdapter( Context context, Handler handler, LoadItemCB cb ) { + public GameListAdapter( Context context, Handler handler, LoadItemCB cb, + String fieldName ) { super( DBUtils.gamesList(context).length ); m_context = context; m_handler = handler; @@ -278,62 +123,92 @@ public class GameListAdapter extends XWListAdapter { m_df = DateFormat.getDateTimeInstance( DateFormat.SHORT, DateFormat.SHORT ); - m_viewsCache = new HashMap(); + m_loadedRows = new HashSet(); + m_fieldID = fieldToID( fieldName ); } - + + @Override public int getCount() { return DBUtils.gamesList(m_context).length; } - - public Object getItem( int position ) + + // Views. A view depends on a summary, which takes time to load. + // When one needs loading it's done via an async task. + public View getView( int position, View convertView, ViewGroup parent ) { - final long rowid = DBUtils.gamesList(m_context)[position]; - View layout; - boolean haveLayout = false; - synchronized( m_viewsCache ) { - ViewInfo vi = m_viewsCache.get( rowid ); - haveLayout = null != vi; - if ( haveLayout ) { - layout = vi.m_view; - } else { - layout = m_factory.inflate( R.layout.game_list_tmp, null ); - vi = new ViewInfo( layout, rowid ); - m_viewsCache.put( rowid, vi ); + GameListItem result; + boolean mustLoad = false; + if ( null == convertView ) { + result = (GameListItem) + m_factory.inflate( R.layout.game_list_item, null ); + result.setRowID( DBUtils.gamesList(m_context)[position] ); + mustLoad = true; + } else { + result = (GameListItem)convertView; + long rowid = result.getRowID(); + if ( isDirty(rowid) || !result.isLoaded() ) { + mustLoad = true; } } - if ( !haveLayout ) { - new LoadItemTask( m_context, rowid/*, ++m_taskCounter*/ ).execute(); + if ( mustLoad ) { + new LoadItemTask( m_context, result ).execute(); } - // this doesn't work. Rather, it breaks highlighting because - // the background, if we don't set it, is a more complicated - // object like @android:drawable/list_selector_background. I - // tried calling getBackground(), expecting to get a Drawable - // I could then clone and modify, but null comes back. So - // layout must be inheriting its background from elsewhere or - // it gets set later, during layout. - // if ( (position%2) == 0 ) { - // layout.setBackgroundColor( 0xFF3F3F3F ); - // } - - return layout; - } // getItem - - public View getView( int position, View convertView, ViewGroup parent ) { - return (View)getItem( position ); + return result; } public void inval( long rowid ) { - synchronized( m_viewsCache ) { - m_viewsCache.remove( rowid ); + synchronized( m_loadedRows ) { + m_loadedRows.remove( rowid ); + } + } + + private void dirtyAll() + { + synchronized( m_loadedRows ) { + m_loadedRows.clear(); + } + } + + private boolean isDirty( long rowid ) + { + synchronized( m_loadedRows ) { + return ! m_loadedRows.contains( rowid ); + } + } + + private void setLoaded( long rowid ) + { + synchronized( m_loadedRows ) { + m_loadedRows.add( rowid ); } } public boolean setField( String fieldName ) { boolean changed = false; + int newID = fieldToID( fieldName ); + if ( -1 == newID ) { + if ( XWApp.DEBUG ) { + DbgUtils.logf( "GameListAdapter.setField(): unable to match" + + " fieldName %s", fieldName ); + } + } else if ( m_fieldID != newID ) { + if ( XWApp.DEBUG ) { + DbgUtils.logf( "setField: clearing views cache for change" + + " from %d to %d", m_fieldID, newID ); + } + m_fieldID = newID; + dirtyAll(); + changed = true; + } + return changed; + } + + private int fieldToID( String fieldName ) + { int[] ids = { R.string.game_summary_field_empty ,R.string.game_summary_field_language @@ -347,21 +222,109 @@ public class GameListAdapter extends XWListAdapter { break; } } - if ( -1 == result ) { - if ( XWApp.DEBUG ) { - DbgUtils.logf( "GameListAdapter.setField(): unable to match" - + " fieldName %s", fieldName ); - } - } else if ( m_fieldID != result ) { - if ( XWApp.DEBUG ) { - DbgUtils.logf( "setField: clearing views cache for change" - + " from %d to %d", m_fieldID, result ); - } - m_viewsCache.clear(); - m_fieldID = result; - changed = true; - } - return changed; + return result; } + private void setData( GameListItem layout, GameSummary summary ) + { + if ( null != summary ) { + final long rowid = layout.getRowID(); + String state = summary.summarizeState(); + + TextView view = (TextView)layout.findViewById( R.id.game_name ); + String value = null; + switch ( m_fieldID ) { + case R.string.game_summary_field_empty: + break; + case R.string.game_summary_field_language: + value = + DictLangCache.getLangName( m_context, + summary.dictLang ); + break; + case R.string.game_summary_field_opponents: + value = summary.playerNames(); + break; + case R.string.game_summary_field_state: + value = state; + break; + } + + String name = GameUtils.getName( m_context, rowid ); + + if ( null != value ) { + value = m_context.getString( R.string.str_game_namef, + name, value ); + } else { + value = name; + } + + view.setText( value ); + + layout.setOnClickListener( new View.OnClickListener() { + @Override + public void onClick( View v ) { + m_cb.itemClicked( rowid ); + } + } ); + + LinearLayout list = + (LinearLayout)layout.findViewById( R.id.player_list ); + boolean haveATurn = false; + boolean haveALocalTurn = false; + boolean[] isLocal = new boolean[1]; + for ( int ii = 0; ii < summary.nPlayers; ++ii ) { + ExpiringLinearLayout tmp = (ExpiringLinearLayout) + m_factory.inflate( R.layout.player_list_elem, null ); + view = (TextView)tmp.findViewById( R.id.item_name ); + view.setText( summary.summarizePlayer( ii ) ); + view = (TextView)tmp.findViewById( R.id.item_score ); + view.setText( String.format( " %d", summary.scores[ii] ) ); + boolean thisHasTurn = summary.isNextToPlay( ii, isLocal ); + if ( thisHasTurn ) { + haveATurn = true; + if ( isLocal[0] ) { + haveALocalTurn = true; + } + } + tmp.setPct( m_handler, thisHasTurn, isLocal[0], + summary.lastMoveTime ); + list.addView( tmp, ii ); + } + + view = (TextView)layout.findViewById( R.id.state ); + view.setText( state ); + view = (TextView)layout.findViewById( R.id.modtime ); + long lastMoveTime = summary.lastMoveTime; + lastMoveTime *= 1000; + view.setText( m_df.format( new Date( lastMoveTime ) ) ); + + int iconID; + ImageView marker = + (ImageView)layout.findViewById( R.id.msg_marker ); + CommsConnType conType = summary.conType; + if ( CommsConnType.COMMS_CONN_RELAY == conType ) { + iconID = R.drawable.relaygame; + } else if ( CommsConnType.COMMS_CONN_BT == conType ) { + iconID = android.R.drawable.stat_sys_data_bluetooth; + } else if ( CommsConnType.COMMS_CONN_SMS == conType ) { + iconID = android.R.drawable.sym_action_chat; + } else { + iconID = R.drawable.sologame; + } + marker.setImageResource( iconID ); + + view = (TextView)layout.findViewById( R.id.role ); + String roleSummary = summary.summarizeRole(); + if ( null != roleSummary ) { + view.setText( roleSummary ); + } else { + view.setVisibility( View.GONE ); + } + + boolean expanded = DBUtils.getExpanded( m_context, rowid ); + + layout.update( m_handler, expanded, summary.lastMoveTime, + haveATurn, haveALocalTurn ); + } + } } \ No newline at end of file diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListItem.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListItem.java new file mode 100644 index 000000000..fb03482d2 --- /dev/null +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListItem.java @@ -0,0 +1,115 @@ +/* -*- compile-command: "cd ../../../../../; ant debug install"; -*- */ +/* + * Copyright 2009-2012 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +package org.eehouse.android.xw4; + +import android.content.Context; +import android.os.Handler; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageButton; +import android.widget.LinearLayout; + +public class GameListItem extends LinearLayout + implements View.OnClickListener { + + private Context m_context; + private boolean m_loaded; + private long m_rowid; + private View m_hideable; + private ExpiringTextView m_name; + private boolean m_expanded, m_haveTurn, m_haveTurnLocal; + private long m_lastMoveTime; + private ImageButton m_expandButton; + private Handler m_handler; + + public GameListItem( Context cx, AttributeSet as ) + { + super( cx, as ); + m_context = cx; + m_loaded = false; + m_rowid = DBUtils.ROWID_NOTFOUND; + m_lastMoveTime = 0; + } + + public void update( Handler handler, boolean expanded, + long lastMoveTime, boolean haveTurn, + boolean haveTurnLocal ) + { + m_handler = handler; + m_expanded = expanded; + m_lastMoveTime = lastMoveTime; + m_haveTurn = haveTurn; + m_haveTurnLocal = haveTurnLocal; + m_hideable = (LinearLayout)findViewById( R.id.hideable ); + m_name = (ExpiringTextView)findViewById( R.id.game_name ); + m_expandButton = (ImageButton)findViewById( R.id.expander ); + m_expandButton.setOnClickListener( this ); + showHide(); + } + + public void setLoaded( boolean loaded ) + { + if ( m_loaded != loaded ) { + m_loaded = loaded; + // This should be enough to invalidate + findViewById( R.id.view_unloaded ) + .setVisibility( loaded ? View.GONE : View.VISIBLE ); + findViewById( R.id.view_loaded ) + .setVisibility( loaded ? View.VISIBLE : View.GONE ); + } + } + + public boolean isLoaded() + { + return m_loaded; + } + + public void setRowID( long rowid ) + { + m_rowid = rowid; + } + + public long getRowID() + { + return m_rowid; + } + + // View.OnClickListener interface + public void onClick( View view ) { + m_expanded = !m_expanded; + DBUtils.setExpanded( m_rowid, m_expanded ); + showHide(); + } + + private void showHide() + { + m_expandButton.setImageResource( m_expanded ? + R.drawable.expander_ic_maximized : + R.drawable.expander_ic_minimized); + m_hideable.setVisibility( m_expanded? View.VISIBLE : View.GONE ); + + m_name.setBackgroundColor( android.R.color.transparent ); + m_name.setPct( m_handler, m_haveTurn && !m_expanded, + m_haveTurnLocal, m_lastMoveTime ); + } + + +} diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java index fbed5b9d7..3bb68c661 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java @@ -282,7 +282,8 @@ public class GamesList extends XWListActivity } }); - m_adapter = new GameListAdapter( this, new Handler(), this ); + String field = CommonPrefs.getSummaryField( this ); + m_adapter = new GameListAdapter( this, new Handler(), this, field ); setListAdapter( m_adapter ); NetUtils.informOfDeaths( this ); @@ -391,11 +392,6 @@ public class GamesList extends XWListActivity } // GameListAdapter.LoadItemCB interface - public void itemLoaded( long rowid ) - { - onContentChanged(); - } - public void itemClicked( long rowid ) { // We need a way to let the user get back to the basic-config diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListAdapter.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListAdapter.java index 069191e81..52306419b 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListAdapter.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListAdapter.java @@ -41,8 +41,11 @@ public abstract class XWListAdapter implements ListAdapter { public boolean areAllItemsEnabled() { return true; } public boolean isEnabled( int position ) { return true; } public int getCount() { return m_count; } + public Object getItem( int position ) { return null; } public long getItemId(int position) { return position; } - public int getItemViewType(int position) { return 0; } + public int getItemViewType( int position ) { + return ListAdapter.IGNORE_ITEM_VIEW_TYPE; + } public int getViewTypeCount() { return 1; } public boolean hasStableIds() { return true; } public boolean isEmpty() { return getCount() == 0; } From ce803a928cdcc3f50ef475b5ba8c2f3613f9e601 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 8 Dec 2012 08:55:45 -0800 Subject: [PATCH 113/146] pass summary rather than have callee refetch it --- .../src/org/eehouse/android/xw4/GameListAdapter.java | 6 +++--- .../XWords4/src/org/eehouse/android/xw4/GamesList.java | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java index 87b65e852..9693a0eba 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java @@ -67,7 +67,7 @@ public class GameListAdapter extends XWListAdapter { private HashSet m_loadedRows; public interface LoadItemCB { - public void itemClicked( long rowid ); + public void itemClicked( long rowid, GameSummary summary ); } private class LoadItemTask extends AsyncTask { @@ -225,7 +225,7 @@ public class GameListAdapter extends XWListAdapter { return result; } - private void setData( GameListItem layout, GameSummary summary ) + private void setData( GameListItem layout, final GameSummary summary ) { if ( null != summary ) { final long rowid = layout.getRowID(); @@ -263,7 +263,7 @@ public class GameListAdapter extends XWListAdapter { layout.setOnClickListener( new View.OnClickListener() { @Override public void onClick( View v ) { - m_cb.itemClicked( rowid ); + m_cb.itemClicked( rowid, summary ); } } ); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java index 3bb68c661..99697680b 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java @@ -392,12 +392,11 @@ public class GamesList extends XWListActivity } // GameListAdapter.LoadItemCB interface - public void itemClicked( long rowid ) + public void itemClicked( long rowid, GameSummary summary ) { // We need a way to let the user get back to the basic-config // dialog in case it was dismissed. That way it to check for // an empty room name. - GameSummary summary = DBUtils.getSummary( this, rowid ); if ( summary.conType == CommsAddrRec.CommsConnType.COMMS_CONN_RELAY && summary.roomName.length() == 0 ) { // If it's unconfigured and of the type RelayGameActivity From 3a45db66cf6a3b50580445959b811704b2e1b0a4 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 8 Dec 2012 20:28:11 -0800 Subject: [PATCH 114/146] cache array of rowids, and clear cache appropriately, rather than query DB for all games every time. --- .../src/org/eehouse/android/xw4/DBUtils.java | 56 ++++++++++++------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java index 303b09af9..e656377e7 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java @@ -60,6 +60,7 @@ public class DBUtils { private static long s_cachedRowID = -1; private static byte[] s_cachedBytes = null; + private static long[] s_cachedRowIDs = null; public static interface DBChangeListener { public void gameSaved( long rowid ); @@ -314,6 +315,9 @@ public class DBUtils { long result = db.update( DBHelper.TABLE_NAME_SUM, values, selection, null ); Assert.assertTrue( result >= 0 ); + if ( result != rowid ) { // new row added + clearRowIDsCache(); + } } notifyListeners( rowid ); db.close(); @@ -675,7 +679,8 @@ public class DBUtils { } } - public static GameUtils.GameLock saveNewGame( Context context, byte[] bytes ) + public static GameUtils.GameLock saveNewGame( Context context, + byte[] bytes ) { GameUtils.GameLock lock = null; @@ -695,9 +700,9 @@ public class DBUtils { long rowid = db.insert( DBHelper.TABLE_NAME_SUM, null, values ); setCached( rowid, null ); // force reread + clearRowIDsCache(); lock = new GameUtils.GameLock( rowid, true ).lock(); - notifyListeners( rowid ); } @@ -771,34 +776,47 @@ public class DBUtils { db.delete( DBHelper.TABLE_NAME_SUM, selection, null ); db.close(); } + clearRowIDsCache(); notifyListeners( lock.getRowid() ); } public static long[] gamesList( Context context ) { - long[] result = null; + long[] result; + synchronized( DBUtils.class ) { + if ( null == s_cachedRowIDs ) { + initDB( context ); + synchronized( s_dbHelper ) { + SQLiteDatabase db = s_dbHelper.getReadableDatabase(); - initDB( context ); - synchronized( s_dbHelper ) { - SQLiteDatabase db = s_dbHelper.getReadableDatabase(); - - String[] columns = { ROW_ID }; - String orderBy = DBHelper.CREATE_TIME + " DESC"; - Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns, - null, null, null, null, orderBy ); - int count = cursor.getCount(); - result = new long[count]; - int index = cursor.getColumnIndex( ROW_ID ); - for ( int ii = 0; cursor.moveToNext(); ++ii ) { - result[ii] = cursor.getLong( index ); + String[] columns = { ROW_ID }; + String orderBy = DBHelper.CREATE_TIME + " DESC"; + Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, + columns, null, null, null, + null, orderBy ); + int count = cursor.getCount(); + s_cachedRowIDs = new long[count]; + int index = cursor.getColumnIndex( ROW_ID ); + for ( int ii = 0; cursor.moveToNext(); ++ii ) { + s_cachedRowIDs[ii] = cursor.getLong( index ); + } + cursor.close(); + db.close(); + } } - cursor.close(); - db.close(); + result = s_cachedRowIDs; } - return result; } + private static void clearRowIDsCache() + { + DbgUtils.logf( "DBUtils.clearRowIDsCache()" ); + synchronized( DBUtils.class ) { + s_cachedRowIDs = null; + } + } + // Get either the file name or game name, preferring the latter. public static String getName( Context context, long rowid ) { From 1bc8070bb1bb3c0141e9e289d277550201404bc6 Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 10 Dec 2012 07:11:13 -0800 Subject: [PATCH 115/146] disable Rematch button (for now) --- .../XWords4/src/org/eehouse/android/xw4/BoardActivity.java | 2 +- xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java index 9998ba315..e638df480 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java @@ -248,7 +248,7 @@ public class BoardActivity extends XWActivity } }; ab.setNegativeButton( R.string.button_retry, lstnr ); - } else if ( GAME_OVER == id ) { + } else if ( XWApp.REMATCH_SUPPORTED && GAME_OVER == id ) { lstnr = new DialogInterface.OnClickListener() { public void onClick( DialogInterface dlg, int whichButton ) { diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java index 57927412b..24edc0066 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java @@ -33,6 +33,7 @@ public class XWApp extends Application { public static final boolean SMSSUPPORTED = true; public static final boolean GCMSUPPORTED = true; public static final boolean ATTACH_SUPPORTED = true; + public static final boolean REMATCH_SUPPORTED = false; public static final boolean DEBUG = true; // DON'T SHIP THIS WAY public static final String SMS_PUBLIC_HEADER = "-XW4"; From d820554ffba3ae463a4105809b2123ee1936440b Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 10 Dec 2012 07:48:15 -0800 Subject: [PATCH 116/146] change game list item strategy since it turns out adapter's findView() doesn't pass in the previous representation of a given item for recycling: move async loading of summary into GameListItem class, and use getChildAt() to invalidate a single list (rather than reloading the whole list) whereever possible. Still need to dump the list whenever the number of items changes since we're depending on DBUtils to determine the order and have no way to reshuffle existing items. --- .../src/org/eehouse/android/xw4/DBUtils.java | 18 +- .../eehouse/android/xw4/GameListAdapter.java | 236 +++--------------- .../org/eehouse/android/xw4/GameListItem.java | 203 +++++++++++++-- .../org/eehouse/android/xw4/GamesList.java | 21 +- .../eehouse/android/xw4/XWListAdapter.java | 6 +- 5 files changed, 241 insertions(+), 243 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java index e656377e7..8deebeeaa 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java @@ -63,7 +63,7 @@ public class DBUtils { private static long[] s_cachedRowIDs = null; public static interface DBChangeListener { - public void gameSaved( long rowid ); + public void gameSaved( long rowid, boolean countChanged ); } private static HashSet s_listeners = new HashSet(); @@ -319,8 +319,8 @@ public class DBUtils { clearRowIDsCache(); } } - notifyListeners( rowid ); db.close(); + notifyListeners( rowid, false ); } } // saveSummary @@ -376,7 +376,7 @@ public class DBUtils { public static void setMsgFlags( long rowid, int flags ) { setInt( rowid, DBHelper.HASMSGS, flags ); - notifyListeners( rowid ); + notifyListeners( rowid, false ); } public static void setExpanded( long rowid, boolean expanded ) @@ -703,7 +703,7 @@ public class DBUtils { clearRowIDsCache(); lock = new GameUtils.GameLock( rowid, true ).lock(); - notifyListeners( rowid ); + notifyListeners( rowid, true ); } return lock; @@ -727,8 +727,8 @@ public class DBUtils { updateRow( context, DBHelper.TABLE_NAME_SUM, rowid, values ); setCached( rowid, null ); // force reread - if ( -1 != rowid ) { // Is this possible? PENDING - notifyListeners( rowid ); + if ( -1 != rowid ) { // Means new game? + notifyListeners( rowid, false ); } return rowid; } @@ -777,7 +777,7 @@ public class DBUtils { db.close(); } clearRowIDsCache(); - notifyListeners( lock.getRowid() ); + notifyListeners( lock.getRowid(), true ); } public static long[] gamesList( Context context ) @@ -1223,12 +1223,12 @@ public class DBUtils { } } - private static void notifyListeners( long rowid ) + private static void notifyListeners( long rowid, boolean countChanged ) { synchronized( s_listeners ) { Iterator iter = s_listeners.iterator(); while ( iter.hasNext() ) { - iter.next().gameSaved( rowid ); + iter.next().gameSaved( rowid, countChanged ); } } } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java index 9693a0eba..26405c0a2 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java @@ -21,7 +21,6 @@ package org.eehouse.android.xw4; import android.content.Context; import android.database.DataSetObserver; -import android.os.AsyncTask; import android.os.Build; import android.os.Handler; import android.view.LayoutInflater; @@ -31,11 +30,11 @@ import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListAdapter; +import android.widget.ListView; import android.widget.TextView; import java.io.FileInputStream; import java.text.DateFormat; import java.util.Date; -import java.util.HashSet; import java.util.Random; import junit.framework.Assert; @@ -46,84 +45,29 @@ import org.eehouse.android.xw4.jni.CurGameInfo.DeviceRole; import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType; public class GameListAdapter extends XWListAdapter { - private static final boolean s_isFire; - private static Random s_random; - static { - s_isFire = Build.MANUFACTURER.equals( "Amazon" ); - if ( s_isFire ) { - s_random = new Random(); - } - } - private Context m_context; + private ListView m_list; private LayoutInflater m_factory; private int m_fieldID; private Handler m_handler; private DateFormat m_df; private LoadItemCB m_cb; - // Track those rows known to be good. If a rowid is not in this - // set, assume it must be loaded. Add rowids to this set as - // they're loaded, and remove one when when it must be redrawn. - private HashSet m_loadedRows; public interface LoadItemCB { public void itemClicked( long rowid, GameSummary summary ); } - private class LoadItemTask extends AsyncTask { - private GameListItem m_view; - private Context m_context; - // private int m_id; - public LoadItemTask( Context context, GameListItem view ) - { - DbgUtils.logf( "Creating LoadItemTask for row %d", - view.getRowID() ); - m_context = context; - m_view = view; - } - - @Override - protected GameSummary doInBackground( Void... unused ) - { - // Without this, on the Fire only the last item in the - // list it tappable. Likely my fault, but this seems to - // work around it. - if ( s_isFire ) { - try { - int sleepTime = 500 + (s_random.nextInt() % 500); - Thread.sleep( sleepTime ); - } catch ( Exception e ) { - } - } - - long rowid = m_view.getRowID(); - GameSummary summary = DBUtils.getSummary( m_context, rowid, 1500 ); - return summary; - } // doInBackground - - @Override - protected void onPostExecute( GameSummary summary ) - { - setData( m_view, summary ); - setLoaded( m_view.getRowID() ); - m_view.setLoaded( true ); - - DbgUtils.logf( "LoadItemTask for row %d finished", - m_view.getRowID() ); - } - } // class LoadItemTask - - public GameListAdapter( Context context, Handler handler, LoadItemCB cb, - String fieldName ) { + public GameListAdapter( Context context, ListView list, + Handler handler, LoadItemCB cb, String fieldName ) { super( DBUtils.gamesList(context).length ); m_context = context; + m_list = list; m_handler = handler; m_cb = cb; m_factory = LayoutInflater.from( context ); m_df = DateFormat.getDateTimeInstance( DateFormat.SHORT, DateFormat.SHORT ); - m_loadedRows = new HashSet(); m_fieldID = fieldToID( fieldName ); } @@ -136,53 +80,29 @@ public class GameListAdapter extends XWListAdapter { // When one needs loading it's done via an async task. public View getView( int position, View convertView, ViewGroup parent ) { - GameListItem result; - boolean mustLoad = false; - if ( null == convertView ) { - result = (GameListItem) - m_factory.inflate( R.layout.game_list_item, null ); - result.setRowID( DBUtils.gamesList(m_context)[position] ); - mustLoad = true; - } else { - result = (GameListItem)convertView; - long rowid = result.getRowID(); - if ( isDirty(rowid) || !result.isLoaded() ) { - mustLoad = true; - } - } - - if ( mustLoad ) { - new LoadItemTask( m_context, result ).execute(); - } - + GameListItem result = (GameListItem) + m_factory.inflate( R.layout.game_list_item, null ); + result.init( m_handler, DBUtils.gamesList(m_context)[position], + m_fieldID, m_cb ); return result; } public void inval( long rowid ) { - synchronized( m_loadedRows ) { - m_loadedRows.remove( rowid ); + GameListItem child = getItemFor( rowid ); + if ( null != child ) { + child.forceReload(); + } else { + DbgUtils.logf( "no child for rowid %d", rowid ); + m_list.invalidate(); } } - private void dirtyAll() + public void invalName( long rowid ) { - synchronized( m_loadedRows ) { - m_loadedRows.clear(); - } - } - - private boolean isDirty( long rowid ) - { - synchronized( m_loadedRows ) { - return ! m_loadedRows.contains( rowid ); - } - } - - private void setLoaded( long rowid ) - { - synchronized( m_loadedRows ) { - m_loadedRows.add( rowid ); + GameListItem item = getItemFor( rowid ); + if ( null != item ) { + item.invalName(); } } @@ -201,12 +121,24 @@ public class GameListAdapter extends XWListAdapter { + " from %d to %d", m_fieldID, newID ); } m_fieldID = newID; - dirtyAll(); + // return true so caller will do onContentChanged. + // There's no other way to signal GameListItem instances + // since we don't maintain a list of them. changed = true; } return changed; } + private GameListItem getItemFor( long rowid ) + { + GameListItem result = null; + int position = positionFor( rowid ); + if ( 0 <= position ) { + result = (GameListItem)m_list.getChildAt( position ); + } + return result; + } + private int fieldToID( String fieldName ) { int[] ids = { @@ -225,106 +157,16 @@ public class GameListAdapter extends XWListAdapter { return result; } - private void setData( GameListItem layout, final GameSummary summary ) + private int positionFor( long rowid ) { - if ( null != summary ) { - final long rowid = layout.getRowID(); - String state = summary.summarizeState(); - - TextView view = (TextView)layout.findViewById( R.id.game_name ); - String value = null; - switch ( m_fieldID ) { - case R.string.game_summary_field_empty: - break; - case R.string.game_summary_field_language: - value = - DictLangCache.getLangName( m_context, - summary.dictLang ); - break; - case R.string.game_summary_field_opponents: - value = summary.playerNames(); - break; - case R.string.game_summary_field_state: - value = state; + int position = -1; + long[] rowids = DBUtils.gamesList( m_context ); + for ( int ii = 0; ii < rowids.length; ++ii ) { + if ( rowids[ii] == rowid ) { + position = ii; break; } - - String name = GameUtils.getName( m_context, rowid ); - - if ( null != value ) { - value = m_context.getString( R.string.str_game_namef, - name, value ); - } else { - value = name; - } - - view.setText( value ); - - layout.setOnClickListener( new View.OnClickListener() { - @Override - public void onClick( View v ) { - m_cb.itemClicked( rowid, summary ); - } - } ); - - LinearLayout list = - (LinearLayout)layout.findViewById( R.id.player_list ); - boolean haveATurn = false; - boolean haveALocalTurn = false; - boolean[] isLocal = new boolean[1]; - for ( int ii = 0; ii < summary.nPlayers; ++ii ) { - ExpiringLinearLayout tmp = (ExpiringLinearLayout) - m_factory.inflate( R.layout.player_list_elem, null ); - view = (TextView)tmp.findViewById( R.id.item_name ); - view.setText( summary.summarizePlayer( ii ) ); - view = (TextView)tmp.findViewById( R.id.item_score ); - view.setText( String.format( " %d", summary.scores[ii] ) ); - boolean thisHasTurn = summary.isNextToPlay( ii, isLocal ); - if ( thisHasTurn ) { - haveATurn = true; - if ( isLocal[0] ) { - haveALocalTurn = true; - } - } - tmp.setPct( m_handler, thisHasTurn, isLocal[0], - summary.lastMoveTime ); - list.addView( tmp, ii ); - } - - view = (TextView)layout.findViewById( R.id.state ); - view.setText( state ); - view = (TextView)layout.findViewById( R.id.modtime ); - long lastMoveTime = summary.lastMoveTime; - lastMoveTime *= 1000; - view.setText( m_df.format( new Date( lastMoveTime ) ) ); - - int iconID; - ImageView marker = - (ImageView)layout.findViewById( R.id.msg_marker ); - CommsConnType conType = summary.conType; - if ( CommsConnType.COMMS_CONN_RELAY == conType ) { - iconID = R.drawable.relaygame; - } else if ( CommsConnType.COMMS_CONN_BT == conType ) { - iconID = android.R.drawable.stat_sys_data_bluetooth; - } else if ( CommsConnType.COMMS_CONN_SMS == conType ) { - iconID = android.R.drawable.sym_action_chat; - } else { - iconID = R.drawable.sologame; - } - marker.setImageResource( iconID ); - - view = (TextView)layout.findViewById( R.id.role ); - String roleSummary = summary.summarizeRole(); - if ( null != roleSummary ) { - view.setText( roleSummary ); - } else { - view.setVisibility( View.GONE ); - } - - boolean expanded = DBUtils.getExpanded( m_context, rowid ); - - layout.update( m_handler, expanded, summary.lastMoveTime, - haveATurn, haveALocalTurn ); } + return position; } } \ No newline at end of file diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListItem.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListItem.java index fb03482d2..154ffb4c3 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListItem.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListItem.java @@ -21,11 +21,19 @@ package org.eehouse.android.xw4; import android.content.Context; +import android.os.AsyncTask; import android.os.Handler; import android.util.AttributeSet; import android.view.View; import android.widget.ImageButton; +import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.TextView; +import java.text.DateFormat; +import java.util.Date; + +import org.eehouse.android.xw4.jni.GameSummary; +import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType; public class GameListItem extends LinearLayout implements View.OnClickListener { @@ -39,6 +47,9 @@ public class GameListItem extends LinearLayout private long m_lastMoveTime; private ImageButton m_expandButton; private Handler m_handler; + private GameSummary m_summary; + private GameListAdapter.LoadItemCB m_cb; + private int m_fieldID; public GameListItem( Context cx, AttributeSet as ) { @@ -49,11 +60,31 @@ public class GameListItem extends LinearLayout m_lastMoveTime = 0; } - public void update( Handler handler, boolean expanded, - long lastMoveTime, boolean haveTurn, - boolean haveTurnLocal ) + public void init( Handler handler, long rowid, int fieldID, + GameListAdapter.LoadItemCB cb ) { m_handler = handler; + m_rowid = rowid; + m_fieldID = fieldID; + m_cb = cb; + + forceReload(); + } + + public void forceReload() + { + m_summary = null; + new LoadItemTask().execute(); + } + + public void invalName() + { + setName(); + } + + private void update( boolean expanded, long lastMoveTime, boolean haveTurn, + boolean haveTurnLocal ) + { m_expanded = expanded; m_lastMoveTime = lastMoveTime; m_haveTurn = haveTurn; @@ -65,28 +96,6 @@ public class GameListItem extends LinearLayout showHide(); } - public void setLoaded( boolean loaded ) - { - if ( m_loaded != loaded ) { - m_loaded = loaded; - // This should be enough to invalidate - findViewById( R.id.view_unloaded ) - .setVisibility( loaded ? View.GONE : View.VISIBLE ); - findViewById( R.id.view_loaded ) - .setVisibility( loaded ? View.VISIBLE : View.GONE ); - } - } - - public boolean isLoaded() - { - return m_loaded; - } - - public void setRowID( long rowid ) - { - m_rowid = rowid; - } - public long getRowID() { return m_rowid; @@ -99,6 +108,18 @@ public class GameListItem extends LinearLayout showHide(); } + private void setLoaded() + { + if ( !m_loaded ) { + m_loaded = true; + // This should be enough to invalidate + findViewById( R.id.view_unloaded ) + .setVisibility( m_loaded ? View.GONE : View.VISIBLE ); + findViewById( R.id.view_loaded ) + .setVisibility( m_loaded ? View.VISIBLE : View.GONE ); + } + } + private void showHide() { m_expandButton.setImageResource( m_expanded ? @@ -111,5 +132,137 @@ public class GameListItem extends LinearLayout m_haveTurnLocal, m_lastMoveTime ); } + private String setName() + { + String state = null; // hack to avoid calling summarizeState twice + if ( null != m_summary ) { + state = m_summary.summarizeState(); + TextView view = (TextView)findViewById( R.id.game_name ); + String value = null; + switch ( m_fieldID ) { + case R.string.game_summary_field_empty: + break; + case R.string.game_summary_field_language: + value = + DictLangCache.getLangName( m_context, + m_summary.dictLang ); + break; + case R.string.game_summary_field_opponents: + value = m_summary.playerNames(); + break; + case R.string.game_summary_field_state: + value = state; + break; + } + + if ( null != value ) { + String name = GameUtils.getName( m_context, m_rowid ); + value = m_context.getString( R.string.str_game_namef, + name, value ); + } else { + value = GameUtils.getName( m_context, m_rowid ); + } + + view.setText( value ); + } + return state; + } + + private void setData() + { + if ( null != m_summary ) { + TextView view; + String state = setName(); + + setOnClickListener( new View.OnClickListener() { + @Override + public void onClick( View v ) { + m_cb.itemClicked( m_rowid, m_summary ); + } + } ); + + LinearLayout list = + (LinearLayout)findViewById( R.id.player_list ); + list.removeAllViews(); + boolean haveATurn = false; + boolean haveALocalTurn = false; + boolean[] isLocal = new boolean[1]; + for ( int ii = 0; ii < m_summary.nPlayers; ++ii ) { + ExpiringLinearLayout tmp = (ExpiringLinearLayout) + Utils.inflate( m_context, R.layout.player_list_elem ); + view = (TextView)tmp.findViewById( R.id.item_name ); + view.setText( m_summary.summarizePlayer( ii ) ); + view = (TextView)tmp.findViewById( R.id.item_score ); + view.setText( String.format( " %d", m_summary.scores[ii] ) ); + boolean thisHasTurn = m_summary.isNextToPlay( ii, isLocal ); + if ( thisHasTurn ) { + haveATurn = true; + if ( isLocal[0] ) { + haveALocalTurn = true; + } + } + tmp.setPct( m_handler, thisHasTurn, isLocal[0], + m_summary.lastMoveTime ); + list.addView( tmp, ii ); + } + + view = (TextView)findViewById( R.id.state ); + view.setText( state ); + view = (TextView)findViewById( R.id.modtime ); + long lastMoveTime = m_summary.lastMoveTime; + lastMoveTime *= 1000; + + DateFormat df = DateFormat.getDateTimeInstance( DateFormat.SHORT, + DateFormat.SHORT ); + view.setText( df.format( new Date( lastMoveTime ) ) ); + + int iconID; + ImageView marker = + (ImageView)findViewById( R.id.msg_marker ); + CommsConnType conType = m_summary.conType; + if ( CommsConnType.COMMS_CONN_RELAY == conType ) { + iconID = R.drawable.relaygame; + } else if ( CommsConnType.COMMS_CONN_BT == conType ) { + iconID = android.R.drawable.stat_sys_data_bluetooth; + } else if ( CommsConnType.COMMS_CONN_SMS == conType ) { + iconID = android.R.drawable.sym_action_chat; + } else { + iconID = R.drawable.sologame; + } + marker.setImageResource( iconID ); + + view = (TextView)findViewById( R.id.role ); + String roleSummary = m_summary.summarizeRole(); + if ( null != roleSummary ) { + view.setText( roleSummary ); + } else { + view.setVisibility( View.GONE ); + } + + boolean expanded = DBUtils.getExpanded( m_context, m_rowid ); + + update( expanded, m_summary.lastMoveTime, haveATurn, + haveALocalTurn ); + } + } + + private class LoadItemTask extends AsyncTask { + @Override + protected GameSummary doInBackground( Void... unused ) + { + return DBUtils.getSummary( m_context, m_rowid, 1500 ); + } // doInBackground + + @Override + protected void onPostExecute( GameSummary summary ) + { + m_summary = summary; + setData(); + // setLoaded( m_view.getRowID() ); + setLoaded(); + + DbgUtils.logf( "LoadItemTask for row %d finished", m_rowid ); + } + } // class LoadItemTask } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java index 99697680b..301f153da 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java @@ -200,8 +200,7 @@ public class GamesList extends XWListActivity public void onClick( DialogInterface dlg, int item ) { String name = namerView.getName(); DBUtils.setName( GamesList.this, m_rowid, name ); - m_adapter.inval( m_rowid ); - onContentChanged(); + m_adapter.invalName( m_rowid ); } }; dialog = new AlertDialog.Builder( this ) @@ -283,7 +282,8 @@ public class GamesList extends XWListActivity }); String field = CommonPrefs.getSummaryField( this ); - m_adapter = new GameListAdapter( this, new Handler(), this, field ); + m_adapter = new GameListAdapter( this, getListView(), new Handler(), + this, field ); setListAdapter( m_adapter ); NetUtils.informOfDeaths( this ); @@ -381,12 +381,15 @@ public class GamesList extends XWListActivity } // DBUtils.DBChangeListener interface - public void gameSaved( final long rowid ) + public void gameSaved( final long rowid, final boolean countChanged ) { post( new Runnable() { public void run() { - m_adapter.inval( rowid ); - onContentChanged(); + if ( countChanged ) { + onContentChanged(); + } else { + m_adapter.inval( rowid ); + } } } ); } @@ -457,7 +460,6 @@ public class GamesList extends XWListActivity long[] games = DBUtils.gamesList( this ); for ( int ii = games.length - 1; ii >= 0; --ii ) { GameUtils.deleteGame( this, games[ii], ii == 0 ); - m_adapter.inval( games[ii] ); } break; case SYNC_MENU_ACTION: @@ -738,7 +740,6 @@ public class GamesList extends XWListActivity } } } - onContentChanged(); } } @@ -846,7 +847,9 @@ public class GamesList extends XWListActivity { String newField = CommonPrefs.getSummaryField( this ); if ( m_adapter.setField( newField ) ) { - onContentChanged(); + // The adapter should be able to decide whether full + // content change is required. PENDING + onContentChanged(); } } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListAdapter.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListAdapter.java index 52306419b..decc6fde6 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListAdapter.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListAdapter.java @@ -42,13 +42,13 @@ public abstract class XWListAdapter implements ListAdapter { public boolean isEnabled( int position ) { return true; } public int getCount() { return m_count; } public Object getItem( int position ) { return null; } - public long getItemId(int position) { return position; } + public long getItemId( int position ) { return position; } public int getItemViewType( int position ) { return ListAdapter.IGNORE_ITEM_VIEW_TYPE; } public int getViewTypeCount() { return 1; } public boolean hasStableIds() { return true; } public boolean isEmpty() { return getCount() == 0; } - public void registerDataSetObserver(DataSetObserver observer) {} - public void unregisterDataSetObserver(DataSetObserver observer) {} + public void registerDataSetObserver( DataSetObserver observer ) {} + public void unregisterDataSetObserver( DataSetObserver observer ) {} } \ No newline at end of file From f599ca8be42b9aed181e0bb703b3d8dcdc5ec33e Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 10 Dec 2012 07:54:42 -0800 Subject: [PATCH 117/146] make tiles square based on rowid so people can see both --- .../src/org/eehouse/android/xw4/BoardActivity.java | 1 + .../XWords4/src/org/eehouse/android/xw4/BoardView.java | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java index e638df480..c4003c5fa 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java @@ -509,6 +509,7 @@ public class BoardActivity extends XWActivity Intent intent = getIntent(); m_rowid = intent.getLongExtra( GameUtils.INTENT_KEY_ROWID, -1 ); + m_view.setRowID( m_rowid ); m_haveInvited = intent.getBooleanExtra( GameUtils.INVITED, false ); m_overNotShown = true; diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardView.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardView.java index d05909c99..9c4b62c67 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardView.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardView.java @@ -53,8 +53,6 @@ public class BoardView extends View implements DrawCtx, BoardHandler, private static final int PINCH_THRESHOLD = 40; private static final int SCORE_HT_DROP = 2; private static final boolean DEBUG_DRAWFRAMES = false; - // this can be a preference - private static final boolean MAKETILESSQUARE = true; private Context m_context; private Paint m_drawPaint; @@ -77,6 +75,7 @@ public class BoardView extends View implements DrawCtx, BoardHandler, private boolean m_blackArrow; private boolean m_inTrade = false; private boolean m_hasSmallScreen; + private long m_rowid; // m_backgroundUsed: alpha not set ensures inequality private int m_backgroundUsed = 0x00000000; private boolean m_darkOnLight; @@ -239,6 +238,11 @@ public class BoardView extends View implements DrawCtx, BoardHandler, return true; // required to get subsequent events } + public void setRowID( long rowid ) + { + m_rowid = rowid; + } + // private void printMode( String comment, int mode ) // { // comment += ": "; @@ -384,7 +388,7 @@ public class BoardView extends View implements DrawCtx, BoardHandler, scoreHt += heightLeft; trayHt += heightLeft * 2; - if ( MAKETILESSQUARE && trayHt > width / 7 ) { + if ( (1 == (m_rowid % 2)) && trayHt > width / 7 ) { trayHt = width / 7; } heightUsed = trayHt + scoreHt + ((nCells - nToScroll) * cellSize); From 877225f59dc3b6248a6a4190c14886a93079395d Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 10 Dec 2012 18:20:44 -0800 Subject: [PATCH 118/146] rename interface; no code change --- .../src/org/eehouse/android/xw4/BTInviteActivity.java | 2 +- .../XWords4/src/org/eehouse/android/xw4/BTService.java | 2 +- .../XWords4/src/org/eehouse/android/xw4/BoardActivity.java | 2 +- .../XWords4/src/org/eehouse/android/xw4/GamesList.java | 2 +- .../XWords4/src/org/eehouse/android/xw4/MultiService.java | 6 +++--- .../src/org/eehouse/android/xw4/NewGameActivity.java | 4 ++-- .../XWords4/src/org/eehouse/android/xw4/SMSService.java | 2 +- .../XWords4/src/org/eehouse/android/xw4/XWActivity.java | 4 ++-- .../XWords4/src/org/eehouse/android/xw4/XWListActivity.java | 4 ++-- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTInviteActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTInviteActivity.java index fcfcbd51a..bcc4c325c 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTInviteActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTInviteActivity.java @@ -65,7 +65,7 @@ public class BTInviteActivity extends InviteActivity BTService.clearDevices( this, null ); // will return names } - // BTService.BTEventListener interface + // MultiService.MultiEventListener interface @Override public void eventOccurred( MultiService.MultiEvent event, final Object ... args ) { diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTService.java index f62100c4e..4d6f8fd6d 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTService.java @@ -147,7 +147,7 @@ public class BTService extends Service { } } - public static void setListener( MultiService.BTEventListener li ) + public static void setListener( MultiService.MultiEventListener li ) { if ( XWApp.BTSUPPORTED ) { if ( null == s_srcMgr ) { diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java index c4003c5fa..f8a5f802c 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java @@ -924,7 +924,7 @@ public class BoardActivity extends XWActivity } ////////////////////////////////////////////////// - // BTService.BTEventListener interface + // MultiService.MultiEventListener interface ////////////////////////////////////////////////// @Override @SuppressWarnings("fallthrough") diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java index 301f153da..306a7a4aa 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java @@ -419,7 +419,7 @@ public class GamesList extends XWListActivity } } - // BTService.BTEventListener interface + // BTService.MultiEventListener interface @Override public void eventOccurred( MultiService.MultiEvent event, final Object ... args ) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiService.java index 4e4d21686..5d2a776eb 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiService.java @@ -42,7 +42,7 @@ public class MultiService { public static final int OWNER_SMS = 1; public static final int OWNER_RELAY = 2; - private BTEventListener m_li; + private MultiEventListener m_li; public enum MultiEvent { BAD_PROTO , BT_ENABLED @@ -64,14 +64,14 @@ public class MultiService { , SMS_SEND_FAILED_NORADIO }; - public interface BTEventListener { + public interface MultiEventListener { public void eventOccurred( MultiEvent event, Object ... args ); } // public interface MultiEventSrc { // public void setBTEventListener( BTEventListener li ); // } - public void setListener( BTEventListener li ) + public void setListener( MultiEventListener li ) { synchronized( this ) { m_li = li; diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NewGameActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NewGameActivity.java index 85bbccd63..d1b90148a 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NewGameActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NewGameActivity.java @@ -256,7 +256,7 @@ public class NewGameActivity extends XWActivity { return dialog; } - // BTService.BTEventListener interface + // MultiService.MultiEventListener interface @Override public void eventOccurred( MultiService.MultiEvent event, final Object ... args ) @@ -299,7 +299,7 @@ public class NewGameActivity extends XWActivity { super.eventOccurred( event, args ); break; } - } // BTService.BTEventListener.eventOccurred + } // MultiService.MultiEventListener.eventOccurred private void makeNewGame( boolean networked, boolean launch ) { diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java index 6734e1220..be0a95ef2 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java @@ -189,7 +189,7 @@ public class SMSService extends Service { return result; } - public static void setListener( MultiService.BTEventListener li ) + public static void setListener( MultiService.MultiEventListener li ) { if ( XWApp.SMSSUPPORTED ) { if ( null == s_srcMgr ) { diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWActivity.java index 18670d379..71e8a922d 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWActivity.java @@ -31,7 +31,7 @@ import android.widget.TextView; import junit.framework.Assert; public class XWActivity extends Activity - implements DlgDelegate.DlgClickNotify, MultiService.BTEventListener { + implements DlgDelegate.DlgClickNotify, MultiService.MultiEventListener { private DlgDelegate m_delegate; @@ -192,7 +192,7 @@ public class XWActivity extends Activity Assert.fail(); } - // BTService.BTEventListener interface + // BTService.MultiEventListener interface public void eventOccurred( MultiService.MultiEvent event, final Object ... args ) { diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListActivity.java index 9f10da3d4..927b73f78 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListActivity.java @@ -28,7 +28,7 @@ import android.os.Bundle; import junit.framework.Assert; public class XWListActivity extends ListActivity - implements DlgDelegate.DlgClickNotify, MultiService.BTEventListener { + implements DlgDelegate.DlgClickNotify, MultiService.MultiEventListener { private DlgDelegate m_delegate; @@ -193,7 +193,7 @@ public class XWListActivity extends ListActivity m_delegate.launchLookup( words, lang, forceList ); } - // BTService.BTEventListener interface + // MultiService.MultiEventListener interface public void eventOccurred( MultiService.MultiEvent event, final Object ... args ) { From b2bd6ce662b382d8c59f8e95342733d42ac50ec9 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 11 Dec 2012 07:25:43 -0800 Subject: [PATCH 119/146] add and use preference for square rack tiles, removing code that alternated for testing purposes. --- xwords4/android/XWords4/res/values/common_rsrc.xml | 1 + xwords4/android/XWords4/res/values/strings.xml | 4 ++++ xwords4/android/XWords4/res/xml/xwprefs.xml | 5 +++++ .../src/org/eehouse/android/xw4/BoardActivity.java | 1 - .../XWords4/src/org/eehouse/android/xw4/BoardView.java | 9 ++------- .../XWords4/src/org/eehouse/android/xw4/XWPrefs.java | 5 +++++ 6 files changed, 17 insertions(+), 8 deletions(-) diff --git a/xwords4/android/XWords4/res/values/common_rsrc.xml b/xwords4/android/XWords4/res/values/common_rsrc.xml index 860b43b11..759d74e00 100644 --- a/xwords4/android/XWords4/res/values/common_rsrc.xml +++ b/xwords4/android/XWords4/res/values/common_rsrc.xml @@ -6,6 +6,7 @@ key_color_tiles key_show_arrow + key_square_tiles key_explain_robot key_skip_confirm key_sort_tiles diff --git a/xwords4/android/XWords4/res/values/strings.xml b/xwords4/android/XWords4/res/values/strings.xml index 55f904e56..cdd796f20 100644 --- a/xwords4/android/XWords4/res/values/strings.xml +++ b/xwords4/android/XWords4/res/values/strings.xml @@ -2135,4 +2135,8 @@ game with the same players and parameters as the one that just ended. --> Rematch + + Square rack tiles + Even if they can be taller + diff --git a/xwords4/android/XWords4/res/xml/xwprefs.xml b/xwords4/android/XWords4/res/xml/xwprefs.xml index 86fdece79..2cb350166 100644 --- a/xwords4/android/XWords4/res/xml/xwprefs.xml +++ b/xwords4/android/XWords4/res/xml/xwprefs.xml @@ -132,6 +132,11 @@ android:summary="@string/show_arrow_summary" android:defaultValue="true" /> + width / 7 ) { + if ( XWPrefs.getSquareTiles( m_context ) + && trayHt > (width / 7) ) { trayHt = width / 7; } heightUsed = trayHt + scoreHt + ((nCells - nToScroll) * cellSize); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java index dce8adae8..b966a051e 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java @@ -83,6 +83,11 @@ public class XWPrefs { return getPrefsBoolean( context, R.string.key_ringer_zoom, false ); } + public static boolean getSquareTiles( Context context ) + { + return getPrefsBoolean( context, R.string.key_square_tiles, false ); + } + public static int getDefaultPlayerMinutes( Context context ) { String value = From 7246ae28c697043c1b6475610c9546f1de97aab1 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 11 Dec 2012 19:09:33 -0800 Subject: [PATCH 120/146] clear the cache after loading a new DB so will redraw correctly in list --- xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java | 1 + 1 file changed, 1 insertion(+) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java index 8deebeeaa..679b62a80 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java @@ -924,6 +924,7 @@ public class DBUtils { public static void loadDB( Context context ) { + clearRowIDsCache(); copyGameDB( context, false ); } From 01d17fe0c50783bfb3033d420e5466099a4b7f5c Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 11 Dec 2012 19:10:05 -0800 Subject: [PATCH 121/146] redraw full list after resetting a game in case position changes --- .../android/XWords4/src/org/eehouse/android/xw4/GamesList.java | 1 + 1 file changed, 1 insertion(+) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java index 306a7a4aa..833c4eac4 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java @@ -452,6 +452,7 @@ public class GamesList extends XWListActivity break; case RESET_GAME_ACTION: GameUtils.resetGame( this, m_rowid ); + onContentChanged(); // required because position may change break; case DELETE_GAME_ACTION: GameUtils.deleteGame( this, m_rowid, true ); From 575d5e97453693041825686a6acfcb6838a14839 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 11 Dec 2012 19:11:23 -0800 Subject: [PATCH 122/146] reduce time we'll wait for a summary to unlock when loading it for games list. --- .../XWords4/src/org/eehouse/android/xw4/GameListItem.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListItem.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListItem.java index 154ffb4c3..89f9e4b7a 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListItem.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListItem.java @@ -250,7 +250,7 @@ public class GameListItem extends LinearLayout @Override protected GameSummary doInBackground( Void... unused ) { - return DBUtils.getSummary( m_context, m_rowid, 1500 ); + return DBUtils.getSummary( m_context, m_rowid, 150 ); } // doInBackground @Override From eee954e705101556a414571dbe7b8f0419e42c4a Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 11 Dec 2012 19:15:15 -0800 Subject: [PATCH 123/146] list changes for next release --- xwords4/android/XWords4/res/raw/changes | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/xwords4/android/XWords4/res/raw/changes b/xwords4/android/XWords4/res/raw/changes index c1165a0bd..d87b350f7 100644 --- a/xwords4/android/XWords4/res/raw/changes +++ b/xwords4/android/XWords4/res/raw/changes @@ -9,11 +9,17 @@

          New with this release

            +
          • Include attachment in email invites for devices that don't dispatch + URLs correctly (e.g. some by HTC)
          • +
          • Fix flickering in main screen (games list)
          • +
          • Add option, off by default, to keep rack tiles square even when + the screen is large enough that they can be taller
          • +
          • Show final scores alert whenever a finished game is opened -- to + make it more clear that it's finished

          Next up

            -
          • One more idea for improving invitations
          • Allow grouping of games in collapsible user-defined categores: "Games with Kati", "Finished games", etc.
          From 69f868722f54e754a1ac2a8da9282cb831e1192c Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 12 Dec 2012 06:41:56 -0800 Subject: [PATCH 124/146] cancel any notification for game when resetting it --- .../android/XWords4/src/org/eehouse/android/xw4/GameUtils.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java index 420819c46..a5c442cc1 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java @@ -246,6 +246,8 @@ public class GameUtils { tellDied( context, lock, true ); resetGame( context, lock, lock, false ); lock.unlock(); + + Utils.cancelNotification( context, (int)rowidIn ); } private static GameSummary summarizeAndClose( Context context, From db8364c28551c19bbc5a0481f4ece10333417342 Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 12 Dec 2012 06:43:18 -0800 Subject: [PATCH 125/146] return empty array rather than null when query succeeds but produces no result. --- .../XWords4/src/org/eehouse/android/xw4/DBUtils.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java index 679b62a80..b584fcc8e 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java @@ -451,10 +451,8 @@ public class DBUtils { String selection = DBHelper.RELAYID + "='" + relayID + "'"; Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns, selection, null, null, null, null ); + result = new long[cursor.getCount()]; for ( int ii = 0; cursor.moveToNext(); ++ii ) { - if ( null == result ) { - result = new long[cursor.getCount()]; - } result[ii] = cursor.getLong( cursor.getColumnIndex(ROW_ID) ); } cursor.close(); @@ -473,11 +471,8 @@ public class DBUtils { String selection = String.format( DBHelper.GAMEID + "=%d", gameID ); Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns, selection, null, null, null, null ); - + result = new long[cursor.getCount()]; for ( int ii = 0; cursor.moveToNext(); ++ii ) { - if ( null == result ) { - result = new long[cursor.getCount()]; - } result[ii] = cursor.getLong( cursor.getColumnIndex(ROW_ID) ); } cursor.close(); From b71046e5aac08f1f920c5245b4c958c187a7327d Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 12 Dec 2012 07:13:25 -0800 Subject: [PATCH 126/146] lock, rather than tryLock, game when feeding it messages. Otherwise messages are dropped e.g. when UI's loading a summary in GameListItem. --- .../org/eehouse/android/xw4/GameUtils.java | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java index a5c442cc1..a1837bd7a 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java @@ -713,35 +713,35 @@ public class GameUtils { { boolean draw = false; Assert.assertTrue( -1 != rowid ); - GameLock lock = new GameLock( rowid, true ); - if ( lock.tryLock() ) { - CurGameInfo gi = new CurGameInfo( context ); - FeedUtilsImpl feedImpl = new FeedUtilsImpl( context, rowid ); - int gamePtr = loadMakeGame( context, gi, feedImpl, sink, lock ); - if ( 0 != gamePtr ) { - XwJNI.comms_resendAll( gamePtr, false, false ); + GameLock lock = new GameLock( rowid, true ).lock(); - if ( null != msgs ) { - for ( byte[] msg : msgs ) { - draw = XwJNI.game_receiveMessage( gamePtr, msg, ret ) - || draw; - } - } - XwJNI.comms_ackAny( gamePtr ); + CurGameInfo gi = new CurGameInfo( context ); + FeedUtilsImpl feedImpl = new FeedUtilsImpl( context, rowid ); + int gamePtr = loadMakeGame( context, gi, feedImpl, sink, lock ); + if ( 0 != gamePtr ) { + XwJNI.comms_resendAll( gamePtr, false, false ); - // update gi to reflect changes due to messages - XwJNI.game_getGi( gamePtr, gi ); - saveGame( context, gamePtr, gi, lock, false ); - summarizeAndClose( context, lock, gamePtr, gi, feedImpl ); - - int flags = setFromFeedImpl( feedImpl ); - if ( GameSummary.MSG_FLAGS_NONE != flags ) { - draw = true; - DBUtils.setMsgFlags( rowid, flags ); + if ( null != msgs ) { + for ( byte[] msg : msgs ) { + draw = XwJNI.game_receiveMessage( gamePtr, msg, ret ) + || draw; } } - lock.unlock(); + XwJNI.comms_ackAny( gamePtr ); + + // update gi to reflect changes due to messages + XwJNI.game_getGi( gamePtr, gi ); + saveGame( context, gamePtr, gi, lock, false ); + summarizeAndClose( context, lock, gamePtr, gi, feedImpl ); + + int flags = setFromFeedImpl( feedImpl ); + if ( GameSummary.MSG_FLAGS_NONE != flags ) { + draw = true; + DBUtils.setMsgFlags( rowid, flags ); + } } + lock.unlock(); + return draw; } // feedMessages From 91ac04b8965825778d697bf3d4b9fa5f9b519dce Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 13 Dec 2012 06:32:57 -0800 Subject: [PATCH 127/146] remove logging --- .../XWords4/src/org/eehouse/android/xw4/GameListItem.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListItem.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListItem.java index 89f9e4b7a..d5d727b08 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListItem.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListItem.java @@ -261,7 +261,7 @@ public class GameListItem extends LinearLayout // setLoaded( m_view.getRowID() ); setLoaded(); - DbgUtils.logf( "LoadItemTask for row %d finished", m_rowid ); + // DbgUtils.logf( "LoadItemTask for row %d finished", m_rowid ); } } // class LoadItemTask From 6060d5e8bdd04e497c58cf932b9329864a3a9fdf Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 13 Dec 2012 06:57:12 -0800 Subject: [PATCH 128/146] Fix hangs when receiving relay messages in background for open game by adding a static feedMessages method like the one used by SMS and BT games. For that to work, rowid and relayid need to be fetched and tracked together -- so do that in RelayService. --- .../eehouse/android/xw4/BoardActivity.java | 22 ++++ .../src/org/eehouse/android/xw4/DBUtils.java | 27 +++-- .../org/eehouse/android/xw4/DlgDelegate.java | 2 +- .../org/eehouse/android/xw4/GameUtils.java | 102 +++++++++--------- .../src/org/eehouse/android/xw4/NetUtils.java | 16 ++- .../org/eehouse/android/xw4/RelayService.java | 47 ++++---- .../src/org/eehouse/android/xw4/XWApp.java | 4 +- 7 files changed, 129 insertions(+), 91 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java index 396270e30..ff5f5a104 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java @@ -189,6 +189,27 @@ public class BoardActivity extends XWActivity return delivered; } + public static boolean feedMessages( long rowid, byte[][] msgs ) + { + boolean delivered = false; + Assert.assertNotNull( msgs ); + synchronized( s_thisLocker ) { + if ( null != s_this ) { + Assert.assertNotNull( s_this.m_gi ); + Assert.assertNotNull( s_this.m_gameLock ); + Assert.assertNotNull( s_this.m_jniThread ); + if ( rowid == s_this.m_rowid ) { + delivered = true; // even if no messages! + for ( byte[] msg : msgs ) { + s_this.m_jniThread.handle( JNICmd.CMD_RECEIVE, msg, + null ); + } + } + } + } + return delivered; + } + private static void setThis( BoardActivity self ) { synchronized( s_thisLocker ) { @@ -509,6 +530,7 @@ public class BoardActivity extends XWActivity Intent intent = getIntent(); m_rowid = intent.getLongExtra( GameUtils.INTENT_KEY_ROWID, -1 ); + DbgUtils.logf( "BoardActivity: opening rowid %d", m_rowid ); m_haveInvited = intent.getBooleanExtra( GameUtils.INVITED, false ); m_overNotShown = true; diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java index b584fcc8e..cc6529755 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java @@ -573,7 +573,7 @@ public class DBUtils { return result; } - public static String[] getRelayIDs( Context context, boolean noMsgs ) + public static String[] getRelayIDs( Context context, long[][] rowIDs ) { String[] result = null; initDB( context ); @@ -581,26 +581,31 @@ public class DBUtils { synchronized( s_dbHelper ) { SQLiteDatabase db = s_dbHelper.getReadableDatabase(); - String[] columns = { DBHelper.RELAYID }; + String[] columns = { ROW_ID, DBHelper.RELAYID }; String selection = DBHelper.RELAYID + " NOT null"; - if ( noMsgs ) { - selection += " AND NOT " + DBHelper.HASMSGS; - } Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns, selection, null, null, null, null ); + int count = cursor.getCount(); + if ( 0 < count ) { + result = new String[count]; + if ( null != rowIDs ) { + rowIDs[0] = new long[count]; + } - while ( cursor.moveToNext() ) { - ids.add( cursor.getString( cursor. - getColumnIndex(DBHelper.RELAYID)) ); + int idIndex = cursor.getColumnIndex(DBHelper.RELAYID); + int rowIndex = cursor.getColumnIndex(ROW_ID); + for ( int ii = 0; cursor.moveToNext(); ++ii ) { + result[ii] = cursor.getString( idIndex ); + if ( null != rowIDs ) { + rowIDs[0][ii] = cursor.getLong( rowIndex ); + } + } } cursor.close(); db.close(); } - if ( 0 < ids.size() ) { - result = ids.toArray( new String[ids.size()] ); - } return result; } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java index b0e642bf1..05ace973a 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java @@ -242,7 +242,7 @@ public class DlgDelegate { public void doSyncMenuitem() { - if ( null == DBUtils.getRelayIDs( m_activity, false ) ) { + if ( null == DBUtils.getRelayIDs( m_activity, null ) ) { showOKOnlyDialog( R.string.no_games_to_refresh ); } else { RelayReceiver.RestartTimer( m_activity, true ); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java index a1837bd7a..e9cc53ce7 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java @@ -111,16 +111,21 @@ public class GameUtils { final long assertTime = 2000; Assert.assertTrue( maxMillis < assertTime ); long sleptTime = 0; - // DbgUtils.logf( "GameLock.lock(%s)", m_path ); - // Utils.printStack(); + + if ( XWApp.DEBUG_LOCKS ) { + DbgUtils.logf( "lock %H (rowid:%d, maxMillis=%d)", + this, m_rowid, maxMillis ); + } + for ( ; ; ) { if ( tryLock() ) { result = this; break; } if ( XWApp.DEBUG_LOCKS ) { - DbgUtils.logf( "GameLock.lock() %H failed; sleeping", this ); - DbgUtils.printStack(); + DbgUtils.logf( "GameLock.lock() %H failed (rowid:%d); sleeping", + this, m_rowid ); + // DbgUtils.printStack(); } try { Thread.sleep( 25 ); // milliseconds @@ -130,12 +135,17 @@ public class GameUtils { break; } + if ( XWApp.DEBUG_LOCKS ) { + DbgUtils.logf( "GameLock.lock() %H awake; " + + "sleptTime now %d millis", this, sleptTime ); + } + if ( 0 < maxMillis && sleptTime >= maxMillis ) { break; } else if ( sleptTime >= assertTime ) { if ( XWApp.DEBUG_LOCKS ) { - DbgUtils.logf( "lock %H overlocked. lock holding stack:", - this ); + DbgUtils.logf( "lock %H overlocked waiting for rowid:%d. " + + "lock holding stack:", m_rowid, this ); DbgUtils.printStack( m_lockTrace ); DbgUtils.logf( "lock %H seeking stack:", this ); DbgUtils.printStack(); @@ -158,8 +168,12 @@ public class GameUtils { Assert.assertTrue( !m_isForWrite ); } --m_lockCount; + + if ( XWApp.DEBUG_LOCKS ) { + DbgUtils.logf( "GameLock.unlock: this: %H (rowid:%d) " + + "unlocked", this, m_rowid ); + } } - // DbgUtils.logf( "GameLock.unlock(%s) done", m_path ); } public long getRowid() @@ -707,41 +721,46 @@ public class GameUtils { } } - private static boolean feedMessages( Context context, long rowid, - byte[][] msgs, CommsAddrRec ret, - MultiMsgSink sink ) + public static boolean feedMessages( Context context, long rowid, + byte[][] msgs, CommsAddrRec ret, + MultiMsgSink sink ) { boolean draw = false; Assert.assertTrue( -1 != rowid ); - GameLock lock = new GameLock( rowid, true ).lock(); + if ( null != msgs ) { + // timed lock: If a game is opened by BoardActivity just + // as we're trying to deliver this message to it it'll + // have the lock and we'll never get it. Better to drop + // the message than fire the hung-lock assert. Messages + // belong in local pre-delivery storage anyway. + GameLock lock = new GameLock( rowid, true ).lock( 150 ); + if ( null != lock ) { + CurGameInfo gi = new CurGameInfo( context ); + FeedUtilsImpl feedImpl = new FeedUtilsImpl( context, rowid ); + int gamePtr = loadMakeGame( context, gi, feedImpl, sink, lock ); + if ( 0 != gamePtr ) { + XwJNI.comms_resendAll( gamePtr, false, false ); - CurGameInfo gi = new CurGameInfo( context ); - FeedUtilsImpl feedImpl = new FeedUtilsImpl( context, rowid ); - int gamePtr = loadMakeGame( context, gi, feedImpl, sink, lock ); - if ( 0 != gamePtr ) { - XwJNI.comms_resendAll( gamePtr, false, false ); + for ( byte[] msg : msgs ) { + draw = XwJNI.game_receiveMessage( gamePtr, msg, ret ) + || draw; + } + XwJNI.comms_ackAny( gamePtr ); - if ( null != msgs ) { - for ( byte[] msg : msgs ) { - draw = XwJNI.game_receiveMessage( gamePtr, msg, ret ) - || draw; + // update gi to reflect changes due to messages + XwJNI.game_getGi( gamePtr, gi ); + saveGame( context, gamePtr, gi, lock, false ); + summarizeAndClose( context, lock, gamePtr, gi, feedImpl ); + + int flags = setFromFeedImpl( feedImpl ); + if ( GameSummary.MSG_FLAGS_NONE != flags ) { + draw = true; + DBUtils.setMsgFlags( rowid, flags ); + } } - } - XwJNI.comms_ackAny( gamePtr ); - - // update gi to reflect changes due to messages - XwJNI.game_getGi( gamePtr, gi ); - saveGame( context, gamePtr, gi, lock, false ); - summarizeAndClose( context, lock, gamePtr, gi, feedImpl ); - - int flags = setFromFeedImpl( feedImpl ); - if ( GameSummary.MSG_FLAGS_NONE != flags ) { - draw = true; - DBUtils.setMsgFlags( rowid, flags ); + lock.unlock(); } } - lock.unlock(); - return draw; } // feedMessages @@ -754,21 +773,6 @@ public class GameUtils { return feedMessages( context, rowid, msgs, ret, sink ); } - // Current assumption: this is the relay case where return address - // can be null. - public static boolean feedMessages( Context context, String relayID, - byte[][] msgs, MultiMsgSink sink ) - { - boolean draw = false; - long[] rowids = DBUtils.getRowIDsFor( context, relayID ); - if ( null != rowids ) { - for ( long rowid : rowids ) { - draw = feedMessages( context, rowid, msgs, null, sink ) || draw; - } - } - return draw; - } - // This *must* involve a reset if the language is changing!!! // Which isn't possible right now, so make sure the old and new // dict have the same langauge code. diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetUtils.java index 4f900a76d..75d3d11b0 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetUtils.java @@ -130,8 +130,7 @@ public class NetUtils { } } - public static byte[][][] queryRelay( Context context, String[] ids, - int nBytes ) + public static byte[][][] queryRelay( Context context, String[] ids ) { byte[][][] msgs = null; try { @@ -141,6 +140,7 @@ public class NetUtils { new DataOutputStream( socket.getOutputStream() ); // total packet size + int nBytes = sumStrings( ids ); outStream.writeShort( 2 + nBytes + ids.length + 1 ); outStream.writeByte( NetUtils.PROTOCOL_VERSION ); @@ -263,4 +263,16 @@ public class NetUtils { DbgUtils.logf( "sendToRelay: null msgs" ); } } // sendToRelay + + private static int sumStrings( final String[] strs ) + { + int len = 0; + if ( null != strs ) { + for ( String str : strs ) { + len += str.length(); + } + } + return len; + } + } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java index c312a8fc8..5da48cd30 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java @@ -74,45 +74,40 @@ public class RelayService extends Service { } } } - - private String[] collectIDs( int[] nBytes ) - { - String[] ids = DBUtils.getRelayIDs( this, false ); - int len = 0; - if ( null != ids ) { - for ( String id : ids ) { - len += id.length(); - } - } - nBytes[0] = len; - return ids; - } private void fetchAndProcess() { - int[] nBytes = new int[1]; - String[] ids = collectIDs( nBytes ); - if ( null != ids && 0 < ids.length ) { - RelayMsgSink sink = new RelayMsgSink(); - byte[][][] msgs = - NetUtils.queryRelay( this, ids, nBytes[0] ); + long[][] rowIDss = new long[1][]; + String[] relayIDs = DBUtils.getRelayIDs( this, rowIDss ); + if ( null != relayIDs && 0 < relayIDs.length ) { + long[] rowIDs = rowIDss[0]; + byte[][][] msgs = NetUtils.queryRelay( this, relayIDs ); if ( null != msgs ) { - int nameCount = ids.length; + RelayMsgSink sink = new RelayMsgSink(); + int nameCount = relayIDs.length; ArrayList idsWMsgs = new ArrayList( nameCount ); for ( int ii = 0; ii < nameCount; ++ii ) { + byte[][] forOne = msgs[ii]; // if game has messages, open it and feed 'em // to it. - if ( GameUtils.feedMessages( this, ids[ii], - msgs[ii], sink ) ) { - idsWMsgs.add( ids[ii] ); + if ( null == forOne ) { + // Nothing for this relayID + } else if ( BoardActivity.feedMessages( rowIDs[ii], forOne ) + || GameUtils.feedMessages( this, rowIDs[ii], + forOne, null, + sink ) ) { + idsWMsgs.add( relayIDs[ii] ); + } else { + DbgUtils.logf( "dropping message for %s (rowid %d)", + relayIDs[ii], rowIDs[ii] ); } } if ( 0 < idsWMsgs.size() ) { - String[] relayIDs = new String[idsWMsgs.size()]; - idsWMsgs.toArray( relayIDs ); - setupNotification( relayIDs ); + String[] tmp = new String[idsWMsgs.size()]; + idsWMsgs.toArray( tmp ); + setupNotification( tmp ); } sink.send( this ); } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java index 24edc0066..3d157d54f 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java @@ -28,13 +28,13 @@ import java.util.UUID; import org.eehouse.android.xw4.jni.XwJNI; public class XWApp extends Application { - public static final boolean DEBUG_LOCKS = false; public static final boolean BTSUPPORTED = false; public static final boolean SMSSUPPORTED = true; public static final boolean GCMSUPPORTED = true; public static final boolean ATTACH_SUPPORTED = true; public static final boolean REMATCH_SUPPORTED = false; - public static final boolean DEBUG = true; // DON'T SHIP THIS WAY + public static final boolean DEBUG = true; // DON'T SHIP THIS WAY + public static final boolean DEBUG_LOCKS = false && DEBUG; public static final String SMS_PUBLIC_HEADER = "-XW4"; From f989dad63c47e3d3efc6d56a3fe5385fadd32e55 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 13 Dec 2012 07:11:51 -0800 Subject: [PATCH 129/146] tweak changed descriptions --- xwords4/android/XWords4/res/raw/changes | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/xwords4/android/XWords4/res/raw/changes b/xwords4/android/XWords4/res/raw/changes index d87b350f7..b9613677d 100644 --- a/xwords4/android/XWords4/res/raw/changes +++ b/xwords4/android/XWords4/res/raw/changes @@ -9,13 +9,14 @@

          New with this release

            -
          • Include attachment in email invites for devices that don't dispatch - URLs correctly (e.g. some by HTC)
          • +
          • Include new game information as an attachment in email invites + for use on devices that don't dispatch URLs correctly in received + email (e.g. some by HTC)
          • +
          • Show final scores alert whenever a finished game is opened -- to + make it more clear that it's finished
          • Fix flickering in main screen (games list)
          • Add option, off by default, to keep rack tiles square even when the screen is large enough that they can be taller
          • -
          • Show final scores alert whenever a finished game is opened -- to - make it more clear that it's finished

          Next up

          From 85953c64dd5d789272474939b14e8adbb75492d7 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 13 Dec 2012 18:47:55 -0800 Subject: [PATCH 130/146] move GameLock into its own file --- .../eehouse/android/xw4/BoardActivity.java | 4 +- .../src/org/eehouse/android/xw4/DBUtils.java | 25 ++- .../org/eehouse/android/xw4/GameConfig.java | 4 +- .../src/org/eehouse/android/xw4/GameLock.java | 165 ++++++++++++++++++ .../org/eehouse/android/xw4/GameUtils.java | 142 --------------- .../org/eehouse/android/xw4/GamesList.java | 3 +- .../android/xw4/RelayGameActivity.java | 4 +- .../eehouse/android/xw4/jni/JNIThread.java | 5 +- 8 files changed, 187 insertions(+), 165 deletions(-) create mode 100644 xwords4/android/XWords4/src/org/eehouse/android/xw4/GameLock.java diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java index ff5f5a104..c0bb10d0b 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java @@ -114,7 +114,7 @@ public class BoardActivity extends XWActivity private BoardView m_view; private int m_jniGamePtr; - private GameUtils.GameLock m_gameLock; + private GameLock m_gameLock; private CurGameInfo m_gi; private CommsTransport m_xport; private Handler m_handler = null; @@ -1673,7 +1673,7 @@ public class BoardActivity extends XWActivity showDictGoneFinish(); } else { Assert.assertNull( m_gameLock ); - m_gameLock = new GameUtils.GameLock( m_rowid, true ).lock(); + m_gameLock = new GameLock( m_rowid, true ).lock(); byte[] stream = GameUtils.savedGame( this, m_gameLock ); m_gi = new CurGameInfo( this ); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java index cc6529755..698c9b905 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java @@ -101,8 +101,7 @@ public class DBUtils { long maxMillis ) { GameSummary result = null; - GameUtils.GameLock lock = - new GameUtils.GameLock( rowid, false ).lock( maxMillis ); + GameLock lock = new GameLock( rowid, false ).lock( maxMillis ); if ( null != lock ) { result = getSummary( context, lock ); lock.unlock(); @@ -116,7 +115,7 @@ public class DBUtils { } public static GameSummary getSummary( Context context, - GameUtils.GameLock lock ) + GameLock lock ) { initDB( context ); GameSummary summary = null; @@ -248,13 +247,13 @@ public class DBUtils { return summary; } // getSummary - public static void saveSummary( Context context, GameUtils.GameLock lock, + public static void saveSummary( Context context, GameLock lock, GameSummary summary ) { saveSummary( context, lock, summary, null ); } - public static void saveSummary( Context context, GameUtils.GameLock lock, + public static void saveSummary( Context context, GameLock lock, GameSummary summary, String inviteID ) { Assert.assertTrue( lock.canWrite() ); @@ -679,10 +678,10 @@ public class DBUtils { } } - public static GameUtils.GameLock saveNewGame( Context context, - byte[] bytes ) + public static GameLock saveNewGame( Context context, + byte[] bytes ) { - GameUtils.GameLock lock = null; + GameLock lock = null; initDB( context ); synchronized( s_dbHelper ) { @@ -702,14 +701,14 @@ public class DBUtils { setCached( rowid, null ); // force reread clearRowIDsCache(); - lock = new GameUtils.GameLock( rowid, true ).lock(); + lock = new GameLock( rowid, true ).lock(); notifyListeners( rowid, true ); } return lock; } - public static long saveGame( Context context, GameUtils.GameLock lock, + public static long saveGame( Context context, GameLock lock, byte[] bytes, boolean setCreate ) { Assert.assertTrue( lock.canWrite() ); @@ -733,7 +732,7 @@ public class DBUtils { return rowid; } - public static byte[] loadGame( Context context, GameUtils.GameLock lock ) + public static byte[] loadGame( Context context, GameLock lock ) { long rowid = lock.getRowid(); Assert.assertTrue( -1 != rowid ); @@ -761,12 +760,12 @@ public class DBUtils { public static void deleteGame( Context context, long rowid ) { - GameUtils.GameLock lock = new GameUtils.GameLock( rowid, true ).lock(); + GameLock lock = new GameLock( rowid, true ).lock(); deleteGame( context, lock ); lock.unlock(); } - public static void deleteGame( Context context, GameUtils.GameLock lock ) + public static void deleteGame( Context context, GameLock lock ) { Assert.assertTrue( lock.canWrite() ); initDB( context ); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameConfig.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameConfig.java index 5fcf33251..c35139e61 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameConfig.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameConfig.java @@ -92,7 +92,7 @@ public class GameConfig extends XWActivity private boolean m_forResult; private CurGameInfo m_gi; private CurGameInfo m_giOrig; - private GameUtils.GameLock m_gameLock; + private GameLock m_gameLock; private int m_whichPlayer; // private Spinner m_roleSpinner; // private Spinner m_connectSpinner; @@ -473,7 +473,7 @@ public class GameConfig extends XWActivity // Lock in case we're going to config. We *could* re-get the // lock once the user decides to make changes. PENDING. - m_gameLock = new GameUtils.GameLock( m_rowid, true ).lock(); + m_gameLock = new GameLock( m_rowid, true ).lock(); int gamePtr = GameUtils.loadMakeGame( this, m_giOrig, m_gameLock ); if ( 0 == gamePtr ) { showDictGoneFinish(); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameLock.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameLock.java new file mode 100644 index 000000000..12c080c23 --- /dev/null +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameLock.java @@ -0,0 +1,165 @@ +/* -*- compile-command: "cd ../../../../../; ant debug install"; -*- */ +/* + * Copyright 2009-2010 by Eric House (xwords@eehouse.org). All + * rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +package org.eehouse.android.xw4; + +import java.util.HashMap; + +import junit.framework.Assert; + +// Implements read-locks and write-locks per game. A read lock is +// obtainable when other read locks are granted but not when a +// write lock is. Write-locks are exclusive. +public class GameLock { + private long m_rowid; + private boolean m_isForWrite; + private int m_lockCount; + StackTraceElement[] m_lockTrace; + + private static HashMap + s_locks = new HashMap(); + + public GameLock( long rowid, boolean isForWrite ) + { + m_rowid = rowid; + m_isForWrite = isForWrite; + m_lockCount = 0; + if ( XWApp.DEBUG_LOCKS ) { + DbgUtils.logf( "GameLock.GameLock(rowid:%d,isForWrite:%b)=>" + + "this: %H", rowid, isForWrite, this ); + DbgUtils.printStack(); + } + } + + // This could be written to allow multiple read locks. Let's + // see if not doing that causes problems. + public boolean tryLock() + { + boolean gotIt = false; + synchronized( s_locks ) { + GameLock owner = s_locks.get( m_rowid ); + if ( null == owner ) { // unowned + Assert.assertTrue( 0 == m_lockCount ); + s_locks.put( m_rowid, this ); + ++m_lockCount; + gotIt = true; + + if ( XWApp.DEBUG_LOCKS ) { + StackTraceElement[] trace = Thread.currentThread(). + getStackTrace(); + m_lockTrace = new StackTraceElement[trace.length]; + System.arraycopy( trace, 0, m_lockTrace, 0, trace.length ); + } + } else if ( this == owner && ! m_isForWrite ) { + Assert.assertTrue( 0 == m_lockCount ); + ++m_lockCount; + gotIt = true; + } + } + return gotIt; + } + + // Wait forever (but may assert if too long) + public GameLock lock() + { + return this.lock( 0 ); + } + + // Version that's allowed to return null -- if maxMillis > 0 + public GameLock lock( long maxMillis ) + { + GameLock result = null; + final long assertTime = 2000; + Assert.assertTrue( maxMillis < assertTime ); + long sleptTime = 0; + + if ( XWApp.DEBUG_LOCKS ) { + DbgUtils.logf( "lock %H (rowid:%d, maxMillis=%d)", this, m_rowid, maxMillis ); + } + + for ( ; ; ) { + if ( tryLock() ) { + result = this; + break; + } + if ( XWApp.DEBUG_LOCKS ) { + DbgUtils.logf( "GameLock.lock() %H failed; sleeping", this ); + DbgUtils.printStack(); + } + try { + Thread.sleep( 25 ); // milliseconds + sleptTime += 25; + } catch( InterruptedException ie ) { + DbgUtils.loge( ie ); + break; + } + + if ( XWApp.DEBUG_LOCKS ) { + DbgUtils.logf( "GameLock.lock() %H awake; " + + "sleptTime now %d millis", this, sleptTime ); + } + + if ( 0 < maxMillis && sleptTime >= maxMillis ) { + break; + } else if ( sleptTime >= assertTime ) { + if ( XWApp.DEBUG_LOCKS ) { + DbgUtils.logf( "lock %H overlocked. lock holding stack:", + this ); + DbgUtils.printStack( m_lockTrace ); + DbgUtils.logf( "lock %H seeking stack:", this ); + DbgUtils.printStack(); + } + Assert.fail(); + } + } + // DbgUtils.logf( "GameLock.lock(%s) done", m_path ); + return result; + } + + public void unlock() + { + // DbgUtils.logf( "GameLock.unlock(%s)", m_path ); + synchronized( s_locks ) { + Assert.assertTrue( this == s_locks.get(m_rowid) ); + if ( 1 == m_lockCount ) { + s_locks.remove( m_rowid ); + } else { + Assert.assertTrue( !m_isForWrite ); + } + --m_lockCount; + + if ( XWApp.DEBUG_LOCKS ) { + DbgUtils.logf( "GameLock.unlock: this: %H (rowid:%d) unlocked", + this, m_rowid ); + } + } + } + + public long getRowid() + { + return m_rowid; + } + + // used only for asserts + public boolean canWrite() + { + return m_isForWrite && 1 == m_lockCount; + } +} diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java index e9cc53ce7..e03101184 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java @@ -46,148 +46,6 @@ public class GameUtils { public static final String INTENT_KEY_ROWID = "rowid"; public static final String INTENT_FORRESULT_ROWID = "forresult"; - // Implements read-locks and write-locks per game. A read lock is - // obtainable when other read locks are granted but not when a - // write lock is. Write-locks are exclusive. - public static class GameLock { - private long m_rowid; - private boolean m_isForWrite; - private int m_lockCount; - StackTraceElement[] m_lockTrace; - - private static HashMap - s_locks = new HashMap(); - - public GameLock( long rowid, boolean isForWrite ) - { - m_rowid = rowid; - m_isForWrite = isForWrite; - m_lockCount = 0; - if ( XWApp.DEBUG_LOCKS ) { - DbgUtils.logf( "GameLock.GameLock(rowid:%d,isForWrite:%b)=>" - + "this: %H", rowid, isForWrite, this ); - DbgUtils.printStack(); - } - } - - // This could be written to allow multiple read locks. Let's - // see if not doing that causes problems. - public boolean tryLock() - { - boolean gotIt = false; - synchronized( s_locks ) { - GameLock owner = s_locks.get( m_rowid ); - if ( null == owner ) { // unowned - Assert.assertTrue( 0 == m_lockCount ); - s_locks.put( m_rowid, this ); - ++m_lockCount; - gotIt = true; - - if ( XWApp.DEBUG_LOCKS ) { - StackTraceElement[] trace = Thread.currentThread(). - getStackTrace(); - m_lockTrace = new StackTraceElement[trace.length]; - System.arraycopy( trace, 0, m_lockTrace, 0, trace.length ); - } - } else if ( this == owner && ! m_isForWrite ) { - Assert.assertTrue( 0 == m_lockCount ); - ++m_lockCount; - gotIt = true; - } - } - return gotIt; - } - - // Wait forever (but may assert if too long) - public GameLock lock() - { - return this.lock( 0 ); - } - - // Version that's allowed to return null -- if maxMillis > 0 - public GameLock lock( long maxMillis ) - { - GameLock result = null; - final long assertTime = 2000; - Assert.assertTrue( maxMillis < assertTime ); - long sleptTime = 0; - - if ( XWApp.DEBUG_LOCKS ) { - DbgUtils.logf( "lock %H (rowid:%d, maxMillis=%d)", - this, m_rowid, maxMillis ); - } - - for ( ; ; ) { - if ( tryLock() ) { - result = this; - break; - } - if ( XWApp.DEBUG_LOCKS ) { - DbgUtils.logf( "GameLock.lock() %H failed (rowid:%d); sleeping", - this, m_rowid ); - // DbgUtils.printStack(); - } - try { - Thread.sleep( 25 ); // milliseconds - sleptTime += 25; - } catch( InterruptedException ie ) { - DbgUtils.loge( ie ); - break; - } - - if ( XWApp.DEBUG_LOCKS ) { - DbgUtils.logf( "GameLock.lock() %H awake; " - + "sleptTime now %d millis", this, sleptTime ); - } - - if ( 0 < maxMillis && sleptTime >= maxMillis ) { - break; - } else if ( sleptTime >= assertTime ) { - if ( XWApp.DEBUG_LOCKS ) { - DbgUtils.logf( "lock %H overlocked waiting for rowid:%d. " - + "lock holding stack:", m_rowid, this ); - DbgUtils.printStack( m_lockTrace ); - DbgUtils.logf( "lock %H seeking stack:", this ); - DbgUtils.printStack(); - } - Assert.fail(); - } - } - // DbgUtils.logf( "GameLock.lock(%s) done", m_path ); - return result; - } - - public void unlock() - { - // DbgUtils.logf( "GameLock.unlock(%s)", m_path ); - synchronized( s_locks ) { - Assert.assertTrue( this == s_locks.get(m_rowid) ); - if ( 1 == m_lockCount ) { - s_locks.remove( m_rowid ); - } else { - Assert.assertTrue( !m_isForWrite ); - } - --m_lockCount; - - if ( XWApp.DEBUG_LOCKS ) { - DbgUtils.logf( "GameLock.unlock: this: %H (rowid:%d) " - + "unlocked", this, m_rowid ); - } - } - } - - public long getRowid() - { - return m_rowid; - } - - // used only for asserts - public boolean canWrite() - { - return m_isForWrite && 1 == m_lockCount; - } - } - private static Object s_syncObj = new Object(); public static byte[] savedGame( Context context, long rowid ) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java index 833c4eac4..2d3ca4f37 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java @@ -651,8 +651,7 @@ public class GamesList extends XWListActivity showOKOnlyDialog( R.string.no_copy_network ); } else { byte[] stream = GameUtils.savedGame( this, m_rowid ); - GameUtils.GameLock lock = - GameUtils.saveNewGame( this, stream ); + GameLock lock = GameUtils.saveNewGame( this, stream ); DBUtils.saveSummary( this, lock, summary ); lock.unlock(); } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayGameActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayGameActivity.java index e5571a6a1..d3f4a6795 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayGameActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayGameActivity.java @@ -41,7 +41,7 @@ public class RelayGameActivity extends XWActivity private long m_rowid; private CurGameInfo m_gi; - private GameUtils.GameLock m_gameLock; + private GameLock m_gameLock; private CommsAddrRec m_car; private Button m_playButton; private Button m_configButton; @@ -68,7 +68,7 @@ public class RelayGameActivity extends XWActivity super.onStart(); m_gi = new CurGameInfo( this ); - m_gameLock = new GameUtils.GameLock( m_rowid, true ).lock(); + m_gameLock = new GameLock( m_rowid, true ).lock(); int gamePtr = GameUtils.loadMakeGame( this, m_gi, m_gameLock ); m_car = new CommsAddrRec(); if ( XwJNI.game_hasComms( gamePtr ) ) { diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java index 71577dc6a..e39adc389 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java @@ -34,6 +34,7 @@ import org.eehouse.android.xw4.R; import org.eehouse.android.xw4.DbgUtils; import org.eehouse.android.xw4.ConnStatusHandler; import org.eehouse.android.xw4.BoardDims; +import org.eehouse.android.xw4.GameLock; import org.eehouse.android.xw4.GameUtils; import org.eehouse.android.xw4.DBUtils; import org.eehouse.android.xw4.Toolbar; @@ -121,7 +122,7 @@ public class JNIThread extends Thread { private boolean m_stopped = false; private boolean m_saveOnStop = false; private int m_jniGamePtr; - private GameUtils.GameLock m_lock; + private GameLock m_lock; private Context m_context; private CurGameInfo m_gi; private Handler m_handler; @@ -143,7 +144,7 @@ public class JNIThread extends Thread { } public JNIThread( int gamePtr, CurGameInfo gi, SyncedDraw drawer, - GameUtils.GameLock lock, Context context, Handler handler ) + GameLock lock, Context context, Handler handler ) { m_jniGamePtr = gamePtr; m_gi = gi; From 7a1de73fb79b71ab5c876934f8ad41cd155ec6a9 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 13 Dec 2012 20:18:36 -0800 Subject: [PATCH 131/146] remove unused file --- xwords4/android/XWords4/res/layout/game_list_tmp.xml | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 xwords4/android/XWords4/res/layout/game_list_tmp.xml diff --git a/xwords4/android/XWords4/res/layout/game_list_tmp.xml b/xwords4/android/XWords4/res/layout/game_list_tmp.xml deleted file mode 100644 index 00ec641a9..000000000 --- a/xwords4/android/XWords4/res/layout/game_list_tmp.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - From 322a65ee412817f0a396fd4032179b137cae47e3 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 13 Dec 2012 20:22:11 -0800 Subject: [PATCH 132/146] in several places where lock() was being called without a timeout, add one, and fail gracefully when a timeout occurs. --- .../src/org/eehouse/android/xw4/DBUtils.java | 13 +-- .../org/eehouse/android/xw4/GameUtils.java | 81 +++++++++++-------- .../org/eehouse/android/xw4/GamesList.java | 20 +++-- .../android/xw4/RelayGameActivity.java | 34 ++++---- 4 files changed, 89 insertions(+), 59 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java index 698c9b905..235dc108f 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java @@ -678,8 +678,7 @@ public class DBUtils { } } - public static GameLock saveNewGame( Context context, - byte[] bytes ) + public static GameLock saveNewGame( Context context, byte[] bytes ) { GameLock lock = null; @@ -760,9 +759,13 @@ public class DBUtils { public static void deleteGame( Context context, long rowid ) { - GameLock lock = new GameLock( rowid, true ).lock(); - deleteGame( context, lock ); - lock.unlock(); + GameLock lock = new GameLock( rowid, true ).lock( 300 ); + if ( null != lock ) { + deleteGame( context, lock ); + lock.unlock(); + } else { + DbgUtils.logf( "deleteGame: unable to lock rowid %d", rowid ); + } } public static void deleteGame( Context context, GameLock lock ) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java index e03101184..eb9f18303 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java @@ -114,12 +114,16 @@ public class GameUtils { public static void resetGame( Context context, long rowidIn ) { - GameLock lock = new GameLock( rowidIn, true ).lock(); - tellDied( context, lock, true ); - resetGame( context, lock, lock, false ); - lock.unlock(); + GameLock lock = new GameLock( rowidIn, true ).lock( 500 ); + if ( null != lock ) { + tellDied( context, lock, true ); + resetGame( context, lock, lock, false ); + lock.unlock(); - Utils.cancelNotification( context, (int)rowidIn ); + Utils.cancelNotification( context, (int)rowidIn ); + } else { + DbgUtils.logf( "resetGame: unable to open rowid %d", rowidIn ); + } } private static GameSummary summarizeAndClose( Context context, @@ -172,12 +176,17 @@ public class GameUtils { public static long dupeGame( Context context, long rowidIn ) { - boolean juggle = CommonPrefs.getAutoJuggle( context ); - GameLock lockSrc = new GameLock( rowidIn, false ).lock(); - GameLock lockDest = resetGame( context, lockSrc, null, juggle ); - long rowid = lockDest.getRowid(); - lockDest.unlock(); - lockSrc.unlock(); + long rowid = DBUtils.ROWID_NOTFOUND; + GameLock lockSrc = new GameLock( rowidIn, false ).lock( 300 ); + if ( null != lockSrc ) { + boolean juggle = CommonPrefs.getAutoJuggle( context ); + GameLock lockDest = resetGame( context, lockSrc, null, juggle ); + rowid = lockDest.getRowid(); + lockDest.unlock(); + lockSrc.unlock(); + } else { + DbgUtils.logf( "dupeGame: unable to open rowid %d", rowidIn ); + } return rowid; } @@ -634,34 +643,42 @@ public class GameUtils { // This *must* involve a reset if the language is changing!!! // Which isn't possible right now, so make sure the old and new // dict have the same langauge code. - public static void replaceDicts( Context context, long rowid, - String oldDict, String newDict ) + public static boolean replaceDicts( Context context, long rowid, + String oldDict, String newDict ) { - GameLock lock = new GameLock( rowid, true ).lock(); - byte[] stream = savedGame( context, lock ); - CurGameInfo gi = new CurGameInfo( context ); - XwJNI.gi_from_stream( gi, stream ); + GameLock lock = new GameLock( rowid, true ).lock(300); + boolean success = null != lock; + if ( success ) { + byte[] stream = savedGame( context, lock ); + CurGameInfo gi = new CurGameInfo( context ); + XwJNI.gi_from_stream( gi, stream ); - // first time required so dictNames() will work - gi.replaceDicts( newDict ); + // first time required so dictNames() will work + gi.replaceDicts( newDict ); - String[] dictNames = gi.dictNames(); - DictUtils.DictPairs pairs = DictUtils.openDicts( context, dictNames ); + String[] dictNames = gi.dictNames(); + DictUtils.DictPairs pairs = DictUtils.openDicts( context, + dictNames ); - int gamePtr = XwJNI.initJNI(); - XwJNI.game_makeFromStream( gamePtr, stream, gi, dictNames, - pairs.m_bytes, pairs.m_paths, - gi.langName(), JNIUtilsImpl.get(context), - CommonPrefs.get( context ) ); - // second time required as game_makeFromStream can overwrite - gi.replaceDicts( newDict ); + int gamePtr = XwJNI.initJNI(); + XwJNI.game_makeFromStream( gamePtr, stream, gi, dictNames, + pairs.m_bytes, pairs.m_paths, + gi.langName(), + JNIUtilsImpl.get(context), + CommonPrefs.get( context ) ); + // second time required as game_makeFromStream can overwrite + gi.replaceDicts( newDict ); - saveGame( context, gamePtr, gi, lock, false ); + saveGame( context, gamePtr, gi, lock, false ); - summarizeAndClose( context, lock, gamePtr, gi ); + summarizeAndClose( context, lock, gamePtr, gi ); - lock.unlock(); - } + lock.unlock(); + } else { + DbgUtils.logf( "replaceDicts: unable to open rowid %d", rowid ); + } + return success; + } // replaceDicts public static void applyChanges( Context context, CurGameInfo gi, CommsAddrRec car, GameLock lock, diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java index 2d3ca4f37..83be99efb 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java @@ -165,11 +165,12 @@ public class GamesList extends XWListActivity getCheckedItemPosition(); String dict = m_sameLangDicts[pos]; dict = DictLangCache.stripCount( dict ); - GameUtils.replaceDicts( GamesList.this, - m_missingDictRowId, - m_missingDictName, - dict ); - launchGameIf(); + if ( GameUtils.replaceDicts( GamesList.this, + m_missingDictRowId, + m_missingDictName, + dict ) ) { + launchGameIf(); + } } }; dialog = new AlertDialog.Builder( this ) @@ -721,9 +722,12 @@ public class GamesList extends XWListActivity } else if ( null != m_missingDictName ) { showDialog( WARN_NODICT_SUBST ); } else { - String dict = DictLangCache.getHaveLang( this, m_missingDictLang)[0]; - GameUtils.replaceDicts( this, m_missingDictRowId, null, dict ); - launchGameIf(); + String dict = + DictLangCache.getHaveLang( this, m_missingDictLang)[0]; + if ( GameUtils.replaceDicts( this, m_missingDictRowId, + null, dict ) ) { + launchGameIf(); + } } } return hasDicts; diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayGameActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayGameActivity.java index d3f4a6795..16ead9288 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayGameActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayGameActivity.java @@ -68,22 +68,28 @@ public class RelayGameActivity extends XWActivity super.onStart(); m_gi = new CurGameInfo( this ); - m_gameLock = new GameLock( m_rowid, true ).lock(); - int gamePtr = GameUtils.loadMakeGame( this, m_gi, m_gameLock ); - m_car = new CommsAddrRec(); - if ( XwJNI.game_hasComms( gamePtr ) ) { - XwJNI.comms_getAddr( gamePtr, m_car ); + m_gameLock = new GameLock( m_rowid, true ).lock( 300 ); + if ( null == m_gameLock ) { + DbgUtils.logf( "RelayGameActivity.onStart(): unable to lock rowid %d", + m_rowid ); + finish(); } else { - Assert.fail(); - // String relayName = CommonPrefs.getDefaultRelayHost( this ); - // int relayPort = CommonPrefs.getDefaultRelayPort( this ); - // XwJNI.comms_getInitialAddr( m_carOrig, relayName, relayPort ); - } - XwJNI.game_dispose( gamePtr ); + int gamePtr = GameUtils.loadMakeGame( this, m_gi, m_gameLock ); + m_car = new CommsAddrRec(); + if ( XwJNI.game_hasComms( gamePtr ) ) { + XwJNI.comms_getAddr( gamePtr, m_car ); + } else { + Assert.fail(); + // String relayName = CommonPrefs.getDefaultRelayHost( this ); + // int relayPort = CommonPrefs.getDefaultRelayPort( this ); + // XwJNI.comms_getInitialAddr( m_carOrig, relayName, relayPort ); + } + XwJNI.game_dispose( gamePtr ); - String lang = DictLangCache.getLangName( this, m_gi.dictLang ); - TextView text = (TextView)findViewById( R.id.explain ); - text.setText( getString( R.string.relay_game_explainf, lang ) ); + String lang = DictLangCache.getLangName( this, m_gi.dictLang ); + TextView text = (TextView)findViewById( R.id.explain ); + text.setText( getString( R.string.relay_game_explainf, lang ) ); + } } @Override From 028899a9c453bb29da9041362466b0d48f146806 Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 14 Dec 2012 07:24:26 -0800 Subject: [PATCH 133/146] no need to set action on local intent --- .../android/XWords4/src/org/eehouse/android/xw4/GameUtils.java | 1 - 1 file changed, 1 deletion(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java index eb9f18303..1e203a90c 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java @@ -538,7 +538,6 @@ public class GameUtils { boolean invited ) { Intent intent = new Intent( activity, BoardActivity.class ); - intent.setAction( Intent.ACTION_EDIT ); intent.putExtra( INTENT_KEY_ROWID, rowid ); if ( invited ) { intent.putExtra( INVITED, true ); From 8125c451cd994d90e0376c31cc53af5bff3593d5 Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 14 Dec 2012 07:26:13 -0800 Subject: [PATCH 134/146] in GamesList, save rowid of launched game and inval it afterwards so scores etc. get updated. --- .../org/eehouse/android/xw4/GamesList.java | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java index 83be99efb..2fa53e3b1 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java @@ -91,7 +91,7 @@ public class GamesList extends XWListActivity private int m_missingDictLang; private long m_rowid; private NetLaunchInfo m_netLaunchInfo; - // private String m_smsPhone; + private long m_invalRowID = DBUtils.ROWID_NOTFOUND; @Override protected Dialog onCreateDialog( int id ) @@ -379,6 +379,10 @@ public class GamesList extends XWListActivity if ( hasFocus ) { updateField(); } + if ( hasFocus && DBUtils.ROWID_NOTFOUND != m_invalRowID ) { + m_adapter.inval( m_invalRowID ); + m_invalRowID = DBUtils.ROWID_NOTFOUND; + } } // DBUtils.DBChangeListener interface @@ -415,7 +419,7 @@ public class GamesList extends XWListActivity GameUtils.doConfig( this, rowid, clazz ); } else { if ( checkWarnNoDict( rowid ) ) { - GameUtils.launchGame( this, rowid ); + launchGame( rowid ); } } } @@ -758,7 +762,7 @@ public class GamesList extends XWListActivity if ( null != rowids ) { for ( long rowid : rowids ) { if ( GameUtils.gameDictsHere( this, rowid ) ) { - GameUtils.launchGame( this, rowid ); + launchGame( rowid ); break outer; } } @@ -816,7 +820,7 @@ public class GamesList extends XWListActivity { long[] rowids = DBUtils.getRowIDsFor( this, gameID ); if ( null != rowids && 0 < rowids.length ) { - GameUtils.launchGame( this, rowids[0] ); + launchGame( rowids[0] ); } } @@ -834,7 +838,7 @@ public class GamesList extends XWListActivity if ( -1 != rowid ) { // this will juggle if the preference is set long newid = GameUtils.dupeGame( this, rowid ); - GameUtils.launchGame( this, newid ); + launchGame( newid ); } } @@ -877,10 +881,21 @@ public class GamesList extends XWListActivity return madeGame; } + private void launchGame( long rowid, boolean invited ) + { + m_invalRowID = rowid; + GameUtils.launchGame( this, rowid, invited ); + } + + private void launchGame( long rowid ) + { + launchGame( rowid, false ); + } + private void makeNewNetGame( NetLaunchInfo info ) { long rowid = GameUtils.makeNewNetGame( this, info ); - GameUtils.launchGame( this, rowid, true ); + launchGame( rowid, true ); } public static void onGameDictDownload( Context context, Intent intent ) From 646ec65d6645e95f35e1ae91f8d3c33193563d7b Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 14 Dec 2012 18:23:33 -0800 Subject: [PATCH 135/146] wrap elapsed time logging in its own debug flag and turn off. --- .../XWords4/src/org/eehouse/android/xw4/ExpiringDelegate.java | 4 ++-- .../android/XWords4/src/org/eehouse/android/xw4/XWApp.java | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/ExpiringDelegate.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/ExpiringDelegate.java index 13083266d..e1cedab05 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/ExpiringDelegate.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/ExpiringDelegate.java @@ -194,7 +194,7 @@ public class ExpiringDelegate { if ( null == m_runnable ) { m_runnable = new Runnable() { public void run() { - if ( XWApp.DEBUG ) { + if ( XWApp.DEBUG_EXP_TIMERS ) { DbgUtils.logf( "ExpiringDelegate: timer fired" + " for %H", this ); } @@ -204,7 +204,7 @@ public class ExpiringDelegate { m_back = null; setBackground(); } - if ( XWApp.DEBUG ) { + if ( XWApp.DEBUG_EXP_TIMERS ) { DbgUtils.logf( "ExpiringDelegate: invalidating" + " view %H", m_view ); } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java index 3d157d54f..51d7db703 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java @@ -35,6 +35,7 @@ public class XWApp extends Application { public static final boolean REMATCH_SUPPORTED = false; public static final boolean DEBUG = true; // DON'T SHIP THIS WAY public static final boolean DEBUG_LOCKS = false && DEBUG; + public static final boolean DEBUG_EXP_TIMERS = false && DEBUG; public static final String SMS_PUBLIC_HEADER = "-XW4"; From 3c4f266b8ffece2f53110e3ffc2311ec1fe06251 Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 14 Dec 2012 18:46:54 -0800 Subject: [PATCH 136/146] work around problems locating GameListItems when it's time to invalidate them by adding static list of those needing invalidating and checking it in a new onDraw override. --- .../eehouse/android/xw4/GameListAdapter.java | 3 +- .../org/eehouse/android/xw4/GameListItem.java | 100 ++++++++++++++---- 2 files changed, 79 insertions(+), 24 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java index 26405c0a2..47b398964 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java @@ -90,10 +90,11 @@ public class GameListAdapter extends XWListAdapter { public void inval( long rowid ) { GameListItem child = getItemFor( rowid ); - if ( null != child ) { + if ( null != child && child.getRowID() == rowid ) { child.forceReload(); } else { DbgUtils.logf( "no child for rowid %d", rowid ); + GameListItem.inval( rowid ); m_list.invalidate(); } } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListItem.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListItem.java index d5d727b08..9cd376d18 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListItem.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListItem.java @@ -21,8 +21,10 @@ package org.eehouse.android.xw4; import android.content.Context; +import android.graphics.Canvas; import android.os.AsyncTask; import android.os.Handler; +// import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.widget.ImageButton; @@ -31,6 +33,8 @@ import android.widget.LinearLayout; import android.widget.TextView; import java.text.DateFormat; import java.util.Date; +import java.util.HashSet; +// import java.util.Iterator; import org.eehouse.android.xw4.jni.GameSummary; import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType; @@ -38,6 +42,8 @@ import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType; public class GameListItem extends LinearLayout implements View.OnClickListener { + private static HashSet s_invalRows = new HashSet(); + private Context m_context; private boolean m_loaded; private long m_rowid; @@ -50,6 +56,7 @@ public class GameListItem extends LinearLayout private GameSummary m_summary; private GameListAdapter.LoadItemCB m_cb; private int m_fieldID; + private int m_loadingCount; public GameListItem( Context cx, AttributeSet as ) { @@ -58,6 +65,7 @@ public class GameListItem extends LinearLayout m_loaded = false; m_rowid = DBUtils.ROWID_NOTFOUND; m_lastMoveTime = 0; + m_loadingCount = 0; } public void init( Handler handler, long rowid, int fieldID, @@ -73,7 +81,13 @@ public class GameListItem extends LinearLayout public void forceReload() { + // DbgUtils.logf( "GameListItem.forceReload: rowid=%d", m_rowid ); m_summary = null; + setLoaded( false ); + // Apparently it's impossible to reliably cancel an existing + // AsyncTask, so let it complete, but drop the results as soon + // as we're back on the UI thread. + ++m_loadingCount; new LoadItemTask().execute(); } @@ -82,6 +96,19 @@ public class GameListItem extends LinearLayout setName(); } + @Override + protected void onDraw( Canvas canvas ) + { + super.onDraw( canvas ); + if ( DBUtils.ROWID_NOTFOUND != m_rowid ) { + synchronized( s_invalRows ) { + if ( s_invalRows.contains( m_rowid ) ) { + forceReload(); + } + } + } + } + private void update( boolean expanded, long lastMoveTime, boolean haveTurn, boolean haveTurnLocal ) { @@ -108,15 +135,15 @@ public class GameListItem extends LinearLayout showHide(); } - private void setLoaded() + private void setLoaded( boolean loaded ) { - if ( !m_loaded ) { - m_loaded = true; + if ( loaded != m_loaded ) { + m_loaded = loaded; // This should be enough to invalidate findViewById( R.id.view_unloaded ) - .setVisibility( m_loaded ? View.GONE : View.VISIBLE ); + .setVisibility( loaded ? View.GONE : View.VISIBLE ); findViewById( R.id.view_loaded ) - .setVisibility( m_loaded ? View.VISIBLE : View.GONE ); + .setVisibility( loaded ? View.VISIBLE : View.GONE ); } } @@ -168,16 +195,16 @@ public class GameListItem extends LinearLayout return state; } - private void setData() + private void setData( final GameSummary summary ) { - if ( null != m_summary ) { + if ( null != summary ) { TextView view; String state = setName(); setOnClickListener( new View.OnClickListener() { @Override public void onClick( View v ) { - m_cb.itemClicked( m_rowid, m_summary ); + m_cb.itemClicked( m_rowid, summary ); } } ); @@ -187,14 +214,14 @@ public class GameListItem extends LinearLayout boolean haveATurn = false; boolean haveALocalTurn = false; boolean[] isLocal = new boolean[1]; - for ( int ii = 0; ii < m_summary.nPlayers; ++ii ) { + for ( int ii = 0; ii < summary.nPlayers; ++ii ) { ExpiringLinearLayout tmp = (ExpiringLinearLayout) Utils.inflate( m_context, R.layout.player_list_elem ); view = (TextView)tmp.findViewById( R.id.item_name ); - view.setText( m_summary.summarizePlayer( ii ) ); + view.setText( summary.summarizePlayer( ii ) ); view = (TextView)tmp.findViewById( R.id.item_score ); - view.setText( String.format( " %d", m_summary.scores[ii] ) ); - boolean thisHasTurn = m_summary.isNextToPlay( ii, isLocal ); + view.setText( String.format( " %d", summary.scores[ii] ) ); + boolean thisHasTurn = summary.isNextToPlay( ii, isLocal ); if ( thisHasTurn ) { haveATurn = true; if ( isLocal[0] ) { @@ -202,14 +229,14 @@ public class GameListItem extends LinearLayout } } tmp.setPct( m_handler, thisHasTurn, isLocal[0], - m_summary.lastMoveTime ); + summary.lastMoveTime ); list.addView( tmp, ii ); } view = (TextView)findViewById( R.id.state ); view.setText( state ); view = (TextView)findViewById( R.id.modtime ); - long lastMoveTime = m_summary.lastMoveTime; + long lastMoveTime = summary.lastMoveTime; lastMoveTime *= 1000; DateFormat df = DateFormat.getDateTimeInstance( DateFormat.SHORT, @@ -219,7 +246,7 @@ public class GameListItem extends LinearLayout int iconID; ImageView marker = (ImageView)findViewById( R.id.msg_marker ); - CommsConnType conType = m_summary.conType; + CommsConnType conType = summary.conType; if ( CommsConnType.COMMS_CONN_RELAY == conType ) { iconID = R.drawable.relaygame; } else if ( CommsConnType.COMMS_CONN_BT == conType ) { @@ -232,7 +259,7 @@ public class GameListItem extends LinearLayout marker.setImageResource( iconID ); view = (TextView)findViewById( R.id.role ); - String roleSummary = m_summary.summarizeRole(); + String roleSummary = summary.summarizeRole(); if ( null != roleSummary ) { view.setText( roleSummary ); } else { @@ -241,7 +268,7 @@ public class GameListItem extends LinearLayout boolean expanded = DBUtils.getExpanded( m_context, m_rowid ); - update( expanded, m_summary.lastMoveTime, haveATurn, + update( expanded, summary.lastMoveTime, haveATurn, haveALocalTurn ); } } @@ -256,13 +283,40 @@ public class GameListItem extends LinearLayout @Override protected void onPostExecute( GameSummary summary ) { - m_summary = summary; - setData(); - // setLoaded( m_view.getRowID() ); - setLoaded(); - - // DbgUtils.logf( "LoadItemTask for row %d finished", m_rowid ); + if ( 0 == --m_loadingCount ) { + m_summary = summary; + setData( summary ); + setLoaded( null != m_summary ); + synchronized( s_invalRows ) { + s_invalRows.remove( m_rowid ); + } + } + // DbgUtils.logf( "LoadItemTask for row %d finished; " + // + "inval rows now %s", + // m_rowid, invalRowsToString() ); } } // class LoadItemTask + public static void inval( long rowid ) + { + synchronized( s_invalRows ) { + s_invalRows.add( rowid ); + } + // DbgUtils.logf( "GameListItem.inval(rowid=%d); inval rows now %s", + // rowid, invalRowsToString() ); + } + + // private static String invalRowsToString() + // { + // String[] strs; + // synchronized( s_invalRows ) { + // strs = new String[s_invalRows.size()]; + // Iterator iter = s_invalRows.iterator(); + // for ( int ii = 0; iter.hasNext(); ++ii ) { + // strs[ii] = String.format("%d", iter.next() ); + // } + // } + // return TextUtils.join(",", strs ); + // } + } From 51a5e80a267f487acdc7691d8ab55aa106711ba7 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 16 Dec 2012 15:13:36 -0800 Subject: [PATCH 137/146] mention attachment in invite email text --- xwords4/android/XWords4/res/values/strings.xml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/xwords4/android/XWords4/res/values/strings.xml b/xwords4/android/XWords4/res/values/strings.xml index cdd796f20..e9288214b 100644 --- a/xwords4/android/XWords4/res/values/strings.xml +++ b/xwords4/android/XWords4/res/values/strings.xml @@ -1229,8 +1229,10 @@ encodings for the greater-than and less-than symbols which are not legal in xml strings.)--> \u003ca href=\"%1$s\"\u003ETap - here\u003c/a\u003E (or the full link below) to accept my invitation and - join this game. + here\u003c/a\u003E (or tap the full link below, or, if you already + have Crosswords installed, open the attachment) to accept my + invitation and join this game. + \u003cbr \\\u003E \u003cbr \\\u003E (full link: %1$s) From e38b99c0c8feedf2aa06030d15c1988aa1497bcf Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 16 Dec 2012 15:14:04 -0800 Subject: [PATCH 138/146] set DEBUG to false for release --- xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java index 51d7db703..1f0790181 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java @@ -33,7 +33,7 @@ public class XWApp extends Application { public static final boolean GCMSUPPORTED = true; public static final boolean ATTACH_SUPPORTED = true; public static final boolean REMATCH_SUPPORTED = false; - public static final boolean DEBUG = true; // DON'T SHIP THIS WAY + public static final boolean DEBUG = false; public static final boolean DEBUG_LOCKS = false && DEBUG; public static final boolean DEBUG_EXP_TIMERS = false && DEBUG; From 60adf36718116aeabe67e65e78accbced74101fc Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 16 Dec 2012 15:14:33 -0800 Subject: [PATCH 139/146] fix typo --- xwords4/android/scripts/and_index.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xwords4/android/scripts/and_index.php b/xwords4/android/scripts/and_index.php index 01dac83c2..7d0a53cc1 100644 --- a/xwords4/android/scripts/and_index.php +++ b/xwords4/android/scripts/and_index.php @@ -75,8 +75,8 @@ link in your invite email (or text) again, and this time let Crosswords handle it.

          (If you get tired of having to having to make that choice, Android -will allow you to make Crosswords the default. What you're saying is -that Crosswords will be giving control of all URLs that start with +will allow you to make Crosswords the default. If you do that +Crosswords will be given control of all URLs that start with "http://eehouse.org/and/" -- not all URLs of any type.)

          Have fun. And as always, let From 9d7d0aca2bf21652047613e5ccac3bbfd70c1580 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 16 Dec 2012 20:14:15 -0800 Subject: [PATCH 140/146] cleanup: logging and unused imports --- .../src/org/eehouse/android/xw4/DBUtils.java | 1 - .../org/eehouse/android/xw4/GameListAdapter.java | 14 -------------- .../src/org/eehouse/android/xw4/GameUtils.java | 2 -- 3 files changed, 17 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java index 235dc108f..84a8bcae5 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java @@ -813,7 +813,6 @@ public class DBUtils { private static void clearRowIDsCache() { - DbgUtils.logf( "DBUtils.clearRowIDsCache()" ); synchronized( DBUtils.class ) { s_cachedRowIDs = null; } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java index 47b398964..4a6290d7e 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java @@ -20,26 +20,15 @@ package org.eehouse.android.xw4; import android.content.Context; -import android.database.DataSetObserver; -import android.os.Build; import android.os.Handler; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.ListAdapter; import android.widget.ListView; -import android.widget.TextView; -import java.io.FileInputStream; -import java.text.DateFormat; -import java.util.Date; -import java.util.Random; import junit.framework.Assert; - import org.eehouse.android.xw4.jni.*; import org.eehouse.android.xw4.jni.CurGameInfo.DeviceRole; import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType; @@ -50,7 +39,6 @@ public class GameListAdapter extends XWListAdapter { private LayoutInflater m_factory; private int m_fieldID; private Handler m_handler; - private DateFormat m_df; private LoadItemCB m_cb; public interface LoadItemCB { @@ -65,8 +53,6 @@ public class GameListAdapter extends XWListAdapter { m_handler = handler; m_cb = cb; m_factory = LayoutInflater.from( context ); - m_df = DateFormat.getDateTimeInstance( DateFormat.SHORT, - DateFormat.SHORT ); m_fieldID = fieldToID( fieldName ); } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java index 1e203a90c..37b765f6a 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java @@ -430,8 +430,6 @@ public class GameUtils { String mime = context.getString( R.string.invite_mime ); intent.setType( mime ); Uri uri = Uri.fromFile( attach ); - DbgUtils.logf( "using file uri %s, type %s for attachment", - uri.toString(), mime ); intent.putExtra( Intent.EXTRA_STREAM, uri ); } From 6988b1424737d9ea4d9aa9efc9d6e56935a03102 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 18 Dec 2012 05:40:25 -0800 Subject: [PATCH 141/146] no need for redraw in onWindowFocusChanged --- .../XWords4/src/org/eehouse/android/xw4/GamesList.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java index 2fa53e3b1..84bfba2ae 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java @@ -91,7 +91,6 @@ public class GamesList extends XWListActivity private int m_missingDictLang; private long m_rowid; private NetLaunchInfo m_netLaunchInfo; - private long m_invalRowID = DBUtils.ROWID_NOTFOUND; @Override protected Dialog onCreateDialog( int id ) @@ -379,10 +378,6 @@ public class GamesList extends XWListActivity if ( hasFocus ) { updateField(); } - if ( hasFocus && DBUtils.ROWID_NOTFOUND != m_invalRowID ) { - m_adapter.inval( m_invalRowID ); - m_invalRowID = DBUtils.ROWID_NOTFOUND; - } } // DBUtils.DBChangeListener interface @@ -883,7 +878,6 @@ public class GamesList extends XWListActivity private void launchGame( long rowid, boolean invited ) { - m_invalRowID = rowid; GameUtils.launchGame( this, rowid, invited ); } From 8cb9d3d66b61a0f2fdd451572d3610523c969b01 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 18 Dec 2012 05:56:14 -0800 Subject: [PATCH 142/146] turn debug back on --- xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java index 1f0790181..d3d02f534 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java @@ -33,7 +33,7 @@ public class XWApp extends Application { public static final boolean GCMSUPPORTED = true; public static final boolean ATTACH_SUPPORTED = true; public static final boolean REMATCH_SUPPORTED = false; - public static final boolean DEBUG = false; + public static final boolean DEBUG = true; public static final boolean DEBUG_LOCKS = false && DEBUG; public static final boolean DEBUG_EXP_TIMERS = false && DEBUG; From db829337f5ae4faff9c93e0e0a1bcb2db05111f8 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 20 Dec 2012 21:51:38 -0800 Subject: [PATCH 143/146] up version strings etc. --- xwords4/android/XWords4/AndroidManifest.xml | 2 +- xwords4/android/XWords4/res/raw/changes | 15 ++++----------- xwords4/android/XWords4/res/values/app_name.xml | 2 +- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/xwords4/android/XWords4/AndroidManifest.xml b/xwords4/android/XWords4/AndroidManifest.xml index 1b634d862..a1446e4b8 100644 --- a/xwords4/android/XWords4/AndroidManifest.xml +++ b/xwords4/android/XWords4/AndroidManifest.xml @@ -22,7 +22,7 @@ to come from a domain that you own or have control over. --> diff --git a/xwords4/android/XWords4/res/raw/changes b/xwords4/android/XWords4/res/raw/changes index b9613677d..98a8b8f2b 100644 --- a/xwords4/android/XWords4/res/raw/changes +++ b/xwords4/android/XWords4/res/raw/changes @@ -5,24 +5,17 @@ -Crosswords 4.4 beta 57 release +Crosswords 4.4 beta 58 release

          New with this release

            -
          • Include new game information as an attachment in email invites - for use on devices that don't dispatch URLs correctly in received - email (e.g. some by HTC)
          • -
          • Show final scores alert whenever a finished game is opened -- to - make it more clear that it's finished
          • -
          • Fix flickering in main screen (games list)
          • -
          • Add option, off by default, to keep rack tiles square even when - the screen is large enough that they can be taller
          • +
          • Allow grouping of games in collapsible user-defined categores: + "Games with Kati", "Finished games", etc.

          Next up

            -
          • Allow grouping of games in collapsible user-defined categores: "Games with - Kati", "Finished games", etc.
          • +
          • Improve communication with relay

          (The full changelog diff --git a/xwords4/android/XWords4/res/values/app_name.xml b/xwords4/android/XWords4/res/values/app_name.xml index 095652a34..59b7fb0f8 100644 --- a/xwords4/android/XWords4/res/values/app_name.xml +++ b/xwords4/android/XWords4/res/values/app_name.xml @@ -1,5 +1,5 @@ - 4.4 beta 57 + 4.4 beta 58 From 1743382e1f7f277a8c8a843e397e5c6dec373dc7 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 20 Dec 2012 21:55:57 -0800 Subject: [PATCH 144/146] cleanup: combine files that belong together --- .../src/org/eehouse/android/xw4/NetUtils.java | 77 ------------ .../org/eehouse/android/xw4/RelayMsgSink.java | 57 --------- .../org/eehouse/android/xw4/RelayService.java | 110 +++++++++++++++++- 3 files changed, 104 insertions(+), 140 deletions(-) delete mode 100644 xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayMsgSink.java diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetUtils.java index 75d3d11b0..4e8ee9942 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetUtils.java @@ -21,21 +21,14 @@ package org.eehouse.android.xw4; import android.content.Context; -import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.net.InetAddress; import java.net.Socket; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; import javax.net.SocketFactory; public class NetUtils { - private static final int MAX_SEND = 1024; - private static final int MAX_BUF = MAX_SEND - 2; - public static final byte PROTOCOL_VERSION = 0; // from xwrelay.h public static byte PRX_PUB_ROOMS = 1; @@ -194,76 +187,6 @@ public class NetUtils { return msgs; } // queryRelay - public static void sendToRelay( Context context, - HashMap> msgHash ) - { - // format: total msg lenth: 2 - // number-of-relayIDs: 2 - // for-each-relayid: relayid + '\n': varies - // message count: 1 - // for-each-message: length: 2 - // message: varies - - if ( null != msgHash ) { - try { - // Build up a buffer containing everything but the total - // message length and number of relayIDs in the message. - ByteArrayOutputStream store = - new ByteArrayOutputStream( MAX_BUF ); // mem - DataOutputStream outBuf = new DataOutputStream( store ); - int msgLen = 4; // relayID count + protocol stuff - int nRelayIDs = 0; - - Iterator iter = msgHash.keySet().iterator(); - while ( iter.hasNext() ) { - String relayID = iter.next(); - int thisLen = 1 + relayID.length(); // string and '\n' - thisLen += 2; // message count - - ArrayList msgs = msgHash.get( relayID ); - for ( byte[] msg : msgs ) { - thisLen += 2 + msg.length; - } - - if ( msgLen + thisLen > MAX_BUF ) { - // Need to deal with this case by sending multiple - // packets. It WILL happen. - break; - } - // got space; now write it - ++nRelayIDs; - outBuf.writeBytes( relayID ); - outBuf.write( '\n' ); - outBuf.writeShort( msgs.size() ); - for ( byte[] msg : msgs ) { - outBuf.writeShort( msg.length ); - outBuf.write( msg ); - } - msgLen += thisLen; - } - - // Now open a real socket, write size and proto, and - // copy in the formatted buffer - Socket socket = makeProxySocket( context, 8000 ); - if ( null != socket ) { - DataOutputStream outStream = - new DataOutputStream( socket.getOutputStream() ); - outStream.writeShort( msgLen ); - outStream.writeByte( NetUtils.PROTOCOL_VERSION ); - outStream.writeByte( NetUtils.PRX_PUT_MSGS ); - outStream.writeShort( nRelayIDs ); - outStream.write( store.toByteArray() ); - outStream.flush(); - socket.close(); - } - } catch ( java.io.IOException ioe ) { - DbgUtils.loge( ioe ); - } - } else { - DbgUtils.logf( "sendToRelay: null msgs" ); - } - } // sendToRelay - private static int sumStrings( final String[] strs ) { int len = 0; diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayMsgSink.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayMsgSink.java deleted file mode 100644 index 53e0ccfdd..000000000 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayMsgSink.java +++ /dev/null @@ -1,57 +0,0 @@ -/* -*- compile-command: "cd ../../../../../; ant install"; -*- */ -/* - * Copyright 2009-2010 by Eric House (xwords@eehouse.org). All - * rights reserved. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -package org.eehouse.android.xw4; - -import android.content.Context; -import java.util.HashMap; -import java.util.ArrayList; - -import junit.framework.Assert; - -import org.eehouse.android.xw4.jni.*; - -public class RelayMsgSink extends MultiMsgSink { - - private HashMap> m_msgLists = null; - - public void send( Context context ) - { - NetUtils.sendToRelay( context, m_msgLists ); - } - - /***** TransportProcs interface *****/ - - public boolean relayNoConnProc( byte[] buf, String relayID ) - { - if ( null == m_msgLists ) { - m_msgLists = new HashMap>(); - } - - ArrayList list = m_msgLists.get( relayID ); - if ( list == null ) { - list = new ArrayList(); - m_msgLists.put( relayID, list ); - } - list.add( buf ); - - return true; - } -} diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java index 5da48cd30..bc4d2d179 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java @@ -24,18 +24,18 @@ import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.IBinder; -import javax.net.SocketFactory; -import java.net.InetAddress; -import java.net.Socket; -import java.io.InputStream; -import java.io.DataInputStream; -import java.io.OutputStream; +import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; +import java.net.Socket; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; import org.eehouse.android.xw4.jni.GameSummary; public class RelayService extends Service { + private static final int MAX_SEND = 1024; + private static final int MAX_BUF = MAX_SEND - 2; @Override public void onCreate() @@ -114,4 +114,102 @@ public class RelayService extends Service { } } + private static void sendToRelay( Context context, + HashMap> msgHash ) + { + // format: total msg lenth: 2 + // number-of-relayIDs: 2 + // for-each-relayid: relayid + '\n': varies + // message count: 1 + // for-each-message: length: 2 + // message: varies + + if ( null != msgHash ) { + try { + // Build up a buffer containing everything but the total + // message length and number of relayIDs in the message. + ByteArrayOutputStream store = + new ByteArrayOutputStream( MAX_BUF ); // mem + DataOutputStream outBuf = new DataOutputStream( store ); + int msgLen = 4; // relayID count + protocol stuff + int nRelayIDs = 0; + + Iterator iter = msgHash.keySet().iterator(); + while ( iter.hasNext() ) { + String relayID = iter.next(); + int thisLen = 1 + relayID.length(); // string and '\n' + thisLen += 2; // message count + + ArrayList msgs = msgHash.get( relayID ); + for ( byte[] msg : msgs ) { + thisLen += 2 + msg.length; + } + + if ( msgLen + thisLen > MAX_BUF ) { + // Need to deal with this case by sending multiple + // packets. It WILL happen. + break; + } + // got space; now write it + ++nRelayIDs; + outBuf.writeBytes( relayID ); + outBuf.write( '\n' ); + outBuf.writeShort( msgs.size() ); + for ( byte[] msg : msgs ) { + outBuf.writeShort( msg.length ); + outBuf.write( msg ); + } + msgLen += thisLen; + } + + // Now open a real socket, write size and proto, and + // copy in the formatted buffer + Socket socket = NetUtils.makeProxySocket( context, 8000 ); + if ( null != socket ) { + DataOutputStream outStream = + new DataOutputStream( socket.getOutputStream() ); + outStream.writeShort( msgLen ); + outStream.writeByte( NetUtils.PROTOCOL_VERSION ); + outStream.writeByte( NetUtils.PRX_PUT_MSGS ); + outStream.writeShort( nRelayIDs ); + outStream.write( store.toByteArray() ); + outStream.flush(); + socket.close(); + } + } catch ( java.io.IOException ioe ) { + DbgUtils.loge( ioe ); + } + } else { + DbgUtils.logf( "sendToRelay: null msgs" ); + } + } // sendToRelay + + private class RelayMsgSink extends MultiMsgSink { + + private HashMap> m_msgLists = null; + + public void send( Context context ) + { + sendToRelay( context, m_msgLists ); + } + + /***** TransportProcs interface *****/ + + public boolean relayNoConnProc( byte[] buf, String relayID ) + { + if ( null == m_msgLists ) { + m_msgLists = new HashMap>(); + } + + ArrayList list = m_msgLists.get( relayID ); + if ( list == null ) { + list = new ArrayList(); + m_msgLists.put( relayID, list ); + } + list.add( buf ); + + return true; + } + } + } From 1998da45decd6a7452105b03ff7c254110cdfe7a Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 21 Dec 2012 17:14:55 -0800 Subject: [PATCH 145/146] don't save game when it hasn't changed -- when saved bytes are identical with those we started with. --- .../eehouse/android/xw4/BoardActivity.java | 5 +-- .../eehouse/android/xw4/jni/JNIThread.java | 34 ++++++++++++------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java index c0bb10d0b..698f2144c 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java @@ -1739,8 +1739,9 @@ public class BoardActivity extends XWActivity } } }; - m_jniThread = new JNIThread( m_jniGamePtr, m_gi, m_view, - m_gameLock, this, handler ); + m_jniThread = + new JNIThread( m_jniGamePtr, stream, m_gi, + m_view, m_gameLock, this, handler ); // see http://stackoverflow.com/questions/680180/where-to-stop-\ // destroy-threads-in-android-service-class m_jniThread.setDaemon( true ); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java index e39adc389..39858aade 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java @@ -22,13 +22,14 @@ package org.eehouse.android.xw4.jni; import android.content.Context; -import java.lang.InterruptedException; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.Iterator; -import android.os.Handler; -import android.os.Message; import android.graphics.Paint; import android.graphics.Rect; +import android.os.Handler; +import android.os.Message; +import java.lang.InterruptedException; +import java.util.Arrays; +import java.util.Iterator; +import java.util.concurrent.LinkedBlockingQueue; import org.eehouse.android.xw4.R; import org.eehouse.android.xw4.DbgUtils; @@ -122,6 +123,7 @@ public class JNIThread extends Thread { private boolean m_stopped = false; private boolean m_saveOnStop = false; private int m_jniGamePtr; + private byte[] m_gameAtStart; private GameLock m_lock; private Context m_context; private CurGameInfo m_gi; @@ -143,10 +145,12 @@ public class JNIThread extends Thread { Object[] m_args; } - public JNIThread( int gamePtr, CurGameInfo gi, SyncedDraw drawer, - GameLock lock, Context context, Handler handler ) + public JNIThread( int gamePtr, byte[] gameAtStart, CurGameInfo gi, + SyncedDraw drawer, GameLock lock, Context context, + Handler handler ) { m_jniGamePtr = gamePtr; + m_gameAtStart = gameAtStart; m_gi = gi; m_drawer = drawer; m_lock = lock; @@ -286,13 +290,17 @@ public class JNIThread extends Thread { if ( null != m_newDict ) { m_gi.dictName = m_newDict; } - GameSummary summary = new GameSummary( m_context, m_gi ); - XwJNI.game_summarize( m_jniGamePtr, summary ); byte[] state = XwJNI.game_saveToStream( m_jniGamePtr, m_gi ); - GameUtils.saveGame( m_context, state, m_lock, false ); - DBUtils.saveSummary( m_context, m_lock, summary ); - // There'd better be no way for saveGame above to fail! - XwJNI.game_saveSucceeded( m_jniGamePtr ); + if ( Arrays.equals( m_gameAtStart, state ) ) { + DbgUtils.logf( "no change in game; can skip saving" ); + } else { + GameSummary summary = new GameSummary( m_context, m_gi ); + XwJNI.game_summarize( m_jniGamePtr, summary ); + DBUtils.saveGame( m_context, m_lock, state, false ); + DBUtils.saveSummary( m_context, m_lock, summary ); + // There'd better be no way for saveGame above to fail! + XwJNI.game_saveSucceeded( m_jniGamePtr ); + } } @SuppressWarnings("fallthrough") From 2461b526a5411933c229089250cd49f873c8ea5a Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 21 Dec 2012 18:06:27 -0800 Subject: [PATCH 146/146] move check for updates into async task --- .../android/xw4/UpdateCheckReceiver.java | 206 ++++++++++-------- 1 file changed, 120 insertions(+), 86 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/UpdateCheckReceiver.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/UpdateCheckReceiver.java index 8b69846d2..932f699db 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/UpdateCheckReceiver.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/UpdateCheckReceiver.java @@ -28,6 +28,7 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.net.Uri; +import android.os.AsyncTask; import android.os.SystemClock; import java.io.File; import java.util.ArrayList; @@ -154,92 +155,8 @@ public class UpdateCheckReceiver extends BroadcastReceiver { } if ( 0 < params.length() ) { - HttpPost post = makePost( context, "getUpdates" ); - String json = runPost( post, params ); - if ( null != json ) { - makeNotificationsIf( context, fromUI, json, pm, packageName, - dals ); - } - } - } - - private static void makeNotificationsIf( Context context, boolean fromUI, - String jstr, PackageManager pm, - String packageName, - DictUtils.DictAndLoc[] dals ) - { - boolean gotOne = false; - try { - JSONObject jobj = new JSONObject( jstr ); - if ( null != jobj ) { - if ( jobj.has( k_APP ) ) { - JSONObject app = jobj.getJSONObject( k_APP ); - if ( app.has( k_URL ) ) { - ApplicationInfo ai = pm.getApplicationInfo( packageName, 0); - String label = pm.getApplicationLabel( ai ).toString(); - - // If there's a download dir AND an installer - // app, handle this ourselves. Otherwise just - // launch the browser - boolean useBrowser; - File downloads = DictUtils.getDownloadDir( context ); - if ( null == downloads ) { - useBrowser = true; - } else { - File tmp = new File( downloads, - "xx" + XWConstants.APK_EXTN ); - useBrowser = !Utils.canInstall( context, tmp ); - } - - Intent intent; - String url = app.getString( k_URL ); - if ( useBrowser ) { - intent = new Intent( Intent.ACTION_VIEW, - Uri.parse(url) ); - } else { - intent = DictImportActivity - .makeAppDownloadIntent( context, url ); - } - - String title = - Utils.format( context, R.string.new_app_availf, label ); - String body = context.getString( R.string.new_app_avail ); - Utils.postNotification( context, intent, title, body, - url.hashCode() ); - gotOne = true; - } - } - if ( jobj.has( k_DICTS ) ) { - JSONArray dicts = jobj.getJSONArray( k_DICTS ); - for ( int ii = 0; ii < dicts.length(); ++ii ) { - JSONObject dict = dicts.getJSONObject( ii ); - if ( dict.has( k_URL ) && dict.has( k_INDEX ) ) { - String url = dict.getString( k_URL ); - int index = dict.getInt( k_INDEX ); - DictUtils.DictAndLoc dal = dals[index]; - Intent intent = - new Intent( context, DictsActivity.class ); - intent.putExtra( NEW_DICT_URL, url ); - intent.putExtra( NEW_DICT_LOC, dal.loc.ordinal() ); - String body = - Utils.format( context, R.string.new_dict_availf, - dal.name ); - Utils.postNotification( context, intent, - R.string.new_dict_avail, - body, url.hashCode() ); - gotOne = true; - } - } - } - } - } catch ( org.json.JSONException jse ) { - DbgUtils.loge( jse ); - } catch ( PackageManager.NameNotFoundException nnfe ) { - DbgUtils.loge( nnfe ); - } - - if ( !gotOne && fromUI ) { - Utils.showToast( context, R.string.checkupdates_none_found ); + new UpdateQueryTask( context, params, fromUI, pm, + packageName, dals ).execute(); } } @@ -303,4 +220,121 @@ public class UpdateCheckReceiver extends BroadcastReceiver { false ); } + private static class UpdateQueryTask extends AsyncTask { + private Context m_context; + private JSONObject m_params; + private boolean m_fromUI; + private PackageManager m_pm; + private String m_packageName; + private DictUtils.DictAndLoc[] m_dals; + + public UpdateQueryTask( Context context, JSONObject params, + boolean fromUI, PackageManager pm, + String packageName, + DictUtils.DictAndLoc[] dals ) + { + m_context = context; + m_params = params; + m_fromUI = fromUI; + m_pm = pm; + m_packageName = packageName; + m_dals = dals; + } + + @Override protected String doInBackground( Void... unused ) + { + HttpPost post = makePost( m_context, "getUpdates" ); + String json = runPost( post, m_params ); + return json; + } + + @Override protected void onPostExecute( String json ) + { + if ( null != json ) { + makeNotificationsIf( json ); + } + } + + private void makeNotificationsIf( String jstr ) + { + boolean gotOne = false; + try { + JSONObject jobj = new JSONObject( jstr ); + if ( null != jobj ) { + if ( jobj.has( k_APP ) ) { + JSONObject app = jobj.getJSONObject( k_APP ); + if ( app.has( k_URL ) ) { + ApplicationInfo ai = + m_pm.getApplicationInfo( m_packageName, 0); + String label = m_pm.getApplicationLabel( ai ).toString(); + + // If there's a download dir AND an installer + // app, handle this ourselves. Otherwise just + // launch the browser + boolean useBrowser; + File downloads = DictUtils.getDownloadDir( m_context ); + if ( null == downloads ) { + useBrowser = true; + } else { + File tmp = new File( downloads, + "xx" + XWConstants.APK_EXTN ); + useBrowser = !Utils.canInstall( m_context, tmp ); + } + + Intent intent; + String url = app.getString( k_URL ); + if ( useBrowser ) { + intent = new Intent( Intent.ACTION_VIEW, + Uri.parse(url) ); + } else { + intent = DictImportActivity + .makeAppDownloadIntent( m_context, url ); + } + + String title = + Utils.format( m_context, R.string.new_app_availf, + label ); + String body = + m_context.getString( R.string.new_app_avail ); + Utils.postNotification( m_context, intent, title, + body, url.hashCode() ); + gotOne = true; + } + } + if ( jobj.has( k_DICTS ) ) { + JSONArray dicts = jobj.getJSONArray( k_DICTS ); + for ( int ii = 0; ii < dicts.length(); ++ii ) { + JSONObject dict = dicts.getJSONObject( ii ); + if ( dict.has( k_URL ) && dict.has( k_INDEX ) ) { + String url = dict.getString( k_URL ); + int index = dict.getInt( k_INDEX ); + DictUtils.DictAndLoc dal = m_dals[index]; + Intent intent = + new Intent( m_context, DictsActivity.class ); + intent.putExtra( NEW_DICT_URL, url ); + intent.putExtra( NEW_DICT_LOC, dal.loc.ordinal() ); + String body = + Utils.format( m_context, + R.string.new_dict_availf, + dal.name ); + Utils.postNotification( m_context, intent, + R.string.new_dict_avail, + body, url.hashCode() ); + gotOne = true; + } + } + } + } + } catch ( org.json.JSONException jse ) { + DbgUtils.loge( jse ); + } catch ( PackageManager.NameNotFoundException nnfe ) { + DbgUtils.loge( nnfe ); + } + + if ( !gotOne && m_fromUI ) { + Utils.showToast( m_context, R.string.checkupdates_none_found ); + } + } + } + }