diff --git a/xwords4/android/app/build.gradle b/xwords4/android/app/build.gradle
index b74c4495a..00aecdfef 100644
--- a/xwords4/android/app/build.gradle
+++ b/xwords4/android/app/build.gradle
@@ -1,9 +1,13 @@
def INITIAL_CLIENT_VERS = 9
-def VERSION_CODE_BASE = 146
-def VERSION_NAME = '4.4.150'
+def VERSION_CODE_BASE = 148
+def VERSION_NAME = '4.4.152'
def FABRIC_API_KEY = System.getenv("FABRIC_API_KEY")
def BUILD_INFO_NAME = "build-info.txt"
+// AID must start with F (first 4 bits) and be from 5 to 16 bytes long
+def NFC_AID_XW4 = "FC8FF510B360"
+def NFC_AID_XW4d = "FDDA0A3EB5E5"
+
boolean forFDroid = hasProperty('forFDroid')
// Get the git revision we're using. Since fdroid modifies files as
@@ -35,6 +39,7 @@ android {
// default changes and .travis.yml can be kept in sync
buildToolsVersion '27.0.3'
defaultConfig {
+ // HostApduService requires 19. But is it a problem?
minSdkVersion 14
targetSdkVersion 28 // must match ../build.gradle
versionCode VERSION_CODE_BASE
@@ -88,6 +93,8 @@ android {
buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "false"
buildConfigField "String", "VARIANT_NAME", "\"Google Play Store\""
buildConfigField "int", "VARIANT_CODE", "1"
+ buildConfigField "String", "NFC_AID", "\"${NFC_AID_XW4}\""
+ resValue "string", "nfc_aid", "$NFC_AID_XW4"
}
xw4fdroid {
@@ -101,10 +108,12 @@ android {
buildConfigField "String", "VARIANT_NAME", "\"F-Droid\""
buildConfigField "int", "VARIANT_CODE", "2"
buildConfigField "boolean", "FOR_FDROID", "true"
+ buildConfigField "String", "NFC_AID", "\"${NFC_AID_XW4}\""
+ resValue "string", "nfc_aid", "$NFC_AID_XW4"
}
xw4d {
dimension "variant"
- buildConfigField "String", "DB_NAME", "\"xwddb\"";
+ buildConfigField "String", "DB_NAME", "\"xwddb\""
applicationId "org.eehouse.android.xw4dbg"
manifestPlaceholders = [ FABRIC_API_KEY: "$FABRIC_API_KEY", APP_ID: applicationId, ]
resValue "string", "app_name", "CrossDbg"
@@ -115,11 +124,13 @@ android {
buildConfigField "int", "VARIANT_CODE", "3"
buildConfigField "boolean", "REPORT_LOCKS", "true"
buildConfigField "boolean", "MOVE_VIA_NFC", "true"
+ buildConfigField "String", "NFC_AID", "\"${NFC_AID_XW4d}\""
+ resValue "string", "nfc_aid", "$NFC_AID_XW4d"
}
xw4dNoSMS {
dimension "variant"
- buildConfigField "String", "DB_NAME", "\"xwddb\"";
+ buildConfigField "String", "DB_NAME", "\"xwddb\""
applicationId "org.eehouse.android.xw4dbg"
manifestPlaceholders = [ FABRIC_API_KEY: "$FABRIC_API_KEY", APP_ID: applicationId, ]
resValue "string", "app_name", "CrossDbg"
@@ -130,6 +141,8 @@ android {
buildConfigField "int", "VARIANT_CODE", "4"
buildConfigField "boolean", "REPORT_LOCKS", "true"
buildConfigField "boolean", "MOVE_VIA_NFC", "true"
+ buildConfigField "String", "NFC_AID", "\"${NFC_AID_XW4d}\""
+ resValue "string", "nfc_aid", "$NFC_AID_XW4d"
}
xw4SMS {
@@ -142,6 +155,8 @@ android {
buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "false"
buildConfigField "String", "VARIANT_NAME", "\"FOSS\""
buildConfigField "int", "VARIANT_CODE", "5"
+ buildConfigField "String", "NFC_AID", "\"${NFC_AID_XW4}\""
+ resValue "string", "nfc_aid", "$NFC_AID_XW4"
}
// WARNING: "all" breaks things. Seems to be a keyword. Need
@@ -390,8 +405,8 @@ task makeBuildAssets() {
out += "date: ${date}\n"
// I want the variant, but that's harder. Here's a quick hack from SO.
- String target = gradle.startParameter.taskNames[-1]
- out += "target: ${target}\n"
+ // String target = gradle.startParameter.taskNames[0]
+ // out += "target: ${target}\n"
String diff = "git diff".execute().text.trim()
if (diff) {
diff --git a/xwords4/android/app/src/main/AndroidManifest.xml b/xwords4/android/app/src/main/AndroidManifest.xml
index 6916ec874..4043fd8ab 100644
--- a/xwords4/android/app/src/main/AndroidManifest.xml
+++ b/xwords4/android/app/src/main/AndroidManifest.xml
@@ -32,10 +32,10 @@
-
+
-
-
-
-
-
-
@@ -213,5 +207,14 @@
+
+
+
+
+
+
+
diff --git a/xwords4/android/app/src/main/assets/changes.html b/xwords4/android/app/src/main/assets/changes.html
index eb0a0bc68..28a3ab1f7 100644
--- a/xwords4/android/app/src/main/assets/changes.html
+++ b/xwords4/android/app/src/main/assets/changes.html
@@ -13,11 +13,9 @@
- CrossWords 4.4.150 release
+ CrossWords 4.4.152 release
- This release speeds move delivery for newly-installed games and
- hides invite-via-NFC on Android 10 (since it doesn't work
- there).
+ This release fixes a nasty crash in some non-English versions.
- New with this release
+ New with this (and previous) release
- - Improve message delivery for new installs
- - Don't offer NFC on Android 10, where it's broken
- - Improve board layout on taller, narrower screens
- - Fix occasional stall sending chat messages
- - Fix another (very rare!) type of stall
+ - Fix crash with some localized versions (Weblate problem...)
+ - Tweak network traffic arrows on game board
+ - Fix crash dragging tiles
+ - Fix invite-via-NFC for Android 10 (and improve for earlier
+ Android versions)
+ - Add ability to send moves via NFC
+ - More translations (via Weblate) in Catalan, French, German,
+ Japanese, Norwegian, Polish, and Spanish
+ - Fix a couple of other crashes reported via Google (thanks!)
(The full changelog
@@ -39,7 +41,7 @@
Next up
- - Look for a workaround to allow NFC on Android 10
+ - Improve move-via-NFC
- Support duplicate-style play (popular in France)
- Improve play-by-data-sms workaround
using NBSProxy
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BTService.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BTService.java
index c0ba93ad0..dbb8cda93 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BTService.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BTService.java
@@ -689,7 +689,7 @@ public class BTService extends XWJIService {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if ( null != adapter ) {
for ( BluetoothDevice dev : adapter.getBondedDevices() ) {
- Log.d( TAG, "%s => %s", dev.getName(), dev.getAddress() );
+ // Log.d( TAG, "%s => %s", dev.getName(), dev.getAddress() );
if ( btName.equals( dev.getName() ) ) {
btAddr = dev.getAddress();
s_namesToAddrs.put( btName, btAddr );
@@ -771,14 +771,14 @@ public class BTService extends XWJIService {
{
Context context = XWApp.getContext();
ConnStatusHandler
- .updateStatusOut( context, null, CommsConnType.COMMS_CONN_BT, success );
+ .updateStatusOut( context, CommsConnType.COMMS_CONN_BT, success );
}
private static void updateStatusIn( boolean success )
{
Context context = XWApp.getContext();
ConnStatusHandler
- .updateStatusIn( context, null, CommsConnType.COMMS_CONN_BT, success );
+ .updateStatusIn( context, CommsConnType.COMMS_CONN_BT, success );
}
private static class KillerIn extends Thread implements AutoCloseable {
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java
index 8ec61e6b9..025567323 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java
@@ -72,12 +72,13 @@ import org.eehouse.android.xw4.jni.XwJNI.GamePtr;
import org.eehouse.android.xw4.jni.XwJNI;
import org.eehouse.android.xw4.loc.LocUtils;
import org.eehouse.android.xw4.TilePickAlert.TilePickState;
+import org.eehouse.android.xw4.NFCUtils.Wrapper;
public class BoardDelegate extends DelegateBase
implements TransportProcs.TPMsgHandler, View.OnClickListener,
DwnldDelegate.DownloadFinishedListener,
ConnStatusHandler.ConnStatusCBacks,
- NFCUtils.NFCActor {
+ Wrapper.Procs {
private static final String TAG = BoardDelegate.class.getSimpleName();
private static final int SCREEN_ON_TIME = 10 * 60 * 1000; // 10 mins
@@ -126,6 +127,8 @@ public class BoardDelegate extends DelegateBase
private DBAlert m_inviteAlert;
private boolean m_haveStartedShowing;
+ private Wrapper mNFCWrapper;
+
public class TimerRunnable implements Runnable {
private int m_why;
private int m_when;
@@ -170,7 +173,7 @@ public class BoardDelegate extends DelegateBase
private boolean alertOrderAt( StartAlertOrder ord )
{
boolean result = m_mySIS.mAlertOrder == ord;
- Log.d( TAG, "alertOrderAt(%s) => %b", ord, result );
+ // Log.d( TAG, "alertOrderAt(%s) => %b", ord, result );
return result;
}
@@ -558,6 +561,9 @@ public class BoardDelegate extends DelegateBase
m_isFirstLaunch = null == savedInstanceState;
getBundledData( savedInstanceState );
+ int devID = DevID.getNFCDevID( m_activity );
+ mNFCWrapper = Wrapper.init( m_activity, this, devID );
+
m_utils = new BoardUtilCtxt();
m_timers = new TimerRunnable[4]; // needs to be in sync with
// XWTimerReason
@@ -601,9 +607,6 @@ public class BoardDelegate extends DelegateBase
m_jniThreadRef.setDaemonOnce( true );
m_jniThreadRef.startOnce();
- // Don't seem to need to unregister...
- NFCUtils.register( m_activity, BoardDelegate.this );
-
setBackgroundColor();
setKeepScreenOn();
@@ -633,6 +636,7 @@ public class BoardDelegate extends DelegateBase
protected void onResume()
{
super.onResume();
+ Wrapper.setResumed( mNFCWrapper, true );
if ( null != m_jniThreadRef ) {
doResume( false );
} else {
@@ -642,6 +646,7 @@ public class BoardDelegate extends DelegateBase
protected void onPause()
{
+ Wrapper.setResumed( mNFCWrapper, false );
closeIfFinishing( false );
m_handler = null;
ConnStatusHandler.setHandler( null );
@@ -1097,9 +1102,6 @@ public class BoardDelegate extends DelegateBase
case LOOKUP_ACTION:
launchLookup( m_mySIS.words, m_gi.dictLang );
break;
- case NFC_TO_SELF:
- GamesListDelegate.sendNFCToSelf( m_activity, makeNFCMessage() );
- break;
case DROP_RELAY_ACTION:
dropConViaAndRestart(CommsConnType.COMMS_CONN_RELAY);
break;
@@ -1273,10 +1275,7 @@ public class BoardDelegate extends DelegateBase
? (SentInvitesInfo)params[0] : null;
switch( means ) {
case NFC:
- if ( XWPrefs.getNFCToSelfEnabled( m_activity ) ) {
- makeConfirmThenBuilder( R.string.nfc_to_self, Action.NFC_TO_SELF )
- .show();
- } else if ( ! NFCUtils.nfcAvail( m_activity )[1] ) {
+ if ( ! NFCUtils.nfcAvail( m_activity )[1] ) {
showDialogFragment( DlgID.ENABLE_NFC );
} else {
makeOkOnlyBuilder( R.string.nfc_just_tap ).show();
@@ -1515,22 +1514,49 @@ public class BoardDelegate extends DelegateBase
}
//////////////////////////////////////////////////
- // NFCUtils.NFCActor
+ // ConnStatusHandler.ConnStatusCBacks
//////////////////////////////////////////////////
@Override
- public String makeNFCMessage()
+ public void invalidateParent()
{
- Log.d( TAG, "makeNFCMessage(): m_mySIS.nMissing: %d", m_mySIS.nMissing );
- String data = null;
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ m_view.invalidate();
+ }
+ });
+ }
+
+ @Override
+ public void onStatusClicked()
+ {
+ onStatusClicked( m_jniGamePtr );
+ }
+
+ @Override
+ public Handler getHandler()
+ {
+ return m_handler;
+ }
+
+ ////////////////////////////////////////////////////////////
+ // NFCCardService.Wrapper.Procs
+ ////////////////////////////////////////////////////////////
+ @Override
+ public void onReadingChange( boolean nowReading )
+ {
+ // Do we need this?
+ }
+
+ private byte[] getInvite()
+ {
+ byte[] result = null;
if ( 0 < m_mySIS.nMissing // Isn't there a better test??
&& DeviceRole.SERVER_ISSERVER == m_gi.serverRole ) {
- Log.d( TAG, "makeNFCMessage(): invite case" );
NetLaunchInfo nli = new NetLaunchInfo( m_gi );
Assert.assertTrue( 0 <= m_nGuestDevs );
nli.forceChannel = 1 + m_nGuestDevs;
- Assert.assertTrue( m_connTypes.contains( CommsConnType.COMMS_CONN_NFC ) );
-
for ( Iterator iter = m_connTypes.iterator();
iter.hasNext(); ) {
CommsConnType typ = iter.next();
@@ -1558,46 +1584,9 @@ public class BoardDelegate extends DelegateBase
typ.toString() );
}
}
- data = nli.makeLaunchJSON();
- if ( null != data ) {
- recordInviteSent( InviteMeans.NFC, null );
- }
- } else if ( BuildConfig.MOVE_VIA_NFC ) {
- Log.d( TAG, "makeNFCMessage(): move case" );
- byte[][] msgs = XwJNI.comms_getPending( m_jniGamePtr );
- data = NFCUtils.makeMsgsJSON( m_gi.gameID, msgs );
- } else {
- Log.d( TAG, "makeNFCMessage(): other (bad!!) case" );
- Assert.assertFalse( BuildConfig.DEBUG );
+ result = nli.asByteArray();
}
- Log.d( TAG, "makeNFCMessage() => %s", data );
- return data;
- }
-
- //////////////////////////////////////////////////
- // ConnStatusHandler.ConnStatusCBacks
- //////////////////////////////////////////////////
- @Override
- public void invalidateParent()
- {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- m_view.invalidate();
- }
- });
- }
-
- @Override
- public void onStatusClicked()
- {
- onStatusClicked( m_jniGamePtr );
- }
-
- @Override
- public Handler getHandler()
- {
- return m_handler;
+ return result;
}
private void launchPhoneNumberInvite( int nMissing, SentInvitesInfo info,
@@ -2269,8 +2258,15 @@ public class BoardDelegate extends DelegateBase
if ( null == m_jniThread ) {
m_jniThread = m_jniThreadRef.retain();
m_gi = m_jniThread.getGI();
+
m_summary = m_jniThread.getSummary();
+ Wrapper.setGameID( mNFCWrapper, m_gi.gameID );
+ byte[] invite = getInvite();
+ if ( null != invite ) {
+ NFCUtils.addInvitationFor( invite, m_gi.gameID );
+ }
+
m_view.startHandling( m_activity, m_jniThread, m_connTypes );
handleViaThread( JNICmd.CMD_START );
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/CommsTransport.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/CommsTransport.java
index 9cbab9934..2b1eeb1b7 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/CommsTransport.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/CommsTransport.java
@@ -181,7 +181,7 @@ public class CommsTransport implements TransportProcs,
addIncoming();
}
ConnStatusHandler.
- updateStatusIn( m_context, null,
+ updateStatusIn( m_context,
CommsConnType.COMMS_CONN_RELAY,
0 <= nRead );
}
@@ -190,7 +190,7 @@ public class CommsTransport implements TransportProcs,
if ( null != m_bytesOut ) {
int nWritten = channel.write( m_bytesOut );
ConnStatusHandler.
- updateStatusOut( m_context, null,
+ updateStatusOut( m_context,
CommsConnType.COMMS_CONN_RELAY,
0 < nWritten );
}
@@ -444,6 +444,7 @@ public class CommsTransport implements TransportProcs,
.sendPacket( context, addr.p2p_addr, gameID, buf );
break;
case COMMS_CONN_NFC:
+ nSent = NFCUtils.addMsgFor( buf, gameID );
break;
default:
Assert.fail();
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/ConnStatusHandler.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/ConnStatusHandler.java
index bd3513dc4..cb39871c3 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/ConnStatusHandler.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/ConnStatusHandler.java
@@ -30,6 +30,7 @@ import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.provider.Settings;
import android.text.format.DateUtils;
+import android.graphics.PorterDuff.Mode;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnTypeSet;
@@ -47,6 +48,7 @@ public class ConnStatusHandler {
private static final String TAG = ConnStatusHandler.class.getSimpleName();
private static final String RECS_KEY = TAG + "/recs";
private static final String STALL_STATS_KEY = TAG + "/stall_stats";
+ private static final int ORANGE = 0XFFFFA500;
public interface ConnStatusCBacks {
public void invalidateParent();
@@ -178,10 +180,6 @@ public class ConnStatusHandler {
R.string.connstat_net_fmt,
connTypes.toString( context, true )));
for ( CommsConnType typ : connTypes.getTypes() ) {
- if ( ! typ.isSelectable() ) {
- continue;
- }
-
sb.append( String.format( "\n\n*** %s ", typ.longName( context ) ) );
String did = addDebugInfo( context, gamePtr, addr, typ );
if ( null != did ) {
@@ -363,12 +361,23 @@ public class ConnStatusHandler {
updateStatusImpl( context, cbacks, connType, success, true );
}
+ public static void updateStatusIn( Context context, CommsConnType connType,
+ boolean success )
+ {
+ updateStatusImpl( context, null, connType, success, true );
+ }
+
public static void updateStatusOut( Context context, ConnStatusCBacks cbacks,
CommsConnType connType, boolean success )
{
updateStatusImpl( context, cbacks, connType, success, false );
}
+ public static void updateStatusOut( Context context, CommsConnType connType, boolean success )
+ {
+ updateStatusImpl( context, null, connType, success, false );
+ }
+
private static void updateStatusImpl( Context context, ConnStatusCBacks cbacks,
CommsConnType connType, boolean success,
boolean isIn )
@@ -457,7 +466,7 @@ public class ConnStatusHandler {
- scratchR.height()) );
drawIn( canvas, res, R.drawable.multigame__gen, scratchR );
- if ( BuildConfig.DEBUG && 0 < s_moveCount ) {
+ if ( 0 < s_moveCount && XWPrefs.moveCountEnabled( context ) ) {
String str = String.format( "%d", s_moveCount );
s_fillPaint.setColor( Color.BLACK );
canvas.drawText( str, s_rect.left + (s_rect.width() / 2),
@@ -480,16 +489,10 @@ public class ConnStatusHandler {
private static void drawArrow( Canvas canvas, Resources res, Rect rect,
boolean isIn )
{
- int arrowID;
boolean showSuccesses = s_showSuccesses[isIn? SUCCESS_IN : SUCCESS_OUT];
- if ( isIn ) {
- arrowID = showSuccesses ?
- R.drawable.in_arrow_active : R.drawable.in_arrow;
- } else {
- arrowID = showSuccesses ?
- R.drawable.out_arrow_active : R.drawable.out_arrow;
- }
- drawIn( canvas, res, arrowID, rect );
+ int color = showSuccesses ? ORANGE : Color.WHITE;
+ int arrowID = isIn ? R.drawable.in_arrow__gen : R.drawable.out_arrow__gen;
+ drawIn( canvas, res, arrowID, rect, color );
}
// This gets rid of lint warning, but I don't like it as it
@@ -569,9 +572,19 @@ public class ConnStatusHandler {
}
private static void drawIn( Canvas canvas, Resources res, int id, Rect rect )
+ {
+ drawIn( canvas, res, id, rect, Color.WHITE );
+ }
+
+ private static void drawIn( Canvas canvas, Resources res, int id, Rect rect, int color )
{
Drawable icon = res.getDrawable( id );
- Assert.assertTrue( icon.getBounds().width() == icon.getBounds().height() );
+ if ( Color.WHITE != color ) {
+ icon = icon.mutate();
+ icon.setColorFilter( color, Mode.MULTIPLY );
+ }
+ Assert.assertTrue( icon.getBounds().width() == icon.getBounds().height()
+ || !BuildConfig.DEBUG );
icon.setBounds( rect );
icon.draw( canvas );
}
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBHelper.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBHelper.java
index f9e1128d8..d13738faa 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBHelper.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBHelper.java
@@ -35,17 +35,26 @@ import java.util.Arrays;
public class DBHelper extends SQLiteOpenHelper {
private static final String TAG = DBHelper.class.getSimpleName();
- public static final String TABLE_NAME_SUM = "summaries";
- 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";
- public static final String TABLE_NAME_STUDYLIST = "study";
- public static final String TABLE_NAME_LOC = "loc";
- public static final String TABLE_NAME_PAIRS = "pairs";
- public static final String TABLE_NAME_INVITES = "invites";
- public static final String TABLE_NAME_CHAT = "chat";
- public static final String TABLE_NAME_LOGS = "logs";
+ public enum TABLE_NAMES {
+ SUM( "summaries", 0 ),
+ OBITS( "obits", 5 ),
+ DICTBROWSE( "dictbrowse", 12 ),
+ DICTINFO( "dictinfo", 12 ),
+ GROUPS( "groups", 14 ),
+ STUDYLIST( "study", 18 ),
+ LOC( "loc", 20 ),
+ PAIRS( "pairs", 21 ),
+ INVITES( "invites", 24 ),
+ CHAT( "chat", 25 ),
+ LOGS( "logs", 26 );
+
+ private String mName;
+ private int mAddedVersion;
+ private TABLE_NAMES(String name, int start) { mName = name; mAddedVersion = start; }
+ @Override
+ public String toString() { return mName; }
+ private int addedVersion() { return mAddedVersion; }
+ }
private static final String DB_NAME = BuildConfig.DB_NAME;
private static final int DB_VERSION = 29;
@@ -245,11 +254,11 @@ public class DBHelper extends SQLiteOpenHelper {
@Override
public void onCreate( SQLiteDatabase 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 );
- forceRowidHigh( db, TABLE_NAME_SUM );
+ createTable( db, TABLE_NAMES.SUM, s_summaryColsAndTypes );
+ createTable( db, TABLE_NAMES.OBITS, s_obitsColsAndTypes );
+ createTable( db, TABLE_NAMES.DICTINFO, s_dictInfoColsAndTypes );
+ createTable( db, TABLE_NAMES.DICTBROWSE, s_dictBrowseColsAndTypes );
+ forceRowidHigh( db, TABLE_NAMES.SUM );
createGroupsTable( db, false );
createStudyTable( db );
createLocTable( db );
@@ -269,7 +278,7 @@ public class DBHelper extends SQLiteOpenHelper {
boolean madeChatTable = false;
switch( oldVersion ) {
case 5:
- createTable( db, TABLE_NAME_OBITS, s_obitsColsAndTypes );
+ createTable( db, TABLE_NAMES.OBITS, s_obitsColsAndTypes );
case 6:
addSumColumn( db, TURN );
addSumColumn( db, GIFLAGS );
@@ -285,8 +294,8 @@ public class DBHelper extends SQLiteOpenHelper {
case 11:
addSumColumn( db, REMOTEDEVS );
case 12:
- createTable( db, TABLE_NAME_DICTINFO, s_dictInfoColsAndTypes );
- createTable( db, TABLE_NAME_DICTBROWSE, s_dictBrowseColsAndTypes );
+ createTable( db, TABLE_NAMES.DICTINFO, s_dictInfoColsAndTypes );
+ createTable( db, TABLE_NAMES.DICTBROWSE, s_dictBrowseColsAndTypes );
case 13:
addSumColumn( db, LASTMOVE );
case 14:
@@ -296,8 +305,8 @@ public class DBHelper extends SQLiteOpenHelper {
moveToCurGames( db );
case 16:
addSumColumn( db, VISID );
- setColumnsEqual( db, TABLE_NAME_SUM, VISID, "rowid" );
- makeAutoincrement( db, TABLE_NAME_SUM, s_summaryColsAndTypes );
+ setColumnsEqual( db, TABLE_NAMES.SUM, VISID, "rowid" );
+ makeAutoincrement( db, TABLE_NAMES.SUM, s_summaryColsAndTypes );
madeSumTable = true;
case 17:
if ( !madeSumTable ) {
@@ -336,25 +345,14 @@ public class DBHelper extends SQLiteOpenHelper {
}
case 28:
if ( !madeChatTable ) {
- addColumn( db, TABLE_NAME_CHAT, s_chatsSchema, CHATTIME );
+ addColumn( db, TABLE_NAMES.CHAT, s_chatsSchema, CHATTIME );
}
break;
default:
- db.execSQL( "DROP TABLE " + TABLE_NAME_SUM + ";" );
-
- TableAndVersion[] tav = new TableAndVersion[] {
- new TableAndVersion( TABLE_NAME_OBITS, 5 ),
- new TableAndVersion( TABLE_NAME_DICTINFO, 12 ),
- new TableAndVersion( TABLE_NAME_DICTBROWSE, 12 ),
- new TableAndVersion( TABLE_NAME_GROUPS, 14 ),
- new TableAndVersion( TABLE_NAME_STUDYLIST, 18 ),
- new TableAndVersion( TABLE_NAME_LOC, 20 ),
- new TableAndVersion( TABLE_NAME_PAIRS, 21 ),
- };
- for ( TableAndVersion entry : tav ) {
- if ( oldVersion >= 1 + entry.addedVersion ) {
- db.execSQL( "DROP TABLE " + entry.name + ";" );
+ for ( TABLE_NAMES table : TABLE_NAMES.values() ) {
+ if ( oldVersion >= 1 + table.addedVersion() ) {
+ db.execSQL( "DROP TABLE " + table + ";" );
}
}
onCreate( db );
@@ -363,10 +361,10 @@ public class DBHelper extends SQLiteOpenHelper {
private void addSumColumn( SQLiteDatabase db, String colName )
{
- addColumn( db, TABLE_NAME_SUM, s_summaryColsAndTypes, colName );
+ addColumn( db, TABLE_NAMES.SUM, s_summaryColsAndTypes, colName );
}
- private void addColumn( SQLiteDatabase db, String tableName,
+ private void addColumn( SQLiteDatabase db, TABLE_NAMES tableName,
String[][] colsAndTypes, String colName )
{
String colType = null;
@@ -382,7 +380,7 @@ public class DBHelper extends SQLiteOpenHelper {
db.execSQL( cmd );
}
- private void createTable( SQLiteDatabase db, String name, String[][] data )
+ private void createTable( SQLiteDatabase db, TABLE_NAMES name, String[][] data )
{
StringBuilder query =
new StringBuilder( String.format("CREATE TABLE %s (", name ) );
@@ -404,7 +402,7 @@ public class DBHelper extends SQLiteOpenHelper {
isUpgrade = 0 < countGames( db );
}
- createTable( db, TABLE_NAME_GROUPS, s_groupsSchema );
+ createTable( db, TABLE_NAMES.GROUPS, s_groupsSchema );
// Create an empty group name
ContentValues values = new ContentValues();
@@ -412,50 +410,50 @@ public class DBHelper extends SQLiteOpenHelper {
values.put( GROUPNAME, LocUtils.getString( m_context, false,
R.string.group_cur_games) );
values.put( EXPANDED, 1 );
- long curGroup = db.insert( TABLE_NAME_GROUPS, null, values );
+ long curGroup = insert( db, TABLE_NAMES.GROUPS, 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 );
+ db.update( DBHelper.TABLE_NAMES.SUM.toString(), values, null, null );
}
values = new ContentValues();
values.put( GROUPNAME, LocUtils.getString( m_context, false,
R.string.group_new_games) );
values.put( EXPANDED, 1 );
- long newGroup = db.insert( TABLE_NAME_GROUPS, null, values );
+ long newGroup = insert( db, TABLE_NAMES.GROUPS, values );
XWPrefs.setDefaultNewGameGroup( m_context, newGroup );
}
private void createStudyTable( SQLiteDatabase db )
{
- createTable( db, TABLE_NAME_STUDYLIST, s_studySchema );
+ createTable( db, TABLE_NAMES.STUDYLIST, s_studySchema );
}
private void createLocTable( SQLiteDatabase db )
{
- createTable( db, TABLE_NAME_LOC, s_locSchema );
+ createTable( db, TABLE_NAMES.LOC, s_locSchema );
}
private void createPairsTable( SQLiteDatabase db )
{
- createTable( db, TABLE_NAME_PAIRS, s_pairsSchema );
+ createTable( db, TABLE_NAMES.PAIRS, s_pairsSchema );
}
private void createInvitesTable( SQLiteDatabase db )
{
- createTable( db, TABLE_NAME_INVITES, s_invitesSchema );
+ createTable( db, TABLE_NAMES.INVITES, s_invitesSchema );
}
private void createChatsTable( SQLiteDatabase db )
{
- createTable( db, TABLE_NAME_CHAT, s_chatsSchema );
+ createTable( db, TABLE_NAMES.CHAT, s_chatsSchema );
}
private void createLogsTable( SQLiteDatabase db )
{
- createTable( db, TABLE_NAME_LOGS, s_logsSchema );
+ createTable( db, TABLE_NAMES.LOGS, s_logsSchema );
}
// Move all existing games to the row previously named "cur games'
@@ -465,19 +463,18 @@ public class DBHelper extends SQLiteOpenHelper {
R.string.group_cur_games );
String[] columns = { "rowid" };
String selection = String.format( "%s = '%s'", GROUPNAME, name );
- Cursor cursor = db.query( DBHelper.TABLE_NAME_GROUPS, columns,
- selection, null, null, null, null );
+ Cursor cursor = query( db, TABLE_NAMES.GROUPS, columns, selection );
if ( 1 == cursor.getCount() && cursor.moveToFirst() ) {
long rowid = cursor.getLong( cursor.getColumnIndex("rowid") );
ContentValues values = new ContentValues();
values.put( GROUPID, rowid );
- db.update( DBHelper.TABLE_NAME_SUM, values, null, null );
+ update( db, TABLE_NAMES.SUM, values, null );
}
cursor.close();
}
- private void makeAutoincrement( SQLiteDatabase db, String name,
+ private void makeAutoincrement( SQLiteDatabase db, TABLE_NAMES name,
String[][] data )
{
db.beginTransaction();
@@ -524,7 +521,7 @@ public class DBHelper extends SQLiteOpenHelper {
}
}
- private void setColumnsEqual( SQLiteDatabase db, String table,
+ private void setColumnsEqual( SQLiteDatabase db, TABLE_NAMES table,
String dest, String src )
{
String query = String.format( "UPDATE %s set %s = %s", table,
@@ -532,7 +529,7 @@ public class DBHelper extends SQLiteOpenHelper {
db.execSQL( query );
}
- private void forceRowidHigh( SQLiteDatabase db, String name )
+ private void forceRowidHigh( SQLiteDatabase db, TABLE_NAMES name )
{
long now = Utils.getCurSeconds();
// knock 20 years off; whose clock can be that far back?
@@ -546,7 +543,7 @@ public class DBHelper extends SQLiteOpenHelper {
private int countGames( SQLiteDatabase db )
{
- final String query = "SELECT COUNT(*) FROM " + TABLE_NAME_SUM;
+ final String query = "SELECT COUNT(*) FROM " + TABLE_NAMES.SUM;
Cursor cursor = db.rawQuery( query, null );
cursor.moveToFirst();
@@ -555,7 +552,7 @@ public class DBHelper extends SQLiteOpenHelper {
return result;
}
- private static String[] getColumns( SQLiteDatabase db, String name )
+ private static String[] getColumns( SQLiteDatabase db, TABLE_NAMES name )
{
String query = String.format( "SELECT * FROM %s LIMIT 1", name );
Cursor cursor = db.rawQuery( query, null );
@@ -564,12 +561,27 @@ public class DBHelper extends SQLiteOpenHelper {
return colNames;
}
- private class TableAndVersion {
- public String name;
- public int addedVersion;
- public TableAndVersion( String nn, int vers ) {
- name = nn; addedVersion = vers;
- }
+ static Cursor query( SQLiteDatabase db, TABLE_NAMES table, String[] columns,
+ String selection, String orderBy )
+ {
+ return db.query( table.toString(), columns, selection,
+ null, null, null, orderBy );
}
+ static Cursor query( SQLiteDatabase db, TABLE_NAMES table, String[] columns,
+ String selection )
+ {
+ return query( db, table, columns, selection, null );
+ }
+
+ public static int update( SQLiteDatabase db, TABLE_NAMES table, ContentValues values,
+ String selection )
+ {
+ return db.update( table.toString(), values, selection, null );
+ }
+
+ static long insert( SQLiteDatabase db, TABLE_NAMES table, ContentValues values )
+ {
+ return db.insert( table.toString(), null, values );
+ }
}
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBUtils.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBUtils.java
index 5997f73a5..65f6a5e18 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBUtils.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBUtils.java
@@ -33,7 +33,7 @@ import android.graphics.BitmapFactory;
import android.os.Environment;
import android.text.TextUtils;
-
+import org.eehouse.android.xw4.DBHelper.TABLE_NAMES;
import org.eehouse.android.xw4.DictUtils.DictLoc;
import org.eehouse.android.xw4.DlgDelegate.DlgClickNotify.InviteMeans;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
@@ -151,8 +151,7 @@ public class DBUtils {
String selection = String.format( ROW_ID_FMT, lock.getRowid() );
synchronized( s_dbHelper ) {
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_SUM, columns,
- selection, null, null, null, null );
+ Cursor cursor = query( TABLE_NAMES.SUM, columns, selection );
if ( 1 == cursor.getCount() && cursor.moveToFirst() ) {
summary = new GameSummary();
summary.nMoves = cursor
@@ -350,10 +349,9 @@ public class DBUtils {
synchronized( s_dbHelper ) {
if ( null == summary ) {
- s_db.delete( DBHelper.TABLE_NAME_SUM, selection, null );
+ delete( TABLE_NAMES.SUM, selection );
} else {
- long result = s_db.update( DBHelper.TABLE_NAME_SUM,
- values, selection, null );
+ long result = update( TABLE_NAMES.SUM, values, selection );
Assert.assertTrue( result >= 0 );
}
notifyListeners( rowid, GameChangeType.GAME_CHANGED );
@@ -404,8 +402,7 @@ public class DBUtils {
String[] columns = { DBHelper.DICTLANG };
initDB( context );
synchronized( s_dbHelper ) {
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_SUM, columns,
- selection, null, null, null, null );
+ Cursor cursor = query( TABLE_NAMES.SUM, columns, selection );
result = cursor.getCount();
cursor.close();
@@ -425,8 +422,7 @@ public class DBUtils {
String[] columns = { DBHelper.DICTLANG };
initDB( context );
synchronized( s_dbHelper ) {
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_SUM, columns,
- selection, null, null, null, null );
+ Cursor cursor = query( TABLE_NAMES.SUM, columns, selection );
result = cursor.getCount();
cursor.close();
}
@@ -442,8 +438,7 @@ public class DBUtils {
initDB( context );
synchronized( s_dbHelper ) {
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_SUM, columns,
- selection, null, null, null, null );
+ Cursor cursor = query( TABLE_NAMES.SUM, columns, selection );
int indx = cursor.getColumnIndex( DBHelper.CONTYPE );
while ( cursor.moveToNext() ) {
CommsConnTypeSet typs = new CommsConnTypeSet( cursor.getInt(indx) );
@@ -622,8 +617,7 @@ public class DBUtils {
String orderBy = DBHelper.TIMESTAMP + " DESC";
synchronized( s_dbHelper ) {
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_INVITES, columns,
- selection, null, null, null, orderBy );
+ Cursor cursor = DBHelper.query( s_db, TABLE_NAMES.INVITES, columns, selection, orderBy );
if ( 0 < cursor.getCount() ) {
int indxMns = cursor.getColumnIndex( DBHelper.MEANS );
int indxTS = cursor.getColumnIndex( DBHelper.TIMESTAMP );
@@ -654,7 +648,7 @@ public class DBUtils {
initDB( context );
synchronized( s_dbHelper ) {
- s_db.insert( DBHelper.TABLE_NAME_INVITES, null, values );
+ insert( TABLE_NAMES.INVITES, values );
}
}
@@ -663,7 +657,7 @@ public class DBUtils {
{
ContentValues values = new ContentValues();
values.put( column, value );
- updateRow( null, DBHelper.TABLE_NAME_SUM, rowid, values );
+ updateRow( null, TABLE_NAMES.SUM, rowid, values );
}
public static void setMsgFlags( long rowid, int flags )
@@ -685,8 +679,7 @@ public class DBUtils {
String[] columns = { column };
initDB( context );
synchronized( s_dbHelper ) {
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_SUM, columns,
- selection, null, null, null, null );
+ Cursor cursor = query( TABLE_NAMES.SUM, columns, selection );
if ( 1 == cursor.getCount() && cursor.moveToFirst() ) {
result =
cursor.getInt( cursor.getColumnIndex(column));
@@ -730,8 +723,7 @@ public class DBUtils {
initDB( context );
synchronized( s_dbHelper ) {
- long result = s_db.update( DBHelper.TABLE_NAME_SUM,
- values, selection, null );
+ long result = update( TABLE_NAMES.SUM, values, selection );
Assert.assertTrue( result >= 0 );
@@ -745,8 +737,7 @@ public class DBUtils {
values.putNull( DBHelper.THUMBNAIL );
initDB( context );
synchronized( s_dbHelper ) {
- long result = s_db.update( DBHelper.TABLE_NAME_SUM,
- values, null, null );
+ long result = update( TABLE_NAMES.SUM, values, null );
notifyListeners( ROWIDS_ALL, GameChangeType.GAME_CHANGED );
}
@@ -759,8 +750,7 @@ public class DBUtils {
String selection = String.format( ROW_ID_FMT, rowid );
initDB( context );
synchronized( s_dbHelper ) {
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_SUM, columns,
- selection, null, null, null, null );
+ Cursor cursor = query( TABLE_NAMES.SUM, columns, selection );
if ( 1 == cursor.getCount() && cursor.moveToFirst() ) {
result =
cursor.getString( cursor.getColumnIndex(DBHelper.RELAYID) );
@@ -779,8 +769,7 @@ public class DBUtils {
DBHelper.GROUPID, getArchiveGroup( context ) );
initDB( context );
synchronized( s_dbHelper ) {
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_SUM, columns,
- selection, null, null, null, null );
+ Cursor cursor = query( TABLE_NAMES.SUM, columns, selection );
int indx1 = cursor.getColumnIndex( ROW_ID );
int indx2 = cursor.getColumnIndex( DBHelper.CONTYPE );
for ( int ii = 0; cursor.moveToNext(); ++ii ) {
@@ -802,8 +791,7 @@ public class DBUtils {
String selection = String.format( "%s = 0", DBHelper.GAME_OVER );
initDB( context );
synchronized( s_dbHelper ) {
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_SUM, columns,
- selection, null, null, null, null );
+ Cursor cursor = query( TABLE_NAMES.SUM, columns, selection );
int indx = cursor.getColumnIndex( DBHelper.CONTYPE );
while ( cursor.moveToNext() ) {
CommsConnTypeSet typs = new CommsConnTypeSet( cursor.getInt(indx) );
@@ -823,8 +811,7 @@ public class DBUtils {
String selection = DBHelper.RELAYID + "='" + relayID + "'";
initDB( context );
synchronized( s_dbHelper ) {
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_SUM, columns,
- selection, null, null, null, null );
+ Cursor cursor = query( TABLE_NAMES.SUM, columns, selection );
result = new long[cursor.getCount()];
for ( int ii = 0; cursor.moveToNext(); ++ii ) {
result[ii] = cursor.getLong( cursor.getColumnIndex(ROW_ID) );
@@ -841,8 +828,7 @@ public class DBUtils {
String selection = String.format( DBHelper.GAMEID + "=%d", gameID );
initDB( context );
synchronized( s_dbHelper ) {
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_SUM, columns,
- selection, null, null, null, null );
+ Cursor cursor = query( TABLE_NAMES.SUM, columns, selection );
result = new long[cursor.getCount()];
for ( int ii = 0; cursor.moveToNext(); ++ii ) {
result[ii] = cursor.getLong( cursor.getColumnIndex(ROW_ID) );
@@ -869,8 +855,7 @@ public class DBUtils {
String selection = String.format( ROW_ID + "=%d", rowid );
initDB( context );
synchronized( s_dbHelper ) {
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_SUM, columns,
- selection, null, null, null, null );
+ Cursor cursor = query( TABLE_NAMES.SUM, columns, selection );
Assert.assertTrue( 1 >= cursor.getCount() );
result = 1 == cursor.getCount();
cursor.close();
@@ -888,8 +873,7 @@ public class DBUtils {
new HashMap >();
initDB( context );
synchronized( s_dbHelper ) {
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_SUM, columns,
- selection, null, null, null, null );
+ Cursor cursor = query( TABLE_NAMES.SUM, columns, selection );
while ( cursor.moveToNext() ) {
int col = cursor.getColumnIndex( DBHelper.GAMEID );
int gameID = cursor.getInt( col );
@@ -936,9 +920,7 @@ public class DBUtils {
initDB( context );
synchronized( s_dbHelper ) {
-
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_SUM, columns,
- selection, null, null, null, null );
+ Cursor cursor = query( TABLE_NAMES.SUM, columns, selection );
if ( cursor.moveToNext() ) {
int indx = cursor.getColumnIndex( columns[0] );
result = new Date( cursor.getLong( indx ) );
@@ -958,9 +940,7 @@ public class DBUtils {
initDB( context );
synchronized( s_dbHelper ) {
-
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_SUM, columns,
- selection, null, null, null, null );
+ Cursor cursor = query( TABLE_NAMES.SUM, columns, selection );
int count = cursor.getCount();
if ( 0 < count ) {
result = new String[count];
@@ -1002,7 +982,7 @@ public class DBUtils {
synchronized( s_dbHelper ) {
try {
- long result = s_db.replaceOrThrow( DBHelper.TABLE_NAME_OBITS,
+ long result = s_db.replaceOrThrow( TABLE_NAMES.OBITS.toString(),
"", values );
} catch ( Exception ex ) {
Log.ex( TAG, ex );
@@ -1018,8 +998,7 @@ public class DBUtils {
initDB( context );
synchronized( s_dbHelper ) {
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_OBITS, columns,
- null, null, null, null, null );
+ Cursor cursor = query( TABLE_NAMES.OBITS, columns, null );
if ( 0 < cursor.getCount() ) {
int idIndex = cursor.getColumnIndex( DBHelper.RELAYID );
int seedIndex = cursor.getColumnIndex( DBHelper.SEED );
@@ -1050,7 +1029,7 @@ public class DBUtils {
for ( Obit obit: obits ) {
String selection = String.format( fmt, obit.m_relayID,
obit.m_seed );
- s_db.delete( DBHelper.TABLE_NAME_OBITS, selection, null );
+ delete( TABLE_NAMES.OBITS, selection );
}
}
}
@@ -1078,7 +1057,7 @@ public class DBUtils {
synchronized( s_dbHelper ) {
values.put( DBHelper.VISID, maxVISID( s_db ) );
- long rowid = s_db.insert( DBHelper.TABLE_NAME_SUM, null, values );
+ long rowid = insert( TABLE_NAMES.SUM, values );
setCached( rowid, null ); // force reread
@@ -1106,7 +1085,7 @@ public class DBUtils {
}
values.put( DBHelper.LASTPLAY_TIME, timestamp );
- updateRow( context, DBHelper.TABLE_NAME_SUM, rowid, values );
+ updateRow( context, TABLE_NAMES.SUM, rowid, values );
setCached( rowid, null ); // force reread
if ( ROWID_NOTFOUND != rowid ) { // Means new game?
@@ -1126,9 +1105,7 @@ public class DBUtils {
String selection = String.format( ROW_ID_FMT, rowid );
initDB( context );
synchronized( s_dbHelper ) {
-
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_SUM, columns,
- selection, null, null, null, null );
+ Cursor cursor = query( TABLE_NAMES.SUM, columns, selection );
if ( 1 == cursor.getCount() && cursor.moveToFirst() ) {
result = cursor.getBlob( cursor
.getColumnIndex(DBHelper.SNAPSHOT));
@@ -1163,13 +1140,13 @@ public class DBUtils {
initDB( context );
synchronized( s_dbHelper ) {
- s_db.delete( DBHelper.TABLE_NAME_SUM, selSummaries, null );
+ delete( TABLE_NAMES.SUM, selSummaries );
// Delete invitations too
- s_db.delete( DBHelper.TABLE_NAME_INVITES, selInvites, null );
+ delete( TABLE_NAMES.INVITES, selInvites );
// Delete chats too -- same sel as for invites
- s_db.delete( DBHelper.TABLE_NAME_CHAT, selInvites, null );
+ delete( TABLE_NAMES.CHAT, selInvites );
deleteCurChatsSync( s_db, rowid );
@@ -1185,9 +1162,7 @@ public class DBUtils {
String selection = String.format( ROW_ID_FMT, rowid );
initDB( context );
synchronized( s_dbHelper ) {
-
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_SUM, columns,
- selection, null, null, null, null );
+ Cursor cursor = query( TABLE_NAMES.SUM, columns, selection );
if ( 1 == cursor.getCount() && cursor.moveToFirst() ) {
result = cursor.getInt( cursor
.getColumnIndex(DBHelper.VISID));
@@ -1206,9 +1181,7 @@ public class DBUtils {
String selection = String.format( ROW_ID_FMT, rowid );
initDB( context );
synchronized( s_dbHelper ) {
-
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_SUM, columns,
- selection, null, null, null, null );
+ Cursor cursor = query( TABLE_NAMES.SUM, columns, selection );
if ( 1 == cursor.getCount() && cursor.moveToFirst() ) {
result = cursor.getString( cursor
.getColumnIndex(DBHelper.GAME_NAME));
@@ -1223,7 +1196,7 @@ public class DBUtils {
{
ContentValues values = new ContentValues();
values.put( DBHelper.GAME_NAME, name );
- updateRow( context, DBHelper.TABLE_NAME_SUM, rowid, values );
+ updateRow( context, TABLE_NAMES.SUM, rowid, values );
}
private static HistoryPair[] convertChatString( Context context, long rowid,
@@ -1291,8 +1264,7 @@ public class DBUtils {
String selection = String.format( "%s=%d", DBHelper.ROW, rowid );
initDB( context );
synchronized( s_dbHelper ) {
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_CHAT, columns,
- selection, null, null, null, null );
+ Cursor cursor = query( TABLE_NAMES.CHAT, columns, selection );
if ( 0 < cursor.getCount() ) {
result = new HistoryPair[cursor.getCount()];
int msgIndex = cursor.getColumnIndex( DBHelper.MESSAGE );
@@ -1386,8 +1358,7 @@ public class DBUtils {
DBHelper.NEXTNAG, now );
initDB( context );
synchronized( s_dbHelper ) {
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_SUM, columns,
- selection, null, null, null, null );
+ Cursor cursor = query( TABLE_NAMES.SUM, columns, selection );
int count = cursor.getCount();
if ( 0 < count ) {
result = new NeedsNagInfo[count];
@@ -1417,8 +1388,7 @@ public class DBUtils {
String selection = "NOT " + DBHelper.NEXTNAG + "= 0";
initDB( context );
synchronized( s_dbHelper ) {
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_SUM, columns,
- selection, null, null, null, null );
+ Cursor cursor = query( TABLE_NAMES.SUM, columns, selection );
if ( cursor.moveToNext() ) {
result = cursor.getLong( cursor.getColumnIndex( "min" ) );
}
@@ -1431,7 +1401,7 @@ public class DBUtils {
{
String updateQuery = "update %s set %s = ? "
+ " WHERE %s = ? ";
- updateQuery = String.format( updateQuery, DBHelper.TABLE_NAME_SUM,
+ updateQuery = String.format( updateQuery, DBHelper.TABLE_NAMES.SUM,
DBHelper.NEXTNAG, ROW_ID );
initDB( context );
@@ -1484,8 +1454,7 @@ public class DBUtils {
initDB( context );
synchronized( s_dbHelper ) {
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_SUM, columns,
- selection, null, null, null, null );
+ Cursor cursor = query( TABLE_NAMES.SUM, columns, selection );
if ( 1 == cursor.getCount() && cursor.moveToFirst() ) {
data = cursor.getBlob( cursor.
getColumnIndex(DBHelper.THUMBNAIL));
@@ -1504,7 +1473,7 @@ public class DBUtils {
HashMap result = new HashMap();
String query = "SELECT %s, count(%s) as cnt FROM %s GROUP BY %s";
query = String.format( query, DBHelper.GROUPID, DBHelper.GROUPID,
- DBHelper.TABLE_NAME_SUM, DBHelper.GROUPID );
+ DBHelper.TABLE_NAMES.SUM, DBHelper.GROUPID );
Cursor cursor = db.rawQuery( query, null );
int rowIndex = cursor.getColumnIndex( DBHelper.GROUPID );
@@ -1573,13 +1542,8 @@ public class DBUtils {
DBHelper.TURN };
String orderBy = DBHelper.LASTMOVE;
String selection = String.format( "%s=%d", DBHelper.GROUPID, groupID );
- Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns,
- selection,
- null, // args
- null, // groupBy,
- null, // having
- orderBy
- );
+ Cursor cursor = DBHelper.query( db, TABLE_NAMES.SUM, columns,
+ selection, orderBy );
// We want the earliest LASTPLAY_TIME (i.e. the first we see
// since they're in order) that's a local turn, if any,
@@ -1625,13 +1589,7 @@ public class DBUtils {
String[] columns = { ROW_ID };
initDB( context );
synchronized( s_dbHelper ) {
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_SUM, columns,
- null, // selection
- null, // args
- null, // groupBy
- null, // having
- null
- );
+ Cursor cursor = query( TABLE_NAMES.SUM, columns, null );
result = cursor.getCount();
cursor.close();
}
@@ -1660,7 +1618,7 @@ public class DBUtils {
String[] columns = { ROW_ID, DBHelper.HASMSGS };
String selection = String.format( "%s=%d", DBHelper.GROUPID, groupID );
synchronized( s_dbHelper ) {
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_SUM, columns,
+ Cursor cursor = s_db.query( TABLE_NAMES.SUM.toString(), columns,
selection, // selection
null, // args
null, // groupBy
@@ -1695,13 +1653,7 @@ public class DBUtils {
}
synchronized( s_dbHelper ) {
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_SUM, columns,
- selection, // selection
- null, // args
- null, // groupBy
- null, // having
- null //orderby
- );
+ Cursor cursor = query( TABLE_NAMES.SUM, columns, selection );
if ( cursor.moveToNext() ) {
int index = cursor.getColumnIndex( DBHelper.GROUPID );
result = cursor.getLong( index );
@@ -1732,7 +1684,7 @@ public class DBUtils {
initDB( context );
synchronized( s_dbHelper ) {
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_GROUPS, columns,
+ Cursor cursor = s_db.query( TABLE_NAMES.GROUPS.toString(), columns,
selection, selArgs,
null, // groupBy
null, // having
@@ -1758,8 +1710,7 @@ public class DBUtils {
// initDB( context ); <- getGroups will have called this
synchronized( s_dbHelper ) {
- rowid = s_db.insert( DBHelper.TABLE_NAME_GROUPS, null,
- values );
+ rowid = insert( TABLE_NAMES.GROUPS, values );
}
invalGroupsCache();
}
@@ -1778,9 +1729,8 @@ public class DBUtils {
initDB( context );
synchronized( s_dbHelper ) {
- s_db.delete( DBHelper.TABLE_NAME_SUM, selectionGames, null );
- s_db.delete( DBHelper.TABLE_NAME_GROUPS, selectionGroups, null );
-
+ delete( TABLE_NAMES.SUM, selectionGames );
+ delete( TABLE_NAMES.GROUPS, selectionGroups );
}
invalGroupsCache();
}
@@ -1790,7 +1740,7 @@ public class DBUtils {
{
ContentValues values = new ContentValues();
values.put( DBHelper.GROUPNAME, name );
- updateRow( context, DBHelper.TABLE_NAME_GROUPS, groupid, values );
+ updateRow( context, TABLE_NAMES.GROUPS, groupid, values );
invalGroupsCache();
}
@@ -1799,7 +1749,7 @@ public class DBUtils {
{
ContentValues values = new ContentValues();
values.put( DBHelper.EXPANDED, expanded? 1 : 0 );
- updateRow( context, DBHelper.TABLE_NAME_GROUPS, groupid, values );
+ updateRow( context, TABLE_NAMES.GROUPS, groupid, values );
invalGroupsCache();
}
@@ -1820,7 +1770,7 @@ public class DBUtils {
Assert.assertTrue( GROUPID_UNSPEC != groupID );
ContentValues values = new ContentValues();
values.put( DBHelper.GROUPID, groupID );
- updateRow( context, DBHelper.TABLE_NAME_SUM, rowid, values );
+ updateRow( context, TABLE_NAMES.SUM, rowid, values );
invalGroupsCache();
notifyListeners( rowid, GameChangeType.GAME_MOVED );
}
@@ -1832,9 +1782,7 @@ public class DBUtils {
String selection = String.format( ROW_ID_FMT, rowid );
initDB( context );
synchronized( s_dbHelper ) {
-
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_SUM, columns,
- selection, null, null, null, null );
+ Cursor cursor = query( TABLE_NAMES.SUM, columns, selection );
if ( 1 == cursor.getCount() && cursor.moveToFirst() ) {
result =
cursor.getString( cursor
@@ -1852,7 +1800,7 @@ public class DBUtils {
initDB( context );
synchronized( s_dbHelper ) {
for ( ContentValues values : valuess ) {
- s_db.insert( DBHelper.TABLE_NAME_CHAT, null, values );
+ insert( TABLE_NAMES.CHAT, values );
}
}
}
@@ -1885,15 +1833,14 @@ public class DBUtils {
String selection = String.format( "%s = %d", DBHelper.ROW, rowid );
initDB( context );
synchronized( s_dbHelper ) {
- s_db.delete( DBHelper.TABLE_NAME_CHAT, selection, null );
+ delete( TABLE_NAMES.CHAT, selection );
// for now, remove any old-format history too. Later when it's
// removed once converted (after that process is completely
// debugged), this can be removed.
ContentValues values = new ContentValues();
values.putNull( DBHelper.CHAT_HISTORY );
- updateRowImpl( s_db, DBHelper.TABLE_NAME_SUM, rowid, values );
-
+ updateRowImpl( s_db, TABLE_NAMES.SUM, rowid, values );
}
}
@@ -1977,8 +1924,7 @@ public class DBUtils {
name, DBHelper.LOC, loc.ordinal() );
initDB( context );
synchronized( s_dbHelper ) {
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_DICTBROWSE, columns,
- selection, null, null, null, null );
+ Cursor cursor = query( TABLE_NAMES.DICTBROWSE, columns, selection );
if ( 1 >= cursor.getCount() && cursor.moveToFirst() ) {
result = new DictBrowseState();
result.m_pos = cursor.getInt( cursor
@@ -2033,12 +1979,11 @@ public class DBUtils {
initDB( context );
synchronized( s_dbHelper ) {
- int result = s_db.update( DBHelper.TABLE_NAME_DICTBROWSE,
- values, selection, null );
+ int result = update( TABLE_NAMES.DICTBROWSE, values, selection );
if ( 0 == result ) {
values.put( DBHelper.DICTNAME, name );
values.put( DBHelper.LOC, loc.ordinal() );
- s_db.insert( DBHelper.TABLE_NAME_DICTBROWSE, null, values );
+ insert( TABLE_NAMES.DICTBROWSE, values );
}
}
}
@@ -2059,11 +2004,10 @@ public class DBUtils {
values.put( DBHelper.MD5SUM, sum );
initDB( context );
synchronized( s_dbHelper ) {
- int result = s_db.update( DBHelper.TABLE_NAME_DICTINFO,
- values, selection, null );
+ int result = update( TABLE_NAMES.DICTINFO, values, selection );
if ( 0 == result ) {
values.put( DBHelper.DICTNAME, name );
- long rowid = s_db.insert( DBHelper.TABLE_NAME_DICTINFO, null, values );
+ long rowid = insert( TABLE_NAMES.DICTINFO, values );
Assert.assertTrue( rowid > 0 || !BuildConfig.DEBUG );
}
}
@@ -2079,8 +2023,7 @@ public class DBUtils {
String selection = String.format( NAME_FMT, DBHelper.DICTNAME, name );
initDB( context );
synchronized( s_dbHelper ) {
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_DICTINFO, columns,
- selection, null, null, null, null );
+ Cursor cursor = query( TABLE_NAMES.DICTINFO, columns, selection );
if ( 1 == cursor.getCount() && cursor.moveToFirst() ) {
result = new DictInfo();
result.name = name;
@@ -2113,12 +2056,10 @@ public class DBUtils {
initDB( context );
synchronized( s_dbHelper ) {
- int result = s_db.update( DBHelper.TABLE_NAME_DICTINFO,
- values, selection, null );
+ int result = update( TABLE_NAMES.DICTINFO, values, selection );
if ( 0 == result ) {
values.put( DBHelper.DICTNAME, dal.name );
- long rowid = s_db.insert( DBHelper.TABLE_NAME_DICTINFO, null,
- values );
+ long rowid = insert( TABLE_NAMES.DICTINFO, values );
Assert.assertTrue( rowid > 0 || !BuildConfig.DEBUG );
}
}
@@ -2135,8 +2076,8 @@ public class DBUtils {
initDB( context );
synchronized( s_dbHelper ) {
- s_db.update( DBHelper.TABLE_NAME_DICTINFO, values, selection, null );
- s_db.update( DBHelper.TABLE_NAME_DICTBROWSE, values, selection, null );
+ update( TABLE_NAMES.DICTINFO, values, selection );
+ update( TABLE_NAMES.DICTBROWSE, values, selection );
}
}
@@ -2147,9 +2088,9 @@ public class DBUtils {
initDB( context );
synchronized( s_dbHelper ) {
- int removed = s_db.delete( DBHelper.TABLE_NAME_DICTINFO, selection, args );
+ int removed = delete( TABLE_NAMES.DICTINFO, selection, args );
// Log.d( TAG, "removed %d rows from %s", removed, DBHelper.TABLE_NAME_DICTINFO );
- removed = s_db.delete( DBHelper.TABLE_NAME_DICTBROWSE, selection, args );
+ removed = delete( TABLE_NAMES.DICTBROWSE, selection, args );
// Log.d( TAG, "removed %d rows from %s", removed, DBHelper.TABLE_NAME_DICTBROWSE );
}
}
@@ -2176,7 +2117,7 @@ public class DBUtils {
initDB( context );
synchronized( s_dbHelper ) {
- s_db.insert( DBHelper.TABLE_NAME_STUDYLIST, null, values );
+ insert( TABLE_NAMES.STUDYLIST, values );
}
notifyStudyListListeners( word, lang );
}
@@ -2190,7 +2131,7 @@ public class DBUtils {
initDB( context );
synchronized( s_dbHelper ) {
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_STUDYLIST, columns,
+ Cursor cursor = s_db.query( TABLE_NAMES.STUDYLIST.toString(), columns,
null, null, groupBy, null, null );
int count = cursor.getCount();
result = new int[count];
@@ -2215,8 +2156,8 @@ public class DBUtils {
initDB( context );
synchronized( s_dbHelper ) {
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_STUDYLIST, columns,
- selection, null, null, null, orderBy );
+ Cursor cursor = DBHelper.query( s_db, TABLE_NAMES.STUDYLIST, columns,
+ selection, orderBy );
int count = cursor.getCount();
result = new String[count];
if ( 0 < count ) {
@@ -2241,7 +2182,7 @@ public class DBUtils {
initDB( context );
synchronized( s_dbHelper ) {
- s_db.delete( DBHelper.TABLE_NAME_STUDYLIST, selection, null );
+ delete( TABLE_NAMES.STUDYLIST, selection );
}
}
@@ -2259,13 +2200,13 @@ public class DBUtils {
String insertQuery = "insert into %s (%s, %s, %s, %s) "
+ " VALUES (?, ?, ?, ?)";
- insertQuery = String.format( insertQuery, DBHelper.TABLE_NAME_LOC,
+ insertQuery = String.format( insertQuery, TABLE_NAMES.LOC,
DBHelper.KEY, DBHelper.LOCALE,
DBHelper.BLESSED, DBHelper.XLATION );
String updateQuery = "update %s set %s = ? "
+ " WHERE %s = ? and %s = ? and %s = ?";
- updateQuery = String.format( updateQuery, DBHelper.TABLE_NAME_LOC,
+ updateQuery = String.format( updateQuery, TABLE_NAMES.LOC,
DBHelper.XLATION, DBHelper.KEY,
DBHelper.LOCALE, DBHelper.BLESSED );
@@ -2319,8 +2260,7 @@ public class DBUtils {
initDB( context );
synchronized( s_dbHelper ) {
- Cursor cursor = s_db.query( DBHelper.TABLE_NAME_LOC, columns,
- selection, null, null, null, null );
+ Cursor cursor = query( TABLE_NAMES.LOC, columns, selection );
int keyIndex = cursor.getColumnIndex( DBHelper.KEY );
int valueIndex = cursor.getColumnIndex( DBHelper.XLATION );
int blessedIndex = cursor.getColumnIndex( DBHelper.BLESSED );
@@ -2345,7 +2285,7 @@ public class DBUtils {
initDB( context );
synchronized( s_dbHelper ) {
- s_db.delete( DBHelper.TABLE_NAME_LOC, selection, null );
+ delete( TABLE_NAMES.LOC, selection );
}
}
@@ -2355,18 +2295,17 @@ public class DBUtils {
ContentValues values = new ContentValues();
values.put( DBHelper.VALUE, value );
- long result = db.update( DBHelper.TABLE_NAME_PAIRS,
- values, selection, null );
+ long result = DBHelper.update( db, TABLE_NAMES.PAIRS, values, selection );
if ( 0 == result ) {
values.put( DBHelper.KEY, key );
- db.insert( DBHelper.TABLE_NAME_PAIRS, null, values );
+ DBHelper.insert( db, TABLE_NAMES.PAIRS, values );
}
}
private static void delStringsLikeSync( SQLiteDatabase db, String like )
{
String selection = String.format( "%s LIKE '%s'", DBHelper.KEY, like );
- db.delete( DBHelper.TABLE_NAME_PAIRS, selection, null );
+ delete( db, TABLE_NAMES.PAIRS, selection, null );
}
private static String getStringForSync( SQLiteDatabase db, String key, String dflt )
@@ -2374,8 +2313,7 @@ public class DBUtils {
String selection = String.format( "%s = '%s'", DBHelper.KEY, key );
String[] columns = { DBHelper.VALUE };
- Cursor cursor = db.query( DBHelper.TABLE_NAME_PAIRS, columns,
- selection, null, null, null, null );
+ Cursor cursor = DBHelper.query( db, TABLE_NAMES.PAIRS, columns, selection );
Assert.assertTrue( 1 >= cursor.getCount() );
int indx = cursor.getColumnIndex( DBHelper.VALUE );
if ( cursor.moveToNext() ) {
@@ -2536,13 +2474,13 @@ public class DBUtils {
initDB( context );
synchronized( s_dbHelper ) {
- long rowid = s_db.insert( DBHelper.TABLE_NAME_LOGS, null, values );
+ long rowid = insert( TABLE_NAMES.LOGS, values );
if ( 0 == (rowid % (LOGLIMIT / 10)) ) {
String where =
String.format( "not rowid in (select rowid from %s order by TIMESTAMP desc limit %d)",
- DBHelper.TABLE_NAME_LOGS, LOGLIMIT );
- int nGone = s_db.delete( DBHelper.TABLE_NAME_LOGS, where, null );
+ TABLE_NAMES.LOGS, LOGLIMIT );
+ int nGone = delete( TABLE_NAMES.LOGS, where );
Log.i( TAG, "appendLog(): deleted %d rows", nGone );
}
}
@@ -2587,7 +2525,7 @@ public class DBUtils {
// {
// ContentValues values = new ContentValues();
// values.putNull( DBHelper.CHAT_HISTORY );
- // updateRow( context, DBHelper.TABLE_NAME_SUM, rowid, values );
+ // updateRow( context, DBHelper.TABLE_NAMES.SUM, rowid, values );
// }
private static void initDB( Context context )
@@ -2603,15 +2541,15 @@ public class DBUtils {
}
}
- private static int updateRowImpl( SQLiteDatabase db, String table,
+ private static int updateRowImpl( SQLiteDatabase db, TABLE_NAMES table,
long rowid, ContentValues values )
{
String selection = String.format( ROW_ID_FMT, rowid );
- return db.update( table, values, selection, null );
+ return DBHelper.update( db, table, values, selection );
}
- private static void updateRow( Context context, String table,
+ private static void updateRow( Context context, TABLE_NAMES table,
long rowid, ContentValues values )
{
initDB( context );
@@ -2627,7 +2565,7 @@ public class DBUtils {
{
int result = 1;
String query = String.format( "SELECT max(%s) FROM %s", DBHelper.VISID,
- DBHelper.TABLE_NAME_SUM );
+ TABLE_NAMES.SUM );
Cursor cursor = null;
try {
cursor = db.rawQuery( query, null );
@@ -2676,4 +2614,39 @@ public class DBUtils {
s_cachedRowID = rowid;
s_cachedBytes = bytes;
}
+
+ private static Cursor query( TABLE_NAMES table, String[] columns, String selection )
+ {
+ return DBHelper.query( s_db, table, columns, selection );
+ }
+
+ private static int delete( SQLiteDatabase db, TABLE_NAMES table, String selection, String[] args )
+ {
+ return db.delete( table.toString(), selection, args );
+ }
+
+ private static int delete( SQLiteDatabase db, TABLE_NAMES table, String selection )
+ {
+ return delete( db, table, selection, null );
+ }
+
+ private static int delete( TABLE_NAMES table, String selection )
+ {
+ return delete( s_db, table, selection, null );
+ }
+
+ private static int delete( TABLE_NAMES table, String selection, String[] args )
+ {
+ return delete( s_db, table, selection, args );
+ }
+
+ private static int update( TABLE_NAMES table, ContentValues values, String selection )
+ {
+ return DBHelper.update( s_db, table, values, selection );
+ }
+
+ private static long insert( TABLE_NAMES table, ContentValues values )
+ {
+ return DBHelper.insert( s_db, table, values );
+ }
}
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DbgUtils.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DbgUtils.java
index ba0c50222..e4fcc33ff 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DbgUtils.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DbgUtils.java
@@ -158,14 +158,18 @@ public class DbgUtils {
// return TextUtils.join( ", ", asStrs );
// }
- // public static String hexDump( byte[] bytes )
- // {
- // StringBuilder dump = new StringBuilder();
- // for ( byte byt : bytes ) {
- // dump.append( String.format( "%02x ", byt ) );
- // }
- // return dump.toString();
- // }
+ public static String hexDump( byte[] bytes )
+ {
+ String result = "";
+ if ( null != bytes ) {
+ StringBuilder dump = new StringBuilder();
+ for ( byte byt : bytes ) {
+ dump.append( String.format( "%02x ", byt ) );
+ }
+ result = dump.toString();
+ }
+ return result;
+ }
private static List sLockHolders = new ArrayList<>();
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DevID.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DevID.java
index 2778b3187..32cfdd7a5 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DevID.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DevID.java
@@ -37,6 +37,7 @@ public class DevID {
private static final String DEVID_KEY = "DevID.devid_key";
private static final String DEVID_ACK_KEY = "key_relay_regid_ackd2";
private static final String FCM_REGVERS_KEY = "key_fcmvers_regid";
+ private static final String NFC_DEVID_KEY = "key_nfc_devid";
private static String s_relayDevID;
private static int s_asInt;
@@ -118,31 +119,21 @@ public class DevID {
DBUtils.setBoolFor( context, DEVID_ACK_KEY, false );
}
- public static String getGCMDevID( Context context )
+ // Just a random number I hang onto as long as possible
+ private static int[] sNFCDevID = {0};
+ public static int getNFCDevID( Context context )
{
- return "";
- // int curVers = Utils.getAppVersion( context );
- // int storedVers = DBUtils.getIntFor( context, GCM_REGVERS_KEY, 0 );
- // // TRANSITIONAL
- // if ( 0 == storedVers ) {
- // storedVers = XWPrefs.getPrefsInt( context,
- // R.string.key_gcmvers_regid, 0 );
- // if ( 0 != storedVers ) {
- // DBUtils.setIntFor( context, GCM_REGVERS_KEY, storedVers );
- // }
- // }
-
- // String result;
- // if ( 0 != storedVers && storedVers < curVers ) {
- // result = ""; // Don't trust what registrar has
- // } else {
- // result = GCMStub.getRegistrationId( context );
- // }
- // return result;
- }
-
- public static void clearGCMDevID( Context context )
- {
- DBUtils.setBoolFor( context, DEVID_ACK_KEY, false );
+ synchronized ( sNFCDevID ) {
+ if ( 0 == sNFCDevID[0] ) {
+ int devid = DBUtils.getIntFor( context, NFC_DEVID_KEY, 0 );
+ while ( 0 == devid ) {
+ devid = Utils.nextRandomInt();
+ DBUtils.setIntFor( context, NFC_DEVID_KEY, devid );
+ }
+ sNFCDevID[0] = devid;
+ }
+ Log.d( TAG, "getNFCDevID() => %d", sNFCDevID[0] );
+ return sNFCDevID[0];
+ }
}
}
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DlgDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DlgDelegate.java
index 35299e55d..80014cf7f 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DlgDelegate.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DlgDelegate.java
@@ -76,7 +76,6 @@ public class DlgDelegate {
VALUES_ACTION,
SMS_CONFIG_ACTION,
BUTTON_BROWSEALL_ACTION,
- NFC_TO_SELF,
DROP_RELAY_ACTION,
DROP_SMS_ACTION,
INVITE_SMS_DATA,
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameConfigDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameConfigDelegate.java
index 235d18e5a..33be78a13 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameConfigDelegate.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameConfigDelegate.java
@@ -1246,19 +1246,12 @@ public class GameConfigDelegate extends DelegateBase
if ( null != m_jniThread ) {
applyChanges( m_jniThread.getLock(), forceNew );
} else {
- try ( GameLock lock = GameLock.tryLock( m_rowid ) ) {
+ try ( GameLock lock = GameLock.lock( m_rowid, 100L ) ) {
applyChanges( lock, forceNew );
+ } catch ( GameLock.GameLockedException gle ) {
+ Log.e( TAG, "applyChanges(): failed to get lock" );
}
}
- // }
- // GameLock gameLock = m_jniThread == null
- // ? GameLock.tryLock( m_rowid ) : m_jniThread.getLock();
- // GameUtils.applyChanges( m_activity, m_gi, m_car, m_disabMap,
- // gameLock, forceNew );
- // DBUtils.saveThumbnail( m_activity, gameLock, null ); // clear it
- // if ( null == m_jniThread ) {
- // gameLock.unlock();
- // }
}
}
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameListItem.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameListItem.java
index e3483b78f..7fed2a1d3 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameListItem.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameListItem.java
@@ -33,7 +33,6 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
-
import org.eehouse.android.xw4.jni.GameSummary;
import org.eehouse.android.xw4.jni.JNIThread;
import org.eehouse.android.xw4.loc.LocUtils;
@@ -250,7 +249,7 @@ public class GameListItem extends LinearLayout
case R.string.game_summary_field_empty:
break;
case R.string.game_summary_field_gameid:
- value = String.format( "%X", m_summary.gameID );
+ value = String.format( "%d", m_summary.gameID );
break;
case R.string.game_summary_field_rowid:
value = String.format( "%d", m_rowid );
@@ -336,6 +335,13 @@ public class GameListItem extends LinearLayout
findViewById( R.id.has_chat_marker )
.setVisibility( hasChat ? View.VISIBLE : View.GONE );
+ if ( XWPrefs.moveCountEnabled( m_context ) ) {
+ TextView tv = (TextView)findViewById( R.id.n_pending );
+ int nPending = summary.nPacketsPending;
+ String str = nPending == 0 ? "" : String.format( "%d", nPending );
+ tv.setText( str );
+ }
+
String roleSummary = summary.summarizeRole( m_context, m_rowid );
m_role.setVisibility( null == roleSummary ? View.GONE : View.VISIBLE );
if ( null != roleSummary ) {
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java
index e9e3507f2..01882a574 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java
@@ -109,7 +109,7 @@ public class GamesListDelegate extends ListDelegateBase
}
int groupSelItem;
boolean nextIsSolo;
- boolean moveAfterNewGroup;
+ long[] moveAfterNewGroup;
Set selGames;
Set selGroupIDs;
}
@@ -818,7 +818,7 @@ public class GamesListDelegate extends ListDelegateBase
@Override
public void onClick( DialogInterface dlg,
int item ) {
- m_mySIS.moveAfterNewGroup = true;
+ m_mySIS.moveAfterNewGroup = games;
showDialogFragment( DlgID.NEW_GROUP );
}
} )
@@ -1596,7 +1596,7 @@ public class GamesListDelegate extends ListDelegateBase
break;
case R.id.games_menu_newgroup:
- m_mySIS.moveAfterNewGroup = false;
+ m_mySIS.moveAfterNewGroup = null;
showDialogFragment( DlgID.NEW_GROUP );
break;
@@ -2353,14 +2353,14 @@ public class GamesListDelegate extends ListDelegateBase
private boolean tryNFCIntent( Intent intent )
{
boolean result = false;
- String data = NFCUtils.getFromIntent( intent );
+ byte[] data = NFCUtils.getFromIntent( intent );
if ( null != data ) {
NetLaunchInfo nli = NetLaunchInfo.makeFrom( m_activity, data );
if ( null != nli && nli.isValid() ) {
startNewNetGame( nli );
result = true;
} else {
- NFCUtils.receiveMsgs( m_activity, data );
+ Assert.assertFalse( BuildConfig.DEBUG );
}
}
return result;
@@ -2448,11 +2448,10 @@ public class GamesListDelegate extends ListDelegateBase
private void showNewGroupIf()
{
- if ( m_mySIS.moveAfterNewGroup ) {
- m_mySIS.moveAfterNewGroup = false;
- Long[] games = m_mySIS.selGames
- .toArray( new Long[m_mySIS.selGames.size()] );
- showDialogFragment( DlgID.CHANGE_GROUP, (Object)games );
+ long[] games = m_mySIS.moveAfterNewGroup;
+ if ( null != games ) {
+ m_mySIS.moveAfterNewGroup = null;
+ showDialogFragment( DlgID.CHANGE_GROUP, games );
}
}
@@ -2844,10 +2843,12 @@ public class GamesListDelegate extends ListDelegateBase
;
}
- public static void sendNFCToSelf( Context context, String data )
+ public static void postNFCInvite( Context context, byte[] data )
{
- Intent intent = makeSelfIntent( context );
- NFCUtils.populateIntent( intent, data );
+ Intent intent = makeSelfIntent( context )
+ .addFlags( Intent.FLAG_ACTIVITY_NEW_TASK )
+ ;
+ NFCUtils.populateIntent( context, intent, data );
context.startActivity( intent );
}
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/InviteChoicesAlert.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/InviteChoicesAlert.java
index b3f13360c..9da3c9c58 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/InviteChoicesAlert.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/InviteChoicesAlert.java
@@ -79,7 +79,7 @@ public class InviteChoicesAlert extends DlgDelegateAlert {
if ( WiDirWrapper.enabled() ) {
add( items, means, R.string.invite_choice_p2p, InviteMeans.WIFIDIRECT );
}
- if ( XWPrefs.getNFCToSelfEnabled( context ) || NFCUtils.nfcAvail( context )[0] ) {
+ if ( NFCUtils.nfcAvail( context )[0] ) {
add( items, means, R.string.invite_choice_nfc, InviteMeans.NFC );
}
add( items, means, R.string.slmenu_copy_sel, InviteMeans.CLIPBOARD );
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/MultiMsgSink.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/MultiMsgSink.java
index bb1a7cdf5..e72d5bafe 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/MultiMsgSink.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/MultiMsgSink.java
@@ -79,6 +79,11 @@ public class MultiMsgSink implements TransportProcs {
.sendPacket( m_context, addr.p2p_addr, gameID, buf );
}
+ int sendViaNFC( byte[] buf, int gameID )
+ {
+ return NFCUtils.addMsgFor( buf, gameID );
+ }
+
public int numSent()
{
return m_sentSet.size();
@@ -107,13 +112,13 @@ public class MultiMsgSink implements TransportProcs {
nSent = sendViaP2P( buf, gameID, addr );
break;
case COMMS_CONN_NFC:
- Log.d( TAG, "transportSend(): got for NFC" );
+ nSent = sendViaNFC( buf, gameID );
break;
default:
Assert.fail();
break;
}
- Log.i( TAG, "transportSend(): sent %d msgs for game %d/%x via %s",
+ Log.i( TAG, "transportSend(): sent %d bytes for game %d/%x via %s",
nSent, gameID, gameID, typ.toString() );
if ( 0 < nSent ) {
Log.d( TAG, "transportSend: adding %s", msgID );
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NBSProto.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NBSProto.java
index 65c6100ca..2394824ee 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NBSProto.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NBSProto.java
@@ -66,8 +66,7 @@ public class NBSProto {
DbgUtils.showf( context, "Got msg %d", s_nReceived );
}
- ConnStatusHandler.updateStatusIn( context, null,
- CommsConnType.COMMS_CONN_SMS,
+ ConnStatusHandler.updateStatusIn( context, CommsConnType.COMMS_CONN_SMS,
true );
}
@@ -380,8 +379,7 @@ public class NBSProto {
DbgUtils.showf( context, "Sent msg %d", s_nSent );
}
- ConnStatusHandler.updateStatusOut( context, null,
- CommsConnType.COMMS_CONN_SMS,
+ ConnStatusHandler.updateStatusOut( context, CommsConnType.COMMS_CONN_SMS,
success );
}
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NFCCardService.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NFCCardService.java
new file mode 100644
index 000000000..393ca9cba
--- /dev/null
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NFCCardService.java
@@ -0,0 +1,157 @@
+/* -*- compile-command: "find-and-gradle.sh inXw4dDeb"; -*- */
+/*
+ * Copyright 2019 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.Activity;
+import android.content.Context;
+import android.nfc.cardemulation.HostApduService;
+import android.os.Bundle;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.eehouse.android.xw4.NFCUtils.HEX_STR;
+import org.eehouse.android.xw4.NFCUtils.MsgToken;
+
+public class NFCCardService extends HostApduService {
+ private static final String TAG = NFCCardService.class.getSimpleName();
+ private static final int LEN_OFFSET = 4;
+
+ private int mMyDevID;
+
+ // Remove this once we don't need logging to confirm stuff's loading
+ @Override
+ public void onCreate()
+ {
+ super.onCreate();
+ mMyDevID = DevID.getNFCDevID( this );
+ Log.d( TAG, "onCreate() got mydevid %d", mMyDevID );
+ }
+
+ private int mGameID;
+
+ @Override
+ public byte[] processCommandApdu( byte[] apdu, Bundle extras )
+ {
+ // Log.d( TAG, "processCommandApdu(%s)", DbgUtils.hexDump(apdu ) );
+
+ HEX_STR resStr = HEX_STR.STATUS_FAILED;
+ boolean isAidCase = false;
+
+ if ( null != apdu ) {
+ if ( HEX_STR.CMD_MSG_PART.matchesFrom( apdu ) ) {
+ resStr = HEX_STR.STATUS_SUCCESS;
+ byte[] all = NFCUtils.reassemble( this, apdu, HEX_STR.CMD_MSG_PART );
+ if ( null != all ) {
+ NFCUtils.addToMsgThread( this, all );
+ }
+ } else {
+ Log.d( TAG, "processCommandApdu(): aid case?" );
+ if ( ! HEX_STR.DEFAULT_CLA.matchesFrom( apdu ) ) {
+ resStr = HEX_STR.CLA_NOT_SUPPORTED;
+ } else if ( ! HEX_STR.SELECT_INS.matchesFrom( apdu, 1 ) ) {
+ resStr = HEX_STR.INS_NOT_SUPPORTED;
+ } else if ( LEN_OFFSET >= apdu.length ) {
+ Log.d( TAG, "processCommandApdu(): apdu too short" );
+ // Not long enough for length byte
+ } else {
+ try {
+ ByteArrayInputStream bais
+ = new ByteArrayInputStream( apdu, LEN_OFFSET,
+ apdu.length - LEN_OFFSET );
+ byte aidLen = (byte)bais.read();
+ Log.d( TAG, "aidLen=%d", aidLen );
+ if ( bais.available() >= aidLen + 1 ) {
+ byte[] aidPart = new byte[aidLen];
+ bais.read( aidPart );
+ String aidStr = Utils.ba2HexStr( aidPart );
+ if ( BuildConfig.NFC_AID.equals( aidStr ) ) {
+ byte minVersion = (byte)bais.read();
+ byte maxVersion = (byte)bais.read();
+ if ( minVersion == NFCUtils.VERSION_1 ) {
+ int devID = NFCUtils.numFrom( bais );
+ Log.d( TAG, "processCommandApdu(): read "
+ + "remote devID: %d", devID );
+ mGameID = NFCUtils.numFrom( bais );
+ Log.d( TAG, "read gameID: %d", mGameID );
+ if ( 0 < bais.available() ) {
+ Log.d( TAG, "processCommandApdu(): "
+ + "leaving anything behind?" );
+ }
+ resStr = HEX_STR.STATUS_SUCCESS;
+ isAidCase = true;
+ } else {
+ Log.e( TAG, "unexpected version %d; I'm too old?",
+ minVersion );
+ }
+ } else {
+ Log.e( TAG, "aid mismatch: got %s but wanted %s",
+ aidStr, BuildConfig.NFC_AID );
+ }
+ }
+ } catch ( IOException ioe ) {
+ Assert.assertFalse( BuildConfig.DEBUG );
+ }
+ }
+ }
+ }
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ baos.write( resStr.asBA() );
+ if ( HEX_STR.STATUS_SUCCESS == resStr ) {
+ if ( isAidCase ) {
+ baos.write( NFCUtils.VERSION_1 ); // min
+ baos.write( NFCUtils.numTo( mMyDevID ) );
+ } else {
+ MsgToken token = NFCUtils.getMsgsFor( mGameID );
+ byte[][] tmp = NFCUtils.wrapMsg( token, Short.MAX_VALUE );
+ Assert.assertTrue( 1 == tmp.length || !BuildConfig.DEBUG );
+ baos.write( tmp[0] );
+ }
+ }
+ } catch ( IOException ioe ) {
+ Assert.assertFalse( BuildConfig.DEBUG );
+ }
+ byte[] result = baos.toByteArray();
+
+ Log.d( TAG, "processCommandApdu(%s) => %s", DbgUtils.hexDump( apdu ),
+ DbgUtils.hexDump( result ) );
+ // this comes out of transceive() below!!!
+ return result;
+ } // processCommandApdu
+
+ @Override
+ public void onDeactivated( int reason )
+ {
+ String str = "";
+ switch ( reason ) {
+ case HostApduService.DEACTIVATION_LINK_LOSS:
+ str = "DEACTIVATION_LINK_LOSS";
+ break;
+ case HostApduService.DEACTIVATION_DESELECTED:
+ str = "DEACTIVATION_DESELECTED";
+ break;
+ }
+
+ Log.d( TAG, "onDeactivated(reason=%s)", str );
+ }
+}
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NFCUtils.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NFCUtils.java
index 02be6b688..f2cebca5a 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NFCUtils.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NFCUtils.java
@@ -25,74 +25,53 @@ import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
-import android.nfc.NdefMessage;
-import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.NfcEvent;
import android.nfc.NfcManager;
+import android.nfc.Tag;
+import android.nfc.tech.IsoDep;
import android.os.Build;
import android.os.Parcelable;
+import android.text.TextUtils;
-import org.json.JSONArray;
-import org.json.JSONObject;
-import org.json.JSONException;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.atomic.AtomicInteger;
-import org.eehouse.android.xw4.loc.LocUtils;
+import org.eehouse.android.xw4.MultiService.MultiEvent;
+import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
import org.eehouse.android.xw4.jni.CommsAddrRec;
+import org.eehouse.android.xw4.loc.LocUtils;
public class NFCUtils {
private static final String TAG = NFCUtils.class.getSimpleName();
+ private static final boolean USE_BIGINTEGER = true;
private static final String NFC_TO_SELF_ACTION = "org.eehouse.nfc_to_self";
private static final String NFC_TO_SELF_DATA = "nfc_data";
- private static final String MSGS = "MSGS";
- private static final String GAMEID = "GAMEID";
+ static final byte VERSION_1 = (byte)0x01;
- public interface NFCActor {
- String makeNFCMessage();
- }
+ private static final byte MESSAGE = 0x01;
+ private static final byte INVITE = 0x02;
+ private static final byte REPLY = 0x03;
- private static boolean s_inSDK;
+ private static final byte REPLY_NOGAME = 0x00;
+
+ private static boolean s_inSDK = 19 <= Build.VERSION.SDK_INT;
private static boolean[] s_nfcAvail;
- private static SafeNFC s_safeNFC;
- static {
- s_inSDK = 14 <= Build.VERSION.SDK_INT
- && Build.VERSION.SDK_INT <= Build.VERSION_CODES.P;
- if ( s_inSDK ) {
- s_safeNFC = new SafeNFCImpl();
- }
- }
-
- private static interface SafeNFC {
- public void register( Activity activity, NFCActor actor );
- }
-
- private static class SafeNFCImpl implements SafeNFC {
- public void register( final Activity activity, final NFCActor actor )
- {
- NfcManager manager =
- (NfcManager)activity.getSystemService( Context.NFC_SERVICE );
- if ( null != manager ) {
- NfcAdapter adapter = manager.getDefaultAdapter();
- if ( null != adapter ) {
- NfcAdapter.CreateNdefMessageCallback cb =
- new NfcAdapter.CreateNdefMessageCallback() {
- public NdefMessage createNdefMessage( NfcEvent evt )
- {
- NdefMessage msg = null;
- String data = actor.makeNFCMessage();
- if ( null != data ) {
- msg = makeMessage( activity, data );
- }
- return msg;
- }
- };
- adapter.setNdefPushMessageCallback( cb, activity );
- }
- }
- }
- }
// Return array of two booleans, the first indicating whether the
// device supports NFC and the second whether it's on. Only the
@@ -108,38 +87,32 @@ public class NFCUtils {
if ( s_nfcAvail[0] ) {
s_nfcAvail[1] = getNFCAdapter( context ).isEnabled();
}
+ // Log.d( TAG, "nfcAvail() => {%b,%b}", s_nfcAvail[0], s_nfcAvail[1] );
return s_nfcAvail;
}
- public static String getFromIntent( Intent intent )
+ public static byte[] getFromIntent( Intent intent )
{
- String result = null;
+ byte[] result = null;
String action = intent.getAction();
- if ( NfcAdapter.ACTION_NDEF_DISCOVERED.equals( action ) ) {
- Parcelable[] rawMsgs =
- intent.getParcelableArrayExtra( NfcAdapter.EXTRA_NDEF_MESSAGES );
- // only one message sent during the beam
- NdefMessage msg = (NdefMessage)rawMsgs[0];
- // record 0 contains the MIME type, record 1 is the AAR, if present
- result = new String( msg.getRecords()[0].getPayload() );
- } else if ( NFC_TO_SELF_ACTION.equals( action ) ) {
- result = intent.getStringExtra( NFC_TO_SELF_DATA );
+ if ( NFC_TO_SELF_ACTION.equals( action ) ) {
+ result = intent.getByteArrayExtra( NFC_TO_SELF_DATA );
}
+ // Log.d( TAG, "getFromIntent() => %s", result );
return result;
}
- public static void populateIntent( Intent intent, String data )
+ public static void populateIntent( Context context, Intent intent,
+ byte[] data )
{
- intent.setAction( NFC_TO_SELF_ACTION )
- .putExtra( NFC_TO_SELF_DATA, data );
- }
-
- public static void register( Activity activity, NFCActor actor )
- {
- if ( null != s_safeNFC ) {
- s_safeNFC.register( activity, actor );
+ NetLaunchInfo nli = NetLaunchInfo.makeFrom( context, data );
+ if ( null != nli ) {
+ intent.setAction( NFC_TO_SELF_ACTION )
+ .putExtra( NFC_TO_SELF_DATA, data );
+ } else {
+ Assert.assertFalse( BuildConfig.DEBUG );
}
}
@@ -162,19 +135,6 @@ public class NFCUtils {
.create();
}
- private static NdefMessage makeMessage( Activity activity, String data )
- {
- String mimeType = LocUtils.getString( activity, R.string.xwords_nfc_mime );
- NdefMessage msg = new NdefMessage( new NdefRecord[] {
- new NdefRecord(NdefRecord.TNF_MIME_MEDIA,
- mimeType.getBytes(), new byte[0],
- data.getBytes())
- ,NdefRecord.
- createApplicationRecord( activity.getPackageName() )
- });
- return msg;
- }
-
private static NfcAdapter getNFCAdapter( Context context )
{
NfcManager manager =
@@ -182,44 +142,922 @@ public class NFCUtils {
return manager.getDefaultAdapter();
}
- static String makeMsgsJSON( int gameID, byte[][] msgs )
+ private static byte[] formatMsgs( int gameID, List msgs )
{
- String result = null;
+ return formatMsgs( gameID, msgs.toArray( new byte[msgs.size()][] ) );
+ }
- JSONArray arr = new JSONArray();
- for ( byte[] msg : msgs ) {
- arr.put( Utils.base64Encode( msg ) );
+ private static byte[] formatMsgs( int gameID, byte[][] msgs )
+ {
+ byte[] result = null;
+
+ if ( null != msgs && 0 < msgs.length ) {
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream( baos );
+ dos.writeInt( gameID );
+ Log.d( TAG, "formatMsgs(): wrote gameID: %d", gameID );
+ dos.flush();
+ baos.write( msgs.length );
+ for ( int ii = 0; ii < msgs.length; ++ii ) {
+ byte[] msg = msgs[ii];
+ short len = (short)msg.length;
+ baos.write( len & 0xFF );
+ baos.write( (len >> 8) & 0xFF );
+ baos.write( msg );
+ }
+ result = baos.toByteArray();
+ } catch ( IOException ioe ) {
+ Assert.assertFalse( BuildConfig.DEBUG );
+ }
+ }
+ Log.d( TAG, "formatMsgs(gameID=%d) => %s", gameID, DbgUtils.hexDump( result ) );
+ return result;
+ }
+
+ private static byte[][] unformatMsgs( byte[] data, int start, int[] gameID )
+ {
+ byte[][] result = null;
+ try {
+ ByteArrayInputStream bais
+ = new ByteArrayInputStream( data, start, data.length );
+ DataInputStream dis = new DataInputStream( bais );
+ gameID[0] = dis.readInt();
+ Log.d( TAG, "unformatMsgs(): read gameID: %d", gameID[0] );
+ int count = bais.read();
+ Log.d( TAG, "unformatMsgs(): read count: %d", count );
+ result = new byte[count][];
+
+ for ( int ii = 0; ii < count; ++ii ) {
+ short len = (short)bais.read();
+ len |= (int)(bais.read() << 8);
+ Log.d( TAG, "unformatMsgs(): read len %d for msg %d", len, ii );
+ byte[] msg = new byte[len];
+ int nRead = bais.read( msg );
+ Assert.assertTrue( nRead == msg.length );
+ result[ii] = msg;
+ }
+ } catch ( IOException ex ) {
+ Log.d( TAG, "ex: %s: %s", ex, ex.getMessage() );
+ result = null;
+ gameID[0] = 0;
+ }
+ Log.d( TAG, "unformatMsgs() => %s (len=%d)", result,
+ null == result ? 0 : result.length );
+ return result;
+ }
+
+ interface HaveDataListener {
+ void onHaveDataChanged( boolean nowHaveData );
+ }
+
+ public static class MsgToken {
+ private MsgsStore mStore;
+ private byte[][] mMsgs;
+ private int mGameID;
+
+ private MsgToken( MsgsStore store, int gameID )
+ {
+ mStore = store;
+ mGameID = gameID;
+ mMsgs = mStore.getMsgsFor( gameID );
}
- try {
- JSONObject obj = new JSONObject();
- obj.put( MSGS, arr );
- obj.put( GAMEID, gameID );
+ byte[] getMsgs()
+ {
+ return formatMsgs( mGameID, mMsgs );
+ }
- result = obj.toString();
- } catch ( JSONException ex ) {
+ void removeSentMsgs()
+ {
+ mStore.removeSentMsgs( mGameID, mMsgs );
+ }
+ }
+
+ private static class MsgsStore {
+ private Map> mListeners
+ = new HashMap<>();
+ private static Map> mMsgMap = new HashMap<>();
+
+ void setHaveDataListener( int gameID, HaveDataListener listener )
+ {
+ Assert.assertFalse( gameID == 0 );
+ WeakReference ref = new WeakReference<>(listener);
+ synchronized ( mListeners ) {
+ mListeners.put( gameID, ref );
+ }
+
+ byte[][] msgs = getMsgsFor( gameID );
+ listener.onHaveDataChanged( null != msgs && 0 < msgs.length );
+ }
+
+ private int addMsgFor( int gameID, byte typ, byte[] msg )
+ {
+ Boolean nowHaveData = null;
+
+ synchronized ( mMsgMap ) {
+ if ( !mMsgMap.containsKey( gameID ) ) {
+ mMsgMap.put( gameID, new ArrayList() );
+ }
+ List msgs = mMsgMap.get( gameID );
+
+ byte[] full = new byte[msg.length + 1];
+ full[0] = typ;
+ System.arraycopy( msg, 0, full, 1, msg.length );
+
+ // Can't use msgs.contains() because it uses equals()
+ boolean isDuplicate = false;
+ for ( byte[] curMsg : msgs ) {
+ if ( Arrays.equals( curMsg, full ) ) {
+ isDuplicate = true;
+ break;
+ }
+ }
+
+ if ( !isDuplicate ) {
+ msgs.add( full );
+ nowHaveData = 0 < msgs.size();
+ Log.d( TAG, "addMsgFor(gameID=%d): added %s; now have %d msgs",
+ gameID, DbgUtils.hexDump(msg), msgs.size() );
+ }
+ }
+
+ reportHaveData( gameID, nowHaveData );
+
+ return msg.length;
+ }
+
+ private byte[][] getMsgsFor( int gameID )
+ {
+ Assert.assertFalse( gameID == 0 );
+ byte[][] result = null;
+ synchronized ( mMsgMap ) {
+ if ( mMsgMap.containsKey( gameID ) ) {
+ List msgs = mMsgMap.get( gameID );
+ result = msgs.toArray( new byte[msgs.size()][] );
+ }
+ }
+ Log.d( TAG, "getMsgsFor() => %d msgs", result == null ? 0 : result.length );
+ return result;
+ }
+
+ private void removeSentMsgs( int gameID, byte[][] msgs )
+ {
+ Boolean nowHaveData = null;
+ if ( null != msgs ) {
+ synchronized ( mMsgMap ) {
+ if ( mMsgMap.containsKey( gameID ) ) {
+ List list = mMsgMap.get( gameID );
+ // Log.d( TAG, "removeSentMsgs(%d): size before: %d", gameID,
+ // list.size() );
+ int origSize = list.size();
+ for ( byte[] msg : msgs ) {
+ list.remove( msg );
+ }
+ if ( 0 < origSize ) {
+ Log.d( TAG, "removeSentMsgs(%d): size was %d, now %d", gameID,
+ origSize, list.size() );
+ }
+ nowHaveData = 0 < list.size();
+ }
+ }
+ }
+ reportHaveData( gameID, nowHaveData );
+ }
+
+ private void reportHaveData( int gameID, Boolean nowHaveData )
+ {
+ Log.d( TAG, "reportHaveData(" + nowHaveData + ")" );
+ if ( null != nowHaveData ) {
+ HaveDataListener proc = null;
+ synchronized ( mListeners ) {
+ WeakReference ref = mListeners.get( gameID );
+ if ( null != ref ) {
+ proc = ref.get();
+ if ( null == proc ) {
+ mListeners.remove( gameID );
+ }
+ } else {
+ Log.d( TAG, "reportHaveData(): no listener for %d", gameID );
+ }
+ }
+ if ( null != proc ) {
+ proc.onHaveDataChanged( nowHaveData );
+ }
+ }
+ }
+
+ static byte[] split( byte[] msg, byte[] headerOut )
+ {
+ headerOut[0] = msg[0];
+ byte[] result = Arrays.copyOfRange( msg, 1, msg.length );
+ Log.d( TAG, "split(%s) => %d/%s", DbgUtils.hexDump( msg ),
+ headerOut[0], DbgUtils.hexDump( result ) );
+ return result;
+ }
+ }
+ private static MsgsStore sMsgsStore = new MsgsStore();
+
+ static void setHaveDataListener( int gameID, HaveDataListener listener )
+ {
+ sMsgsStore.setHaveDataListener( gameID, listener );
+ }
+
+ static int addMsgFor( byte[] msg, int gameID )
+ {
+ return sMsgsStore.addMsgFor( gameID, MESSAGE, msg );
+ }
+
+ static int addInvitationFor( byte[] msg, int gameID )
+ {
+ return sMsgsStore.addMsgFor( gameID, INVITE, msg );
+ }
+
+ static int addReplyFor( byte[] msg, int gameID )
+ {
+ return sMsgsStore.addMsgFor( gameID, REPLY, msg );
+ }
+
+ static MsgToken getMsgsFor( int gameID )
+ {
+ MsgToken token = new MsgToken( sMsgsStore, gameID );
+ return token;
+ }
+
+ static void receiveMsgs( Context context, byte[] data )
+ {
+ receiveMsgs( context, data, 0 );
+ }
+
+ static void receiveMsgs( Context context, byte[] data, int offset )
+ {
+ // Log.d( TAG, "receiveMsgs(gameID=%d, %s, offset=%d)", gameID,
+ // DbgUtils.hexDump(data), offset );
+ DbgUtils.assertOnUIThread( false );
+ int[] gameID = {0};
+ byte[][] msgs = unformatMsgs( data, offset, gameID );
+ if ( null != msgs ) {
+ NFCServiceHelper helper = new NFCServiceHelper( context );
+ for ( byte[] msg : msgs ) {
+ byte[] typ = {0};
+ byte[] body = MsgsStore.split( msg, typ );
+ switch ( typ[0] ) {
+ case MESSAGE:
+ long[] rowids = DBUtils.getRowIDsFor( context, gameID[0] );
+ if ( null == rowids || 0 == rowids.length ) {
+ addReplyFor( new byte[]{REPLY_NOGAME}, gameID[0] );
+ } else {
+ for ( long rowid : rowids ) {
+ NFCMsgSink sink = new NFCMsgSink( context, rowid );
+ helper.receiveMessage( rowid, sink, body );
+ }
+ }
+ break;
+ case INVITE:
+ GamesListDelegate.postNFCInvite( context, body );
+ break;
+ case REPLY:
+ switch( body[0] ) {
+ case REPLY_NOGAME:
+ // PENDING Don't enable this until deviceID is being
+ // checked. Otherwise it'll happen every time I tap my
+ // device against another that doesn't have my game,
+ // which could be common.
+ // helper.postEvent( MultiEvent.MESSAGE_NOGAME, gameID );
+ Log.e( TAG, "receiveMsgs(): not calling helper.postEvent( "
+ + "MultiEvent.MESSAGE_NOGAME, gameID );" );
+ break;
+ default:
+ Log.e( TAG, "unexpected reply %d", body[0] );
+ Assert.assertFalse( BuildConfig.DEBUG );
+ break;
+ }
+ break;
+ default:
+ Assert.assertFalse( BuildConfig.DEBUG );
+ break;
+ }
+ }
+ }
+ }
+
+ static enum HEX_STR {
+ DEFAULT_CLA( "00" )
+ , SELECT_INS( "A4" )
+ , STATUS_FAILED( "6F00" )
+ , CLA_NOT_SUPPORTED( "6E00" )
+ , INS_NOT_SUPPORTED( "6D00" )
+ , STATUS_SUCCESS( "9000" )
+ , CMD_MSG_PART( "70FC" )
+ ;
+
+ private byte[] mBytes;
+ private HEX_STR( String hex ) { mBytes = Utils.hexStr2ba(hex); }
+ byte[] asBA() { return mBytes; }
+ boolean matchesFrom( byte[] src )
+ {
+ return matchesFrom( src, 0 );
+ }
+ boolean matchesFrom( byte[] src, int offset )
+ {
+ boolean result = offset + mBytes.length <= src.length;
+ for ( int ii = 0; result && ii < mBytes.length; ++ii ) {
+ result = src[offset + ii] == mBytes[ii];
+ }
+ // Log.d( TAG, "%s.matchesFrom(%s) => %b", this, src, result );
+ return result;
+ }
+ int length() { return asBA().length; }
+ }
+
+ private static int sNextMsgID = 0;
+ private static synchronized int getNextMsgID()
+ {
+ return ++sNextMsgID;
+ }
+
+ static byte[] numTo( int num )
+ {
+ byte[] result;
+ if ( USE_BIGINTEGER ) {
+ BigInteger bi = BigInteger.valueOf( num );
+ byte[] bibytes = bi.toByteArray();
+ result = new byte[1 + bibytes.length];
+ result[0] = (byte)bibytes.length;
+ System.arraycopy( bibytes, 0, result, 1, bibytes.length );
+ } else {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream( baos );
+ try {
+ dos.writeInt( num );
+ dos.flush();
+ } catch ( IOException ioe ) {
+ Assert.assertFalse( BuildConfig.DEBUG );
+ }
+ result = baos.toByteArray();
+ }
+ // Log.d( TAG, "numTo(%d) => %s", num, DbgUtils.hexDump(result) );
+ return result;
+ }
+
+ static int numFrom( ByteArrayInputStream bais ) throws IOException
+ {
+ int biLen = bais.read();
+ // Log.d( TAG, "numFrom(): read biLen: %d", biLen );
+ byte[] bytes = new byte[biLen];
+ bais.read( bytes );
+ BigInteger bi = new BigInteger( bytes );
+ int result = bi.intValue();
+
+ // Log.d( TAG, "numFrom() => %d", result );
+ return result;
+ }
+
+ static int numFrom( byte[] bytes, int start, int out[] )
+ {
+ int result;
+ if ( USE_BIGINTEGER ) {
+ byte biLen = bytes[start];
+ byte[] rest = Arrays.copyOfRange( bytes, start + 1, start + 1 + biLen );
+ BigInteger bi = new BigInteger(rest);
+ out[0] = bi.intValue();
+ result = biLen + 1;
+ } else {
+ ByteArrayInputStream bais = new ByteArrayInputStream( bytes, start,
+ bytes.length - start );
+ DataInputStream dis = new DataInputStream( bais );
+ try {
+ out[0] = dis.readInt();
+ } catch ( IOException ioe ) {
+ Log.e( TAG, "from readInt(): %s", ioe.getMessage() );
+ }
+ result = bais.available() - start;
+ }
+ return result;
+ }
+
+ // private static void testNumThing()
+ // {
+ // Log.d( TAG, "testNumThing() starting" );
+
+ // int[] out = {0};
+ // for ( int ii = 1; ii > 0 && ii < Integer.MAX_VALUE; ii *= 2 ) {
+ // byte[] tmp = numTo( ii );
+ // numFrom( tmp, 0, out );
+ // if ( ii != out[0] ) {
+ // Log.d( TAG, "testNumThing(): %d failed; got %d", ii, out[0] );
+ // break;
+ // } else {
+ // Log.d( TAG, "testNumThing(): %d ok", ii );
+ // }
+ // }
+ // Log.d( TAG, "testNumThing() DONE" );
+ // }
+
+ private static AtomicInteger sLatestAck = new AtomicInteger(0);
+ static int getLatestAck()
+ {
+ int result = sLatestAck.getAndSet(0);
+ if ( 0 != result ) {
+ Log.d( TAG, "getLatestAck() => %d", result );
+ }
+ return result;
+ }
+
+ static void setLatestAck( int ack )
+ {
+ if ( 0 != ack ) {
+ Log.e( TAG, "setLatestAck(%d)", ack );
+ }
+ int oldVal = sLatestAck.getAndSet( ack );
+ if ( 0 != oldVal ) {
+ Log.e( TAG, "setLatestAck(%d): dropping ack msgID %d", ack, oldVal );
+ }
+ }
+
+ private static void updateStatus( Context context, boolean in )
+ {
+ if ( in ) {
+ ConnStatusHandler
+ .updateStatusIn( context, CommsConnType.COMMS_CONN_NFC, true );
+ } else {
+ ConnStatusHandler
+ .updateStatusOut( context, CommsConnType.COMMS_CONN_NFC, true );
+ }
+ }
+
+ private static Map sSentTokens = new HashMap<>();
+ private static void removeSentMsgs( Context context, int ack )
+ {
+ MsgToken msgs = null;
+ if ( 0 != ack ) {
+ Log.d( TAG, "removeSentMsgs(msgID=%d)", ack );
+ synchronized ( sSentTokens ) {
+ msgs = sSentTokens.remove( ack );
+ Log.d( TAG, "removeSentMsgs(): removed %s, now have %s", msgs, keysFor() );
+ }
+ updateStatus( context, false );
+ }
+ if ( null != msgs ) {
+ msgs.removeSentMsgs();
+ }
+ }
+
+ private static void remember( int msgID, MsgToken msgs )
+ {
+ if ( 0 != msgID ) {
+ Log.d( TAG, "remember(msgID=%d)", msgID );
+ synchronized ( sSentTokens ) {
+ sSentTokens.put( msgID, msgs );
+ Log.d( TAG, "remember(): now have %s", keysFor() );
+ }
+ }
+ }
+
+ private static String keysFor()
+ {
+ String result = "";
+ if ( BuildConfig.DEBUG ) {
+ result = TextUtils.join( ",", sSentTokens.keySet() );
+ }
+ return result;
+ }
+
+ private static byte[][] sParts = null;
+ private static int sMsgID = 0;
+ synchronized static byte[] reassemble( Context context, byte[] part,
+ HEX_STR cmd )
+ {
+ return reassemble( context, part, cmd.length() );
+ }
+
+ synchronized static byte[] reassemble( Context context, byte[] part,
+ int offset )
+ {
+ part = Arrays.copyOfRange( part, offset, part.length );
+ return reassemble( context, part );
+ }
+
+ synchronized static byte[] reassemble( Context context, byte[] part )
+ {
+ byte[] result = null;
+ try {
+ ByteArrayInputStream bais = new ByteArrayInputStream( part );
+
+ final int cur = bais.read();
+ final int count = bais.read();
+ if ( 0 == cur ) {
+ sMsgID = NFCUtils.numFrom( bais );
+ int ack = NFCUtils.numFrom( bais );
+ removeSentMsgs( context, ack );
+ }
+
+ boolean inSequence = true;
+ if ( sParts == null ) {
+ if ( 0 == cur ) {
+ sParts = new byte[count][];
+ } else {
+ Log.e( TAG, "reassemble(): out-of-order message 1" );
+ inSequence = false;
+ }
+ } else if ( cur >= count || count != sParts.length || null != sParts[cur] ) {
+ // result = HEX_STR.STATUS_FAILED;
+ inSequence = false;
+ Log.e( TAG, "reassemble(): out-of-order message 2" );
+ }
+
+ if ( !inSequence ) {
+ sParts = null; // so we can try again later
+ } else {
+ // write rest into array
+ byte[] rest = new byte[bais.available()];
+ bais.read( rest, 0, rest.length );
+ sParts[cur] = rest;
+ // Log.d( TAG, "addOrProcess(): added elem %d: %s", cur, DbgUtils.hexDump( rest ) );
+
+ // Done? Process!!
+ if ( cur + 1 == count ) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ for ( int ii = 0; ii < sParts.length; ++ii ) {
+ baos.write( sParts[ii] );
+ }
+ sParts = null;
+
+ result = baos.toByteArray();
+ setLatestAck( sMsgID );
+ if ( 0 != sMsgID ) {
+ Log.d( TAG, "reassemble(): done reassembling msgID=%d: %s",
+ sMsgID, DbgUtils.hexDump(result) );
+ }
+ }
+ }
+ } catch ( IOException ioe ) {
Assert.assertFalse( BuildConfig.DEBUG );
}
return result;
}
- static boolean receiveMsgs( Context context, String data )
+ private static final int HEADER_SIZE = 10;
+ static byte[][] wrapMsg( MsgToken token, int maxLen )
{
- Log.d( TAG, "receiveMsgs()" );
- int gameID[] = {0};
- byte[][] msgs = msgsFrom( data, gameID );
- boolean success = null != msgs && 0 < msgs.length;
- if ( success ) {
- NFCServiceHelper helper = new NFCServiceHelper( context );
- long[] rowids = DBUtils.getRowIDsFor( context, gameID[0] );
- for ( long rowid : rowids ) {
- NFCMsgSink sink = new NFCMsgSink( context, rowid );
- for ( byte[] msg : msgs ) {
- helper.receiveMessage( rowid, sink, msg );
+ byte[] msg = token.getMsgs();
+ final int length = null == msg ? 0 : msg.length;
+ final int msgID = (0 == length) ? 0 : getNextMsgID();
+ if ( 0 < msgID ) {
+ Log.d( TAG, "wrapMsg(%s); msgID=%d", DbgUtils.hexDump( msg ), msgID );
+ }
+ final int count = 1 + (length / (maxLen - HEADER_SIZE));
+ byte[][] result = new byte[count][];
+ try {
+ int offset = 0;
+ for ( int ii = 0; ii < count; ++ii ) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ baos.write( HEX_STR.CMD_MSG_PART.asBA() );
+ baos.write( (byte)ii );
+ baos.write( (byte)count );
+ if ( 0 == ii ) {
+ baos.write( numTo( msgID ) );
+ int latestAck = getLatestAck();
+ baos.write( numTo( latestAck ) );
+ }
+ Assert.assertTrue( HEADER_SIZE >= baos.toByteArray().length
+ || !BuildConfig.DEBUG );
+
+ int thisLen = Math.min( maxLen - HEADER_SIZE, length - offset );
+ if ( 0 < thisLen ) {
+ // Log.d( TAG, "writing %d bytes starting from offset %d",
+ // thisLen, offset );
+ baos.write( msg, offset, thisLen );
+ offset += thisLen;
+ }
+ byte[] tmp = baos.toByteArray();
+ // Log.d( TAG, "wrapMsg(): adding res[%d]: %s", ii, DbgUtils.hexDump(tmp) );
+ result[ii] = tmp;
+ }
+ remember( msgID, token );
+ } catch ( IOException ioe ) {
+ Assert.assertFalse( BuildConfig.DEBUG );
+ }
+ return result;
+ }
+
+ private static class QueueElem {
+ Context context;
+ byte[] msg;
+ QueueElem( Context pContext, byte[] pMsg ) {
+ context = pContext;
+ msg = pMsg;
+ }
+ }
+
+ private static LinkedBlockingQueue sQueue = null;
+ synchronized static void addToMsgThread( Context context, byte[] msg )
+ {
+ if ( 0 < msg.length ) {
+ QueueElem elem = new QueueElem( context, msg );
+ if ( null == sQueue ) {
+ sQueue = new LinkedBlockingQueue<>();
+ new Thread( new Runnable() {
+ @Override
+ public void run() {
+ Log.d( TAG, "addToMsgThread(): run starting" );
+ for ( ; ; ) {
+ try {
+ QueueElem elem = sQueue.take();
+ NFCUtils.receiveMsgs( elem.context, elem.msg );
+ updateStatus( elem.context, true );
+ } catch ( InterruptedException ie ) {
+ break;
+ }
+ }
+ Log.d( TAG, "addToMsgThread(): run exiting" );
+ }
+ } ).start();
+ }
+ sQueue.add( elem );
+ // } else {
+ // // This is very common right now
+ // Log.d( TAG, "addToMsgThread(): dropping 0-length msg" );
+ }
+ }
+
+ public static class Wrapper {
+ private Reader mReader;
+
+ public interface Procs {
+ void onReadingChange( boolean nowReading );
+ }
+
+ private Wrapper( Activity activity, Procs procs, int devID )
+ {
+ mReader = new Reader( activity, procs, devID );
+ }
+
+ public static Wrapper init( Activity activity, Procs procs, int devID )
+ {
+ Wrapper instance = null;
+
+ if ( nfcAvail( activity )[1] ) {
+ instance = new Wrapper( activity, procs, devID );
+ }
+ Log.d( TAG, "Wrapper.init(devID=%d) => %s", devID, instance );
+ return instance;
+ }
+
+ static void setResumed( Wrapper instance, boolean resumed )
+ {
+ if ( null != instance ) {
+ instance.mReader.setResumed( resumed );
+ }
+ }
+
+ static void setGameID( Wrapper instance, int gameID )
+ {
+ if ( null != instance ) {
+ instance.mReader.setGameID( gameID );
+ }
+ }
+ }
+
+ private static class Reader implements NfcAdapter.ReaderCallback,
+ HaveDataListener {
+ private Activity mActivity;
+ private boolean mHaveData;
+ private Wrapper.Procs mProcs;
+ private NfcAdapter mAdapter;
+ private int mMinMS = 300;
+ private int mMaxMS = 500;
+ private boolean mConnected = false;
+ private int mMyDevID;
+
+ private Reader( Activity activity, Wrapper.Procs procs, int devID )
+ {
+ mActivity = activity;
+ mProcs = procs;
+ mMyDevID = devID;
+ mAdapter = NfcAdapter.getDefaultAdapter( activity );
+ }
+
+ private void setResumed( boolean resumed )
+ {
+ if ( resumed ) {
+ startReadModeThread();
+ } else {
+ stopReadModeThread();
+ }
+ }
+
+ @Override
+ public void onHaveDataChanged( boolean haveData )
+ {
+ if ( mHaveData != haveData ) {
+ mHaveData = haveData;
+ Log.d( TAG, "onHaveDataChanged(): mHaveData now %b", mHaveData );
+ interruptThread();
+ }
+ }
+
+ private boolean haveData()
+ {
+ boolean result = mHaveData;
+ // Log.d( TAG, "haveData() => %b", result );
+ return result;
+ }
+
+ private int mGameID;
+ private void setGameID( int gameID )
+ {
+ Log.d( TAG, "setGameID(%d)", gameID );
+ mGameID = gameID;
+ NFCUtils.setHaveDataListener( gameID, this );
+ interruptThread();
+ }
+
+ private void interruptThread()
+ {
+ synchronized ( mThreadRef ) {
+ if ( null != mThreadRef[0] ) {
+ mThreadRef[0].interrupt();
+ }
+ }
+ }
+
+ @Override
+ public void onTagDiscovered( Tag tag )
+ {
+ mConnected = true;
+ IsoDep isoDep = IsoDep.get( tag );
+ try {
+ isoDep.connect();
+ int maxLen = isoDep.getMaxTransceiveLength();
+ Log.d( TAG, "onTagDiscovered() connected; max len: %d", maxLen );
+ byte[] aidBytes = Utils.hexStr2ba( BuildConfig.NFC_AID );
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ baos.write( Utils.hexStr2ba( "00A40400" ) );
+ baos.write( (byte)aidBytes.length );
+ baos.write( aidBytes );
+ baos.write( VERSION_1 ); // min
+ baos.write( VERSION_1 ); // max
+ baos.write( numTo( mMyDevID ) );
+ baos.write( numTo( mGameID ) );
+ byte[] msg = baos.toByteArray();
+ Assert.assertTrue( msg.length < maxLen || !BuildConfig.DEBUG );
+ byte[] response = isoDep.transceive( msg );
+
+ // The first reply from transceive() is special. If it starts
+ // with STATUS_SUCCESS then it also includes the version we'll
+ // be using to communicate, either what we sent over or
+ // something lower (for older code on the other side), and the
+ // remote's deviceID
+ if ( HEX_STR.STATUS_SUCCESS.matchesFrom( response ) ) {
+ int offset = HEX_STR.STATUS_SUCCESS.length();
+ byte version = response[offset++];
+ if ( version == VERSION_1 ) {
+ int[] out = {0};
+ offset += numFrom( response, offset, out );
+ Log.d( TAG, "onTagDiscovered(): read remote devID: %d",
+ out[0] );
+ runMessageLoop( isoDep, maxLen );
+ } else {
+ Log.e( TAG, "onTagDiscovered(): remote sent version %d, "
+ + "not %d; exiting", version, VERSION_1 );
+ }
+ }
+ isoDep.close();
+ } catch ( IOException ioe ) {
+ Log.e( TAG, "got ioe: " + ioe.getMessage() );
+ }
+
+ mConnected = false;
+ interruptThread(); // make sure we leave read mode!
+ Log.d( TAG, "onTagDiscovered() DONE" );
+ }
+
+ private void runMessageLoop( IsoDep isoDep, int maxLen ) throws IOException
+ {
+ outer:
+ for ( ; ; ) {
+ MsgToken token = NFCUtils.getMsgsFor( mGameID );
+ // PENDING: no need for this Math.min thing once well tested
+ byte[][] toFit = wrapMsg( token, Math.min( 50, maxLen ) );
+ for ( int ii = 0; ii < toFit.length; ++ii ) {
+ byte[] one = toFit[ii];
+ Assert.assertTrue( one.length < maxLen || !BuildConfig.DEBUG );
+ byte[] response = isoDep.transceive( one );
+ if ( ! receiveAny( response ) ) {
+ break outer;
+ }
+ }
+ }
+ }
+
+ private boolean receiveAny( byte[] response )
+ {
+ boolean statusOK = HEX_STR.STATUS_SUCCESS.matchesFrom( response );
+ if ( statusOK ) {
+ int offset = HEX_STR.STATUS_SUCCESS.length();
+ if ( HEX_STR.CMD_MSG_PART.matchesFrom( response, offset ) ) {
+ byte[] all = reassemble( mActivity, response,
+ offset + HEX_STR.CMD_MSG_PART.length() );
+ Log.d( TAG, "receiveAny(%s) => %b", DbgUtils.hexDump( response ), statusOK );
+ if ( null != all ) {
+ addToMsgThread( mActivity, all );
+ }
+ }
+ }
+ if ( !statusOK ) {
+ Log.d( TAG, "receiveAny(%s) => %b", DbgUtils.hexDump( response ), statusOK );
+ }
+ return statusOK;
+ }
+
+ private class ReadModeThread extends Thread {
+ private boolean mShouldStop = false;
+ private boolean mInReadMode = false;
+ private final int mFlags = NfcAdapter.FLAG_READER_NFC_A
+ | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK;
+
+ @Override
+ public void run()
+ {
+ Log.d( TAG, "ReadModeThread.run() starting" );
+ Random random = new Random();
+
+ while ( !mShouldStop ) {
+ boolean wantReadMode = mConnected || !mInReadMode && haveData();
+ if ( wantReadMode && !mInReadMode ) {
+ mAdapter.enableReaderMode( mActivity, Reader.this, mFlags, null );
+ } else if ( mInReadMode && !wantReadMode ) {
+ mAdapter.disableReaderMode( mActivity );
+ }
+ mInReadMode = wantReadMode;
+ Log.d( TAG, "run(): inReadMode now: %b", mInReadMode );
+
+ // Now sleep. If we aren't going to want to toggle read
+ // mode soon, sleep until interrupted by a state change,
+ // e.g. getting data or losing connection.
+ long intervalMS = Long.MAX_VALUE;
+ if ( (mInReadMode && !mConnected) || haveData() ) {
+ intervalMS = mMinMS + (Math.abs(random.nextInt())
+ % (mMaxMS - mMinMS));
+ }
+ try {
+ Thread.sleep( intervalMS );
+ } catch ( InterruptedException ie ) {
+ Log.d( TAG, "run interrupted" );
+ }
+ }
+
+ // Kill read mode on the way out
+ if ( mInReadMode ) {
+ mAdapter.disableReaderMode( mActivity );
+ mInReadMode = false;
+ }
+
+ // Clear the reference only if it's me
+ synchronized ( mThreadRef ) {
+ if ( mThreadRef[0] == this ) {
+ mThreadRef[0] = null;
+ }
+ }
+ Log.d( TAG, "ReadModeThread.run() exiting" );
+ }
+
+ public void doStop()
+ {
+ mShouldStop = true;
+ interrupt();
+ }
+ }
+
+ private ReadModeThread[] mThreadRef = {null};
+ private void startReadModeThread()
+ {
+ synchronized ( mThreadRef ) {
+ if ( null == mThreadRef[0] ) {
+ mThreadRef[0] = new ReadModeThread();
+ mThreadRef[0].start();
+ }
+ }
+ }
+
+ private void stopReadModeThread()
+ {
+ ReadModeThread thread;
+ synchronized ( mThreadRef ) {
+ thread = mThreadRef[0];
+ mThreadRef[0] = null;
+ }
+
+ if ( null != thread ) {
+ thread.doStop();
+ try {
+ thread.join();
+ } catch ( InterruptedException ex ) {
+ Log.d( TAG, "stopReadModeThread(): %s", ex );
}
}
}
- return success;
}
private static class NFCServiceHelper extends XWServiceHelper {
@@ -248,7 +1086,7 @@ public class NFCUtils {
private void receiveMessage( long rowid, NFCMsgSink sink, byte[] msg )
{
- Log.d( TAG, "receiveMessage()" );
+ Log.d( TAG, "receiveMessage(rowid=%d, len=%d)", rowid, msg.length );
receiveMessage( rowid, sink, msg, mAddr );
}
}
@@ -259,27 +1097,4 @@ public class NFCUtils {
super( context, rowid );
}
}
-
- private static byte[][] msgsFrom( String json, /*out*/ int[] gameID )
- {
- byte[][] result = null;
- try {
- JSONObject obj = new JSONObject( json );
- gameID[0] = obj.getInt( GAMEID );
- JSONArray arr = obj.getJSONArray( MSGS );
- if ( null != arr ) {
- result = new byte[arr.length()][];
- for ( int ii = 0; ii < arr.length(); ++ii ) {
- String str = arr.getString( ii );
- result[ii] = Utils.base64Decode( str );
- }
- }
- } catch ( JSONException ex ) {
- Assert.assertFalse( BuildConfig.DEBUG );
- result = null;
- }
- Log.d( TAG, "msgsFrom() => %s", (Object)result );
- return result;
- }
-
}
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NetLaunchInfo.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NetLaunchInfo.java
index 2a5bd78bc..183d9edce 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NetLaunchInfo.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NetLaunchInfo.java
@@ -294,6 +294,7 @@ public class NetLaunchInfo implements Serializable {
addP2PInfo( context );
break;
case COMMS_CONN_NFC:
+ addNFCInfo();
break;
default:
Assert.fail();
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RelayService.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RelayService.java
index db684dd54..f49343e3a 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RelayService.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RelayService.java
@@ -735,9 +735,9 @@ public class RelayService extends XWJIService
Log.e( TAG, "fail sending to %s", udpSocket );
Log.ex( TAG, ex );
Log.i( TAG, "Restarting threads to force new socket" );
- ConnStatusHandler.updateStatusOut( service, null,
- CommsConnType.COMMS_CONN_RELAY,
- true );
+ ConnStatusHandler
+ .updateStatusOut( service, CommsConnType.COMMS_CONN_RELAY,
+ true );
closeUDPSocket( udpSocket );
service.m_handler.post( new Runnable() {
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Utils.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Utils.java
index f0a355c51..69fb6c28e 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Utils.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Utils.java
@@ -615,6 +615,40 @@ public class Utils {
return Looper.getMainLooper().equals(Looper.myLooper());
}
+ // But see hexArray above
+ private static final String HEX_CHARS = "0123456789ABCDEF";
+ private static char[] HEX_CHARS_ARRAY = HEX_CHARS.toCharArray();
+
+ public static String ba2HexStr( byte[] input )
+ {
+ StringBuffer sb = new StringBuffer();
+
+ for ( byte byt : input ) {
+ sb.append(HEX_CHARS_ARRAY[(byt >> 4) & 0x0F]);
+ sb.append(HEX_CHARS_ARRAY[byt & 0x0F]);
+ }
+
+ String result = sb.toString();
+ return result;
+ }
+
+ public static byte[] hexStr2ba( String data )
+ {
+ data = data.toUpperCase();
+ Assert.assertTrue( 0 == data.length() % 2 );
+ byte[] result = new byte[data.length() / 2];
+
+ for (int ii = 0; ii < data.length(); ii += 2 ) {
+ int one = HEX_CHARS.indexOf(data.charAt(ii));
+ Assert.assertTrue( one >= 0 );
+ int two = HEX_CHARS.indexOf(data.charAt(ii + 1));
+ Assert.assertTrue( two >= 0 );
+ result[ii/2] = (byte)((one << 4) | two);
+ }
+
+ return result;
+ }
+
public static String base64Encode( byte[] in )
{
return Base64.encodeToString( in, Base64.NO_WRAP );
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/WiDirService.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/WiDirService.java
index 315f095c5..f795ddfb8 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/WiDirService.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/WiDirService.java
@@ -172,15 +172,15 @@ public class WiDirService extends XWService {
private static void updateStatusOut( boolean success )
{
ConnStatusHandler
- .updateStatusOut( XWApp.getContext(), null,
+ .updateStatusOut( XWApp.getContext(),
CommsConnType.COMMS_CONN_P2P, success );
}
private static void updateStatusIn( boolean success )
{
ConnStatusHandler
- .updateStatusIn( XWApp.getContext(), null,
- CommsConnType.COMMS_CONN_P2P, success );
+ .updateStatusIn( XWApp.getContext(), CommsConnType.COMMS_CONN_P2P,
+ success );
}
public static void init( Context context )
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWPrefs.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWPrefs.java
index 22cdd8972..cb5f1a372 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWPrefs.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWPrefs.java
@@ -61,9 +61,10 @@ public class XWPrefs {
return getPrefsBoolean( context, R.string.key_enable_dup_invite, false );
}
- public static boolean getNFCToSelfEnabled( Context context )
+ public static boolean moveCountEnabled( Context context )
{
- return getPrefsBoolean( context, R.string.key_enable_nfc_toself, false );
+ return getPrefsBoolean( context, R.string.key_enable_pending_count,
+ BuildConfig.DEBUG );
}
public static boolean getIgnoreFCM( Context context )
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/CommsAddrRec.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/CommsAddrRec.java
index d419e496d..8a04996af 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/CommsAddrRec.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/CommsAddrRec.java
@@ -80,6 +80,8 @@ public class CommsAddrRec {
id = R.string.invite_choice_data_sms; break;
case COMMS_CONN_P2P:
id = R.string.invite_choice_p2p; break;
+ case COMMS_CONN_NFC:
+ id = R.string.invite_choice_nfc; break;
default:
Assert.assertFalse( BuildConfig.DEBUG );
}
diff --git a/xwords4/android/app/src/main/res/drawable/in_arrow.png b/xwords4/android/app/src/main/res/drawable/in_arrow.png
deleted file mode 100644
index aa6d87f03..000000000
Binary files a/xwords4/android/app/src/main/res/drawable/in_arrow.png and /dev/null differ
diff --git a/xwords4/android/app/src/main/res/drawable/in_arrow_active.png b/xwords4/android/app/src/main/res/drawable/in_arrow_active.png
deleted file mode 100644
index fac65b7a6..000000000
Binary files a/xwords4/android/app/src/main/res/drawable/in_arrow_active.png and /dev/null differ
diff --git a/xwords4/android/app/src/main/res/drawable/out_arrow.png b/xwords4/android/app/src/main/res/drawable/out_arrow.png
deleted file mode 100644
index 4ee78c051..000000000
Binary files a/xwords4/android/app/src/main/res/drawable/out_arrow.png and /dev/null differ
diff --git a/xwords4/android/app/src/main/res/drawable/out_arrow_active.png b/xwords4/android/app/src/main/res/drawable/out_arrow_active.png
deleted file mode 100644
index 46e0c14ae..000000000
Binary files a/xwords4/android/app/src/main/res/drawable/out_arrow_active.png and /dev/null differ
diff --git a/xwords4/android/app/src/main/res/layout/game_list_item.xml b/xwords4/android/app/src/main/res/layout/game_list_item.xml
index 49e924837..1c3ee0231 100644
--- a/xwords4/android/app/src/main/res/layout/game_list_item.xml
+++ b/xwords4/android/app/src/main/res/layout/game_list_item.xml
@@ -38,6 +38,11 @@
android:paddingLeft="8dip"
android:paddingRight="8dip"
>
+
+
key_na_dicts
key_enable_debug
key_enable_dup_invite
- key_enable_nfc_toself
+ key_enable_pending_count
key_enable_sms_toself
key_show_fcm2
key_nag_intervals
@@ -152,7 +152,6 @@
eehouse.org
- application/org.eehouse.android.xw4
eehouse.org
application/x-xwordsinvite
diff --git a/xwords4/android/app/src/main/res/values/strings.xml b/xwords4/android/app/src/main/res/values/strings.xml
index 0b55fb325..66c9a69ca 100644
--- a/xwords4/android/app/src/main/res/values/strings.xml
+++ b/xwords4/android/app/src/main/res/values/strings.xml
@@ -58,7 +58,7 @@
how far along they are. I may list "tiles left" someday
instead... -->
- - %1$d move played
+ - One move played
- %1$d moves played
- - This game is waiting for %1$d remote
+
- This game is waiting for one remote
player. Would you like to invite someone to join -- assuming you
haven\'t already?
- This game is waiting for %1$d remote
@@ -677,6 +677,7 @@
+
- One tile left in pool.
- %1$d tiles left in pool.
@@ -1039,7 +1040,7 @@
substituted for "%1$s". (The funky \u003c and friends are
encodings for the greater-than and less-than symbols which
are not legal in xml strings.)-->
- <a href=\"%1$s\">Tap here</a> (or the full link below to join this game. If you already have CrossWords you can open the attachment to do so). <br \\> <br \\> (Full link: %1$s )
+ Tap the full link below to join this game. <br \\> <br \\> (Full link: %1$s )
Tap the link to accept my
invitation and join a CrossWords game: %1$s
@@ -1845,9 +1846,9 @@
Tap to download and install
- [Winner] %s: %d
- [Resigned] %s: %d
- [#%d] %s: %d
+ [Winner] %1$s: %2$d
+ [Resigned] %1$s: %2$d
+ [#%1$d] %2$s: %3$d
You and the host of this
game are using different versions of the wordlist %1$s.
@@ -2078,6 +2079,7 @@
%1$s passed (0 points)
+ - %1$s played %2$s for one point
- %1$s played %2$s for %3$d points
@@ -2197,9 +2199,9 @@
Accept duplicate invites
Fake locale for translation
Accept invitations more than once
+ Show Pending messages
+ Show number not yet acknowledged
Reminder intervals (minutes1,minutes2,…)
- Enable NFC to self
- Fake invitation to aid debugging
Short-circuit SMS to self
Skip radio when phone numbers same
Pretend to have radio
@@ -2397,5 +2399,7 @@
they\'re committed as moves -- by long-tapping, same as committed
words.\n\nUse this feature to check the validity of words you\'re
thinking of playing, or to look up an unfamiliar word provided as a
- hint.
+ hint.
+
+ For transmitting CrossWords moves
diff --git a/xwords4/android/app/src/main/res/xml/apduservice.xml b/xwords4/android/app/src/main/res/xml/apduservice.xml
new file mode 100644
index 000000000..adf18e767
--- /dev/null
+++ b/xwords4/android/app/src/main/res/xml/apduservice.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
diff --git a/xwords4/android/app/src/main/res/xml/xwprefs.xml b/xwords4/android/app/src/main/res/xml/xwprefs.xml
index c911ca6fc..755ac4bbf 100644
--- a/xwords4/android/app/src/main/res/xml/xwprefs.xml
+++ b/xwords4/android/app/src/main/res/xml/xwprefs.xml
@@ -390,10 +390,10 @@
android:defaultValue="false"
/>
-
+
+
+
diff --git a/xwords4/android/img_src/out_arrow.svg b/xwords4/android/img_src/out_arrow.svg
new file mode 100644
index 000000000..3cd4537d4
--- /dev/null
+++ b/xwords4/android/img_src/out_arrow.svg
@@ -0,0 +1,69 @@
+
+
+
+
diff --git a/xwords4/android/jni/xportwrapper.c b/xwords4/android/jni/xportwrapper.c
index f3c8597d3..ca5ed0b65 100644
--- a/xwords4/android/jni/xportwrapper.c
+++ b/xwords4/android/jni/xportwrapper.c
@@ -148,7 +148,6 @@ and_xport_sendNoConn( const XP_U8* buf, XP_U16 len, const XP_UCHAR* msgNo,
static void
and_xport_countChanged( void* closure, XP_U16 count )
{
- XP_LOGF( "%s(count=%d)", __func__, count );
AndTransportProcs* aprocs = (AndTransportProcs*)closure;
if ( NULL != aprocs && NULL != aprocs->jxport ) {
JNIEnv* env = ENVFORME( aprocs->ti );
diff --git a/xwords4/android/res_src/values-b+zh+HANS+CN/strings.xml b/xwords4/android/res_src/values-b+zh+HANS+CN/strings.xml
index f0e3bda09..f8a7fa5da 100644
--- a/xwords4/android/res_src/values-b+zh+HANS+CN/strings.xml
+++ b/xwords4/android/res_src/values-b+zh+HANS+CN/strings.xml
@@ -24,7 +24,7 @@
%1$s(%2$d)
%1$s:
设置
- 【#%d】%s:%d
+ 【#%1$d】%2$s:%3$d
我的游戏
新游戏
diff --git a/xwords4/android/res_src/values-ba_CK/strings.xml b/xwords4/android/res_src/values-ba_CK/strings.xml
index 2256db37b..aa23c28d0 100644
--- a/xwords4/android/res_src/values-ba_CK/strings.xml
+++ b/xwords4/android/res_src/values-ba_CK/strings.xml
@@ -59,7 +59,7 @@
how far along they are. I may list "tiles left" someday
instead... -->
- - %1$d evom deyalp
+ - 1 evom deyalp
- %1$d sevom deyalp
- - %1$d MOVE PLAYED
+ - 1 MOVE PLAYED
- %1$d MOVES PLAYED
- - %1$d coup joué
+ - 1 coup joué
- %1$d coups joués
- - %1$d jeton restant dans le sac.
+ - 1 jeton restant dans le sac.
- %1$d jetons restants dans le sac.
@@ -774,7 +774,7 @@ tous les chevalets :\n-->
- Un jeton restant dans les chevalets cachés :
\n
- - %d jetons restants dans le sac et les chevalets cachés :
+
- %1$d jetons restants dans le sac et les chevalets cachés :
\n
Toucher pour télécharger et installer
- [Vainqueur] %s : %d
+ [Vainqueur] %1$s : %2$d
- [Abandon] %s : %d
+ [Abandon] %1$s : %2$d
- [%de] %s : %d
+ [%1$de] %2$s : %3$d
@@ -2653,7 +2653,7 @@ pour voir ce qui est disponible.
- - %1$s a joué %2$s pour %3$d point
+ - %1$s a joué %2$s pour un point
- %1$s a joué %2$s pour %3$d points
@@ -2763,7 +2763,7 @@ d\'autres joueurs quand elle sera ouverte.)
- - %1$d joueur
+ - 1 joueur
- %1$d joueurs
@@ -2924,13 +2924,6 @@ les release builds)
Intervalles des rappels (minutes1,minutes2,…)
-
-
- Activer la NFC à soi-même
-
-
- Fausse invitation pour aider le
-débuggage
Court-circuiter les SMS vers soi-même
@@ -3145,10 +3138,6 @@ Merci de me faire savoir si vous aimez cette fonctionnalité, de signaler les pl
Wifi Direct
En appairer d\'autres
Invitation par WiFi direct
-
- - Sélectionnez le nom de l\'appareil WiFi Direct que vous souhaitez inviter à votre nouvelle partie, puis touchez \"%2$s\".
- - Sélectionnez les noms des appareils WiFi Direct que vous souhaitez inviter à votre nouvelle partie, puis touchez \"%2$s\".
-
Seuls les appareils actuellement disponibles sont affichés. Si un appareil à proximité ne s\'affiche pas, assurez-vous que le WiFi est allumé, que CrossWords est installé, et que le WiFi Direct est activé.
Il n\'y a actuellement pas d\'appareils accessibles par WiFi Direct qui ont CrossWords installé.
Utiliser le WiFi Direct pour jouer contre un appareil faisant du WiFi Direct, sur lequel CrossWords est installé.
@@ -3212,7 +3201,7 @@ Vous pouvez la ré-ouvrir pour que la permission soit redemandée. Ou vous pouve
Rescanner
- Êtes-vous sûr de vouloir oublier l\'appareil sélectionné \?
- - "Êtes-vous sûr de vouloir oublier les %1d appareils sélectionnés \?"
+ - "Êtes-vous sûr de vouloir oublier les %1$d appareils sélectionnés \?"
"
\n
@@ -3223,4 +3212,4 @@ Vous pouvez la ré-ouvrir pour que la permission soit redemandée. Ou vous pouve
Cette notification est présente dès que CrossWords tourne en tâche de fond pour recevoir des messages Bluetooth. Elle reste pendant environ 15 minutes après que CrossWords ait démarré ou qu\'un message Bluetooth ait été reçu.
Cette nouvelle option lance l\'appli SMS par défaut avec une invitation prête à l\'envoie… si elle marche. Les applis SMS sont toutes différentes : certaine sont réticentes.
Impossible de lancer l\'appli SMS
-
\ No newline at end of file
+
diff --git a/xwords4/android/res_src/values-ja/strings.xml b/xwords4/android/res_src/values-ja/strings.xml
index 0101da473..37943147c 100644
--- a/xwords4/android/res_src/values-ja/strings.xml
+++ b/xwords4/android/res_src/values-ja/strings.xml
@@ -331,7 +331,7 @@
ゲームの履歴
最終スコア
辞めてもよろしいですか?
- [辞めました] %s: %d
+ [辞めました] %1$s: %2$d
質問…
ヒントがあります
今後表示しない
@@ -505,8 +505,8 @@
タップすると %1$s を更新
新しいバージョンの %1$s
タップすると、ダウンロードとインストール
- [勝ち] %s: %d
- [#%d] %s: %d
+ [勝ち] %1$s: %2$d
+ [#%1$d] %2$s: %3$d
あなたとこのゲームのホストは、異なるバージョンの単語リスト %1$s を使用しています。
単語リストの不一致
あなたは単語リスト %1$s を使用していますが、ゲームのホストは %2$s を使用しています。%3$s も使用しますか?
@@ -727,8 +727,6 @@
翻訳の擬似地域
2 回以上の招待を受け入れます
リマインダーの間隔 (分1、分2、...)
- 自分への NFC を有効にする
- デバッグを支援するための擬似招待状
自分に SMS を短絡させる
電話番号が同じときに無線をスキップします
無線があるとみなす
@@ -855,12 +853,6 @@
Wifi ダイレクト
ペアの詳細
WiFi ダイレクトへの招待
-
- - 新しいゲームに招待する WiFi
-ダイレクトデバイスの名前を選択し、
-\"%2$s\" をタップしてください。
-
-
現在利用可能なデバイスだけが表示されます。
近くのデバイス表示されていない場合、WiFi がオンになっていること、
クロスワードがインストールされていること、WiFi ダイレクトのプレイが
@@ -1006,4 +998,4 @@ WiFi ダイレクト経由で接続可能なデバイスはありません。単語を移動としてコミットする前に -- 長押しして、コミットされた単語と同じように単語を調べることができます。
\n
\nこの機能を使用して、考えている単語の有効性を確認したり、ヒントとして提供される見慣れない単語を調べたりします。
-
\ No newline at end of file
+
diff --git a/xwords4/android/res_src/values-nb-rNO/strings.xml b/xwords4/android/res_src/values-nb-rNO/strings.xml
index 729a51b44..1ed6c9430 100644
--- a/xwords4/android/res_src/values-nb-rNO/strings.xml
+++ b/xwords4/android/res_src/values-nb-rNO/strings.xml
@@ -12,7 +12,7 @@
Spillere invitert til rom \"%1$s\"
Spill over
- - %1$d trekk gjort
+ - 1 trekk gjort
- %1$d trekk gjort
Slett
@@ -388,7 +388,7 @@
Siste advarsel: %1$s
Din motstander
- - %1$s spilte %2$s for %3$d poeng
+ - %1$s spilte %2$s for 1 poeng
%1$s mistet en tur
@@ -566,10 +566,6 @@
La oss spille CrossWords (rom %1$s)
Send invitasjon via %1$s
e-post
-
- - Er du sikker på at du ønsker å bytte valgt fil (%2$s)?
- - Er du sikker på at du ønsker å bytte valgt filer (%2$s)?
-
Sludring for %1$s
Tomme felter kan ikke dele spilte flis.
Må spille to eller flere flis i første trekk.
@@ -623,8 +619,8 @@
\nDu kan skru på spill via SMS nå, eller senere.
Skru på relé-spill
Skru av relé-spill
- [Seierherre] %s: %d
- [Resignert] %d: %d
+ [Seierherre] %1$s: %2$d
+ [Resignert] %1$s: %2$d
Samsvarte ikke med ordliste
Gjeninnlaster spill med %1$s
(Ikke i eksternt-/SD-kort -minne)
@@ -681,8 +677,6 @@
Godta duplikatinvitasjoner
Juks til lokale for oversettelse
Påminnelsesintervall (minutt1,minutt2,…)
- Skru på NFC til egen enhet
- Juks til invitasjon for å hjelpe i feilrettingsøyemed
Bruk ny/eksperimentell SMS-kode
(Krever at motparten bruker det også)
Send via NFC til egen ehet?
@@ -764,7 +758,7 @@
Du kan skru på relé-spill nå, eller senere.
Du kan skru på relé-spill nå, eller senere, eller fjerne det fra dette spillet.
Er du sikker på at du ønsker å skru av spill på relé-et
- [Nr.%d] %s: %d
+ [Nr.%1$d] %2$s: %3$d
Du og verten for dette spillet bruker forskjellige versjoner av ordlisten %1$s.
Selv om de kan være høyere
NFC er skrudd av på denne enheten. Du kan bruke systeminnstillingene for å skru det på.
@@ -798,10 +792,6 @@
\n
\n(Sletting av arkivgruppen er trygt fordi den vil bli gjenopprettet igjen når det trengs.)
Plukk flis med bokstavene avdekt
-
- - Velg navnet på Wi-Fi Direct-enheten du ønsker å invitere til spillet ditt, trykk så \"%2$s\".
- - Velg navnet på Wi-Fi Direct-enhetene du ønsker å invitere til spillet ditt, trykk så \"%2$s\".
-
Kun spillere som er tilgjengelig vises. Hvis en enhet i nærheten ikke vises, forsikre deg om at Wi-Fi er slått på, og at CrossWords er installert, og at spill via Wi-Fi Direct er påslått.
Listen over enheter er tom. Bruk \"Skann spill\"-knappen for å skanne dine gamle spill for motstandere. Bruk \"+\"knappen for å skrive inn enhets-ID-er direkte.
@@ -1002,4 +992,4 @@
Kun prøv: Ingen FCM-meldinger
Kun FCM: Ingen prøving
Forvalg: Miks prøving og FCM
-
\ No newline at end of file
+
diff --git a/xwords4/android/res_src/values-nl/strings.xml b/xwords4/android/res_src/values-nl/strings.xml
index e47373f8c..d0520ccd4 100644
--- a/xwords4/android/res_src/values-nl/strings.xml
+++ b/xwords4/android/res_src/values-nl/strings.xml
@@ -15,7 +15,7 @@
Spel voorbij is kamer \"%1$s\"
Spel voorbij
- - %1$d zet gespeeld
+ - 1 zet gespeeld
- %1$d zetten gespeeld
Verwijder
@@ -41,11 +41,11 @@
Verander de naam van dit spel naar:
Verander de naam van dit spel (op dit apparaat) naar:
- - Weet je zeker dat je %1$d geselecteerd spel wilt verwijderen? Deze actie kan niet ongedaan gemaakt worden.
+ - Weet je zeker dat je 1 geselecteerd spel wilt verwijderen? Deze actie kan niet ongedaan gemaakt worden.
- Weet je zeker dat je %1$d geselecteerde spellen wilt verwijderen? Deze actie kan niet ongedaan gemaakt worden.
- - Weet je zeker dat je %1$d geselecteerd spel wilt resetten?\n\n(Resetten verwijderd alle zetten en verbindingsinformatie.)
+ - Weet je zeker dat je 1 geselecteerd spel wilt resetten?\n\n(Resetten verwijderd alle zetten en verbindingsinformatie.)
- Weet je zeker dat je %1$d geselecteerde spellen wilt resetten?\n\n(Resetten verwijderd alle zetten en verbindingsinformatie.)
CrossWords Woordenlijsten
@@ -120,14 +120,14 @@
3W
(Nog geen zetten gedaan)
- - Dit spel wacht op %1$d speler op afstand. Wil je iemand uitnodigen om mee te doen, indien je dit nog niet gedaan hebt?
+ - Dit spel wacht op 1 speler op afstand. Wil je iemand uitnodigen om mee te doen, indien je dit nog niet gedaan hebt?
- Dit spel wacht op %1$d spelers op afstand. Wil je iemand uitnodigen om mee te doen, indien je dit nog niet gedaan hebt?
Of druk om uit te nodigen, indien het andere apparaat dichtbij is en Android Beam (NFC) ondersteund.
" (Je verwacht meerdere spelers op afstand. Je hoeft ze niet allemaal tegelijk uit te nodigen, maar dit bericht zal niet verdwijnen totdat iedereen uitgenodigd is en de uitnodiging geaccepteerd heeft.)"
pntn
- - Apparaat %1$d verbonden met relay in kamer \"%2$s\". Wachtend op %3$d speler.
+ - Apparaat 1 verbonden met relay in kamer \"%2$s\". Wachtend op %1$d speler.
- Apparaat %1$d verbonden met relay in kamer \"%2$s\". Wachtend op %3$d spelers.
Alle spelers spelers zijn hier in kamer \"%1$s\".
@@ -181,11 +181,11 @@
Bonus voor het gebruiken van alle letters: 50\n
Score voor beurt: %1$d\n
- - %1$d letter in de zak.
+ - 1 letter in de zak.
- %1$d letters in de zak.
- - %1$d letter in de zak en op alle rekken:\n
+ - 1 letter in de zak en op alle rekken:\n
- %1$d letters in de zak en op alle rekken:\n
Weet je zeker dat je de laatste zet ongedaan wilt maken? (Je kan deze niet opnieuw uitvoeren.)
@@ -439,7 +439,7 @@
%1$s heeft je uitgenodigd om te spelen
Importeer contact
- - Selecteer %1$d telefoonnummer dat je voor je nieuwe spel uit wilt nodigen en druk op \"%2$s\".
+ - Selecteer 1 telefoonnummer dat je voor je nieuwe spel uit wilt nodigen en druk op \"%2$s\".
- Selecteer de %1$d telefoonnummers die je voor je nieuwe spel uit wilt nodigen en druk op \"%2$s\".
(Niet in contacten)
@@ -496,12 +496,12 @@
Druk om %1$s te updaten
Nieuwe versie van %1$s
Druk om te downloaden en te installeren
- [Winnaar] %s: %d
- [Opgegeven] %s: %d
- [#%d] %s: %d
+ [Winnaar] %1$s: %2$d
+ [Opgegeven] %1$s: %2$d
+ [#%1$d] %2$s: %3$d
Jij en de host van dit spel gebruiken een verschillende versie van de woordenlijst %1$s.
Woordenlijsten komen niet overeen
- Jij gebruikt woordenlijst %1$s, maar de host gebruikt %2$s. Wil je ook %2$s gebruiken?
+ Jij gebruikt woordenlijst %1$s, maar de host gebruikt %2$s. Wil je ook %3$s gebruiken?
" (Je zult deze eerst moeten downloaden.)"
Spel herladen met %1$s
Wachten op speluitnodiging
@@ -661,7 +661,7 @@
Jouw tegenstander
%1$s heeft de beurt overgeslagen (0 punten)
- - %1$s speelde %2$s voor %3$d punt
+ - %1$s speelde %2$s voor 1 punt
- %1$s speelde %2$s voor %3$d punten
@@ -682,7 +682,7 @@
Standaard
Hoeveelheid op dit apparaat
- - %1$d speler
+ - 1 speler
- %1$d spelers
Dubbele uitnodiging geweigerd: apparaat \"%1$s\" heeft al een uitnodiging voor dit spel geaccepteerd.
@@ -729,8 +729,6 @@
Neppe taal voor vertalingen
Accepteer uitnodigingen meer dan eens
Herinnering intervallen (minuten1,minuten2,...)
- Schakel NFC naar zelf in
- Nepuitnodiging om debuggen te ondersteunen
Stuur SMS berichten naar zelf
Schakel radio over als telefoonnummers gelijk zijn
Doe net alsof er een radio is
diff --git a/xwords4/android/res_src/values-pl/strings.xml b/xwords4/android/res_src/values-pl/strings.xml
index a1f00e7ff..5522c1ace 100644
--- a/xwords4/android/res_src/values-pl/strings.xml
+++ b/xwords4/android/res_src/values-pl/strings.xml
@@ -217,7 +217,7 @@
Używasz domyślnej nazwy gracza \"%1$s\". Czy chciałbyś spersonalizować swoje imię przed utworzeniem tej gry\?
%1$s (robot)
- - %1$d ruch zagrany
+ - 1 ruch zagrany
- %1$d ruchy zagrane
- %1$d ruchów zagranych
@@ -323,7 +323,7 @@
\n
przenieś (od %1$s w poprzek)
\n
- przenieś (z %1$ w dół)
+ przenieś (z %1$s w dół)
\n
Półka na początku: %1$s
\n
@@ -569,9 +569,8 @@
Stuknij, aby zaktualizować %1$s
Nowa wersja %1$s
Stuknij, aby pobrać i zainstalować
- [Zwycięzca] %1$s
- [Zrezygnował] %s: %d
- [#%d] %s: %d
+ [Zrezygnował] %1$s: %2$d
+ [#%1$d] %2$s: %3$d
Ty i gospodarz tej gry używacie różnych wersji listy słów %1$s.
Niedopasowanie listy słów
Używasz listy słów %1$s, ale gospodarz gry używa %2$s. Czy chciałbyś również użyć %3$s\?
@@ -709,7 +708,7 @@
Twój przeciwnik
%1$s minęło (0 punktów)
- - %1$s zagrał %2$s za %3$d punktów
+ - %1$s zagrał %2$s za 1 punktów
@@ -784,8 +783,6 @@
Fałszywy język do tłumaczenia
Akceptuj zaproszenia więcej niż raz
Przypomnienie w odstępach (1 minuty, 2 minut,...)
- Włącz NFC dla siebie (samoczynnie\?)
- Fałszywe zaproszenie do pomocy w debugowaniu
Akceptuj SMS do siebie
GSM
CDMA
@@ -907,7 +904,7 @@
Wybierz odkryte kafelki
Wymieszaj kafelki
" %."
- Dodaj %1$ do listy nauki
+ Dodaj %1$s do listy nauki
Włącz listę nauki
Lista do nauczenia…
%1$s dodane do %2$s listy do nauki
@@ -1005,4 +1002,4 @@
Możesz spojrzeć na słowa PRZED tym, jak są one popełnione jako ruchy - przez długie podsłuchiwanie, tak samo jak słowa popełnione.
\n
\nUżyj tej funkcji, aby sprawdzić poprawność słów, którymi zamierzasz zagrać, lub poszukaj nieznanego słowa podanego jako wskazówka.
-
\ No newline at end of file
+
diff --git a/xwords4/android/res_src/values-pt/strings.xml b/xwords4/android/res_src/values-pt/strings.xml
index 1b3e8786e..9af3a7ea5 100644
--- a/xwords4/android/res_src/values-pt/strings.xml
+++ b/xwords4/android/res_src/values-pt/strings.xml
@@ -1744,17 +1744,17 @@
Listas de palavras
Altere o nome deste jogo (apenas neste dispositivo) para:
- - Você tem certeza que deseja excluir o %1$d jogo selecionado? Esta ação não poderá ser desfeita.
+ - Você tem certeza que deseja excluir o 1 jogo selecionado? Esta ação não poderá ser desfeita.
- Você tem certeza que deseja excluir os %1$d jogos selecionados? Esta ação não poderá ser desfeita.
- - Você tem certeza de que deseja redefinir o %1$d jogo selecionado?\n\n(Redefinir apaga todos os movimentos e as informações de conexão.)
+ - Você tem certeza de que deseja redefinir o 1 jogo selecionado?\n\n(Redefinir apaga todos os movimentos e as informações de conexão.)
- Você tem certeza de que deseja redefinir os %1$d jogos selecionados?\n\n(Redefinir apaga todos os movimentos e as informações de conexão.)
Jogadores convidados
Jogadores convidados para a sala \"%1$s\"
- - %1$d movimento jogado
+ - 1 movimento jogado
- %1$d movimenotos jogados
@@ -1765,7 +1765,7 @@
Para quais jogadores deve a list de palavras %1$s ser o predefinição para os jogos novos\? (O idioma de %2$s será a predefinição para ambos.)
Conexão (através de %1$s)
- - Este jogo está aguardando %1$d jogador remoto. Gostaria de convidar alguém para se juntar -- supondo que você ainda não o fez\?
+ - Este jogo está aguardando 1 jogador remoto. Gostaria de convidar alguém para se juntar -- supondo que você ainda não o fez\?
- Este jogo está aguardando %1$d jogadores remotos. Gostaria de convidar alguém para se juntar -- supondo que você ainda não o fez\?
Conexões…
diff --git a/xwords4/android/res_src/values-sk/strings.xml b/xwords4/android/res_src/values-sk/strings.xml
index fbb0d6d85..ed75d7ca9 100644
--- a/xwords4/android/res_src/values-sk/strings.xml
+++ b/xwords4/android/res_src/values-sk/strings.xml
@@ -950,7 +950,6 @@
XLATE ME: This game has been deleted on
another device. You will not be able to play any
further.
- Slovo[á] %1$s nebolo nájdené v slovníku.
Chcete napriek tomu potvrdiť tento ťah?
Ťah stratený.
Neplatné slovo[á]
diff --git a/xwords4/android/scripts/copy-strings.py b/xwords4/android/scripts/copy-strings.py
index 8dcd73644..8b6ed1669 100755
--- a/xwords4/android/scripts/copy-strings.py
+++ b/xwords4/android/scripts/copy-strings.py
@@ -12,6 +12,7 @@ s_prefix = 'XLATE ME: '
# languages in which it's ok to make a standalone quantity="one" into
# quantity="other"
g_oneToOthers = ['values-ja']
+g_formatsPat = re.compile( '(%\d\$[sd])', re.DOTALL | re.MULTILINE )
sComment = """
DO NOT EDIT THIS FILE!!!!
@@ -19,6 +20,10 @@ sComment = """
Any changes you make to it will be lost.
"""
+def exitWithError(msg):
+ print 'ERROR:', msg
+ sys.exit(1)
+
def usage():
print "usage:", sys.argv[0], '[-k ]'
sys.exit(1)
@@ -85,9 +90,8 @@ def pluralsIsSame(engNames, plurals):
for item in plurals.getchildren():
text = item.text
if not text or 0 == len(text):
- print "bogus empty plurals item in", plurals.get('name')
+ exitWithError( "bogus empty plurals item in " + plurals.get('name'))
engItem = engItem
- sys.exit(1)
quantity = item.get('quantity')
if quantity in strings:
@@ -119,8 +123,7 @@ def checkPlurals( engNames, elem, src, verbose ):
for item in elem.getchildren():
if 0 == len(item.text):
ok = False
- print 'bad empty item', name
- sys.exit(1)
+ exitWithError( 'bad empty item ' + name )
return ok
def loadPlural(plural):
@@ -138,11 +141,15 @@ def writeDoc(doc, src, dest):
out = open( dest, "w" )
out.write( etree.tostring( doc, pretty_print=True, encoding="utf-8", xml_declaration=True ) )
+def exitWithFormatError(engSet, otherSet, name, path):
+ exitWithError( 'formats set mismatch: ' + str(engSet) \
+ + ' vs ' + str(otherSet) + '; ' + name \
+ + ' in file ' + path )
+
def checkOrConvertString(engNames, elem, verbose):
name = elem.get('name')
if not elem.text:
- print "ERROR: elem", name, "is empty"
- sys.exit(1)
+ exitWithError( 'elem' + name + " is empty" )
elif not name in engNames or elem.text.startswith(s_prefix):
ok = False
elif not 'string' == engNames[name]['type']:
@@ -167,7 +174,7 @@ def checkOrConvertString(engNames, elem, verbose):
ok = True
return ok
-def checkAndCopy( parser, engNames, src, dest, verbose ):
+def checkAndCopy( parser, engNames, engFormats, src, dest, verbose ):
doc = etree.parse(src, parser)
# strings
@@ -179,8 +186,43 @@ def checkAndCopy( parser, engNames, src, dest, verbose ):
if not checkPlurals(engNames, elem, src, verbose):
elem.getparent().remove(elem)
+ formats = getFormats( doc, src )
+ for name in formats:
+ if name in formats and not engFormats[name] == formats[name]:
+ exitWithFormatError( engFormats[name], formats[name], name, dest )
+
writeDoc(doc, src, dest)
+def setForElem( elem, name ):
+ result = set()
+ splits = re.split( g_formatsPat, elem.text )
+ nParts = len(splits)
+ if 1 < nParts:
+ for ii in range(nParts):
+ part = splits[ii]
+ if re.match( g_formatsPat, part ):
+ result.add( part )
+ # print 'setForElem(', name, ') =>', result
+ return result
+
+def getFormats( doc, path ):
+ result = {}
+ for elem in doc.findall('string'):
+ name = elem.get('name')
+ result[name] = setForElem( elem, name )
+ for elem in doc.findall('plurals'):
+ name = elem.get('name')
+ for item in elem.findall('item'):
+ quantity = item.get('quantity')
+ if not item.text or 0 == len(item.text):
+ exitWithError( 'plurals ' + name + ' has empty quantity ' + quantity \
+ + ' in file ' + lang )
+ else:
+ add = name + '/' + quantity
+ result[add] = setForElem( item, add )
+ # print 'getFormats(', path, ') => ', result
+ return result
+
def main():
# add these via params later
excepts = ['values-ca_PS', 'values-ba_CK']
@@ -198,12 +240,12 @@ def main():
# summarize the english file
wd = os.path.dirname(sys.argv[0])
path = wd + '/../app/src/main/res/values/strings.xml'
- engNames = {}
- engFormats = {}
parser = etree.XMLParser(remove_blank_text=True, encoding="utf-8")
engDoc = etree.parse(path, parser)
- pat = re.compile( '(%\d\$[sd])', re.DOTALL | re.MULTILINE )
+ engFormats = getFormats( engDoc, path )
+
+ engNames = {}
for typ in ['string', 'plurals']:
for elem in engDoc.findall(typ):
name = elem.get('name')
@@ -227,7 +269,7 @@ def main():
verbose = 0 == len(verboses) or 0 < len([verb for verb in verboses if verb in path])
print "*** looking at %s ***" % (path)
dest = path.replace( 'res_src', 'app/src/main/res', 1 )
- checkAndCopy( parser, engNames, path, dest, verbose )
+ checkAndCopy( parser, engNames, engFormats, path, dest, verbose )
##############################################################################
if __name__ == '__main__':
diff --git a/xwords4/common/board.c b/xwords4/common/board.c
index f5cfb3ec8..6aab7422c 100644
--- a/xwords4/common/board.c
+++ b/xwords4/common/board.c
@@ -2382,19 +2382,16 @@ XP_Bool
coordToCell( const BoardCtxt* board, XP_S16 xx, XP_S16 yy, XP_U16* colP,
XP_U16* rowP )
{
- XP_U16 col, row;
- XP_U16 maxCols = model_numCols( board->model );
+ const XP_U16 maxCols = model_numCols( board->model );
XP_S16 gotCol = -1;
XP_S16 gotRow = -1;
- const ScrollData* hsd = &board->sd[SCROLL_H];
- const ScrollData* vsd = &board->sd[SCROLL_V];
xx -= board->boardBounds.left;
yy -= board->boardBounds.top;
if ( xx >= 0 && yy >= 0 ) {
-
- for ( col = hsd->offset; col < maxCols; ++col ) {
+ const ScrollData* hsd = &board->sd[SCROLL_H];
+ for ( XP_U16 col = hsd->offset; col < maxCols; ++col ) {
xx -= hsd->dims[col];
if ( xx <= 0 ) {
gotCol = col;
@@ -2402,8 +2399,9 @@ coordToCell( const BoardCtxt* board, XP_S16 xx, XP_S16 yy, XP_U16* colP,
}
}
- for ( row = vsd->offset; row < maxCols; ++row ) {
- yy -= vsd->dims[col];
+ const ScrollData* vsd = &board->sd[SCROLL_V];
+ for ( XP_U16 row = vsd->offset; row < maxCols; ++row ) {
+ yy -= vsd->dims[row];
if ( yy <= 0 ) {
gotRow = row;
break;
diff --git a/xwords4/common/boarddrw.c b/xwords4/common/boarddrw.c
index 7b732e54d..7010dfd1b 100644
--- a/xwords4/common/boarddrw.c
+++ b/xwords4/common/boarddrw.c
@@ -373,7 +373,7 @@ drawCell( BoardCtxt* board, const XP_U16 col, const XP_U16 row, XP_Bool skipBlan
XP_Bool success = XP_TRUE;
XP_Rect cellRect = {0};
Tile tile;
- XP_Bool isBlank, isEmpty, recent, pending = XP_FALSE;
+ XP_Bool isBlank, isEmpty, recent = XP_FALSE, pending = XP_FALSE;
XWBonusType bonus;
ModelCtxt* model = board->model;
DictionaryCtxt* dict = model_getDictionary( model );
diff --git a/xwords4/common/comms.c b/xwords4/common/comms.c
index bbf0cd805..825184a80 100644
--- a/xwords4/common/comms.c
+++ b/xwords4/common/comms.c
@@ -2647,26 +2647,27 @@ comms_getStats( CommsCtxt* comms, XWStreamCtxt* stream )
(XP_UCHAR*)"msg queue len: %d\n", comms->queueLen );
stream_catString( stream, buf );
+ XP_U16 indx = 0;
for ( elem = comms->msgQueueHead; !!elem; elem = elem->next ) {
XP_SNPRINTF( buf, sizeof(buf),
- " - channelNo=%.4X; msgID=" XP_LD "; len=%d\n",
- elem->channelNo, elem->msgID, elem->len );
+ "%d: - channelNo=%.4X; msgID=" XP_LD "; len=%d\n",
+ indx++, elem->channelNo, elem->msgID, elem->len );
stream_catString( stream, buf );
}
for ( rec = comms->recs; !!rec; rec = rec->next ) {
- XP_SNPRINTF( (XP_UCHAR*)buf, sizeof(buf),
- (XP_UCHAR*)" Stats for channel: %.4X\n",
+ XP_SNPRINTF( (XP_UCHAR*)buf, sizeof(buf),
+ (XP_UCHAR*)"Stats for channel %.4X\n",
rec->channelNo );
stream_catString( stream, buf );
- XP_SNPRINTF( (XP_UCHAR*)buf, sizeof(buf),
- (XP_UCHAR*)"Last msg sent: " XP_LD "\n",
+ XP_SNPRINTF( (XP_UCHAR*)buf, sizeof(buf),
+ (XP_UCHAR*)" Last msg sent: " XP_LD "; ",
rec->nextMsgID );
stream_catString( stream, buf );
- XP_SNPRINTF( (XP_UCHAR*)buf, sizeof(buf),
- (XP_UCHAR*)"Last msg received: %d\n",
+ XP_SNPRINTF( (XP_UCHAR*)buf, sizeof(buf),
+ (XP_UCHAR*)"last msg received: %d\n",
rec->lastMsgRcd );
stream_catString( stream, buf );
}
diff --git a/xwords4/common/comtypes.h b/xwords4/common/comtypes.h
index b36d99994..42130e2ae 100644
--- a/xwords4/common/comtypes.h
+++ b/xwords4/common/comtypes.h
@@ -47,6 +47,7 @@
#endif
#define MAX_COLS MAX_ROWS
+#define STREAM_VERS_DUPLICATE 0x1B
#define STREAM_VERS_DISABLEDS 0x1A
#define STREAM_VERS_DEVIDS 0x19
#define STREAM_VERS_MULTIADDR 0x18
@@ -88,7 +89,7 @@
#define STREAM_VERS_405 0x01
/* search for FIX_NEXT_VERSION_CHANGE next time this is changed */
-#define CUR_STREAM_VERS STREAM_VERS_DISABLEDS
+#define CUR_STREAM_VERS STREAM_VERS_DUPLICATE
typedef struct XP_Rect {
XP_S16 left;
diff --git a/xwords4/common/game.c b/xwords4/common/game.c
index 78e4f0a35..187a6e1c7 100644
--- a/xwords4/common/game.c
+++ b/xwords4/common/game.c
@@ -339,7 +339,8 @@ game_saveSucceeded( const XWGame* game, XP_U16 saveToken )
}
XP_Bool
-game_receiveMessage( XWGame* game, XWStreamCtxt* stream, CommsAddrRec* retAddr )
+game_receiveMessage( XWGame* game, XWStreamCtxt* stream,
+ const CommsAddrRec* retAddr )
{
ServerCtxt* server = game->server;
CommsMsgState commsState;
@@ -486,7 +487,9 @@ gi_copy( MPFORMAL CurGameInfo* destGI, const CurGameInfo* srcGI )
destGI->phoniesAction = srcGI->phoniesAction;
destGI->allowPickTiles = srcGI->allowPickTiles;
destGI->forceChannel = srcGI->forceChannel;
- XP_LOGF( "%s: copied forceChannel: %d", __func__, destGI->forceChannel );
+ destGI->inDuplicateMode = srcGI->inDuplicateMode;
+ XP_LOGF( "%s: copied forceChannel: %d; inDuplicateMode: %d", __func__,
+ destGI->forceChannel, destGI->inDuplicateMode );
for ( srcPl = srcGI->players, destPl = destGI->players, ii = 0;
ii < nPlayers; ++srcPl, ++destPl, ++ii ) {
@@ -565,6 +568,9 @@ gi_readFromStream( MPFORMAL XWStreamCtxt* stream, CurGameInfo* gi )
gi->phoniesAction = (XWPhoniesChoice)stream_getBits( stream, 2 );
gi->timerEnabled = stream_getBits( stream, 1 );
+ gi->inDuplicateMode = strVersion >= STREAM_VERS_DUPLICATE
+ ? stream_getBits( stream, 1 )
+ : XP_FALSE;
if ( strVersion >= STREAM_VERS_41B4 ) {
gi->allowPickTiles = stream_getBits( stream, 1 );
gi->allowHintRect = stream_getBits( stream, 1 );
@@ -640,6 +646,7 @@ gi_writeToStream( XWStreamCtxt* stream, const CurGameInfo* gi )
stream_putBits( stream, 1, gi->hintsNotAllowed );
stream_putBits( stream, 2, gi->phoniesAction );
stream_putBits( stream, 1, gi->timerEnabled );
+ stream_putBits( stream, 1, gi->inDuplicateMode );
stream_putBits( stream, 1, gi->allowPickTiles );
stream_putBits( stream, 1, gi->allowHintRect );
stream_putBits( stream, 1, gi->confirmBTConnect );
diff --git a/xwords4/common/game.h b/xwords4/common/game.h
index caeea7140..10d4d7187 100644
--- a/xwords4/common/game.h
+++ b/xwords4/common/game.h
@@ -84,7 +84,7 @@ void game_saveToStream( const XWGame* game, const CurGameInfo* gi,
void game_saveSucceeded( const XWGame* game, XP_U16 saveToken );
XP_Bool game_receiveMessage( XWGame* game, XWStreamCtxt* stream,
- CommsAddrRec* retAddr );
+ const CommsAddrRec* retAddr );
void game_dispose( XWGame* game );
diff --git a/xwords4/common/gameinfo.h b/xwords4/common/gameinfo.h
index aa931bc5b..608b7c059 100644
--- a/xwords4/common/gameinfo.h
+++ b/xwords4/common/gameinfo.h
@@ -58,6 +58,7 @@ typedef struct CurGameInfo {
XP_Bool timerEnabled;
XP_Bool allowPickTiles;
XP_Bool allowHintRect;
+ XP_Bool inDuplicateMode;
XWPhoniesChoice phoniesAction;
XP_Bool confirmBTConnect; /* only used for BT */
} CurGameInfo;
diff --git a/xwords4/common/nli.c b/xwords4/common/nli.c
index 1da8ebf6f..a49802443 100644
--- a/xwords4/common/nli.c
+++ b/xwords4/common/nli.c
@@ -89,7 +89,9 @@ nli_saveToStream( const NetLaunchInfo* nli, XWStreamCtxt* stream )
if ( types_hasType( nli->_conTypes, COMMS_CONN_RELAY ) ) {
stringToStream( stream, nli->room );
stringToStream( stream, nli->inviteID );
- stream_putU32( stream, nli->devID );
+ if ( 0 == NLI_VERSION ) {
+ stream_putU32( stream, nli->devID );
+ }
}
if ( types_hasType( nli->_conTypes, COMMS_CONN_BT ) ) {
stringToStream( stream, nli->btName );
@@ -128,7 +130,9 @@ nli_makeFromStream( NetLaunchInfo* nli, XWStreamCtxt* stream )
if ( types_hasType( nli->_conTypes, COMMS_CONN_RELAY ) ) {
stringFromStreamHere( stream, nli->room, sizeof(nli->room) );
stringFromStreamHere( stream, nli->inviteID, sizeof(nli->inviteID) );
- nli->devID = stream_getU32( stream );
+ if ( version == 0 ) {
+ nli->devID = stream_getU32( stream );
+ }
}
if ( types_hasType( nli->_conTypes, COMMS_CONN_BT ) ) {
stringFromStreamHere( stream, nli->btName, sizeof(nli->btName) );
diff --git a/xwords4/common/server.c b/xwords4/common/server.c
index 325f3b38a..85d5404dd 100644
--- a/xwords4/common/server.c
+++ b/xwords4/common/server.c
@@ -1,6 +1,6 @@
/* -*- compile-command: "cd ../linux && make -j3 MEMDEBUG=TRUE"; -*- */
/*
- * Copyright 1997-2009 by Eric House (xwords@eehouse.org). All rights
+ * Copyright 1997 - 2019 by Eric House (xwords@eehouse.org). All rights
* reserved.
*
* This program is free software; you can redistribute it and/or
@@ -84,6 +84,7 @@ typedef struct ServerVolatiles {
typedef struct ServerNonvolatiles {
XP_U32 lastMoveTime; /* seconds of last turn change */
+ XP_S32 dupTimerExpires;
XP_U8 nDevices;
XW_State gameState;
XW_State stateAfterShow;
@@ -103,6 +104,9 @@ typedef struct ServerNonvolatiles {
RemoteAddress addresses[MAX_NUM_PLAYERS];
XWStreamCtxt* prevMoveStream; /* save it to print later */
XWStreamCtxt* prevWordsStream;
+ XP_Bool dupTurnsMade[MAX_NUM_PLAYERS];
+ XP_Bool dupTurnsForced[MAX_NUM_PLAYERS];
+ XP_Bool dupTurnsSent; /* used on client only */
} ServerNonvolatiles;
struct ServerCtxt {
@@ -290,6 +294,9 @@ getNV( XWStreamCtxt* stream, ServerNonvolatiles* nv, XP_U16 nPlayers )
if ( STREAM_VERS_DICTNAME <= version ) {
nv->lastMoveTime = stream_getU32( stream );
}
+ if ( STREAM_VERS_DUPLICATE <= version ) {
+ nv->dupTimerExpires = stream_getU32( stream );
+ }
if ( version < STREAM_VERS_SERVER_SAVES_TOSHOW ) {
/* no longer used */
@@ -327,6 +334,15 @@ getNV( XWStreamCtxt* stream, ServerNonvolatiles* nv, XP_U16 nPlayers )
}
/* XP_LOGF( "%s: read streamVersion: 0x%x", __func__, nv->streamVersion ); */
#endif
+
+ if ( version >= STREAM_VERS_DUPLICATE ) {
+ for ( ii = 0; ii < nPlayers; ++ii ) {
+ nv->dupTurnsMade[ii] = stream_getBits( stream, 1 );
+ XP_LOGF( "%s(): dupTurnsMade[%d]: %d", __func__, ii, nv->dupTurnsMade[ii] );
+ nv->dupTurnsForced[ii] = stream_getBits( stream, 1 );
+ }
+ nv->dupTurnsSent = stream_getBits( stream, 1 );
+ }
} /* getNV */
static void
@@ -335,6 +351,7 @@ putNV( XWStreamCtxt* stream, const ServerNonvolatiles* nv, XP_U16 nPlayers )
XP_U16 ii;
stream_putU32( stream, nv->lastMoveTime );
+ stream_putU32( stream, nv->dupTimerExpires );
/* number of players is upper limit on device count */
stream_putBits( stream, NDEVICES_NBITS, nv->nDevices-1 );
@@ -358,6 +375,12 @@ putNV( XWStreamCtxt* stream, const ServerNonvolatiles* nv, XP_U16 nPlayers )
stream_putU8( stream, nv->streamVersion );
/* XP_LOGF( "%s: wrote streamVersion: 0x%x", __func__, nv->streamVersion ); */
#endif
+
+ for ( ii = 0; ii < nPlayers; ++ii ) {
+ stream_putBits( stream, 1, nv->dupTurnsMade[ii] );
+ stream_putBits( stream, 1, nv->dupTurnsForced[ii] );
+ }
+ stream_putBits( stream, 1, nv->dupTurnsSent );
} /* putNV */
static XWStreamCtxt*
diff --git a/xwords4/dawg/French/Makefile.ODS8 b/xwords4/dawg/French/Makefile.ODS8
new file mode 100644
index 000000000..4348f1e18
--- /dev/null
+++ b/xwords4/dawg/French/Makefile.ODS8
@@ -0,0 +1,36 @@
+# -*-mode: Makefile -*-
+# Copyright 2016 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.
+
+XWLANG=ODS8
+LANGCODE=fr_FR
+DICTNOTE = "From ods8.zip submitted by a user"
+
+TARGET_TYPE ?= WINCE
+
+include ../Makefile.langcommon
+
+# use sed to strip the bogus utf8 identifier. There must be a better
+# way, e.g. with iconv.
+$(XWLANG)Main.dict.gz: $(XWDICTPATH)/French/ods8.txt
+ cat $< | tr -d '\r' | sed 's,\xEF\xBB\xBF,,' | tr a-z A-Z | gzip >$@
+
+# Everything but creating of the Main.dict file is inherited from the
+# "parent" Makefile.langcommon in the parent directory.
+
+clean: clean_common
+ rm -f $(XWLANG)Main.dict.gz *.bin $(XWLANG)*.pdb $(XWLANG)*.seb
diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c
index c39445e2e..38555c936 100644
--- a/xwords4/linux/linuxmain.c
+++ b/xwords4/linux/linuxmain.c
@@ -178,27 +178,6 @@ makeDictForStream( CommonGlobals* cGlobals, XWStreamCtxt* stream )
return dict;
}
-static XP_Bool
-processMessage( CommonGlobals* cGlobals, XWStreamCtxt* stream,
- const CommsAddrRec* from, XP_Bool doDo )
-{
- XWGame* game = &cGlobals->game;
- CommsMsgState state;
- XP_Bool received = comms_checkIncomingStream( game->comms, stream, from, &state );
- XP_Bool draw = received && server_receiveMessage( game->server, stream );
- comms_msgProcessed( game->comms, &state, !draw );
-
- if ( doDo && draw ) {
- ServerCtxt* server = cGlobals->game.server;
- XP_U16 ii;
- for ( ii = 0; ii < 5; ++ii ) {
- (void)server_do( server );
- }
- }
-
- return draw;
-}
-
void
gameGotBuf( CommonGlobals* cGlobals, XP_Bool hasDraw, const XP_U8* buf,
XP_U16 len, const CommsAddrRec* from )
@@ -208,7 +187,7 @@ gameGotBuf( CommonGlobals* cGlobals, XP_Bool hasDraw, const XP_U8* buf,
XWGame* game = &cGlobals->game;
XWStreamCtxt* stream = stream_from_msgbuf( cGlobals, buf, len );
if ( !!stream ) {
- redraw = processMessage( cGlobals, stream, from, XP_FALSE );
+ redraw = game_receiveMessage( game, stream, from );
if ( redraw ) {
saveGame( cGlobals );
}
@@ -433,7 +412,7 @@ handle_messages_from( CommonGlobals* cGlobals, const TransportProcs* procs,
stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool)
params->vtMgr );
stream_putBytes( stream, buf, len );
- (void)processMessage( cGlobals, stream, NULL, XP_TRUE );
+ (void)game_receiveMessage( &cGlobals->game, stream, NULL );
stream_destroy( stream );
}
@@ -483,7 +462,7 @@ read_pipe_then_close( CommonGlobals* cGlobals, const TransportProcs* procs )
stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool)
params->vtMgr );
stream_putBytes( stream, buf, len );
- (void)processMessage( cGlobals, stream, NULL, XP_TRUE );
+ (void)game_receiveMessage( &cGlobals->game, stream, NULL );
stream_destroy( stream );
}
@@ -1162,7 +1141,7 @@ linux_relay_ioproc( GIOChannel* source, GIOCondition condition, gpointer data )
CommsAddrRec addr = {0};
addr_addType( &addr, COMMS_CONN_RELAY );
- redraw = processMessage( cGlobals, inboundS, &addr, XP_FALSE );
+ redraw = game_receiveMessage( &cGlobals->game, inboundS, &addr );
stream_destroy( inboundS );
}