diff --git a/xwords4/android/XWords4/AndroidManifest.xml b/xwords4/android/XWords4/AndroidManifest.xml
index 640303a36..1b634d862 100644
--- a/xwords4/android/XWords4/AndroidManifest.xml
+++ b/xwords4/android/XWords4/AndroidManifest.xml
@@ -22,11 +22,11 @@
to come from a domain that you own or have control over. -->
-
+
@@ -134,14 +134,12 @@
/>
-
-
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/xwords4/android/XWords4/project.properties b/xwords4/android/XWords4/project.properties
index 797fb4fc3..c1fd41ab1 100644
--- a/xwords4/android/XWords4/project.properties
+++ b/xwords4/android/XWords4/project.properties
@@ -10,4 +10,4 @@
# Indicates whether an apk should be generated for each density.
split.density=false
# Project target.
-target=android-7
+target=android-8
diff --git a/xwords4/android/XWords4/res/layout/game_list_item.xml b/xwords4/android/XWords4/res/layout/game_list_item.xml
index 1a6ab2cb1..771af6471 100644
--- a/xwords4/android/XWords4/res/layout/game_list_item.xml
+++ b/xwords4/android/XWords4/res/layout/game_list_item.xml
@@ -3,95 +3,114 @@
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:visibility="gone"
+ >
-
-
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
diff --git a/xwords4/android/XWords4/res/layout/game_list_tmp.xml b/xwords4/android/XWords4/res/layout/game_list_tmp.xml
deleted file mode 100644
index 00ec641a9..000000000
--- a/xwords4/android/XWords4/res/layout/game_list_tmp.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
diff --git a/xwords4/android/XWords4/res/raw/changes b/xwords4/android/XWords4/res/raw/changes
index 20c716fa5..b9613677d 100644
--- a/xwords4/android/XWords4/res/raw/changes
+++ b/xwords4/android/XWords4/res/raw/changes
@@ -5,18 +5,22 @@
-Crosswords 4.4 beta 56 release
-New with this release
- - Improve invitations: no more redirection through a website, and
- confirm before creating duplicate games
- - (For sideloading users only) No more browser involvement in
- updates: app can launch the installer directly to update
- itself
- - Remove notifications when their games are deleted
+Crosswords 4.4 beta 57 release
+
+New with this release
+
+ - Include new game information as an attachment in email invites
+ for use on devices that don't dispatch URLs correctly in received
+ email (e.g. some by HTC)
+ - Show final scores alert whenever a finished game is opened -- to
+ make it more clear that it's finished
+ - Fix flickering in main screen (games list)
+ - Add option, off by default, to keep rack tiles square even when
+ the screen is large enough that they can be taller
-Next up
- - One more idea for improving invitations
+Next up
+
- Allow grouping of games in collapsible user-defined categores: "Games with
Kati", "Finished games", etc.
diff --git a/xwords4/android/XWords4/res/values/app_name.xml b/xwords4/android/XWords4/res/values/app_name.xml
index 844bb12a6..095652a34 100644
--- a/xwords4/android/XWords4/res/values/app_name.xml
+++ b/xwords4/android/XWords4/res/values/app_name.xml
@@ -1,5 +1,5 @@
- 4.4 beta 56
+ 4.4 beta 57
diff --git a/xwords4/android/XWords4/res/values/common_rsrc.xml b/xwords4/android/XWords4/res/values/common_rsrc.xml
index 860b43b11..759d74e00 100644
--- a/xwords4/android/XWords4/res/values/common_rsrc.xml
+++ b/xwords4/android/XWords4/res/values/common_rsrc.xml
@@ -6,6 +6,7 @@
key_color_tiles
key_show_arrow
+ key_square_tiles
key_explain_robot
key_skip_confirm
key_sort_tiles
diff --git a/xwords4/android/XWords4/res/values/strings.xml b/xwords4/android/XWords4/res/values/strings.xml
index 41a15afcc..f6aab751a 100644
--- a/xwords4/android/XWords4/res/values/strings.xml
+++ b/xwords4/android/XWords4/res/values/strings.xml
@@ -1233,8 +1233,10 @@
encodings for the greater-than and less-than symbols which
are not legal in xml strings.)-->
\u003ca href=\"%1$s\"\u003ETap
- here\u003c/a\u003E (or the full link below) to accept my invitation and
- join this game.
+ here\u003c/a\u003E (or tap the full link below, or, if you already
+ have Crosswords installed, open the attachment) to accept my
+ invitation and join this game.
+
\u003cbr \\\u003E
\u003cbr \\\u003E
(full link: %1$s)
@@ -2154,4 +2156,13 @@
Moving is impossible until there
is more than one group.
+
+
+ Rematch
+
+ Square rack tiles
+ Even if they can be taller
+
diff --git a/xwords4/android/XWords4/res/xml/xwprefs.xml b/xwords4/android/XWords4/res/xml/xwprefs.xml
index 86fdece79..2cb350166 100644
--- a/xwords4/android/XWords4/res/xml/xwprefs.xml
+++ b/xwords4/android/XWords4/res/xml/xwprefs.xml
@@ -132,6 +132,11 @@
android:summary="@string/show_arrow_summary"
android:defaultValue="true"
/>
+
(width / 7) ) {
+ trayHt = width / 7;
+ }
heightUsed = trayHt + scoreHt + ((nCells - nToScroll) * cellSize);
}
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java
index b44021f29..cf6a4d6bd 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java
@@ -60,7 +60,7 @@ public class DBHelper extends SQLiteOpenHelper {
public static final String INVITEID = "INVITEID";
public static final String RELAYID = "RELAYID";
public static final String SEED = "SEED";
- public static final String SMSPHONE = "SMSPHONE";
+ public static final String SMSPHONE = "SMSPHONE"; // unused -- so far
public static final String LASTMOVE = "LASTMOVE";
public static final String GROUPID = "GROUPID";
@@ -100,7 +100,7 @@ public class DBHelper extends SQLiteOpenHelper {
,SEED, "INTEGER"
,DICTLANG, "INTEGER"
,DICTLIST, "TEXT"
- ,SMSPHONE, "TEXT"
+ ,SMSPHONE, "TEXT" // unused
,SCORES, "TEXT"
,CHAT_HISTORY, "TEXT"
,GAMEID, "INTEGER"
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java
index 7ace05033..a9495ef3b 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java
@@ -60,9 +60,10 @@ public class DBUtils {
private static long s_cachedRowID = -1;
private static byte[] s_cachedBytes = null;
+ private static long[] s_cachedRowIDs = null;
public static interface DBChangeListener {
- public void gameSaved( long rowid );
+ public void gameSaved( long rowid, boolean countChanged );
}
private static HashSet s_listeners =
new HashSet();
@@ -100,8 +101,7 @@ public class DBUtils {
long maxMillis )
{
GameSummary result = null;
- GameUtils.GameLock lock =
- new GameUtils.GameLock( rowid, false ).lock( maxMillis );
+ GameLock lock = new GameLock( rowid, false ).lock( maxMillis );
if ( null != lock ) {
result = getSummary( context, lock );
lock.unlock();
@@ -115,7 +115,7 @@ public class DBUtils {
}
public static GameSummary getSummary( Context context,
- GameUtils.GameLock lock )
+ GameLock lock )
{
initDB( context );
GameSummary summary = null;
@@ -129,7 +129,7 @@ public class DBUtils {
DBHelper.TURN, DBHelper.GIFLAGS,
DBHelper.CONTYPE, DBHelper.SERVERROLE,
DBHelper.ROOMNAME, DBHelper.RELAYID,
- DBHelper.SMSPHONE, DBHelper.SEED,
+ /*DBHelper.SMSPHONE,*/ DBHelper.SEED,
DBHelper.DICTLANG, DBHelper.GAMEID,
DBHelper.SCORES, DBHelper.HASMSGS,
DBHelper.LASTPLAY_TIME, DBHelper.REMOTEDEVS,
@@ -247,13 +247,13 @@ public class DBUtils {
return summary;
} // getSummary
- public static void saveSummary( Context context, GameUtils.GameLock lock,
+ public static void saveSummary( Context context, GameLock lock,
GameSummary summary )
{
saveSummary( context, lock, summary, null );
}
- public static void saveSummary( Context context, GameUtils.GameLock lock,
+ public static void saveSummary( Context context, GameLock lock,
GameSummary summary, String inviteID )
{
Assert.assertTrue( lock.canWrite() );
@@ -314,9 +314,12 @@ public class DBUtils {
long result = db.update( DBHelper.TABLE_NAME_SUM,
values, selection, null );
Assert.assertTrue( result >= 0 );
+ if ( result != rowid ) { // new row added
+ clearRowIDsCache();
+ }
}
- notifyListeners( rowid );
db.close();
+ notifyListeners( rowid, false );
}
} // saveSummary
@@ -372,7 +375,7 @@ public class DBUtils {
public static void setMsgFlags( long rowid, int flags )
{
setInt( rowid, DBHelper.HASMSGS, flags );
- notifyListeners( rowid );
+ notifyListeners( rowid, false );
}
public static void setExpanded( long rowid, boolean expanded )
@@ -447,10 +450,8 @@ public class DBUtils {
String selection = DBHelper.RELAYID + "='" + relayID + "'";
Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns,
selection, null, null, null, null );
+ result = new long[cursor.getCount()];
for ( int ii = 0; cursor.moveToNext(); ++ii ) {
- if ( null == result ) {
- result = new long[cursor.getCount()];
- }
result[ii] = cursor.getLong( cursor.getColumnIndex(ROW_ID) );
}
cursor.close();
@@ -469,11 +470,8 @@ public class DBUtils {
String selection = String.format( DBHelper.GAMEID + "=%d", gameID );
Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns,
selection, null, null, null, null );
-
+ result = new long[cursor.getCount()];
for ( int ii = 0; cursor.moveToNext(); ++ii ) {
- if ( null == result ) {
- result = new long[cursor.getCount()];
- }
result[ii] = cursor.getLong( cursor.getColumnIndex(ROW_ID) );
}
cursor.close();
@@ -574,7 +572,7 @@ public class DBUtils {
return result;
}
- public static String[] getRelayIDs( Context context, boolean noMsgs )
+ public static String[] getRelayIDs( Context context, long[][] rowIDs )
{
String[] result = null;
initDB( context );
@@ -582,26 +580,31 @@ public class DBUtils {
synchronized( s_dbHelper ) {
SQLiteDatabase db = s_dbHelper.getReadableDatabase();
- String[] columns = { DBHelper.RELAYID };
+ String[] columns = { ROW_ID, DBHelper.RELAYID };
String selection = DBHelper.RELAYID + " NOT null";
- if ( noMsgs ) {
- selection += " AND NOT " + DBHelper.HASMSGS;
- }
Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns,
selection, null, null, null, null );
+ int count = cursor.getCount();
+ if ( 0 < count ) {
+ result = new String[count];
+ if ( null != rowIDs ) {
+ rowIDs[0] = new long[count];
+ }
- while ( cursor.moveToNext() ) {
- ids.add( cursor.getString( cursor.
- getColumnIndex(DBHelper.RELAYID)) );
+ int idIndex = cursor.getColumnIndex(DBHelper.RELAYID);
+ int rowIndex = cursor.getColumnIndex(ROW_ID);
+ for ( int ii = 0; cursor.moveToNext(); ++ii ) {
+ result[ii] = cursor.getString( idIndex );
+ if ( null != rowIDs ) {
+ rowIDs[0][ii] = cursor.getLong( rowIndex );
+ }
+ }
}
cursor.close();
db.close();
}
- if ( 0 < ids.size() ) {
- result = ids.toArray( new String[ids.size()] );
- }
return result;
}
@@ -675,9 +678,9 @@ public class DBUtils {
}
}
- public static GameUtils.GameLock saveNewGame( Context context, byte[] bytes )
+ public static GameLock saveNewGame( Context context, byte[] bytes )
{
- GameUtils.GameLock lock = null;
+ GameLock lock = null;
initDB( context );
synchronized( s_dbHelper ) {
@@ -695,16 +698,16 @@ public class DBUtils {
long rowid = db.insert( DBHelper.TABLE_NAME_SUM, null, values );
setCached( rowid, null ); // force reread
+ clearRowIDsCache();
- lock = new GameUtils.GameLock( rowid, true ).lock();
-
- notifyListeners( rowid );
+ lock = new GameLock( rowid, true ).lock();
+ notifyListeners( rowid, true );
}
return lock;
}
- public static long saveGame( Context context, GameUtils.GameLock lock,
+ public static long saveGame( Context context, GameLock lock,
byte[] bytes, boolean setCreate )
{
Assert.assertTrue( lock.canWrite() );
@@ -722,13 +725,13 @@ public class DBUtils {
updateRow( context, DBHelper.TABLE_NAME_SUM, rowid, values );
setCached( rowid, null ); // force reread
- if ( -1 != rowid ) { // Is this possible? PENDING
- notifyListeners( rowid );
+ if ( -1 != rowid ) { // Means new game?
+ notifyListeners( rowid, false );
}
return rowid;
}
- public static byte[] loadGame( Context context, GameUtils.GameLock lock )
+ public static byte[] loadGame( Context context, GameLock lock )
{
long rowid = lock.getRowid();
Assert.assertTrue( -1 != rowid );
@@ -756,12 +759,16 @@ public class DBUtils {
public static void deleteGame( Context context, long rowid )
{
- GameUtils.GameLock lock = new GameUtils.GameLock( rowid, true ).lock();
- deleteGame( context, lock );
- lock.unlock();
+ GameLock lock = new GameLock( rowid, true ).lock( 300 );
+ if ( null != lock ) {
+ deleteGame( context, lock );
+ lock.unlock();
+ } else {
+ DbgUtils.logf( "deleteGame: unable to lock rowid %d", rowid );
+ }
}
- public static void deleteGame( Context context, GameUtils.GameLock lock )
+ public static void deleteGame( Context context, GameLock lock )
{
Assert.assertTrue( lock.canWrite() );
initDB( context );
@@ -771,34 +778,46 @@ public class DBUtils {
db.delete( DBHelper.TABLE_NAME_SUM, selection, null );
db.close();
}
- notifyListeners( lock.getRowid() );
+ clearRowIDsCache();
+ notifyListeners( lock.getRowid(), true );
}
public static long[] gamesList( Context context )
{
- long[] result = null;
+ long[] result;
+ synchronized( DBUtils.class ) {
+ if ( null == s_cachedRowIDs ) {
+ initDB( context );
+ synchronized( s_dbHelper ) {
+ SQLiteDatabase db = s_dbHelper.getReadableDatabase();
- initDB( context );
- synchronized( s_dbHelper ) {
- SQLiteDatabase db = s_dbHelper.getReadableDatabase();
-
- String[] columns = { ROW_ID };
- String orderBy = DBHelper.CREATE_TIME + " DESC";
- Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns,
- null, null, null, null, orderBy );
- int count = cursor.getCount();
- result = new long[count];
- int index = cursor.getColumnIndex( ROW_ID );
- for ( int ii = 0; cursor.moveToNext(); ++ii ) {
- result[ii] = cursor.getLong( index );
+ String[] columns = { ROW_ID };
+ String orderBy = DBHelper.CREATE_TIME + " DESC";
+ Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM,
+ columns, null, null, null,
+ null, orderBy );
+ int count = cursor.getCount();
+ s_cachedRowIDs = new long[count];
+ int index = cursor.getColumnIndex( ROW_ID );
+ for ( int ii = 0; cursor.moveToNext(); ++ii ) {
+ s_cachedRowIDs[ii] = cursor.getLong( index );
+ }
+ cursor.close();
+ db.close();
+ }
}
- cursor.close();
- db.close();
+ result = s_cachedRowIDs;
}
-
return result;
}
+ private static void clearRowIDsCache()
+ {
+ synchronized( DBUtils.class ) {
+ s_cachedRowIDs = null;
+ }
+ }
+
// Get either the file name or game name, preferring the latter.
public static String getName( Context context, long rowid )
{
@@ -1099,6 +1118,7 @@ public class DBUtils {
public static void loadDB( Context context )
{
+ clearRowIDsCache();
copyGameDB( context, false );
}
@@ -1381,12 +1401,29 @@ public class DBUtils {
}
}
- private static void notifyListeners( long rowid )
+ private static void updateRow( Context context, String table,
+ long rowid, ContentValues values )
+ {
+ initDB( context );
+ synchronized( s_dbHelper ) {
+ SQLiteDatabase db = s_dbHelper.getWritableDatabase();
+
+ String selection = String.format( ROW_ID_FMT, rowid );
+
+ int result = db.update( table, values, selection, null );
+ db.close();
+ if ( 0 == result ) {
+ DbgUtils.logf( "updateRow failed" );
+ }
+ }
+ }
+
+ private static void notifyListeners( long rowid, boolean countChanged )
{
synchronized( s_listeners ) {
Iterator iter = s_listeners.iterator();
while ( iter.hasNext() ) {
- iter.next().gameSaved( rowid );
+ iter.next().gameSaved( rowid, countChanged );
}
}
}
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictUtils.java
index 126410cfd..05fff08d0 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictUtils.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictUtils.java
@@ -47,6 +47,20 @@ import org.eehouse.android.xw4.jni.CurGameInfo.DeviceRole;
public class DictUtils {
+ // Standard hack for using APIs from an SDK in code to ship on
+ // older devices that don't support it: prevent class loader from
+ // seeing something it'll barf on by loading it manually
+ private static interface SafeDirGetter {
+ public File getDownloadDir();
+ }
+ private static SafeDirGetter s_dirGetter = null;
+ static {
+ int sdkVersion = Integer.valueOf( android.os.Build.VERSION.SDK );
+ if ( 8 <= sdkVersion ) {
+ s_dirGetter = new DirGetter();
+ }
+ }
+
// keep in sync with loc_names string-array
public enum DictLoc { UNKNOWN, BUILT_IN, INTERNAL, EXTERNAL, DOWNLOAD };
public static final String INVITED = "invited";
@@ -566,22 +580,45 @@ public class DictUtils {
return null != getDownloadDir( context );
}
+ // Loop through three ways of getting the directory until one
+ // produces a directory I can write to.
public static File getDownloadDir( Context context )
{
File result = null;
- if ( haveWriteableSD() ) {
- File file = null;
- String myPath = XWPrefs.getMyDownloadDir( context );
- if ( null != myPath && 0 < myPath.length() ) {
- file = new File( myPath );
- } else {
- file = Environment.getExternalStorageDirectory();
- if ( null != file ) {
- file = new File( file, "download/" );
+ outer:
+ for ( int attempt = 0; attempt < 4; ++attempt ) {
+ switch ( attempt ) {
+ case 0:
+ String myPath = XWPrefs.getMyDownloadDir( context );
+ if ( null == myPath || 0 == myPath.length() ) {
+ continue;
}
+ result = new File( myPath );
+ break;
+ case 1:
+ if ( null == s_dirGetter ) {
+ continue;
+ }
+ result = s_dirGetter.getDownloadDir();
+ break;
+ case 2:
+ case 3:
+ if ( !haveWriteableSD() ) {
+ continue;
+ }
+ result = Environment.getExternalStorageDirectory();
+ if ( 2 == attempt && null != result ) {
+ // the old way...
+ result = new File( result, "download/" );
+ }
+ break;
}
- if ( null != file && file.exists() && file.isDirectory() ) {
- result = file;
+
+ // Exit test for loop
+ if ( null != result ) {
+ if ( result.exists() && result.isDirectory() && result.canWrite() ) {
+ break outer;
+ }
}
}
return result;
@@ -596,4 +633,13 @@ public class DictUtils {
}
return result;
}
+
+ private static class DirGetter implements SafeDirGetter {
+ public File getDownloadDir()
+ {
+ File path = Environment.
+ getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
+ return path;
+ }
+ }
}
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java
index b0e642bf1..05ace973a 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java
@@ -242,7 +242,7 @@ public class DlgDelegate {
public void doSyncMenuitem()
{
- if ( null == DBUtils.getRelayIDs( m_activity, false ) ) {
+ if ( null == DBUtils.getRelayIDs( m_activity, null ) ) {
showOKOnlyDialog( R.string.no_games_to_refresh );
} else {
RelayReceiver.RestartTimer( m_activity, true );
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/ExpiringDelegate.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/ExpiringDelegate.java
index 7d1b13968..e1cedab05 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/ExpiringDelegate.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/ExpiringDelegate.java
@@ -194,12 +194,20 @@ public class ExpiringDelegate {
if ( null == m_runnable ) {
m_runnable = new Runnable() {
public void run() {
+ if ( XWApp.DEBUG_EXP_TIMERS ) {
+ DbgUtils.logf( "ExpiringDelegate: timer fired"
+ + " for %H", this );
+ }
if ( m_active ) {
figurePct();
if ( m_haveTurnLocal ) {
m_back = null;
setBackground();
}
+ if ( XWApp.DEBUG_EXP_TIMERS ) {
+ DbgUtils.logf( "ExpiringDelegate: invalidating"
+ + " view %H", m_view );
+ }
m_view.invalidate();
}
}
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameConfig.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameConfig.java
index 5fcf33251..c35139e61 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameConfig.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameConfig.java
@@ -92,7 +92,7 @@ public class GameConfig extends XWActivity
private boolean m_forResult;
private CurGameInfo m_gi;
private CurGameInfo m_giOrig;
- private GameUtils.GameLock m_gameLock;
+ private GameLock m_gameLock;
private int m_whichPlayer;
// private Spinner m_roleSpinner;
// private Spinner m_connectSpinner;
@@ -473,7 +473,7 @@ public class GameConfig extends XWActivity
// Lock in case we're going to config. We *could* re-get the
// lock once the user decides to make changes. PENDING.
- m_gameLock = new GameUtils.GameLock( m_rowid, true ).lock();
+ m_gameLock = new GameLock( m_rowid, true ).lock();
int gamePtr = GameUtils.loadMakeGame( this, m_giOrig, m_gameLock );
if ( 0 == gamePtr ) {
showDictGoneFinish();
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java
index abb37a82a..320594293 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java
@@ -1,7 +1,7 @@
/* -*- compile-command: "cd ../../../../../; ant debug install"; -*- */
/*
- * Copyright 2009-2010 by Eric House (xwords@eehouse.org). All
- * rights reserved.
+ * Copyright 2009-2012 by Eric House (xwords@eehouse.org). All rights
+ * reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -21,8 +21,6 @@ package org.eehouse.android.xw4;
import android.content.Context;
import android.database.DataSetObserver;
-import android.os.AsyncTask;
-import android.os.Build;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
@@ -44,7 +42,6 @@ import java.util.Set;
import junit.framework.Assert;
-
import org.eehouse.android.xw4.jni.*;
import org.eehouse.android.xw4.jni.CurGameInfo.DeviceRole;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
@@ -52,242 +49,35 @@ import org.eehouse.android.xw4.DBUtils.GameGroupInfo;
public class GameListAdapter implements ExpandableListAdapter {
private Context m_context;
+ private ExpandableListView m_list;
private LayoutInflater m_factory;
private int m_fieldID;
private Handler m_handler;
- private static final boolean s_isFire;
- private static Random s_random;
- static {
- s_isFire = Build.MANUFACTURER.equals( "Amazon" );
- if ( s_isFire ) {
- s_random = new Random();
- }
- }
-
- private class ViewInfo implements View.OnClickListener {
- private View m_view;
- private View m_hideable;
- private ExpiringTextView m_name;
- private boolean m_expanded, m_haveTurn, m_haveTurnLocal;
- private long m_rowid;
- private long m_lastMoveTime;
- private ImageButton m_expandButton;
-
- public ViewInfo( View view, long rowid )
- {
- m_view = view;
- m_rowid = rowid;
- m_lastMoveTime = 0;
- }
-
- public ViewInfo( View view, long rowid, boolean expanded,
- long lastMoveTime, boolean haveTurn,
- boolean haveTurnLocal ) {
- this( view, rowid );
- m_expanded = expanded;
- m_lastMoveTime = lastMoveTime;
- m_haveTurn = haveTurn;
- m_haveTurnLocal = haveTurnLocal;
- m_hideable = (LinearLayout)view.findViewById( R.id.hideable );
- m_name = (ExpiringTextView)m_view.findViewById( R.id.game_name );
- m_expandButton = (ImageButton)view.findViewById( R.id.expander );
- m_expandButton.setOnClickListener( this );
- showHide();
- }
-
- private void showHide()
- {
- m_expandButton.setImageResource( m_expanded ?
- R.drawable.expander_ic_maximized :
- R.drawable.expander_ic_minimized);
- m_hideable.setVisibility( m_expanded? View.VISIBLE : View.GONE );
-
- m_name.setBackgroundColor( android.R.color.transparent );
- m_name.setPct( m_handler, m_haveTurn && !m_expanded,
- m_haveTurnLocal, m_lastMoveTime );
- }
-
- public void onClick( View view ) {
- m_expanded = !m_expanded;
- DBUtils.setExpanded( m_rowid, m_expanded );
- showHide();
- }
- }
-
- private HashMap m_viewsCache;
- private DateFormat m_df;
private LoadItemCB m_cb;
public interface LoadItemCB {
- public void itemLoaded( long rowid );
- public void itemClicked( long rowid );
+ public void itemClicked( long rowid, GameSummary summary );
}
- private class LoadItemTask extends AsyncTask {
- private long m_rowid;
- private Context m_context;
- // private int m_id;
- public LoadItemTask( Context context, long rowid/*, int id*/ )
- {
- m_context = context;
- m_rowid = rowid;
- // m_id = id;
- }
-
- @Override
- protected Void doInBackground( Void... unused )
- {
- // Without this, on the Fire only the last item in the
- // list it tappable. Likely my fault, but this seems to
- // work around it.
- if ( s_isFire ) {
- try {
- int sleepTime = 500 + (s_random.nextInt() % 500);
- Thread.sleep( sleepTime );
- } catch ( Exception e ) {
- }
- }
- View layout = m_factory.inflate( R.layout.game_list_item, null );
- boolean hideTitle = false;//CommonPrefs.getHideTitleBar(m_context);
- GameSummary summary = DBUtils.getSummary( m_context, m_rowid, 1500 );
- if ( null == summary ) {
- m_rowid = -1;
- } else {
- String state = summary.summarizeState();
-
- TextView view = (TextView)layout.findViewById( R.id.game_name );
- if ( hideTitle ) {
- view.setVisibility( View.GONE );
- } else {
- String value = null;
- switch ( m_fieldID ) {
- case R.string.game_summary_field_empty:
- break;
- case R.string.game_summary_field_language:
- value =
- DictLangCache.getLangName( m_context,
- summary.dictLang );
- break;
- case R.string.game_summary_field_opponents:
- value = summary.playerNames();
- break;
- case R.string.game_summary_field_state:
- value = state;
- break;
- }
-
- String name = GameUtils.getName( m_context, m_rowid );
-
- if ( null != value ) {
- value = m_context.getString( R.string.str_game_namef,
- name, value );
- } else {
- value = name;
- }
-
- view.setText( value );
- }
-
- layout.setOnClickListener( new View.OnClickListener() {
- @Override
- public void onClick( View v ) {
- m_cb.itemClicked( m_rowid );
- }
- } );
-
- LinearLayout list =
- (LinearLayout)layout.findViewById( R.id.player_list );
- boolean haveATurn = false;
- boolean haveALocalTurn = false;
- boolean[] isLocal = new boolean[1];
- for ( int ii = 0; ii < summary.nPlayers; ++ii ) {
- ExpiringLinearLayout tmp = (ExpiringLinearLayout)
- m_factory.inflate( R.layout.player_list_elem, null );
- view = (TextView)tmp.findViewById( R.id.item_name );
- view.setText( summary.summarizePlayer( ii ) );
- view = (TextView)tmp.findViewById( R.id.item_score );
- view.setText( String.format( " %d", summary.scores[ii] ) );
- boolean thisHasTurn = summary.isNextToPlay( ii, isLocal );
- if ( thisHasTurn ) {
- haveATurn = true;
- if ( isLocal[0] ) {
- haveALocalTurn = true;
- }
- }
- tmp.setPct( m_handler, thisHasTurn, isLocal[0],
- summary.lastMoveTime );
- list.addView( tmp, ii );
- }
-
- view = (TextView)layout.findViewById( R.id.state );
- view.setText( state );
- view = (TextView)layout.findViewById( R.id.modtime );
- long lastMoveTime = summary.lastMoveTime;
- lastMoveTime *= 1000;
- view.setText( m_df.format( new Date( lastMoveTime ) ) );
-
- int iconID;
- ImageView marker =
- (ImageView)layout.findViewById( R.id.msg_marker );
- CommsConnType conType = summary.conType;
- if ( CommsConnType.COMMS_CONN_RELAY == conType ) {
- iconID = R.drawable.relaygame;
- } else if ( CommsConnType.COMMS_CONN_BT == conType ) {
- iconID = android.R.drawable.stat_sys_data_bluetooth;
- } else if ( CommsConnType.COMMS_CONN_SMS == conType ) {
- iconID = android.R.drawable.sym_action_chat;
- } else {
- iconID = R.drawable.sologame;
- }
- marker.setImageResource( iconID );
-
- view = (TextView)layout.findViewById( R.id.role );
- String roleSummary = summary.summarizeRole();
- if ( null != roleSummary ) {
- view.setText( roleSummary );
- } else {
- view.setVisibility( View.GONE );
- }
-
- boolean expanded = DBUtils.getExpanded( m_context, m_rowid );
- ViewInfo vi = new ViewInfo( layout, m_rowid, expanded,
- summary.lastMoveTime, haveATurn,
- haveALocalTurn );
-
- synchronized( m_viewsCache ) {
- m_viewsCache.put( m_rowid, vi );
- }
- }
- return null;
- } // doInBackground
-
- @Override
- protected void onPostExecute( Void unused )
- {
- if ( -1 != m_rowid ) {
- m_cb.itemLoaded( m_rowid );
- }
- }
- } // class LoadItemTask
-
- public GameListAdapter( Context context, Handler handler, LoadItemCB cb ) {
+ public GameListAdapter( Context context, ExpandableListView list,
+ Handler handler, LoadItemCB cb, String fieldName )
+ {
// super( DBUtils.gamesList(context).length );
m_context = context;
+ m_list = list;
m_handler = handler;
m_cb = cb;
m_factory = LayoutInflater.from( context );
- m_df = DateFormat.getDateTimeInstance( DateFormat.SHORT,
- DateFormat.SHORT );
- m_viewsCache = new HashMap();
+ m_fieldID = fieldToID( fieldName );
}
- public void inval( long rowid )
- {
- synchronized( m_viewsCache ) {
- m_viewsCache.remove( rowid );
- }
- }
+ // public void inval( long rowid )
+ // {
+ // synchronized( m_viewsCache ) {
+ // m_viewsCache.remove( rowid );
+ // }
+ // }
public void expandGroups( ExpandableListView view )
{
@@ -301,26 +91,26 @@ public class GameListAdapter implements ExpandableListAdapter {
}
}
- public void setField( String field )
- {
- int[] ids = {
- R.string.game_summary_field_empty
- ,R.string.game_summary_field_language
- ,R.string.game_summary_field_opponents
- ,R.string.game_summary_field_state
- };
- int result = -1;
- for ( int id : ids ) {
- if ( m_context.getString( id ).equals( field ) ) {
- result = id;
- break;
- }
- }
- if ( m_fieldID != result ) {
- m_viewsCache.clear();
- m_fieldID = result;
- }
- }
+ // public void setField( String field )
+ // {
+ // int[] ids = {
+ // R.string.game_summary_field_empty
+ // ,R.string.game_summary_field_language
+ // ,R.string.game_summary_field_opponents
+ // ,R.string.game_summary_field_state
+ // };
+ // int result = -1;
+ // for ( int id : ids ) {
+ // if ( m_context.getString( id ).equals( field ) ) {
+ // result = id;
+ // break;
+ // }
+ // }
+ // if ( m_fieldID != result ) {
+ // m_viewsCache.clear();
+ // m_fieldID = result;
+ // }
+ // }
public long getRowIDFor( int group, int child )
{
@@ -473,39 +263,57 @@ public class GameListAdapter implements ExpandableListAdapter {
public void registerDataSetObserver( DataSetObserver obs ){}
public void unregisterDataSetObserver( DataSetObserver obs ){}
- private View getItem( final long rowid )
+ // private View getItem( final long rowid )
+ // {
+ // View layout;
+ // boolean haveLayout = false;
+ // synchronized( m_viewsCache ) {
+ // ViewInfo vi = m_viewsCache.get( rowid );
+ // haveLayout = null != vi;
+ // if ( haveLayout ) {
+ // layout = vi.m_view;
+ // } else {
+ // layout = m_factory.inflate( R.layout.game_list_tmp, null );
+ // vi = new ViewInfo( layout, rowid );
+ // m_viewsCache.put( rowid, vi );
+ // }
+ // }
+ // }
+ // @Override
+ // public int getCount() {
+ // return DBUtils.gamesList(m_context).length;
+ // }
+
+ // // Views. A view depends on a summary, which takes time to load.
+ // // When one needs loading it's done via an async task.
+ // public View getView( int position, View convertView, ViewGroup parent )
+ // {
+ // GameListItem result = (GameListItem)
+ // m_factory.inflate( R.layout.game_list_item, null );
+ // result.init( m_handler, DBUtils.gamesList(m_context)[position],
+ // m_fieldID, m_cb );
+ // return result;
+ // }
+
+ public void inval( long rowid )
{
- View layout;
- boolean haveLayout = false;
- synchronized( m_viewsCache ) {
- ViewInfo vi = m_viewsCache.get( rowid );
- haveLayout = null != vi;
- if ( haveLayout ) {
- layout = vi.m_view;
- } else {
- layout = m_factory.inflate( R.layout.game_list_tmp, null );
- vi = new ViewInfo( layout, rowid );
- m_viewsCache.put( rowid, vi );
- }
+ GameListItem child = getItemFor( rowid );
+ if ( null != child && child.getRowID() == rowid ) {
+ child.forceReload();
+ } else {
+ DbgUtils.logf( "no child for rowid %d", rowid );
+ GameListItem.inval( rowid );
+ m_list.invalidate();
}
+ }
- if ( !haveLayout ) {
- new LoadItemTask( m_context, rowid/*, ++m_taskCounter*/ ).execute();
+ public void invalName( long rowid )
+ {
+ GameListItem item = getItemFor( rowid );
+ if ( null != item ) {
+ item.invalName();
}
-
- // this doesn't work. Rather, it breaks highlighting because
- // the background, if we don't set it, is a more complicated
- // object like @android:drawable/list_selector_background. I
- // tried calling getBackground(), expecting to get a Drawable
- // I could then clone and modify, but null comes back. So
- // layout must be inheriting its background from elsewhere or
- // it gets set later, during layout.
- // if ( (position%2) == 0 ) {
- // layout.setBackgroundColor( 0xFF3F3F3F );
- // }
-
- return layout;
- } // getItem
+ }
private long[] getRows( String group )
{
@@ -538,9 +346,67 @@ public class GameListAdapter implements ExpandableListAdapter {
return pos;
}
- private HashMap gameInfo()
+ public boolean setField( String fieldName )
{
- return DBUtils.getGroups( m_context );
+ boolean changed = false;
+ int newID = fieldToID( fieldName );
+ if ( -1 == newID ) {
+ if ( XWApp.DEBUG ) {
+ DbgUtils.logf( "GameListAdapter.setField(): unable to match"
+ + " fieldName %s", fieldName );
+ }
+ } else if ( m_fieldID != newID ) {
+ if ( XWApp.DEBUG ) {
+ DbgUtils.logf( "setField: clearing views cache for change"
+ + " from %d to %d", m_fieldID, newID );
+ }
+ m_fieldID = newID;
+ // return true so caller will do onContentChanged.
+ // There's no other way to signal GameListItem instances
+ // since we don't maintain a list of them.
+ changed = true;
+ }
+ return changed;
}
-}
\ No newline at end of file
+ private GameListItem getItemFor( long rowid )
+ {
+ GameListItem result = null;
+ int position = positionFor( rowid );
+ if ( 0 <= position ) {
+ result = (GameListItem)m_list.getChildAt( position );
+ }
+ return result;
+ }
+
+ private int fieldToID( String fieldName )
+ {
+ int[] ids = {
+ R.string.game_summary_field_empty
+ ,R.string.game_summary_field_language
+ ,R.string.game_summary_field_opponents
+ ,R.string.game_summary_field_state
+ };
+ int result = -1;
+ for ( int id : ids ) {
+ if ( m_context.getString( id ).equals( fieldName ) ) {
+ result = id;
+ break;
+ }
+ }
+ return result;
+ }
+
+ private int positionFor( long rowid )
+ {
+ int position = -1;
+ long[] rowids = DBUtils.gamesList( m_context );
+ for ( int ii = 0; ii < rowids.length; ++ii ) {
+ if ( rowids[ii] == rowid ) {
+ position = ii;
+ break;
+ }
+ }
+ return position;
+ }
+}
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListItem.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListItem.java
new file mode 100644
index 000000000..9cd376d18
--- /dev/null
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListItem.java
@@ -0,0 +1,322 @@
+/* -*- compile-command: "cd ../../../../../; ant debug install"; -*- */
+/*
+ * Copyright 2009-2012 by Eric House (xwords@eehouse.org). All rights
+ * reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+package org.eehouse.android.xw4;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.os.AsyncTask;
+import android.os.Handler;
+// import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import java.text.DateFormat;
+import java.util.Date;
+import java.util.HashSet;
+// import java.util.Iterator;
+
+import org.eehouse.android.xw4.jni.GameSummary;
+import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
+
+public class GameListItem extends LinearLayout
+ implements View.OnClickListener {
+
+ private static HashSet s_invalRows = new HashSet();
+
+ private Context m_context;
+ private boolean m_loaded;
+ private long m_rowid;
+ private View m_hideable;
+ private ExpiringTextView m_name;
+ private boolean m_expanded, m_haveTurn, m_haveTurnLocal;
+ private long m_lastMoveTime;
+ private ImageButton m_expandButton;
+ private Handler m_handler;
+ private GameSummary m_summary;
+ private GameListAdapter.LoadItemCB m_cb;
+ private int m_fieldID;
+ private int m_loadingCount;
+
+ public GameListItem( Context cx, AttributeSet as )
+ {
+ super( cx, as );
+ m_context = cx;
+ m_loaded = false;
+ m_rowid = DBUtils.ROWID_NOTFOUND;
+ m_lastMoveTime = 0;
+ m_loadingCount = 0;
+ }
+
+ public void init( Handler handler, long rowid, int fieldID,
+ GameListAdapter.LoadItemCB cb )
+ {
+ m_handler = handler;
+ m_rowid = rowid;
+ m_fieldID = fieldID;
+ m_cb = cb;
+
+ forceReload();
+ }
+
+ public void forceReload()
+ {
+ // DbgUtils.logf( "GameListItem.forceReload: rowid=%d", m_rowid );
+ m_summary = null;
+ setLoaded( false );
+ // Apparently it's impossible to reliably cancel an existing
+ // AsyncTask, so let it complete, but drop the results as soon
+ // as we're back on the UI thread.
+ ++m_loadingCount;
+ new LoadItemTask().execute();
+ }
+
+ public void invalName()
+ {
+ setName();
+ }
+
+ @Override
+ protected void onDraw( Canvas canvas )
+ {
+ super.onDraw( canvas );
+ if ( DBUtils.ROWID_NOTFOUND != m_rowid ) {
+ synchronized( s_invalRows ) {
+ if ( s_invalRows.contains( m_rowid ) ) {
+ forceReload();
+ }
+ }
+ }
+ }
+
+ private void update( boolean expanded, long lastMoveTime, boolean haveTurn,
+ boolean haveTurnLocal )
+ {
+ m_expanded = expanded;
+ m_lastMoveTime = lastMoveTime;
+ m_haveTurn = haveTurn;
+ m_haveTurnLocal = haveTurnLocal;
+ m_hideable = (LinearLayout)findViewById( R.id.hideable );
+ m_name = (ExpiringTextView)findViewById( R.id.game_name );
+ m_expandButton = (ImageButton)findViewById( R.id.expander );
+ m_expandButton.setOnClickListener( this );
+ showHide();
+ }
+
+ public long getRowID()
+ {
+ return m_rowid;
+ }
+
+ // View.OnClickListener interface
+ public void onClick( View view ) {
+ m_expanded = !m_expanded;
+ DBUtils.setExpanded( m_rowid, m_expanded );
+ showHide();
+ }
+
+ private void setLoaded( boolean loaded )
+ {
+ if ( loaded != m_loaded ) {
+ m_loaded = loaded;
+ // This should be enough to invalidate
+ findViewById( R.id.view_unloaded )
+ .setVisibility( loaded ? View.GONE : View.VISIBLE );
+ findViewById( R.id.view_loaded )
+ .setVisibility( loaded ? View.VISIBLE : View.GONE );
+ }
+ }
+
+ private void showHide()
+ {
+ m_expandButton.setImageResource( m_expanded ?
+ R.drawable.expander_ic_maximized :
+ R.drawable.expander_ic_minimized);
+ m_hideable.setVisibility( m_expanded? View.VISIBLE : View.GONE );
+
+ m_name.setBackgroundColor( android.R.color.transparent );
+ m_name.setPct( m_handler, m_haveTurn && !m_expanded,
+ m_haveTurnLocal, m_lastMoveTime );
+ }
+
+ private String setName()
+ {
+ String state = null; // hack to avoid calling summarizeState twice
+ if ( null != m_summary ) {
+ state = m_summary.summarizeState();
+ TextView view = (TextView)findViewById( R.id.game_name );
+ String value = null;
+ switch ( m_fieldID ) {
+ case R.string.game_summary_field_empty:
+ break;
+ case R.string.game_summary_field_language:
+ value =
+ DictLangCache.getLangName( m_context,
+ m_summary.dictLang );
+ break;
+ case R.string.game_summary_field_opponents:
+ value = m_summary.playerNames();
+ break;
+ case R.string.game_summary_field_state:
+ value = state;
+ break;
+ }
+
+ if ( null != value ) {
+ String name = GameUtils.getName( m_context, m_rowid );
+ value = m_context.getString( R.string.str_game_namef,
+ name, value );
+ } else {
+ value = GameUtils.getName( m_context, m_rowid );
+ }
+
+ view.setText( value );
+ }
+ return state;
+ }
+
+ private void setData( final GameSummary summary )
+ {
+ if ( null != summary ) {
+ TextView view;
+ String state = setName();
+
+ setOnClickListener( new View.OnClickListener() {
+ @Override
+ public void onClick( View v ) {
+ m_cb.itemClicked( m_rowid, summary );
+ }
+ } );
+
+ LinearLayout list =
+ (LinearLayout)findViewById( R.id.player_list );
+ list.removeAllViews();
+ boolean haveATurn = false;
+ boolean haveALocalTurn = false;
+ boolean[] isLocal = new boolean[1];
+ for ( int ii = 0; ii < summary.nPlayers; ++ii ) {
+ ExpiringLinearLayout tmp = (ExpiringLinearLayout)
+ Utils.inflate( m_context, R.layout.player_list_elem );
+ view = (TextView)tmp.findViewById( R.id.item_name );
+ view.setText( summary.summarizePlayer( ii ) );
+ view = (TextView)tmp.findViewById( R.id.item_score );
+ view.setText( String.format( " %d", summary.scores[ii] ) );
+ boolean thisHasTurn = summary.isNextToPlay( ii, isLocal );
+ if ( thisHasTurn ) {
+ haveATurn = true;
+ if ( isLocal[0] ) {
+ haveALocalTurn = true;
+ }
+ }
+ tmp.setPct( m_handler, thisHasTurn, isLocal[0],
+ summary.lastMoveTime );
+ list.addView( tmp, ii );
+ }
+
+ view = (TextView)findViewById( R.id.state );
+ view.setText( state );
+ view = (TextView)findViewById( R.id.modtime );
+ long lastMoveTime = summary.lastMoveTime;
+ lastMoveTime *= 1000;
+
+ DateFormat df = DateFormat.getDateTimeInstance( DateFormat.SHORT,
+ DateFormat.SHORT );
+ view.setText( df.format( new Date( lastMoveTime ) ) );
+
+ int iconID;
+ ImageView marker =
+ (ImageView)findViewById( R.id.msg_marker );
+ CommsConnType conType = summary.conType;
+ if ( CommsConnType.COMMS_CONN_RELAY == conType ) {
+ iconID = R.drawable.relaygame;
+ } else if ( CommsConnType.COMMS_CONN_BT == conType ) {
+ iconID = android.R.drawable.stat_sys_data_bluetooth;
+ } else if ( CommsConnType.COMMS_CONN_SMS == conType ) {
+ iconID = android.R.drawable.sym_action_chat;
+ } else {
+ iconID = R.drawable.sologame;
+ }
+ marker.setImageResource( iconID );
+
+ view = (TextView)findViewById( R.id.role );
+ String roleSummary = summary.summarizeRole();
+ if ( null != roleSummary ) {
+ view.setText( roleSummary );
+ } else {
+ view.setVisibility( View.GONE );
+ }
+
+ boolean expanded = DBUtils.getExpanded( m_context, m_rowid );
+
+ update( expanded, summary.lastMoveTime, haveATurn,
+ haveALocalTurn );
+ }
+ }
+
+ private class LoadItemTask extends AsyncTask {
+ @Override
+ protected GameSummary doInBackground( Void... unused )
+ {
+ return DBUtils.getSummary( m_context, m_rowid, 150 );
+ } // doInBackground
+
+ @Override
+ protected void onPostExecute( GameSummary summary )
+ {
+ if ( 0 == --m_loadingCount ) {
+ m_summary = summary;
+ setData( summary );
+ setLoaded( null != m_summary );
+ synchronized( s_invalRows ) {
+ s_invalRows.remove( m_rowid );
+ }
+ }
+ // DbgUtils.logf( "LoadItemTask for row %d finished; "
+ // + "inval rows now %s",
+ // m_rowid, invalRowsToString() );
+ }
+ } // class LoadItemTask
+
+ public static void inval( long rowid )
+ {
+ synchronized( s_invalRows ) {
+ s_invalRows.add( rowid );
+ }
+ // DbgUtils.logf( "GameListItem.inval(rowid=%d); inval rows now %s",
+ // rowid, invalRowsToString() );
+ }
+
+ // private static String invalRowsToString()
+ // {
+ // String[] strs;
+ // synchronized( s_invalRows ) {
+ // strs = new String[s_invalRows.size()];
+ // Iterator iter = s_invalRows.iterator();
+ // for ( int ii = 0; iter.hasNext(); ++ii ) {
+ // strs[ii] = String.format("%d", iter.next() );
+ // }
+ // }
+ // return TextUtils.join(",", strs );
+ // }
+
+}
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameLock.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameLock.java
new file mode 100644
index 000000000..12c080c23
--- /dev/null
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameLock.java
@@ -0,0 +1,165 @@
+/* -*- compile-command: "cd ../../../../../; ant debug install"; -*- */
+/*
+ * Copyright 2009-2010 by Eric House (xwords@eehouse.org). All
+ * rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+package org.eehouse.android.xw4;
+
+import java.util.HashMap;
+
+import junit.framework.Assert;
+
+// Implements read-locks and write-locks per game. A read lock is
+// obtainable when other read locks are granted but not when a
+// write lock is. Write-locks are exclusive.
+public class GameLock {
+ private long m_rowid;
+ private boolean m_isForWrite;
+ private int m_lockCount;
+ StackTraceElement[] m_lockTrace;
+
+ private static HashMap
+ s_locks = new HashMap();
+
+ public GameLock( long rowid, boolean isForWrite )
+ {
+ m_rowid = rowid;
+ m_isForWrite = isForWrite;
+ m_lockCount = 0;
+ if ( XWApp.DEBUG_LOCKS ) {
+ DbgUtils.logf( "GameLock.GameLock(rowid:%d,isForWrite:%b)=>"
+ + "this: %H", rowid, isForWrite, this );
+ DbgUtils.printStack();
+ }
+ }
+
+ // This could be written to allow multiple read locks. Let's
+ // see if not doing that causes problems.
+ public boolean tryLock()
+ {
+ boolean gotIt = false;
+ synchronized( s_locks ) {
+ GameLock owner = s_locks.get( m_rowid );
+ if ( null == owner ) { // unowned
+ Assert.assertTrue( 0 == m_lockCount );
+ s_locks.put( m_rowid, this );
+ ++m_lockCount;
+ gotIt = true;
+
+ if ( XWApp.DEBUG_LOCKS ) {
+ StackTraceElement[] trace = Thread.currentThread().
+ getStackTrace();
+ m_lockTrace = new StackTraceElement[trace.length];
+ System.arraycopy( trace, 0, m_lockTrace, 0, trace.length );
+ }
+ } else if ( this == owner && ! m_isForWrite ) {
+ Assert.assertTrue( 0 == m_lockCount );
+ ++m_lockCount;
+ gotIt = true;
+ }
+ }
+ return gotIt;
+ }
+
+ // Wait forever (but may assert if too long)
+ public GameLock lock()
+ {
+ return this.lock( 0 );
+ }
+
+ // Version that's allowed to return null -- if maxMillis > 0
+ public GameLock lock( long maxMillis )
+ {
+ GameLock result = null;
+ final long assertTime = 2000;
+ Assert.assertTrue( maxMillis < assertTime );
+ long sleptTime = 0;
+
+ if ( XWApp.DEBUG_LOCKS ) {
+ DbgUtils.logf( "lock %H (rowid:%d, maxMillis=%d)", this, m_rowid, maxMillis );
+ }
+
+ for ( ; ; ) {
+ if ( tryLock() ) {
+ result = this;
+ break;
+ }
+ if ( XWApp.DEBUG_LOCKS ) {
+ DbgUtils.logf( "GameLock.lock() %H failed; sleeping", this );
+ DbgUtils.printStack();
+ }
+ try {
+ Thread.sleep( 25 ); // milliseconds
+ sleptTime += 25;
+ } catch( InterruptedException ie ) {
+ DbgUtils.loge( ie );
+ break;
+ }
+
+ if ( XWApp.DEBUG_LOCKS ) {
+ DbgUtils.logf( "GameLock.lock() %H awake; "
+ + "sleptTime now %d millis", this, sleptTime );
+ }
+
+ if ( 0 < maxMillis && sleptTime >= maxMillis ) {
+ break;
+ } else if ( sleptTime >= assertTime ) {
+ if ( XWApp.DEBUG_LOCKS ) {
+ DbgUtils.logf( "lock %H overlocked. lock holding stack:",
+ this );
+ DbgUtils.printStack( m_lockTrace );
+ DbgUtils.logf( "lock %H seeking stack:", this );
+ DbgUtils.printStack();
+ }
+ Assert.fail();
+ }
+ }
+ // DbgUtils.logf( "GameLock.lock(%s) done", m_path );
+ return result;
+ }
+
+ public void unlock()
+ {
+ // DbgUtils.logf( "GameLock.unlock(%s)", m_path );
+ synchronized( s_locks ) {
+ Assert.assertTrue( this == s_locks.get(m_rowid) );
+ if ( 1 == m_lockCount ) {
+ s_locks.remove( m_rowid );
+ } else {
+ Assert.assertTrue( !m_isForWrite );
+ }
+ --m_lockCount;
+
+ if ( XWApp.DEBUG_LOCKS ) {
+ DbgUtils.logf( "GameLock.unlock: this: %H (rowid:%d) unlocked",
+ this, m_rowid );
+ }
+ }
+ }
+
+ public long getRowid()
+ {
+ return m_rowid;
+ }
+
+ // used only for asserts
+ public boolean canWrite()
+ {
+ return m_isForWrite && 1 == m_lockCount;
+ }
+}
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java
index bbd5f0185..a471e1f40 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java
@@ -46,134 +46,6 @@ public class GameUtils {
public static final String INTENT_KEY_ROWID = "rowid";
public static final String INTENT_FORRESULT_ROWID = "forresult";
- // Implements read-locks and write-locks per game. A read lock is
- // obtainable when other read locks are granted but not when a
- // write lock is. Write-locks are exclusive.
- public static class GameLock {
- private long m_rowid;
- private boolean m_isForWrite;
- private int m_lockCount;
- StackTraceElement[] m_lockTrace;
-
- private static HashMap
- s_locks = new HashMap();
-
- public GameLock( long rowid, boolean isForWrite )
- {
- m_rowid = rowid;
- m_isForWrite = isForWrite;
- m_lockCount = 0;
- if ( XWApp.DEBUG_LOCKS ) {
- DbgUtils.logf( "GameLock.GameLock(rowid:%d,isForWrite:%b)=>"
- + "this: %H", rowid, isForWrite, this );
- DbgUtils.printStack();
- }
- }
-
- // This could be written to allow multiple read locks. Let's
- // see if not doing that causes problems.
- public boolean tryLock()
- {
- boolean gotIt = false;
- synchronized( s_locks ) {
- GameLock owner = s_locks.get( m_rowid );
- if ( null == owner ) { // unowned
- Assert.assertTrue( 0 == m_lockCount );
- s_locks.put( m_rowid, this );
- ++m_lockCount;
- gotIt = true;
-
- if ( XWApp.DEBUG_LOCKS ) {
- StackTraceElement[] trace = Thread.currentThread().
- getStackTrace();
- m_lockTrace = new StackTraceElement[trace.length];
- System.arraycopy( trace, 0, m_lockTrace, 0, trace.length );
- }
- } else if ( this == owner && ! m_isForWrite ) {
- Assert.assertTrue( 0 == m_lockCount );
- ++m_lockCount;
- gotIt = true;
- }
- }
- return gotIt;
- }
-
- // Wait forever (but may assert if too long)
- public GameLock lock()
- {
- return this.lock( 0 );
- }
-
- // Version that's allowed to return null -- if maxMillis > 0
- public GameLock lock( long maxMillis )
- {
- GameLock result = null;
- final long assertTime = 2000;
- Assert.assertTrue( maxMillis < assertTime );
- long sleptTime = 0;
- // DbgUtils.logf( "GameLock.lock(%s)", m_path );
- // Utils.printStack();
- for ( ; ; ) {
- if ( tryLock() ) {
- result = this;
- break;
- }
- if ( XWApp.DEBUG_LOCKS ) {
- DbgUtils.logf( "GameLock.lock() %H failed; sleeping", this );
- DbgUtils.printStack();
- }
- try {
- Thread.sleep( 25 ); // milliseconds
- sleptTime += 25;
- } catch( InterruptedException ie ) {
- DbgUtils.loge( ie );
- break;
- }
-
- if ( 0 < maxMillis && sleptTime >= maxMillis ) {
- break;
- } else if ( sleptTime >= assertTime ) {
- if ( XWApp.DEBUG_LOCKS ) {
- DbgUtils.logf( "lock %H overlocked. lock holding stack:",
- this );
- DbgUtils.printStack( m_lockTrace );
- DbgUtils.logf( "lock %H seeking stack:", this );
- DbgUtils.printStack();
- }
- Assert.fail();
- }
- }
- // DbgUtils.logf( "GameLock.lock(%s) done", m_path );
- return result;
- }
-
- public void unlock()
- {
- // DbgUtils.logf( "GameLock.unlock(%s)", m_path );
- synchronized( s_locks ) {
- Assert.assertTrue( this == s_locks.get(m_rowid) );
- if ( 1 == m_lockCount ) {
- s_locks.remove( m_rowid );
- } else {
- Assert.assertTrue( !m_isForWrite );
- }
- --m_lockCount;
- }
- // DbgUtils.logf( "GameLock.unlock(%s) done", m_path );
- }
-
- public long getRowid()
- {
- return m_rowid;
- }
-
- // used only for asserts
- public boolean canWrite()
- {
- return m_isForWrite && 1 == m_lockCount;
- }
- }
-
private static Object s_syncObj = new Object();
public static byte[] savedGame( Context context, long rowid )
@@ -242,10 +114,16 @@ public class GameUtils {
public static void resetGame( Context context, long rowidIn )
{
- GameLock lock = new GameLock( rowidIn, true ).lock();
- tellDied( context, lock, true );
- resetGame( context, lock, lock, false );
- lock.unlock();
+ GameLock lock = new GameLock( rowidIn, true ).lock( 500 );
+ if ( null != lock ) {
+ tellDied( context, lock, true );
+ resetGame( context, lock, lock, false );
+ lock.unlock();
+
+ Utils.cancelNotification( context, (int)rowidIn );
+ } else {
+ DbgUtils.logf( "resetGame: unable to open rowid %d", rowidIn );
+ }
}
private static GameSummary summarizeAndClose( Context context,
@@ -298,12 +176,17 @@ public class GameUtils {
public static long dupeGame( Context context, long rowidIn )
{
- boolean juggle = CommonPrefs.getAutoJuggle( context );
- GameLock lockSrc = new GameLock( rowidIn, false ).lock();
- GameLock lockDest = resetGame( context, lockSrc, null, juggle );
- long rowid = lockDest.getRowid();
- lockDest.unlock();
- lockSrc.unlock();
+ long rowid = DBUtils.ROWID_NOTFOUND;
+ GameLock lockSrc = new GameLock( rowidIn, false ).lock( 300 );
+ if ( null != lockSrc ) {
+ boolean juggle = CommonPrefs.getAutoJuggle( context );
+ GameLock lockDest = resetGame( context, lockSrc, null, juggle );
+ rowid = lockDest.getRowid();
+ lockDest.unlock();
+ lockSrc.unlock();
+ } else {
+ DbgUtils.logf( "dupeGame: unable to open rowid %d", rowidIn );
+ }
return rowid;
}
@@ -525,22 +408,6 @@ public class GameUtils {
nPlayersH, null, gameID, isHost );
}
- public static void launchBTInviter( Activity activity, int nMissing,
- int requestCode )
- {
- Intent intent = new Intent( activity, BTInviteActivity.class );
- intent.putExtra( BTInviteActivity.INTENT_KEY_NMISSING, nMissing );
- activity.startActivityForResult( intent, requestCode );
- }
-
- public static void launchSMSInviter( Activity activity, int nMissing,
- int requestCode )
- {
- Intent intent = new Intent( activity, SMSInviteActivity.class );
- intent.putExtra( SMSInviteActivity.INTENT_KEY_NMISSING, nMissing );
- activity.startActivityForResult( intent, requestCode );
- }
-
public static void launchInviteActivity( Context context,
boolean choseEmail,
String room, String inviteID,
@@ -566,18 +433,20 @@ public class GameUtils {
intent.putExtra( Intent.EXTRA_SUBJECT, subject );
intent.putExtra( Intent.EXTRA_TEXT, Html.fromHtml(message) );
+ File attach = null;
File tmpdir = XWApp.ATTACH_SUPPORTED ?
DictUtils.getDownloadDir( context ) : null;
- if ( null == tmpdir ) { // no attachment
+ if ( null != tmpdir ) { // no attachment
+ attach = makeJsonFor( tmpdir, room, inviteID, lang,
+ dict, nPlayers );
+ }
+
+ if ( null == attach ) { // no attachment
intent.setType( "message/rfc822");
} else {
- intent.setType( context.getString( R.string.invite_mime ) );
-
- File attach = makeJsonFor( tmpdir, room, inviteID, lang,
- dict, nPlayers );
+ String mime = context.getString( R.string.invite_mime );
+ intent.setType( mime );
Uri uri = Uri.fromFile( attach );
- DbgUtils.logf( "using file uri for attachment: %s",
- uri.toString() );
intent.putExtra( Intent.EXTRA_STREAM, uri );
}
@@ -684,7 +553,6 @@ public class GameUtils {
boolean invited )
{
Intent intent = new Intent( activity, BoardActivity.class );
- intent.setAction( Intent.ACTION_EDIT );
intent.putExtra( INTENT_KEY_ROWID, rowid );
if ( invited ) {
intent.putExtra( INVITED, true );
@@ -734,40 +602,45 @@ public class GameUtils {
}
}
- private static boolean feedMessages( Context context, long rowid,
- byte[][] msgs, CommsAddrRec ret,
- MultiMsgSink sink )
+ public static boolean feedMessages( Context context, long rowid,
+ byte[][] msgs, CommsAddrRec ret,
+ MultiMsgSink sink )
{
boolean draw = false;
Assert.assertTrue( -1 != rowid );
- GameLock lock = new GameLock( rowid, true );
- if ( lock.tryLock() ) {
- CurGameInfo gi = new CurGameInfo( context );
- FeedUtilsImpl feedImpl = new FeedUtilsImpl( context, rowid );
- int gamePtr = loadMakeGame( context, gi, feedImpl, sink, lock );
- if ( 0 != gamePtr ) {
- XwJNI.comms_resendAll( gamePtr, false, false );
+ if ( null != msgs ) {
+ // timed lock: If a game is opened by BoardActivity just
+ // as we're trying to deliver this message to it it'll
+ // have the lock and we'll never get it. Better to drop
+ // the message than fire the hung-lock assert. Messages
+ // belong in local pre-delivery storage anyway.
+ GameLock lock = new GameLock( rowid, true ).lock( 150 );
+ if ( null != lock ) {
+ CurGameInfo gi = new CurGameInfo( context );
+ FeedUtilsImpl feedImpl = new FeedUtilsImpl( context, rowid );
+ int gamePtr = loadMakeGame( context, gi, feedImpl, sink, lock );
+ if ( 0 != gamePtr ) {
+ XwJNI.comms_resendAll( gamePtr, false, false );
- if ( null != msgs ) {
for ( byte[] msg : msgs ) {
draw = XwJNI.game_receiveMessage( gamePtr, msg, ret )
|| draw;
}
- }
- XwJNI.comms_ackAny( gamePtr );
+ XwJNI.comms_ackAny( gamePtr );
- // update gi to reflect changes due to messages
- XwJNI.game_getGi( gamePtr, gi );
- saveGame( context, gamePtr, gi, lock, false );
- summarizeAndClose( context, lock, gamePtr, gi, feedImpl );
+ // update gi to reflect changes due to messages
+ XwJNI.game_getGi( gamePtr, gi );
+ saveGame( context, gamePtr, gi, lock, false );
+ summarizeAndClose( context, lock, gamePtr, gi, feedImpl );
- int flags = setFromFeedImpl( feedImpl );
- if ( GameSummary.MSG_FLAGS_NONE != flags ) {
- draw = true;
- DBUtils.setMsgFlags( rowid, flags );
+ int flags = setFromFeedImpl( feedImpl );
+ if ( GameSummary.MSG_FLAGS_NONE != flags ) {
+ draw = true;
+ DBUtils.setMsgFlags( rowid, flags );
+ }
}
+ lock.unlock();
}
- lock.unlock();
}
return draw;
} // feedMessages
@@ -781,52 +654,45 @@ public class GameUtils {
return feedMessages( context, rowid, msgs, ret, sink );
}
- // Current assumption: this is the relay case where return address
- // can be null.
- public static boolean feedMessages( Context context, String relayID,
- byte[][] msgs, MultiMsgSink sink )
- {
- boolean draw = false;
- long[] rowids = DBUtils.getRowIDsFor( context, relayID );
- if ( null != rowids ) {
- for ( long rowid : rowids ) {
- draw = feedMessages( context, rowid, msgs, null, sink ) || draw;
- }
- }
- return draw;
- }
-
// This *must* involve a reset if the language is changing!!!
// Which isn't possible right now, so make sure the old and new
// dict have the same langauge code.
- public static void replaceDicts( Context context, long rowid,
- String oldDict, String newDict )
+ public static boolean replaceDicts( Context context, long rowid,
+ String oldDict, String newDict )
{
- GameLock lock = new GameLock( rowid, true ).lock();
- byte[] stream = savedGame( context, lock );
- CurGameInfo gi = new CurGameInfo( context );
- XwJNI.gi_from_stream( gi, stream );
+ GameLock lock = new GameLock( rowid, true ).lock(300);
+ boolean success = null != lock;
+ if ( success ) {
+ byte[] stream = savedGame( context, lock );
+ CurGameInfo gi = new CurGameInfo( context );
+ XwJNI.gi_from_stream( gi, stream );
- // first time required so dictNames() will work
- gi.replaceDicts( newDict );
+ // first time required so dictNames() will work
+ gi.replaceDicts( newDict );
- String[] dictNames = gi.dictNames();
- DictUtils.DictPairs pairs = DictUtils.openDicts( context, dictNames );
+ String[] dictNames = gi.dictNames();
+ DictUtils.DictPairs pairs = DictUtils.openDicts( context,
+ dictNames );
- int gamePtr = XwJNI.initJNI();
- XwJNI.game_makeFromStream( gamePtr, stream, gi, dictNames,
- pairs.m_bytes, pairs.m_paths,
- gi.langName(), JNIUtilsImpl.get(context),
- CommonPrefs.get( context ) );
- // second time required as game_makeFromStream can overwrite
- gi.replaceDicts( newDict );
+ int gamePtr = XwJNI.initJNI();
+ XwJNI.game_makeFromStream( gamePtr, stream, gi, dictNames,
+ pairs.m_bytes, pairs.m_paths,
+ gi.langName(),
+ JNIUtilsImpl.get(context),
+ CommonPrefs.get( context ) );
+ // second time required as game_makeFromStream can overwrite
+ gi.replaceDicts( newDict );
- saveGame( context, gamePtr, gi, lock, false );
+ saveGame( context, gamePtr, gi, lock, false );
- summarizeAndClose( context, lock, gamePtr, gi );
+ summarizeAndClose( context, lock, gamePtr, gi );
- lock.unlock();
- }
+ lock.unlock();
+ } else {
+ DbgUtils.logf( "replaceDicts: unable to open rowid %d", rowid );
+ }
+ return success;
+ } // replaceDicts
public static void applyChanges( Context context, CurGameInfo gi,
CommsAddrRec car, GameLock lock,
@@ -953,7 +819,7 @@ public class GameUtils {
byte[] data = json.toString().getBytes();
File file = new File( dir,
- String.format("invite_%s.json", room ) );
+ String.format("invite_%s", room ) );
FileOutputStream fos = new FileOutputStream( file );
fos.write( data, 0, data.length );
fos.close();
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java
index 9104bd5e1..53e062129 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java
@@ -75,6 +75,7 @@ public class GamesList extends XWExpandableListActivity
private static final String RELAYIDS_EXTRA = "relayids";
private static final String GAMEID_EXTRA = "gameid";
+ private static final String REMATCH_ROWID_EXTRA = "rowid";
private static final int NEW_NET_GAME_ACTION = 1;
private static final int RESET_GAME_ACTION = 2;
@@ -102,7 +103,6 @@ public class GamesList extends XWExpandableListActivity
private String m_nameField;
private NetLaunchInfo m_netLaunchInfo;
private GameNamer m_namer;
- // private String m_smsPhone;
@Override
protected Dialog onCreateDialog( int id )
@@ -177,11 +177,12 @@ public class GamesList extends XWExpandableListActivity
getCheckedItemPosition();
String dict = m_sameLangDicts[pos];
dict = DictLangCache.stripCount( dict );
- GameUtils.replaceDicts( GamesList.this,
- m_missingDictRowId,
- m_missingDictName,
- dict );
- launchGameIf();
+ if ( GameUtils.replaceDicts( GamesList.this,
+ m_missingDictRowId,
+ m_missingDictName,
+ dict ) ) {
+ launchGameIf();
+ }
}
};
dialog = new AlertDialog.Builder( this )
@@ -202,8 +203,7 @@ public class GamesList extends XWExpandableListActivity
public void onClick( DialogInterface dlg, int item ) {
String name = m_namer.getName();
DBUtils.setName( GamesList.this, m_rowid, name );
- m_adapter.inval( m_rowid );
- onContentChanged();
+ m_adapter.invalName( m_rowid );
}
};
dialog = buildNamerDlg( GameUtils.getName( this, m_rowid ),
@@ -346,7 +346,9 @@ public class GamesList extends XWExpandableListActivity
}
});
- m_adapter = new GameListAdapter( this, new Handler(), this );
+ String field = CommonPrefs.getSummaryField( this );
+ m_adapter = new GameListAdapter( this, getListView(), new Handler(),
+ this, field );
setListAdapter( m_adapter );
NetUtils.informOfDeaths( this );
@@ -355,6 +357,7 @@ public class GamesList extends XWExpandableListActivity
startFirstHasDict( intent );
startNewNetGame( intent );
startHasGameID( intent );
+ startHasRowID( intent );
askDefaultNameIf();
} // onCreate
@@ -369,6 +372,7 @@ public class GamesList extends XWExpandableListActivity
startFirstHasDict( intent );
startNewNetGame( intent );
startHasGameID( intent );
+ startHasRowID( intent );
}
@Override
@@ -444,28 +448,25 @@ public class GamesList extends XWExpandableListActivity
}
// DBUtils.DBChangeListener interface
- public void gameSaved( final long rowid )
+ public void gameSaved( final long rowid, final boolean countChanged )
{
post( new Runnable() {
public void run() {
- m_adapter.inval( rowid );
- onContentChanged();
+ if ( countChanged ) {
+ onContentChanged();
+ } else {
+ m_adapter.inval( rowid );
+ }
}
} );
}
// GameListAdapter.LoadItemCB interface
- public void itemLoaded( long rowid )
- {
- onContentChanged();
- }
-
- public void itemClicked( long rowid )
+ public void itemClicked( long rowid, GameSummary summary )
{
// We need a way to let the user get back to the basic-config
// dialog in case it was dismissed. That way it to check for
// an empty room name.
- GameSummary summary = DBUtils.getSummary( this, rowid );
if ( summary.conType == CommsAddrRec.CommsConnType.COMMS_CONN_RELAY
&& summary.roomName.length() == 0 ) {
// If it's unconfigured and of the type RelayGameActivity
@@ -480,12 +481,12 @@ public class GamesList extends XWExpandableListActivity
GameUtils.doConfig( this, rowid, clazz );
} else {
if ( checkWarnNoDict( rowid ) ) {
- GameUtils.launchGame( this, rowid );
+ launchGame( rowid );
}
}
}
- // BTService.BTEventListener interface
+ // BTService.MultiEventListener interface
@Override
public void eventOccurred( MultiService.MultiEvent event,
final Object ... args )
@@ -518,6 +519,7 @@ public class GamesList extends XWExpandableListActivity
break;
case RESET_GAME_ACTION:
GameUtils.resetGame( this, m_rowid );
+ onContentChanged(); // required because position may change
break;
case DELETE_GAME_ACTION:
GameUtils.deleteGame( this, m_rowid, true );
@@ -526,7 +528,6 @@ public class GamesList extends XWExpandableListActivity
long[] games = DBUtils.gamesList( this );
for ( int ii = games.length - 1; ii >= 0; --ii ) {
GameUtils.deleteGame( this, games[ii], ii == 0 );
- m_adapter.inval( games[ii] );
}
break;
case SYNC_MENU_ACTION:
@@ -759,8 +760,7 @@ public class GamesList extends XWExpandableListActivity
showOKOnlyDialog( R.string.no_copy_network );
} else {
byte[] stream = GameUtils.savedGame( this, m_rowid );
- GameUtils.GameLock lock =
- GameUtils.saveNewGame( this, stream );
+ GameLock lock = GameUtils.saveNewGame( this, stream );
DBUtils.saveSummary( this, lock, summary );
lock.unlock();
}
@@ -859,9 +859,12 @@ public class GamesList extends XWExpandableListActivity
} else if ( null != m_missingDictName ) {
showDialog( WARN_NODICT_SUBST );
} else {
- String dict = DictLangCache.getHaveLang( this, m_missingDictLang)[0];
- GameUtils.replaceDicts( this, m_missingDictRowId, null, dict );
- launchGameIf();
+ String dict =
+ DictLangCache.getHaveLang( this, m_missingDictLang)[0];
+ if ( GameUtils.replaceDicts( this, m_missingDictRowId,
+ null, dict ) ) {
+ launchGameIf();
+ }
}
}
return hasDicts;
@@ -878,7 +881,6 @@ public class GamesList extends XWExpandableListActivity
}
}
}
- onContentChanged();
}
}
@@ -893,7 +895,7 @@ public class GamesList extends XWExpandableListActivity
if ( null != rowids ) {
for ( long rowid : rowids ) {
if ( GameUtils.gameDictsHere( this, rowid ) ) {
- GameUtils.launchGame( this, rowid );
+ launchGame( rowid );
break outer;
}
}
@@ -951,7 +953,7 @@ public class GamesList extends XWExpandableListActivity
{
long[] rowids = DBUtils.getRowIDsFor( this, gameID );
if ( null != rowids && 0 < rowids.length ) {
- GameUtils.launchGame( this, rowids[0] );
+ launchGame( rowids[0] );
}
}
@@ -963,6 +965,16 @@ public class GamesList extends XWExpandableListActivity
}
}
+ private void startHasRowID( Intent intent )
+ {
+ long rowid = intent.getLongExtra( REMATCH_ROWID_EXTRA, -1 );
+ if ( -1 != rowid ) {
+ // this will juggle if the preference is set
+ long newid = GameUtils.dupeGame( this, rowid );
+ launchGame( newid );
+ }
+ }
+
private void askDefaultNameIf()
{
if ( null == CommonPrefs.getDefaultPlayerName( this, 0, false ) ) {
@@ -975,9 +987,9 @@ public class GamesList extends XWExpandableListActivity
private void updateField()
{
String newField = CommonPrefs.getSummaryField( this );
- if ( ! newField.equals( m_nameField ) ) {
- m_nameField = newField;
- m_adapter.setField( newField );
+ if ( m_adapter.setField( newField ) ) {
+ // The adapter should be able to decide whether full
+ // content change is required. PENDING
onContentChanged();
}
}
@@ -1019,10 +1031,20 @@ public class GamesList extends XWExpandableListActivity
return madeGame;
}
+ private void launchGame( long rowid, boolean invited )
+ {
+ GameUtils.launchGame( this, rowid, invited );
+ }
+
+ private void launchGame( long rowid )
+ {
+ launchGame( rowid, false );
+ }
+
private void makeNewNetGame( NetLaunchInfo info )
{
long rowid = GameUtils.makeNewNetGame( this, info );
- GameUtils.launchGame( this, rowid, true );
+ launchGame( rowid, true );
}
public static void onGameDictDownload( Context context, Intent intent )
@@ -1054,6 +1076,20 @@ public class GamesList extends XWExpandableListActivity
return intent;
}
+ public static Intent makeRematchIntent( Context context, CurGameInfo gi,
+ long rowid )
+ {
+ Intent intent = makeSelfIntent( context );
+
+ if ( CurGameInfo.DeviceRole.SERVER_STANDALONE == gi.serverRole ) {
+ intent.putExtra( REMATCH_ROWID_EXTRA, rowid );
+ } else {
+ Utils.notImpl( context );
+ }
+
+ return intent;
+ }
+
public static void openGame( Context context, Uri data )
{
Intent intent = makeSelfIntent( context );
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/InviteActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/InviteActivity.java
index 1a5c803f0..41dd2ae7c 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/InviteActivity.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/InviteActivity.java
@@ -43,7 +43,7 @@ abstract class InviteActivity extends XWListActivity
implements View.OnClickListener {
public static final String DEVS = "DEVS";
- public static final String INTENT_KEY_NMISSING = "NMISSING";
+ protected static final String INTENT_KEY_NMISSING = "NMISSING";
protected int m_nMissing;
protected Button m_okButton;
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiService.java
index 4e4d21686..5d2a776eb 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiService.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiService.java
@@ -42,7 +42,7 @@ public class MultiService {
public static final int OWNER_SMS = 1;
public static final int OWNER_RELAY = 2;
- private BTEventListener m_li;
+ private MultiEventListener m_li;
public enum MultiEvent { BAD_PROTO
, BT_ENABLED
@@ -64,14 +64,14 @@ public class MultiService {
, SMS_SEND_FAILED_NORADIO
};
- public interface BTEventListener {
+ public interface MultiEventListener {
public void eventOccurred( MultiEvent event, Object ... args );
}
// public interface MultiEventSrc {
// public void setBTEventListener( BTEventListener li );
// }
- public void setListener( BTEventListener li )
+ public void setListener( MultiEventListener li )
{
synchronized( this ) {
m_li = li;
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetLaunchInfo.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetLaunchInfo.java
index e2f79b4ed..878316cf1 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetLaunchInfo.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetLaunchInfo.java
@@ -73,7 +73,7 @@ public class NetLaunchInfo {
if ( null != data ) {
String scheme = data.getScheme();
try {
- if ( "content".equals(scheme) ) {
+ if ( "content".equals(scheme) || "file".equals(scheme) ) {
Assert.assertNotNull( context );
ContentResolver resolver = context.getContentResolver();
InputStream is = resolver.openInputStream( data );
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetUtils.java
index 4f900a76d..75d3d11b0 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetUtils.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NetUtils.java
@@ -130,8 +130,7 @@ public class NetUtils {
}
}
- public static byte[][][] queryRelay( Context context, String[] ids,
- int nBytes )
+ public static byte[][][] queryRelay( Context context, String[] ids )
{
byte[][][] msgs = null;
try {
@@ -141,6 +140,7 @@ public class NetUtils {
new DataOutputStream( socket.getOutputStream() );
// total packet size
+ int nBytes = sumStrings( ids );
outStream.writeShort( 2 + nBytes + ids.length + 1 );
outStream.writeByte( NetUtils.PROTOCOL_VERSION );
@@ -263,4 +263,16 @@ public class NetUtils {
DbgUtils.logf( "sendToRelay: null msgs" );
}
} // sendToRelay
+
+ private static int sumStrings( final String[] strs )
+ {
+ int len = 0;
+ if ( null != strs ) {
+ for ( String str : strs ) {
+ len += str.length();
+ }
+ }
+ return len;
+ }
+
}
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NewGameActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NewGameActivity.java
index 039aaa23d..d1b90148a 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NewGameActivity.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NewGameActivity.java
@@ -256,7 +256,7 @@ public class NewGameActivity extends XWActivity {
return dialog;
}
- // BTService.BTEventListener interface
+ // MultiService.MultiEventListener interface
@Override
public void eventOccurred( MultiService.MultiEvent event,
final Object ... args )
@@ -299,7 +299,7 @@ public class NewGameActivity extends XWActivity {
super.eventOccurred( event, args );
break;
}
- } // BTService.BTEventListener.eventOccurred
+ } // MultiService.MultiEventListener.eventOccurred
private void makeNewGame( boolean networked, boolean launch )
{
@@ -357,7 +357,7 @@ public class NewGameActivity extends XWActivity {
intent.putExtra( GameUtils.INTENT_FORRESULT_ROWID, true );
startActivityForResult( intent, CONFIG_FOR_BT );
} else {
- GameUtils.launchBTInviter( this, 1, INVITE_FOR_BT );
+ BTInviteActivity.launchForResult( this, 1, INVITE_FOR_BT );
}
}
@@ -378,7 +378,7 @@ public class NewGameActivity extends XWActivity {
intent.putExtra( GameUtils.INTENT_FORRESULT_ROWID, true );
startActivityForResult( intent, CONFIG_FOR_SMS );
} else {
- GameUtils.launchSMSInviter( this, 1, INVITE_FOR_SMS );
+ SMSInviteActivity.launchForResult( this, 1, INVITE_FOR_SMS );
}
}
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayGameActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayGameActivity.java
index e5571a6a1..16ead9288 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayGameActivity.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayGameActivity.java
@@ -41,7 +41,7 @@ public class RelayGameActivity extends XWActivity
private long m_rowid;
private CurGameInfo m_gi;
- private GameUtils.GameLock m_gameLock;
+ private GameLock m_gameLock;
private CommsAddrRec m_car;
private Button m_playButton;
private Button m_configButton;
@@ -68,22 +68,28 @@ public class RelayGameActivity extends XWActivity
super.onStart();
m_gi = new CurGameInfo( this );
- m_gameLock = new GameUtils.GameLock( m_rowid, true ).lock();
- int gamePtr = GameUtils.loadMakeGame( this, m_gi, m_gameLock );
- m_car = new CommsAddrRec();
- if ( XwJNI.game_hasComms( gamePtr ) ) {
- XwJNI.comms_getAddr( gamePtr, m_car );
+ m_gameLock = new GameLock( m_rowid, true ).lock( 300 );
+ if ( null == m_gameLock ) {
+ DbgUtils.logf( "RelayGameActivity.onStart(): unable to lock rowid %d",
+ m_rowid );
+ finish();
} else {
- Assert.fail();
- // String relayName = CommonPrefs.getDefaultRelayHost( this );
- // int relayPort = CommonPrefs.getDefaultRelayPort( this );
- // XwJNI.comms_getInitialAddr( m_carOrig, relayName, relayPort );
- }
- XwJNI.game_dispose( gamePtr );
+ int gamePtr = GameUtils.loadMakeGame( this, m_gi, m_gameLock );
+ m_car = new CommsAddrRec();
+ if ( XwJNI.game_hasComms( gamePtr ) ) {
+ XwJNI.comms_getAddr( gamePtr, m_car );
+ } else {
+ Assert.fail();
+ // String relayName = CommonPrefs.getDefaultRelayHost( this );
+ // int relayPort = CommonPrefs.getDefaultRelayPort( this );
+ // XwJNI.comms_getInitialAddr( m_carOrig, relayName, relayPort );
+ }
+ XwJNI.game_dispose( gamePtr );
- String lang = DictLangCache.getLangName( this, m_gi.dictLang );
- TextView text = (TextView)findViewById( R.id.explain );
- text.setText( getString( R.string.relay_game_explainf, lang ) );
+ String lang = DictLangCache.getLangName( this, m_gi.dictLang );
+ TextView text = (TextView)findViewById( R.id.explain );
+ text.setText( getString( R.string.relay_game_explainf, lang ) );
+ }
}
@Override
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java
index c312a8fc8..5da48cd30 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java
@@ -74,45 +74,40 @@ public class RelayService extends Service {
}
}
}
-
- private String[] collectIDs( int[] nBytes )
- {
- String[] ids = DBUtils.getRelayIDs( this, false );
- int len = 0;
- if ( null != ids ) {
- for ( String id : ids ) {
- len += id.length();
- }
- }
- nBytes[0] = len;
- return ids;
- }
private void fetchAndProcess()
{
- int[] nBytes = new int[1];
- String[] ids = collectIDs( nBytes );
- if ( null != ids && 0 < ids.length ) {
- RelayMsgSink sink = new RelayMsgSink();
- byte[][][] msgs =
- NetUtils.queryRelay( this, ids, nBytes[0] );
+ long[][] rowIDss = new long[1][];
+ String[] relayIDs = DBUtils.getRelayIDs( this, rowIDss );
+ if ( null != relayIDs && 0 < relayIDs.length ) {
+ long[] rowIDs = rowIDss[0];
+ byte[][][] msgs = NetUtils.queryRelay( this, relayIDs );
if ( null != msgs ) {
- int nameCount = ids.length;
+ RelayMsgSink sink = new RelayMsgSink();
+ int nameCount = relayIDs.length;
ArrayList idsWMsgs =
new ArrayList( nameCount );
for ( int ii = 0; ii < nameCount; ++ii ) {
+ byte[][] forOne = msgs[ii];
// if game has messages, open it and feed 'em
// to it.
- if ( GameUtils.feedMessages( this, ids[ii],
- msgs[ii], sink ) ) {
- idsWMsgs.add( ids[ii] );
+ if ( null == forOne ) {
+ // Nothing for this relayID
+ } else if ( BoardActivity.feedMessages( rowIDs[ii], forOne )
+ || GameUtils.feedMessages( this, rowIDs[ii],
+ forOne, null,
+ sink ) ) {
+ idsWMsgs.add( relayIDs[ii] );
+ } else {
+ DbgUtils.logf( "dropping message for %s (rowid %d)",
+ relayIDs[ii], rowIDs[ii] );
}
}
if ( 0 < idsWMsgs.size() ) {
- String[] relayIDs = new String[idsWMsgs.size()];
- idsWMsgs.toArray( relayIDs );
- setupNotification( relayIDs );
+ String[] tmp = new String[idsWMsgs.size()];
+ idsWMsgs.toArray( tmp );
+ setupNotification( tmp );
}
sink.send( this );
}
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSInviteActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSInviteActivity.java
index 60a7da3dd..aa955374b 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSInviteActivity.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSInviteActivity.java
@@ -32,9 +32,9 @@ import android.os.Bundle;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds;
import android.provider.ContactsContract;
-import android.text.method.DialerKeyListener;
import android.text.Editable;
import android.text.TextWatcher;
+import android.text.method.DialerKeyListener;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
@@ -64,6 +64,14 @@ public class SMSInviteActivity extends InviteActivity {
private String m_pendingNumber;
private boolean m_immobileConfirmed;
+ public static void launchForResult( Activity activity, int nMissing,
+ int requestCode )
+ {
+ Intent intent = new Intent( activity, SMSInviteActivity.class );
+ intent.putExtra( INTENT_KEY_NMISSING, nMissing );
+ activity.startActivityForResult( intent, requestCode );
+ }
+
@Override
protected void onCreate( Bundle savedInstanceState )
{
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java
index 6734e1220..be0a95ef2 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java
@@ -189,7 +189,7 @@ public class SMSService extends Service {
return result;
}
- public static void setListener( MultiService.BTEventListener li )
+ public static void setListener( MultiService.MultiEventListener li )
{
if ( XWApp.SMSSUPPORTED ) {
if ( null == s_srcMgr ) {
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java
index 4744e48ab..6d74998aa 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java
@@ -40,6 +40,8 @@ import android.net.Uri;
import android.provider.ContactsContract.PhoneLookup;
import android.telephony.TelephonyManager;
import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
import android.widget.CheckBox;
import android.widget.EditText;
@@ -345,6 +347,12 @@ public class Utils {
}
}
+ public static void setItemVisible( Menu menu, int id, boolean enabled )
+ {
+ MenuItem item = menu.findItem( id );
+ item.setVisible( enabled );
+ }
+
public static boolean hasSmallScreen( Context context )
{
if ( null == s_hasSmallScreen ) {
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWActivity.java
index 18670d379..71e8a922d 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWActivity.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWActivity.java
@@ -31,7 +31,7 @@ import android.widget.TextView;
import junit.framework.Assert;
public class XWActivity extends Activity
- implements DlgDelegate.DlgClickNotify, MultiService.BTEventListener {
+ implements DlgDelegate.DlgClickNotify, MultiService.MultiEventListener {
private DlgDelegate m_delegate;
@@ -192,7 +192,7 @@ public class XWActivity extends Activity
Assert.fail();
}
- // BTService.BTEventListener interface
+ // BTService.MultiEventListener interface
public void eventOccurred( MultiService.MultiEvent event,
final Object ... args )
{
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java
index 6c05dc270..d3d02f534 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java
@@ -28,12 +28,14 @@ import java.util.UUID;
import org.eehouse.android.xw4.jni.XwJNI;
public class XWApp extends Application {
- public static final boolean DEBUG_LOCKS = false;
public static final boolean BTSUPPORTED = false;
public static final boolean SMSSUPPORTED = true;
public static final boolean GCMSUPPORTED = true;
- public static final boolean ATTACH_SUPPORTED = false;
+ public static final boolean ATTACH_SUPPORTED = true;
+ public static final boolean REMATCH_SUPPORTED = false;
public static final boolean DEBUG = true;
+ public static final boolean DEBUG_LOCKS = false && DEBUG;
+ public static final boolean DEBUG_EXP_TIMERS = false && DEBUG;
public static final String SMS_PUBLIC_HEADER = "-XW4";
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListActivity.java
index e2adf4364..5b843f8f1 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListActivity.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListActivity.java
@@ -28,7 +28,7 @@ import android.os.Bundle;
import junit.framework.Assert;
public class XWListActivity extends ListActivity
- implements DlgDelegate.DlgClickNotify, MultiService.BTEventListener {
+ implements DlgDelegate.DlgClickNotify, MultiService.MultiEventListener {
private DlgDelegate m_delegate;
@@ -187,7 +187,7 @@ public class XWListActivity extends ListActivity
m_delegate.launchLookup( words, lang, forceList );
}
- // BTService.BTEventListener interface
+ // MultiService.MultiEventListener interface
public void eventOccurred( MultiService.MultiEvent event,
final Object ... args )
{
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListAdapter.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListAdapter.java
index 069191e81..decc6fde6 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListAdapter.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListAdapter.java
@@ -41,11 +41,14 @@ public abstract class XWListAdapter implements ListAdapter {
public boolean areAllItemsEnabled() { return true; }
public boolean isEnabled( int position ) { return true; }
public int getCount() { return m_count; }
- public long getItemId(int position) { return position; }
- public int getItemViewType(int position) { return 0; }
+ public Object getItem( int position ) { return null; }
+ public long getItemId( int position ) { return position; }
+ public int getItemViewType( int position ) {
+ return ListAdapter.IGNORE_ITEM_VIEW_TYPE;
+ }
public int getViewTypeCount() { return 1; }
public boolean hasStableIds() { return true; }
public boolean isEmpty() { return getCount() == 0; }
- public void registerDataSetObserver(DataSetObserver observer) {}
- public void unregisterDataSetObserver(DataSetObserver observer) {}
+ public void registerDataSetObserver( DataSetObserver observer ) {}
+ public void unregisterDataSetObserver( DataSetObserver observer ) {}
}
\ No newline at end of file
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java
index dce8adae8..b966a051e 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java
@@ -83,6 +83,11 @@ public class XWPrefs {
return getPrefsBoolean( context, R.string.key_ringer_zoom, false );
}
+ public static boolean getSquareTiles( Context context )
+ {
+ return getPrefsBoolean( context, R.string.key_square_tiles, false );
+ }
+
public static int getDefaultPlayerMinutes( Context context )
{
String value =
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java
index 24b82e392..e39adc389 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java
@@ -34,6 +34,7 @@ import org.eehouse.android.xw4.R;
import org.eehouse.android.xw4.DbgUtils;
import org.eehouse.android.xw4.ConnStatusHandler;
import org.eehouse.android.xw4.BoardDims;
+import org.eehouse.android.xw4.GameLock;
import org.eehouse.android.xw4.GameUtils;
import org.eehouse.android.xw4.DBUtils;
import org.eehouse.android.xw4.Toolbar;
@@ -94,6 +95,7 @@ public class JNIThread extends Thread {
public static final int QUERY_ENDGAME = 4;
public static final int TOOLBAR_STATES = 5;
public static final int GOT_WORDS = 6;
+ public static final int GAME_OVER = 7;
public class GameStateInfo implements Cloneable {
public int visTileCount;
@@ -120,7 +122,7 @@ public class JNIThread extends Thread {
private boolean m_stopped = false;
private boolean m_saveOnStop = false;
private int m_jniGamePtr;
- private GameUtils.GameLock m_lock;
+ private GameLock m_lock;
private Context m_context;
private CurGameInfo m_gi;
private Handler m_handler;
@@ -142,7 +144,7 @@ public class JNIThread extends Thread {
}
public JNIThread( int gamePtr, CurGameInfo gi, SyncedDraw drawer,
- GameUtils.GameLock lock, Context context, Handler handler )
+ GameLock lock, Context context, Handler handler )
{
m_jniGamePtr = gamePtr;
m_gi = gi;
@@ -524,8 +526,14 @@ public class JNIThread extends Thread {
case CMD_POST_OVER:
if ( XwJNI.server_getGameIsOver( m_jniGamePtr ) ) {
- sendForDialog( R.string.finalscores_title,
- XwJNI.server_writeFinalScores( m_jniGamePtr ) );
+ boolean auto = 0 < args.length &&
+ ((Boolean)args[0]).booleanValue();
+ int titleID = auto? R.string.summary_gameover
+ : R.string.finalscores_title;
+
+ String text = XwJNI.server_writeFinalScores( m_jniGamePtr );
+ Message.obtain( m_handler, GAME_OVER, titleID, 0, text )
+ .sendToTarget();
}
break;
diff --git a/xwords4/android/scripts/and_index.php b/xwords4/android/scripts/and_index.php
index baee28084..7d0a53cc1 100644
--- a/xwords4/android/scripts/and_index.php
+++ b/xwords4/android/scripts/and_index.php
@@ -30,16 +30,17 @@ function printNonAndroid($agent) {
$subject = "Android device not identified";
$body = htmlentities("My browser is running on an android device but"
- . " says its user agent is: \"$agent\". Please fix your script to recognize"
+ . " says its user agent is: \"$agent\"."
+ . " Please fix your website to recognize"
. " this as an Android browser.");
print <<
This page is meant to be viewed on an Android device.
- (If you are viewing this on an Android device, you've
- found a bug! Please email me (and be
- sure to leave the user agent string in the email body.)
+
(If you are viewing this on an Android device,
+ you've found a bug! Please email me
+ (and be sure to leave the user agent string in the email body.)
@@ -73,6 +74,11 @@ invite email (or text) and tap the link again.
link in your invite email (or text) again, and this time let
Crosswords handle it.
+(If you get tired of having to having to make that choice, Android
+will allow you to make Crosswords the default. If you do that
+Crosswords will be given control of all URLs that start with
+"http://eehouse.org/and/" -- not all URLs of any type.)
+
Have fun. And as always, let
me know if you have problems or suggestions.
diff --git a/xwords4/common/game.c b/xwords4/common/game.c
index f61e25ab2..ae73da301 100644
--- a/xwords4/common/game.c
+++ b/xwords4/common/game.c
@@ -93,10 +93,12 @@ game_makeNewGame( MPFORMAL XWGame* game, CurGameInfo* gi,
#endif
)
{
- XP_U16 nPlayersHere, nPlayersTotal;
-
- assertUtilOK( util );
+#ifndef XWFEATURE_STANDALONE_ONLY
+ XP_U16 nPlayersHere = 0;
+ XP_U16 nPlayersTotal = 0;
checkServerRole( gi, &nPlayersHere, &nPlayersTotal );
+#endif
+ assertUtilOK( util );
gi->gameID = makeGameID( util );
@@ -137,15 +139,17 @@ game_reset( MPFORMAL XWGame* game, CurGameInfo* gi,
CommonPrefs* cp, const TransportProcs* procs )
{
XP_U16 ii;
- XP_U16 nPlayersHere, nPlayersTotal;
XP_ASSERT( !!game->model );
XP_ASSERT( !!gi );
- checkServerRole( gi, &nPlayersHere, &nPlayersTotal );
gi->gameID = makeGameID( util );
#ifndef XWFEATURE_STANDALONE_ONLY
+ XP_U16 nPlayersHere = 0;
+ XP_U16 nPlayersTotal = 0;
+ checkServerRole( gi, &nPlayersHere, &nPlayersTotal );
+
if ( !!game->comms ) {
if ( gi->serverRole == SERVER_STANDALONE ) {
comms_destroy( game->comms );
diff --git a/xwords4/common/server.c b/xwords4/common/server.c
index 95e4c0759..aebb41ab2 100644
--- a/xwords4/common/server.c
+++ b/xwords4/common/server.c
@@ -686,7 +686,7 @@ handleRegistrationMsg( ServerCtxt* server, XWStreamCtxt* stream )
{
XP_Bool success = XP_TRUE;
XP_U16 playersInMsg;
- XP_S8 clientIndex;
+ XP_S8 clientIndex = 0; /* quiet compiler */
XP_U16 ii = 0;
LOG_FUNC();
diff --git a/xwords4/relay/scripts/gcm_loop.py b/xwords4/relay/scripts/gcm_loop.py
index 0e9b48015..666df85dd 100755
--- a/xwords4/relay/scripts/gcm_loop.py
+++ b/xwords4/relay/scripts/gcm_loop.py
@@ -55,13 +55,19 @@ def init():
def getPendingMsgs( con, typ ):
cur = con.cursor()
query = """SELECT id, devid FROM msgs
- WHERE devid IN (SELECT id FROM devices WHERE devtype=%d)
+ WHERE devid IN (SELECT id FROM devices WHERE devtype=%d and NOT unreg)
AND NOT connname IN (SELECT connname FROM games WHERE dead); """
cur.execute(query % typ)
result = cur.fetchall()
if g_debug: print "getPendingMsgs=>", result
return result
+def unregister( gcmid ):
+ global g_con
+ print "unregister(", gcmid, ")"
+ query = "UPDATE devices SET unreg=TRUE WHERE id = '%s'" % gcmid
+ g_con.cursor().execute( query )
+
def asGCMIds(con, devids, typ):
cur = con.cursor()
query = "SELECT devid FROM devices WHERE devtype = %d AND id IN (%s)" \
@@ -72,20 +78,15 @@ def asGCMIds(con, devids, typ):
def notifyGCM( devids, typ ):
if typ == DEVTYPE_GCM:
instance = gcm.GCM( mykey.myKey )
- data = { 'getMoves': True,
- # 'title' : 'Msg from Darth',
- # 'msg' : "I am your father, Luke.",
- }
+ data = { 'getMoves': True, }
response = instance.json_request( registration_ids = devids,
- # restricted_package_name = 'org.eehouse.android.xw4',
data = data,
- # collapse_key = 'NewMove',
)
if 'errors' in response:
response = response['errors']
if 'NotRegistered' in response:
- for id in response['NotRegistered']:
- print 'need to remove "', id, '" from db'
+ for gcmid in response['NotRegistered']:
+ unregister( gcmid )
else:
print "got some kind of error"
else:
@@ -184,6 +185,7 @@ def main():
print "devices needing notification:", targets
notifyGCM( asGCMIds( g_con, targets, typ ), typ )
pruneSent( devids )
+ elif g_debug: print "no targets after backoff"
else:
emptyCount += 1
if (0 == (emptyCount%5)) and not g_debug: