Merge branch 'android_branch' into android_groups

Conflicts:
	xwords4/android/XWords4/res/values/strings.xml
	xwords4/android/XWords4/src/org/eehouse/android/xw4/DBUtils.java
	xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java
	xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java
	xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java
This commit is contained in:
Eric House 2012-12-18 06:35:07 -08:00
commit 8a58492389
44 changed files with 1333 additions and 828 deletions

View file

@ -22,11 +22,11 @@
to come from a domain that you own or have control over. --> to come from a domain that you own or have control over. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.eehouse.android.xw4" package="org.eehouse.android.xw4"
android:versionCode="48" android:versionCode="49"
android:versionName="@string/app_version" android:versionName="@string/app_version"
> >
<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="7" /> <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="8" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@ -134,14 +134,12 @@
/> />
</intent-filter> </intent-filter>
<!-- <intent-filter> --> <intent-filter>
<!-- <action android:name="android.intent.action.VIEW" /> --> <action android:name="android.intent.action.VIEW" />
<!-- <category android:name="android.intent.category.DEFAULT" /> --> <category android:name="android.intent.category.DEFAULT" />
<!-- <category android:name="android.intent.category.BROWSABLE" /> --> <category android:name="android.intent.category.BROWSABLE" />
<!-- <data android:mimeType="*/*" --> <data android:mimeType="@string/invite_mime" />
<!-- android:scheme="content" --> </intent-filter>
<!-- /> -->
<!-- </intent-filter> -->
</activity> </activity>
<!-- downloading dicts --> <!-- downloading dicts -->

View file

@ -10,4 +10,4 @@
# Indicates whether an apk should be generated for each density. # Indicates whether an apk should be generated for each density.
split.density=false split.density=false
# Project target. # Project target.
target=android-7 target=android-8

View file

@ -3,95 +3,114 @@
<!-- top-level layout is hozontal, with an image and another layout --> <!-- top-level layout is hozontal, with an image and another layout -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <org.eehouse.android.xw4.GameListItem
android:orientation="horizontal" xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:longClickable="true"
android:focusable="true"
android:clickable="true"
android:background="@android:drawable/list_selector_background"
>
<TextView android:id="@+id/view_unloaded"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:longClickable="true" android:textAppearance="?android:attr/textAppearanceLarge"
android:focusable="true" android:gravity="center"
android:clickable="true" android:paddingTop="10dp"
android:background="@android:drawable/list_selector_background" android:paddingBottom="10dp"
> android:text="@string/game_list_tmp"
/>
<ImageView android:id="@+id/msg_marker" <LinearLayout android:id="@+id/view_loaded"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_gravity="center_vertical|center_horizontal"
android:layout_weight="0"
/>
<!-- this layout is vertical, holds everything but the status
icon[s] (plural later) -->
<LinearLayout android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<!-- This is the game name and expander -->
<LinearLayout android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<org.eehouse.android.xw4.ExpiringTextView
android:id="@+id/game_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_weight="1"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceMedium"
/>
<ImageButton android:id="@+id/expander"
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@drawable/expander_ic_maximized"
/>
</LinearLayout>
<!-- This is everything below the name (which can be hidden) -->
<LinearLayout android:id="@+id/hideable"
android:orientation="horizontal" android:orientation="horizontal"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="4sp"> android:visibility="gone"
>
<!-- Player list plus connection status --> <ImageView android:id="@+id/msg_marker"
<LinearLayout android:id="@+id/player_list" android:layout_width="wrap_content"
android:orientation="vertical" android:layout_height="fill_parent"
android:layout_width="wrap_content" android:layout_gravity="center_vertical|center_horizontal"
android:layout_height="fill_parent" android:layout_weight="0"
android:layout_weight="1" />
android:layout_marginRight="4dip"
/> <!-- end players column -->
<!-- holds right column. Could hold more... --> <!-- this layout is vertical, holds everything but the status
<LinearLayout android:orientation="vertical" icon[s] (plural later) -->
android:layout_width="wrap_content" <LinearLayout android:orientation="vertical"
android:layout_height="fill_parent" android:layout_width="fill_parent"
> android:layout_height="wrap_content"
<TextView android:id="@+id/modtime" >
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="right"
/>
<TextView android:id="@+id/state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="right"
/>
</LinearLayout>
<!-- This is the game name and expander -->
<LinearLayout android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<org.eehouse.android.xw4.ExpiringTextView
android:id="@+id/game_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_weight="1"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceMedium"
/>
<ImageButton android:id="@+id/expander"
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@drawable/expander_ic_maximized"
/>
</LinearLayout>
<!-- This is everything below the name (which can be hidden) -->
<LinearLayout android:id="@+id/hideable"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="4sp">
<!-- Player list plus connection status -->
<LinearLayout android:id="@+id/player_list"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_weight="1"
android:layout_marginRight="4dip"
/> <!-- end players column -->
<!-- holds right column. Could hold more... -->
<LinearLayout android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
>
<TextView android:id="@+id/modtime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="right"
/>
<TextView android:id="@+id/state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="right"
/>
</LinearLayout>
</LinearLayout>
<TextView android:id="@+id/role"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|center_horizontal"
/>
</LinearLayout>
</LinearLayout> </LinearLayout>
</org.eehouse.android.xw4.GameListItem>
<TextView android:id="@+id/role"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|center_horizontal"
/>
</LinearLayout>
</LinearLayout>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:text="@string/game_list_tmp"
/>

View file

@ -5,18 +5,22 @@
</style> </style>
</head> </head>
<body> <body>
<b>Crosswords 4.4 beta 56 release</b> <b>Crosswords 4.4 beta 57 release</b>
<ul>New with this release
<li>Improve invitations: no more redirection through a website, and <h3>New with this release</h3>
confirm before creating duplicate games</li> <ul>
<li>(For sideloading users only) No more browser involvement in <li>Include new game information as an attachment in email invites
updates: app can launch the installer directly to update for use on devices that don't dispatch URLs correctly in received
itself</li> email (e.g. some by HTC) </li>
<li>Remove notifications when their games are deleted</li> <li>Show final scores alert whenever a finished game is opened -- to
make it more clear that it's finished</li>
<li>Fix flickering in main screen (games list)</li>
<li>Add option, off by default, to keep rack tiles square even when
the screen is large enough that they can be taller</li>
</ul> </ul>
<ul>Next up <h3>Next up</h3>
<li>One more idea for improving invitations</li> <ul>
<li>Allow grouping of games in collapsible user-defined categores: "Games with <li>Allow grouping of games in collapsible user-defined categores: "Games with
Kati", "Finished games", etc.</li> Kati", "Finished games", etc.</li>
</ul> </ul>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_version">4.4 beta 56</string> <string name="app_version">4.4 beta 57</string>
</resources> </resources>

View file

@ -6,6 +6,7 @@
<!-- prefs keys --> <!-- prefs keys -->
<string name="key_color_tiles">key_color_tiles</string> <string name="key_color_tiles">key_color_tiles</string>
<string name="key_show_arrow">key_show_arrow</string> <string name="key_show_arrow">key_show_arrow</string>
<string name="key_square_tiles">key_square_tiles</string>
<string name="key_explain_robot">key_explain_robot</string> <string name="key_explain_robot">key_explain_robot</string>
<string name="key_skip_confirm">key_skip_confirm</string> <string name="key_skip_confirm">key_skip_confirm</string>
<string name="key_sort_tiles">key_sort_tiles</string> <string name="key_sort_tiles">key_sort_tiles</string>

View file

@ -1233,8 +1233,10 @@
encodings for the greater-than and less-than symbols which encodings for the greater-than and less-than symbols which
are not legal in xml strings.)--> are not legal in xml strings.)-->
<string name="invite_htmf">\u003ca href=\"%1$s\"\u003ETap <string name="invite_htmf">\u003ca href=\"%1$s\"\u003ETap
here\u003c/a\u003E (or the full link below) to accept my invitation and here\u003c/a\u003E (or tap the full link below, or, if you already
join this game. have Crosswords installed, open the attachment) to accept my
invitation and join this game.
\u003cbr \\\u003E \u003cbr \\\u003E
\u003cbr \\\u003E \u003cbr \\\u003E
(full link: %1$s) (full link: %1$s)
@ -2154,4 +2156,13 @@
<string name="no_move_onegroup">Moving is impossible until there <string name="no_move_onegroup">Moving is impossible until there
is more than one group.</string> is more than one group.</string>
<!-- Button shown in game over dialog triggering creation of new
game with the same players and parameters as the one that
just ended. -->
<string name="button_rematch">Rematch</string>
<string name="square_tiles">Square rack tiles</string>
<string name="square_tiles_summary">Even if they can be taller</string>
</resources> </resources>

View file

@ -132,6 +132,11 @@
android:summary="@string/show_arrow_summary" android:summary="@string/show_arrow_summary"
android:defaultValue="true" android:defaultValue="true"
/> />
<CheckBoxPreference android:key="@string/key_square_tiles"
android:title="@string/square_tiles"
android:summary="@string/square_tiles_summary"
android:defaultValue="false"
/>
<CheckBoxPreference android:key="@string/key_keep_screenon" <CheckBoxPreference android:key="@string/key_keep_screenon"
android:title="@string/keep_screenon" android:title="@string/keep_screenon"
android:summary="@string/keep_screenon_summary" android:summary="@string/keep_screenon_summary"

View file

@ -45,6 +45,14 @@ public class BTInviteActivity extends InviteActivity
private boolean m_firstScan; private boolean m_firstScan;
private int m_checkCount; private int m_checkCount;
public static void launchForResult( Activity activity, int nMissing,
int requestCode )
{
Intent intent = new Intent( activity, BTInviteActivity.class );
intent.putExtra( INTENT_KEY_NMISSING, nMissing );
activity.startActivityForResult( intent, requestCode );
}
@Override @Override
protected void onCreate( Bundle savedInstanceState ) protected void onCreate( Bundle savedInstanceState )
{ {
@ -57,7 +65,7 @@ public class BTInviteActivity extends InviteActivity
BTService.clearDevices( this, null ); // will return names BTService.clearDevices( this, null ); // will return names
} }
// BTService.BTEventListener interface // MultiService.MultiEventListener interface
@Override @Override
public void eventOccurred( MultiService.MultiEvent event, final Object ... args ) public void eventOccurred( MultiService.MultiEvent event, final Object ... args )
{ {

View file

@ -147,7 +147,7 @@ public class BTService extends Service {
} }
} }
public static void setListener( MultiService.BTEventListener li ) public static void setListener( MultiService.MultiEventListener li )
{ {
if ( XWApp.BTSUPPORTED ) { if ( XWApp.BTSUPPORTED ) {
if ( null == s_srcMgr ) { if ( null == s_srcMgr ) {

View file

@ -75,7 +75,7 @@ public class BoardActivity extends XWActivity
private static final int PICK_TILE_REQUESTTRAY_BLK = DLG_OKONLY + 11; private static final int PICK_TILE_REQUESTTRAY_BLK = DLG_OKONLY + 11;
private static final int DLG_USEDICT = DLG_OKONLY + 12; private static final int DLG_USEDICT = DLG_OKONLY + 12;
private static final int DLG_GETDICT = DLG_OKONLY + 13; private static final int DLG_GETDICT = DLG_OKONLY + 13;
private static final int GAME_OVER = DLG_OKONLY + 14;
private static final int CHAT_REQUEST = 1; private static final int CHAT_REQUEST = 1;
private static final int BT_INVITE_RESULT = 2; private static final int BT_INVITE_RESULT = 2;
@ -114,7 +114,7 @@ public class BoardActivity extends XWActivity
private BoardView m_view; private BoardView m_view;
private int m_jniGamePtr; private int m_jniGamePtr;
private GameUtils.GameLock m_gameLock; private GameLock m_gameLock;
private CurGameInfo m_gi; private CurGameInfo m_gi;
private CommsTransport m_xport; private CommsTransport m_xport;
private Handler m_handler = null; private Handler m_handler = null;
@ -165,6 +165,7 @@ public class BoardActivity extends XWActivity
private int m_missing; private int m_missing;
private boolean m_haveInvited = false; private boolean m_haveInvited = false;
private boolean m_overNotShown;
private static BoardActivity s_this = null; private static BoardActivity s_this = null;
private static Class s_thisLocker = BoardActivity.class; private static Class s_thisLocker = BoardActivity.class;
@ -188,6 +189,27 @@ public class BoardActivity extends XWActivity
return delivered; return delivered;
} }
public static boolean feedMessages( long rowid, byte[][] msgs )
{
boolean delivered = false;
Assert.assertNotNull( msgs );
synchronized( s_thisLocker ) {
if ( null != s_this ) {
Assert.assertNotNull( s_this.m_gi );
Assert.assertNotNull( s_this.m_gameLock );
Assert.assertNotNull( s_this.m_jniThread );
if ( rowid == s_this.m_rowid ) {
delivered = true; // even if no messages!
for ( byte[] msg : msgs ) {
s_this.m_jniThread.handle( JNICmd.CMD_RECEIVE, msg,
null );
}
}
}
}
return delivered;
}
private static void setThis( BoardActivity self ) private static void setThis( BoardActivity self )
{ {
synchronized( s_thisLocker ) { synchronized( s_thisLocker ) {
@ -234,6 +256,7 @@ public class BoardActivity extends XWActivity
case DLG_OKONLY: case DLG_OKONLY:
case DLG_BADWORDS: case DLG_BADWORDS:
case DLG_RETRY: case DLG_RETRY:
case GAME_OVER:
ab = new AlertDialog.Builder( this ) ab = new AlertDialog.Builder( this )
.setTitle( m_dlgTitle ) .setTitle( m_dlgTitle )
.setMessage( m_dlgBytes ) .setMessage( m_dlgBytes )
@ -246,6 +269,14 @@ public class BoardActivity extends XWActivity
} }
}; };
ab.setNegativeButton( R.string.button_retry, lstnr ); ab.setNegativeButton( R.string.button_retry, lstnr );
} else if ( XWApp.REMATCH_SUPPORTED && GAME_OVER == id ) {
lstnr = new DialogInterface.OnClickListener() {
public void onClick( DialogInterface dlg,
int whichButton ) {
doRematch();
}
};
ab.setNegativeButton( R.string.button_rematch, lstnr );
} }
dialog = ab.create(); dialog = ab.create();
Utils.setRemoveOnDismiss( this, dialog, id ); Utils.setRemoveOnDismiss( this, dialog, id );
@ -499,7 +530,9 @@ public class BoardActivity extends XWActivity
Intent intent = getIntent(); Intent intent = getIntent();
m_rowid = intent.getLongExtra( GameUtils.INTENT_KEY_ROWID, -1 ); m_rowid = intent.getLongExtra( GameUtils.INTENT_KEY_ROWID, -1 );
DbgUtils.logf( "BoardActivity: opening rowid %d", m_rowid );
m_haveInvited = intent.getBooleanExtra( GameUtils.INVITED, false ); m_haveInvited = intent.getBooleanExtra( GameUtils.INVITED, false );
m_overNotShown = true;
setBackgroundColor(); setBackgroundColor();
setKeepScreenOn(); setKeepScreenOn();
@ -691,8 +724,13 @@ public class BoardActivity extends XWActivity
item.setTitle( R.string.board_menu_game_final ); item.setTitle( R.string.board_menu_game_final );
} }
if ( DeviceRole.SERVER_STANDALONE == m_gi.serverRole ) {
Utils.setItemVisible( menu, R.id.board_menu_game_resend, false );
Utils.setItemVisible( menu, R.id.gamel_menu_checkmoves, false );
}
return true; return true;
} } // onPrepareOptionsMenu
public boolean onOptionsItemSelected( MenuItem item ) public boolean onOptionsItemSelected( MenuItem item )
{ {
@ -830,12 +868,12 @@ public class BoardActivity extends XWActivity
doSyncMenuitem(); doSyncMenuitem();
break; break;
case BT_PICK_ACTION: case BT_PICK_ACTION:
GameUtils.launchBTInviter( this, m_nMissingPlayers, BTInviteActivity.launchForResult( this, m_nMissingPlayers,
BT_INVITE_RESULT ); BT_INVITE_RESULT );
break; break;
case SMS_PICK_ACTION: case SMS_PICK_ACTION:
GameUtils.launchSMSInviter( this, m_nMissingPlayers, SMSInviteActivity.launchForResult( this, m_nMissingPlayers,
SMS_INVITE_RESULT ); SMS_INVITE_RESULT );
break; break;
case SMS_CONFIG_ACTION: case SMS_CONFIG_ACTION:
Utils.launchSettings( this ); Utils.launchSettings( this );
@ -907,7 +945,7 @@ public class BoardActivity extends XWActivity
} }
////////////////////////////////////////////////// //////////////////////////////////////////////////
// BTService.BTEventListener interface // MultiService.MultiEventListener interface
////////////////////////////////////////////////// //////////////////////////////////////////////////
@Override @Override
@SuppressWarnings("fallthrough") @SuppressWarnings("fallthrough")
@ -1635,7 +1673,7 @@ public class BoardActivity extends XWActivity
showDictGoneFinish(); showDictGoneFinish();
} else { } else {
Assert.assertNull( m_gameLock ); Assert.assertNull( m_gameLock );
m_gameLock = new GameUtils.GameLock( m_rowid, true ).lock(); m_gameLock = new GameLock( m_rowid, true ).lock();
byte[] stream = GameUtils.savedGame( this, m_gameLock ); byte[] stream = GameUtils.savedGame( this, m_gameLock );
m_gi = new CurGameInfo( this ); m_gi = new CurGameInfo( this );
@ -1693,6 +1731,11 @@ public class BoardActivity extends XWActivity
launchLookup( wordsToArray((String)msg.obj), launchLookup( wordsToArray((String)msg.obj),
m_gi.dictLang ); m_gi.dictLang );
break; break;
case JNIThread.GAME_OVER:
m_dlgBytes = (String)msg.obj;
m_dlgTitle = msg.arg1;
showDialog( GAME_OVER );
break;
} }
} }
}; };
@ -1722,8 +1765,18 @@ public class BoardActivity extends XWActivity
if ( 0 != (GameSummary.MSG_FLAGS_CHAT & flags) ) { if ( 0 != (GameSummary.MSG_FLAGS_CHAT & flags) ) {
startChatActivity(); startChatActivity();
} }
if ( 0 != (GameSummary.MSG_FLAGS_GAMEOVER & flags) ) { if ( m_overNotShown ) {
m_jniThread.handle( JNICmd.CMD_POST_OVER ); boolean auto = false;
if ( 0 != (GameSummary.MSG_FLAGS_GAMEOVER & flags) ) {
m_gameOver = true;
} else if ( DBUtils.gameOver( this, m_rowid ) ) {
m_gameOver = true;
auto = true;
}
if ( m_gameOver ) {
m_overNotShown = false;
m_jniThread.handle( JNICmd.CMD_POST_OVER, auto );
}
} }
if ( 0 != flags ) { if ( 0 != flags ) {
DBUtils.setMsgFlags( m_rowid, GameSummary.MSG_FLAGS_NONE ); DBUtils.setMsgFlags( m_rowid, GameSummary.MSG_FLAGS_NONE );
@ -2074,4 +2127,11 @@ public class BoardActivity extends XWActivity
m_passwdEdit = (EditText)m_passwdLyt.findViewById( R.id.edit ); m_passwdEdit = (EditText)m_passwdLyt.findViewById( R.id.edit );
} }
private void doRematch()
{
Intent intent = GamesList.makeRematchIntent( this, m_gi, m_rowid );
startActivity( intent );
finish();
}
} // class BoardActivity } // class BoardActivity

View file

@ -379,8 +379,13 @@ public class BoardView extends View implements DrawCtx, BoardHandler,
heightLeft = cellSize * 3 / 2; heightLeft = cellSize * 3 / 2;
} }
heightLeft /= 3; heightLeft /= 3;
trayHt += heightLeft * 2;
scoreHt += heightLeft; scoreHt += heightLeft;
trayHt += heightLeft * 2;
if ( XWPrefs.getSquareTiles( m_context )
&& trayHt > (width / 7) ) {
trayHt = width / 7;
}
heightUsed = trayHt + scoreHt + ((nCells - nToScroll) * cellSize); heightUsed = trayHt + scoreHt + ((nCells - nToScroll) * cellSize);
} }

View file

@ -60,7 +60,7 @@ public class DBHelper extends SQLiteOpenHelper {
public static final String INVITEID = "INVITEID"; public static final String INVITEID = "INVITEID";
public static final String RELAYID = "RELAYID"; public static final String RELAYID = "RELAYID";
public static final String SEED = "SEED"; 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 LASTMOVE = "LASTMOVE";
public static final String GROUPID = "GROUPID"; public static final String GROUPID = "GROUPID";
@ -100,7 +100,7 @@ public class DBHelper extends SQLiteOpenHelper {
,SEED, "INTEGER" ,SEED, "INTEGER"
,DICTLANG, "INTEGER" ,DICTLANG, "INTEGER"
,DICTLIST, "TEXT" ,DICTLIST, "TEXT"
,SMSPHONE, "TEXT" ,SMSPHONE, "TEXT" // unused
,SCORES, "TEXT" ,SCORES, "TEXT"
,CHAT_HISTORY, "TEXT" ,CHAT_HISTORY, "TEXT"
,GAMEID, "INTEGER" ,GAMEID, "INTEGER"

View file

@ -60,9 +60,10 @@ public class DBUtils {
private static long s_cachedRowID = -1; private static long s_cachedRowID = -1;
private static byte[] s_cachedBytes = null; private static byte[] s_cachedBytes = null;
private static long[] s_cachedRowIDs = null;
public static interface DBChangeListener { public static interface DBChangeListener {
public void gameSaved( long rowid ); public void gameSaved( long rowid, boolean countChanged );
} }
private static HashSet<DBChangeListener> s_listeners = private static HashSet<DBChangeListener> s_listeners =
new HashSet<DBChangeListener>(); new HashSet<DBChangeListener>();
@ -100,8 +101,7 @@ public class DBUtils {
long maxMillis ) long maxMillis )
{ {
GameSummary result = null; GameSummary result = null;
GameUtils.GameLock lock = GameLock lock = new GameLock( rowid, false ).lock( maxMillis );
new GameUtils.GameLock( rowid, false ).lock( maxMillis );
if ( null != lock ) { if ( null != lock ) {
result = getSummary( context, lock ); result = getSummary( context, lock );
lock.unlock(); lock.unlock();
@ -115,7 +115,7 @@ public class DBUtils {
} }
public static GameSummary getSummary( Context context, public static GameSummary getSummary( Context context,
GameUtils.GameLock lock ) GameLock lock )
{ {
initDB( context ); initDB( context );
GameSummary summary = null; GameSummary summary = null;
@ -129,7 +129,7 @@ public class DBUtils {
DBHelper.TURN, DBHelper.GIFLAGS, DBHelper.TURN, DBHelper.GIFLAGS,
DBHelper.CONTYPE, DBHelper.SERVERROLE, DBHelper.CONTYPE, DBHelper.SERVERROLE,
DBHelper.ROOMNAME, DBHelper.RELAYID, DBHelper.ROOMNAME, DBHelper.RELAYID,
DBHelper.SMSPHONE, DBHelper.SEED, /*DBHelper.SMSPHONE,*/ DBHelper.SEED,
DBHelper.DICTLANG, DBHelper.GAMEID, DBHelper.DICTLANG, DBHelper.GAMEID,
DBHelper.SCORES, DBHelper.HASMSGS, DBHelper.SCORES, DBHelper.HASMSGS,
DBHelper.LASTPLAY_TIME, DBHelper.REMOTEDEVS, DBHelper.LASTPLAY_TIME, DBHelper.REMOTEDEVS,
@ -247,13 +247,13 @@ public class DBUtils {
return summary; return summary;
} // getSummary } // getSummary
public static void saveSummary( Context context, GameUtils.GameLock lock, public static void saveSummary( Context context, GameLock lock,
GameSummary summary ) GameSummary summary )
{ {
saveSummary( context, lock, summary, null ); 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 ) GameSummary summary, String inviteID )
{ {
Assert.assertTrue( lock.canWrite() ); Assert.assertTrue( lock.canWrite() );
@ -314,9 +314,12 @@ public class DBUtils {
long result = db.update( DBHelper.TABLE_NAME_SUM, long result = db.update( DBHelper.TABLE_NAME_SUM,
values, selection, null ); values, selection, null );
Assert.assertTrue( result >= 0 ); Assert.assertTrue( result >= 0 );
if ( result != rowid ) { // new row added
clearRowIDsCache();
}
} }
notifyListeners( rowid );
db.close(); db.close();
notifyListeners( rowid, false );
} }
} // saveSummary } // saveSummary
@ -372,7 +375,7 @@ public class DBUtils {
public static void setMsgFlags( long rowid, int flags ) public static void setMsgFlags( long rowid, int flags )
{ {
setInt( rowid, DBHelper.HASMSGS, flags ); setInt( rowid, DBHelper.HASMSGS, flags );
notifyListeners( rowid ); notifyListeners( rowid, false );
} }
public static void setExpanded( long rowid, boolean expanded ) public static void setExpanded( long rowid, boolean expanded )
@ -447,10 +450,8 @@ public class DBUtils {
String selection = DBHelper.RELAYID + "='" + relayID + "'"; String selection = DBHelper.RELAYID + "='" + relayID + "'";
Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns, Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns,
selection, null, null, null, null ); selection, null, null, null, null );
result = new long[cursor.getCount()];
for ( int ii = 0; cursor.moveToNext(); ++ii ) { for ( int ii = 0; cursor.moveToNext(); ++ii ) {
if ( null == result ) {
result = new long[cursor.getCount()];
}
result[ii] = cursor.getLong( cursor.getColumnIndex(ROW_ID) ); result[ii] = cursor.getLong( cursor.getColumnIndex(ROW_ID) );
} }
cursor.close(); cursor.close();
@ -469,11 +470,8 @@ public class DBUtils {
String selection = String.format( DBHelper.GAMEID + "=%d", gameID ); String selection = String.format( DBHelper.GAMEID + "=%d", gameID );
Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns, Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns,
selection, null, null, null, null ); selection, null, null, null, null );
result = new long[cursor.getCount()];
for ( int ii = 0; cursor.moveToNext(); ++ii ) { for ( int ii = 0; cursor.moveToNext(); ++ii ) {
if ( null == result ) {
result = new long[cursor.getCount()];
}
result[ii] = cursor.getLong( cursor.getColumnIndex(ROW_ID) ); result[ii] = cursor.getLong( cursor.getColumnIndex(ROW_ID) );
} }
cursor.close(); cursor.close();
@ -574,7 +572,7 @@ public class DBUtils {
return result; return result;
} }
public static String[] getRelayIDs( Context context, boolean noMsgs ) public static String[] getRelayIDs( Context context, long[][] rowIDs )
{ {
String[] result = null; String[] result = null;
initDB( context ); initDB( context );
@ -582,26 +580,31 @@ public class DBUtils {
synchronized( s_dbHelper ) { synchronized( s_dbHelper ) {
SQLiteDatabase db = s_dbHelper.getReadableDatabase(); SQLiteDatabase db = s_dbHelper.getReadableDatabase();
String[] columns = { DBHelper.RELAYID }; String[] columns = { ROW_ID, DBHelper.RELAYID };
String selection = DBHelper.RELAYID + " NOT null"; String selection = DBHelper.RELAYID + " NOT null";
if ( noMsgs ) {
selection += " AND NOT " + DBHelper.HASMSGS;
}
Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns, Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns,
selection, null, null, null, null ); 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() ) { int idIndex = cursor.getColumnIndex(DBHelper.RELAYID);
ids.add( cursor.getString( cursor. int rowIndex = cursor.getColumnIndex(ROW_ID);
getColumnIndex(DBHelper.RELAYID)) ); for ( int ii = 0; cursor.moveToNext(); ++ii ) {
result[ii] = cursor.getString( idIndex );
if ( null != rowIDs ) {
rowIDs[0][ii] = cursor.getLong( rowIndex );
}
}
} }
cursor.close(); cursor.close();
db.close(); db.close();
} }
if ( 0 < ids.size() ) {
result = ids.toArray( new String[ids.size()] );
}
return result; 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 ); initDB( context );
synchronized( s_dbHelper ) { synchronized( s_dbHelper ) {
@ -695,16 +698,16 @@ public class DBUtils {
long rowid = db.insert( DBHelper.TABLE_NAME_SUM, null, values ); long rowid = db.insert( DBHelper.TABLE_NAME_SUM, null, values );
setCached( rowid, null ); // force reread setCached( rowid, null ); // force reread
clearRowIDsCache();
lock = new GameUtils.GameLock( rowid, true ).lock(); lock = new GameLock( rowid, true ).lock();
notifyListeners( rowid, true );
notifyListeners( rowid );
} }
return lock; return lock;
} }
public static long saveGame( Context context, GameUtils.GameLock lock, public static long saveGame( Context context, GameLock lock,
byte[] bytes, boolean setCreate ) byte[] bytes, boolean setCreate )
{ {
Assert.assertTrue( lock.canWrite() ); Assert.assertTrue( lock.canWrite() );
@ -722,13 +725,13 @@ public class DBUtils {
updateRow( context, DBHelper.TABLE_NAME_SUM, rowid, values ); updateRow( context, DBHelper.TABLE_NAME_SUM, rowid, values );
setCached( rowid, null ); // force reread setCached( rowid, null ); // force reread
if ( -1 != rowid ) { // Is this possible? PENDING if ( -1 != rowid ) { // Means new game?
notifyListeners( rowid ); notifyListeners( rowid, false );
} }
return rowid; return rowid;
} }
public static byte[] loadGame( Context context, GameUtils.GameLock lock ) public static byte[] loadGame( Context context, GameLock lock )
{ {
long rowid = lock.getRowid(); long rowid = lock.getRowid();
Assert.assertTrue( -1 != rowid ); Assert.assertTrue( -1 != rowid );
@ -756,12 +759,16 @@ public class DBUtils {
public static void deleteGame( Context context, long rowid ) public static void deleteGame( Context context, long rowid )
{ {
GameUtils.GameLock lock = new GameUtils.GameLock( rowid, true ).lock(); GameLock lock = new GameLock( rowid, true ).lock( 300 );
deleteGame( context, lock ); if ( null != lock ) {
lock.unlock(); 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() ); Assert.assertTrue( lock.canWrite() );
initDB( context ); initDB( context );
@ -771,34 +778,46 @@ public class DBUtils {
db.delete( DBHelper.TABLE_NAME_SUM, selection, null ); db.delete( DBHelper.TABLE_NAME_SUM, selection, null );
db.close(); db.close();
} }
notifyListeners( lock.getRowid() ); clearRowIDsCache();
notifyListeners( lock.getRowid(), true );
} }
public static long[] gamesList( Context context ) 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 ); String[] columns = { ROW_ID };
synchronized( s_dbHelper ) { String orderBy = DBHelper.CREATE_TIME + " DESC";
SQLiteDatabase db = s_dbHelper.getReadableDatabase(); Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM,
columns, null, null, null,
String[] columns = { ROW_ID }; null, orderBy );
String orderBy = DBHelper.CREATE_TIME + " DESC"; int count = cursor.getCount();
Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns, s_cachedRowIDs = new long[count];
null, null, null, null, orderBy ); int index = cursor.getColumnIndex( ROW_ID );
int count = cursor.getCount(); for ( int ii = 0; cursor.moveToNext(); ++ii ) {
result = new long[count]; s_cachedRowIDs[ii] = cursor.getLong( index );
int index = cursor.getColumnIndex( ROW_ID ); }
for ( int ii = 0; cursor.moveToNext(); ++ii ) { cursor.close();
result[ii] = cursor.getLong( index ); db.close();
}
} }
cursor.close(); result = s_cachedRowIDs;
db.close();
} }
return result; return result;
} }
private static void clearRowIDsCache()
{
synchronized( DBUtils.class ) {
s_cachedRowIDs = null;
}
}
// Get either the file name or game name, preferring the latter. // Get either the file name or game name, preferring the latter.
public static String getName( Context context, long rowid ) public static String getName( Context context, long rowid )
{ {
@ -1099,6 +1118,7 @@ public class DBUtils {
public static void loadDB( Context context ) public static void loadDB( Context context )
{ {
clearRowIDsCache();
copyGameDB( context, false ); 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 ) { synchronized( s_listeners ) {
Iterator<DBChangeListener> iter = s_listeners.iterator(); Iterator<DBChangeListener> iter = s_listeners.iterator();
while ( iter.hasNext() ) { while ( iter.hasNext() ) {
iter.next().gameSaved( rowid ); iter.next().gameSaved( rowid, countChanged );
} }
} }
} }

View file

@ -47,6 +47,20 @@ import org.eehouse.android.xw4.jni.CurGameInfo.DeviceRole;
public class DictUtils { 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 // keep in sync with loc_names string-array
public enum DictLoc { UNKNOWN, BUILT_IN, INTERNAL, EXTERNAL, DOWNLOAD }; public enum DictLoc { UNKNOWN, BUILT_IN, INTERNAL, EXTERNAL, DOWNLOAD };
public static final String INVITED = "invited"; public static final String INVITED = "invited";
@ -566,22 +580,45 @@ public class DictUtils {
return null != getDownloadDir( context ); 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 ) public static File getDownloadDir( Context context )
{ {
File result = null; File result = null;
if ( haveWriteableSD() ) { outer:
File file = null; for ( int attempt = 0; attempt < 4; ++attempt ) {
String myPath = XWPrefs.getMyDownloadDir( context ); switch ( attempt ) {
if ( null != myPath && 0 < myPath.length() ) { case 0:
file = new File( myPath ); String myPath = XWPrefs.getMyDownloadDir( context );
} else { if ( null == myPath || 0 == myPath.length() ) {
file = Environment.getExternalStorageDirectory(); continue;
if ( null != file ) {
file = new File( file, "download/" );
} }
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; return result;
@ -596,4 +633,13 @@ public class DictUtils {
} }
return result; return result;
} }
private static class DirGetter implements SafeDirGetter {
public File getDownloadDir()
{
File path = Environment.
getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
return path;
}
}
} }

View file

@ -242,7 +242,7 @@ public class DlgDelegate {
public void doSyncMenuitem() public void doSyncMenuitem()
{ {
if ( null == DBUtils.getRelayIDs( m_activity, false ) ) { if ( null == DBUtils.getRelayIDs( m_activity, null ) ) {
showOKOnlyDialog( R.string.no_games_to_refresh ); showOKOnlyDialog( R.string.no_games_to_refresh );
} else { } else {
RelayReceiver.RestartTimer( m_activity, true ); RelayReceiver.RestartTimer( m_activity, true );

View file

@ -194,12 +194,20 @@ public class ExpiringDelegate {
if ( null == m_runnable ) { if ( null == m_runnable ) {
m_runnable = new Runnable() { m_runnable = new Runnable() {
public void run() { public void run() {
if ( XWApp.DEBUG_EXP_TIMERS ) {
DbgUtils.logf( "ExpiringDelegate: timer fired"
+ " for %H", this );
}
if ( m_active ) { if ( m_active ) {
figurePct(); figurePct();
if ( m_haveTurnLocal ) { if ( m_haveTurnLocal ) {
m_back = null; m_back = null;
setBackground(); setBackground();
} }
if ( XWApp.DEBUG_EXP_TIMERS ) {
DbgUtils.logf( "ExpiringDelegate: invalidating"
+ " view %H", m_view );
}
m_view.invalidate(); m_view.invalidate();
} }
} }

View file

@ -92,7 +92,7 @@ public class GameConfig extends XWActivity
private boolean m_forResult; private boolean m_forResult;
private CurGameInfo m_gi; private CurGameInfo m_gi;
private CurGameInfo m_giOrig; private CurGameInfo m_giOrig;
private GameUtils.GameLock m_gameLock; private GameLock m_gameLock;
private int m_whichPlayer; private int m_whichPlayer;
// private Spinner m_roleSpinner; // private Spinner m_roleSpinner;
// private Spinner m_connectSpinner; // 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 in case we're going to config. We *could* re-get the
// lock once the user decides to make changes. PENDING. // 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 ); int gamePtr = GameUtils.loadMakeGame( this, m_giOrig, m_gameLock );
if ( 0 == gamePtr ) { if ( 0 == gamePtr ) {
showDictGoneFinish(); showDictGoneFinish();

View file

@ -1,7 +1,7 @@
/* -*- compile-command: "cd ../../../../../; ant debug install"; -*- */ /* -*- compile-command: "cd ../../../../../; ant debug install"; -*- */
/* /*
* Copyright 2009-2010 by Eric House (xwords@eehouse.org). All * Copyright 2009-2012 by Eric House (xwords@eehouse.org). All rights
* rights reserved. * reserved.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as * 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.content.Context;
import android.database.DataSetObserver; import android.database.DataSetObserver;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@ -44,7 +42,6 @@ import java.util.Set;
import junit.framework.Assert; import junit.framework.Assert;
import org.eehouse.android.xw4.jni.*; import org.eehouse.android.xw4.jni.*;
import org.eehouse.android.xw4.jni.CurGameInfo.DeviceRole; import org.eehouse.android.xw4.jni.CurGameInfo.DeviceRole;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType; import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
@ -52,242 +49,35 @@ import org.eehouse.android.xw4.DBUtils.GameGroupInfo;
public class GameListAdapter implements ExpandableListAdapter { public class GameListAdapter implements ExpandableListAdapter {
private Context m_context; private Context m_context;
private ExpandableListView m_list;
private LayoutInflater m_factory; private LayoutInflater m_factory;
private int m_fieldID; private int m_fieldID;
private Handler m_handler; 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<Long,ViewInfo> m_viewsCache;
private DateFormat m_df;
private LoadItemCB m_cb; private LoadItemCB m_cb;
public interface LoadItemCB { public interface LoadItemCB {
public void itemLoaded( long rowid ); public void itemClicked( long rowid, GameSummary summary );
public void itemClicked( long rowid );
} }
private class LoadItemTask extends AsyncTask<Void, Void, Void> { public GameListAdapter( Context context, ExpandableListView list,
private long m_rowid; Handler handler, LoadItemCB cb, String fieldName )
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 ) {
// super( DBUtils.gamesList(context).length ); // super( DBUtils.gamesList(context).length );
m_context = context; m_context = context;
m_list = list;
m_handler = handler; m_handler = handler;
m_cb = cb; m_cb = cb;
m_factory = LayoutInflater.from( context ); m_factory = LayoutInflater.from( context );
m_df = DateFormat.getDateTimeInstance( DateFormat.SHORT,
DateFormat.SHORT );
m_viewsCache = new HashMap<Long,ViewInfo>(); m_fieldID = fieldToID( fieldName );
} }
public void inval( long rowid ) // public void inval( long rowid )
{ // {
synchronized( m_viewsCache ) { // synchronized( m_viewsCache ) {
m_viewsCache.remove( rowid ); // m_viewsCache.remove( rowid );
} // }
} // }
public void expandGroups( ExpandableListView view ) public void expandGroups( ExpandableListView view )
{ {
@ -301,26 +91,26 @@ public class GameListAdapter implements ExpandableListAdapter {
} }
} }
public void setField( String field ) // public void setField( String field )
{ // {
int[] ids = { // int[] ids = {
R.string.game_summary_field_empty // R.string.game_summary_field_empty
,R.string.game_summary_field_language // ,R.string.game_summary_field_language
,R.string.game_summary_field_opponents // ,R.string.game_summary_field_opponents
,R.string.game_summary_field_state // ,R.string.game_summary_field_state
}; // };
int result = -1; // int result = -1;
for ( int id : ids ) { // for ( int id : ids ) {
if ( m_context.getString( id ).equals( field ) ) { // if ( m_context.getString( id ).equals( field ) ) {
result = id; // result = id;
break; // break;
} // }
} // }
if ( m_fieldID != result ) { // if ( m_fieldID != result ) {
m_viewsCache.clear(); // m_viewsCache.clear();
m_fieldID = result; // m_fieldID = result;
} // }
} // }
public long getRowIDFor( int group, int child ) public long getRowIDFor( int group, int child )
{ {
@ -473,39 +263,57 @@ public class GameListAdapter implements ExpandableListAdapter {
public void registerDataSetObserver( DataSetObserver obs ){} public void registerDataSetObserver( DataSetObserver obs ){}
public void unregisterDataSetObserver( 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; GameListItem child = getItemFor( rowid );
boolean haveLayout = false; if ( null != child && child.getRowID() == rowid ) {
synchronized( m_viewsCache ) { child.forceReload();
ViewInfo vi = m_viewsCache.get( rowid ); } else {
haveLayout = null != vi; DbgUtils.logf( "no child for rowid %d", rowid );
if ( haveLayout ) { GameListItem.inval( rowid );
layout = vi.m_view; m_list.invalidate();
} else {
layout = m_factory.inflate( R.layout.game_list_tmp, null );
vi = new ViewInfo( layout, rowid );
m_viewsCache.put( rowid, vi );
}
} }
}
if ( !haveLayout ) { public void invalName( long rowid )
new LoadItemTask( m_context, rowid/*, ++m_taskCounter*/ ).execute(); {
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 ) private long[] getRows( String group )
{ {
@ -538,9 +346,67 @@ public class GameListAdapter implements ExpandableListAdapter {
return pos; return pos;
} }
private HashMap<String,GameGroupInfo> 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;
} }
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;
}
} }

View file

@ -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<Long> s_invalRows = new HashSet<Long>();
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<Void, Void, GameSummary> {
@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<Long> iter = s_invalRows.iterator();
// for ( int ii = 0; iter.hasNext(); ++ii ) {
// strs[ii] = String.format("%d", iter.next() );
// }
// }
// return TextUtils.join(",", strs );
// }
}

View file

@ -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<Long, GameLock>
s_locks = new HashMap<Long,GameLock>();
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;
}
}

View file

@ -46,134 +46,6 @@ public class GameUtils {
public static final String INTENT_KEY_ROWID = "rowid"; public static final String INTENT_KEY_ROWID = "rowid";
public static final String INTENT_FORRESULT_ROWID = "forresult"; 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<Long, GameLock>
s_locks = new HashMap<Long,GameLock>();
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(); private static Object s_syncObj = new Object();
public static byte[] savedGame( Context context, long rowid ) public static byte[] savedGame( Context context, long rowid )
@ -242,10 +114,16 @@ public class GameUtils {
public static void resetGame( Context context, long rowidIn ) public static void resetGame( Context context, long rowidIn )
{ {
GameLock lock = new GameLock( rowidIn, true ).lock(); GameLock lock = new GameLock( rowidIn, true ).lock( 500 );
tellDied( context, lock, true ); if ( null != lock ) {
resetGame( context, lock, lock, false ); tellDied( context, lock, true );
lock.unlock(); 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, private static GameSummary summarizeAndClose( Context context,
@ -298,12 +176,17 @@ public class GameUtils {
public static long dupeGame( Context context, long rowidIn ) public static long dupeGame( Context context, long rowidIn )
{ {
boolean juggle = CommonPrefs.getAutoJuggle( context ); long rowid = DBUtils.ROWID_NOTFOUND;
GameLock lockSrc = new GameLock( rowidIn, false ).lock(); GameLock lockSrc = new GameLock( rowidIn, false ).lock( 300 );
GameLock lockDest = resetGame( context, lockSrc, null, juggle ); if ( null != lockSrc ) {
long rowid = lockDest.getRowid(); boolean juggle = CommonPrefs.getAutoJuggle( context );
lockDest.unlock(); GameLock lockDest = resetGame( context, lockSrc, null, juggle );
lockSrc.unlock(); rowid = lockDest.getRowid();
lockDest.unlock();
lockSrc.unlock();
} else {
DbgUtils.logf( "dupeGame: unable to open rowid %d", rowidIn );
}
return rowid; return rowid;
} }
@ -525,22 +408,6 @@ public class GameUtils {
nPlayersH, null, gameID, isHost ); 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, public static void launchInviteActivity( Context context,
boolean choseEmail, boolean choseEmail,
String room, String inviteID, String room, String inviteID,
@ -566,18 +433,20 @@ public class GameUtils {
intent.putExtra( Intent.EXTRA_SUBJECT, subject ); intent.putExtra( Intent.EXTRA_SUBJECT, subject );
intent.putExtra( Intent.EXTRA_TEXT, Html.fromHtml(message) ); intent.putExtra( Intent.EXTRA_TEXT, Html.fromHtml(message) );
File attach = null;
File tmpdir = XWApp.ATTACH_SUPPORTED ? File tmpdir = XWApp.ATTACH_SUPPORTED ?
DictUtils.getDownloadDir( context ) : null; 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"); intent.setType( "message/rfc822");
} else { } else {
intent.setType( context.getString( R.string.invite_mime ) ); String mime = context.getString( R.string.invite_mime );
intent.setType( mime );
File attach = makeJsonFor( tmpdir, room, inviteID, lang,
dict, nPlayers );
Uri uri = Uri.fromFile( attach ); Uri uri = Uri.fromFile( attach );
DbgUtils.logf( "using file uri for attachment: %s",
uri.toString() );
intent.putExtra( Intent.EXTRA_STREAM, uri ); intent.putExtra( Intent.EXTRA_STREAM, uri );
} }
@ -684,7 +553,6 @@ public class GameUtils {
boolean invited ) boolean invited )
{ {
Intent intent = new Intent( activity, BoardActivity.class ); Intent intent = new Intent( activity, BoardActivity.class );
intent.setAction( Intent.ACTION_EDIT );
intent.putExtra( INTENT_KEY_ROWID, rowid ); intent.putExtra( INTENT_KEY_ROWID, rowid );
if ( invited ) { if ( invited ) {
intent.putExtra( INVITED, true ); intent.putExtra( INVITED, true );
@ -734,40 +602,45 @@ public class GameUtils {
} }
} }
private static boolean feedMessages( Context context, long rowid, public static boolean feedMessages( Context context, long rowid,
byte[][] msgs, CommsAddrRec ret, byte[][] msgs, CommsAddrRec ret,
MultiMsgSink sink ) MultiMsgSink sink )
{ {
boolean draw = false; boolean draw = false;
Assert.assertTrue( -1 != rowid ); Assert.assertTrue( -1 != rowid );
GameLock lock = new GameLock( rowid, true ); if ( null != msgs ) {
if ( lock.tryLock() ) { // timed lock: If a game is opened by BoardActivity just
CurGameInfo gi = new CurGameInfo( context ); // as we're trying to deliver this message to it it'll
FeedUtilsImpl feedImpl = new FeedUtilsImpl( context, rowid ); // have the lock and we'll never get it. Better to drop
int gamePtr = loadMakeGame( context, gi, feedImpl, sink, lock ); // the message than fire the hung-lock assert. Messages
if ( 0 != gamePtr ) { // belong in local pre-delivery storage anyway.
XwJNI.comms_resendAll( gamePtr, false, false ); 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 ) { for ( byte[] msg : msgs ) {
draw = XwJNI.game_receiveMessage( gamePtr, msg, ret ) draw = XwJNI.game_receiveMessage( gamePtr, msg, ret )
|| draw; || draw;
} }
} XwJNI.comms_ackAny( gamePtr );
XwJNI.comms_ackAny( gamePtr );
// update gi to reflect changes due to messages // update gi to reflect changes due to messages
XwJNI.game_getGi( gamePtr, gi ); XwJNI.game_getGi( gamePtr, gi );
saveGame( context, gamePtr, gi, lock, false ); saveGame( context, gamePtr, gi, lock, false );
summarizeAndClose( context, lock, gamePtr, gi, feedImpl ); summarizeAndClose( context, lock, gamePtr, gi, feedImpl );
int flags = setFromFeedImpl( feedImpl ); int flags = setFromFeedImpl( feedImpl );
if ( GameSummary.MSG_FLAGS_NONE != flags ) { if ( GameSummary.MSG_FLAGS_NONE != flags ) {
draw = true; draw = true;
DBUtils.setMsgFlags( rowid, flags ); DBUtils.setMsgFlags( rowid, flags );
}
} }
lock.unlock();
} }
lock.unlock();
} }
return draw; return draw;
} // feedMessages } // feedMessages
@ -781,52 +654,45 @@ public class GameUtils {
return feedMessages( context, rowid, msgs, ret, sink ); 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!!! // This *must* involve a reset if the language is changing!!!
// Which isn't possible right now, so make sure the old and new // Which isn't possible right now, so make sure the old and new
// dict have the same langauge code. // dict have the same langauge code.
public static void replaceDicts( Context context, long rowid, public static boolean replaceDicts( Context context, long rowid,
String oldDict, String newDict ) String oldDict, String newDict )
{ {
GameLock lock = new GameLock( rowid, true ).lock(); GameLock lock = new GameLock( rowid, true ).lock(300);
byte[] stream = savedGame( context, lock ); boolean success = null != lock;
CurGameInfo gi = new CurGameInfo( context ); if ( success ) {
XwJNI.gi_from_stream( gi, stream ); byte[] stream = savedGame( context, lock );
CurGameInfo gi = new CurGameInfo( context );
XwJNI.gi_from_stream( gi, stream );
// first time required so dictNames() will work // first time required so dictNames() will work
gi.replaceDicts( newDict ); gi.replaceDicts( newDict );
String[] dictNames = gi.dictNames(); String[] dictNames = gi.dictNames();
DictUtils.DictPairs pairs = DictUtils.openDicts( context, dictNames ); DictUtils.DictPairs pairs = DictUtils.openDicts( context,
dictNames );
int gamePtr = XwJNI.initJNI(); int gamePtr = XwJNI.initJNI();
XwJNI.game_makeFromStream( gamePtr, stream, gi, dictNames, XwJNI.game_makeFromStream( gamePtr, stream, gi, dictNames,
pairs.m_bytes, pairs.m_paths, pairs.m_bytes, pairs.m_paths,
gi.langName(), JNIUtilsImpl.get(context), gi.langName(),
CommonPrefs.get( context ) ); JNIUtilsImpl.get(context),
// second time required as game_makeFromStream can overwrite CommonPrefs.get( context ) );
gi.replaceDicts( newDict ); // 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, public static void applyChanges( Context context, CurGameInfo gi,
CommsAddrRec car, GameLock lock, CommsAddrRec car, GameLock lock,
@ -953,7 +819,7 @@ public class GameUtils {
byte[] data = json.toString().getBytes(); byte[] data = json.toString().getBytes();
File file = new File( dir, File file = new File( dir,
String.format("invite_%s.json", room ) ); String.format("invite_%s", room ) );
FileOutputStream fos = new FileOutputStream( file ); FileOutputStream fos = new FileOutputStream( file );
fos.write( data, 0, data.length ); fos.write( data, 0, data.length );
fos.close(); fos.close();

View file

@ -75,6 +75,7 @@ public class GamesList extends XWExpandableListActivity
private static final String RELAYIDS_EXTRA = "relayids"; private static final String RELAYIDS_EXTRA = "relayids";
private static final String GAMEID_EXTRA = "gameid"; 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 NEW_NET_GAME_ACTION = 1;
private static final int RESET_GAME_ACTION = 2; private static final int RESET_GAME_ACTION = 2;
@ -102,7 +103,6 @@ public class GamesList extends XWExpandableListActivity
private String m_nameField; private String m_nameField;
private NetLaunchInfo m_netLaunchInfo; private NetLaunchInfo m_netLaunchInfo;
private GameNamer m_namer; private GameNamer m_namer;
// private String m_smsPhone;
@Override @Override
protected Dialog onCreateDialog( int id ) protected Dialog onCreateDialog( int id )
@ -177,11 +177,12 @@ public class GamesList extends XWExpandableListActivity
getCheckedItemPosition(); getCheckedItemPosition();
String dict = m_sameLangDicts[pos]; String dict = m_sameLangDicts[pos];
dict = DictLangCache.stripCount( dict ); dict = DictLangCache.stripCount( dict );
GameUtils.replaceDicts( GamesList.this, if ( GameUtils.replaceDicts( GamesList.this,
m_missingDictRowId, m_missingDictRowId,
m_missingDictName, m_missingDictName,
dict ); dict ) ) {
launchGameIf(); launchGameIf();
}
} }
}; };
dialog = new AlertDialog.Builder( this ) dialog = new AlertDialog.Builder( this )
@ -202,8 +203,7 @@ public class GamesList extends XWExpandableListActivity
public void onClick( DialogInterface dlg, int item ) { public void onClick( DialogInterface dlg, int item ) {
String name = m_namer.getName(); String name = m_namer.getName();
DBUtils.setName( GamesList.this, m_rowid, name ); DBUtils.setName( GamesList.this, m_rowid, name );
m_adapter.inval( m_rowid ); m_adapter.invalName( m_rowid );
onContentChanged();
} }
}; };
dialog = buildNamerDlg( GameUtils.getName( this, 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 ); setListAdapter( m_adapter );
NetUtils.informOfDeaths( this ); NetUtils.informOfDeaths( this );
@ -355,6 +357,7 @@ public class GamesList extends XWExpandableListActivity
startFirstHasDict( intent ); startFirstHasDict( intent );
startNewNetGame( intent ); startNewNetGame( intent );
startHasGameID( intent ); startHasGameID( intent );
startHasRowID( intent );
askDefaultNameIf(); askDefaultNameIf();
} // onCreate } // onCreate
@ -369,6 +372,7 @@ public class GamesList extends XWExpandableListActivity
startFirstHasDict( intent ); startFirstHasDict( intent );
startNewNetGame( intent ); startNewNetGame( intent );
startHasGameID( intent ); startHasGameID( intent );
startHasRowID( intent );
} }
@Override @Override
@ -444,28 +448,25 @@ public class GamesList extends XWExpandableListActivity
} }
// DBUtils.DBChangeListener interface // DBUtils.DBChangeListener interface
public void gameSaved( final long rowid ) public void gameSaved( final long rowid, final boolean countChanged )
{ {
post( new Runnable() { post( new Runnable() {
public void run() { public void run() {
m_adapter.inval( rowid ); if ( countChanged ) {
onContentChanged(); onContentChanged();
} else {
m_adapter.inval( rowid );
}
} }
} ); } );
} }
// GameListAdapter.LoadItemCB interface // GameListAdapter.LoadItemCB interface
public void itemLoaded( long rowid ) public void itemClicked( long rowid, GameSummary summary )
{
onContentChanged();
}
public void itemClicked( long rowid )
{ {
// We need a way to let the user get back to the basic-config // 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 // dialog in case it was dismissed. That way it to check for
// an empty room name. // an empty room name.
GameSummary summary = DBUtils.getSummary( this, rowid );
if ( summary.conType == CommsAddrRec.CommsConnType.COMMS_CONN_RELAY if ( summary.conType == CommsAddrRec.CommsConnType.COMMS_CONN_RELAY
&& summary.roomName.length() == 0 ) { && summary.roomName.length() == 0 ) {
// If it's unconfigured and of the type RelayGameActivity // If it's unconfigured and of the type RelayGameActivity
@ -480,12 +481,12 @@ public class GamesList extends XWExpandableListActivity
GameUtils.doConfig( this, rowid, clazz ); GameUtils.doConfig( this, rowid, clazz );
} else { } else {
if ( checkWarnNoDict( rowid ) ) { if ( checkWarnNoDict( rowid ) ) {
GameUtils.launchGame( this, rowid ); launchGame( rowid );
} }
} }
} }
// BTService.BTEventListener interface // BTService.MultiEventListener interface
@Override @Override
public void eventOccurred( MultiService.MultiEvent event, public void eventOccurred( MultiService.MultiEvent event,
final Object ... args ) final Object ... args )
@ -518,6 +519,7 @@ public class GamesList extends XWExpandableListActivity
break; break;
case RESET_GAME_ACTION: case RESET_GAME_ACTION:
GameUtils.resetGame( this, m_rowid ); GameUtils.resetGame( this, m_rowid );
onContentChanged(); // required because position may change
break; break;
case DELETE_GAME_ACTION: case DELETE_GAME_ACTION:
GameUtils.deleteGame( this, m_rowid, true ); GameUtils.deleteGame( this, m_rowid, true );
@ -526,7 +528,6 @@ public class GamesList extends XWExpandableListActivity
long[] games = DBUtils.gamesList( this ); long[] games = DBUtils.gamesList( this );
for ( int ii = games.length - 1; ii >= 0; --ii ) { for ( int ii = games.length - 1; ii >= 0; --ii ) {
GameUtils.deleteGame( this, games[ii], ii == 0 ); GameUtils.deleteGame( this, games[ii], ii == 0 );
m_adapter.inval( games[ii] );
} }
break; break;
case SYNC_MENU_ACTION: case SYNC_MENU_ACTION:
@ -759,8 +760,7 @@ public class GamesList extends XWExpandableListActivity
showOKOnlyDialog( R.string.no_copy_network ); showOKOnlyDialog( R.string.no_copy_network );
} else { } else {
byte[] stream = GameUtils.savedGame( this, m_rowid ); byte[] stream = GameUtils.savedGame( this, m_rowid );
GameUtils.GameLock lock = GameLock lock = GameUtils.saveNewGame( this, stream );
GameUtils.saveNewGame( this, stream );
DBUtils.saveSummary( this, lock, summary ); DBUtils.saveSummary( this, lock, summary );
lock.unlock(); lock.unlock();
} }
@ -859,9 +859,12 @@ public class GamesList extends XWExpandableListActivity
} else if ( null != m_missingDictName ) { } else if ( null != m_missingDictName ) {
showDialog( WARN_NODICT_SUBST ); showDialog( WARN_NODICT_SUBST );
} else { } else {
String dict = DictLangCache.getHaveLang( this, m_missingDictLang)[0]; String dict =
GameUtils.replaceDicts( this, m_missingDictRowId, null, dict ); DictLangCache.getHaveLang( this, m_missingDictLang)[0];
launchGameIf(); if ( GameUtils.replaceDicts( this, m_missingDictRowId,
null, dict ) ) {
launchGameIf();
}
} }
} }
return hasDicts; return hasDicts;
@ -878,7 +881,6 @@ public class GamesList extends XWExpandableListActivity
} }
} }
} }
onContentChanged();
} }
} }
@ -893,7 +895,7 @@ public class GamesList extends XWExpandableListActivity
if ( null != rowids ) { if ( null != rowids ) {
for ( long rowid : rowids ) { for ( long rowid : rowids ) {
if ( GameUtils.gameDictsHere( this, rowid ) ) { if ( GameUtils.gameDictsHere( this, rowid ) ) {
GameUtils.launchGame( this, rowid ); launchGame( rowid );
break outer; break outer;
} }
} }
@ -951,7 +953,7 @@ public class GamesList extends XWExpandableListActivity
{ {
long[] rowids = DBUtils.getRowIDsFor( this, gameID ); long[] rowids = DBUtils.getRowIDsFor( this, gameID );
if ( null != rowids && 0 < rowids.length ) { 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() private void askDefaultNameIf()
{ {
if ( null == CommonPrefs.getDefaultPlayerName( this, 0, false ) ) { if ( null == CommonPrefs.getDefaultPlayerName( this, 0, false ) ) {
@ -975,9 +987,9 @@ public class GamesList extends XWExpandableListActivity
private void updateField() private void updateField()
{ {
String newField = CommonPrefs.getSummaryField( this ); String newField = CommonPrefs.getSummaryField( this );
if ( ! newField.equals( m_nameField ) ) { if ( m_adapter.setField( newField ) ) {
m_nameField = newField; // The adapter should be able to decide whether full
m_adapter.setField( newField ); // content change is required. PENDING
onContentChanged(); onContentChanged();
} }
} }
@ -1019,10 +1031,20 @@ public class GamesList extends XWExpandableListActivity
return madeGame; 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 ) private void makeNewNetGame( NetLaunchInfo info )
{ {
long rowid = GameUtils.makeNewNetGame( this, info ); long rowid = GameUtils.makeNewNetGame( this, info );
GameUtils.launchGame( this, rowid, true ); launchGame( rowid, true );
} }
public static void onGameDictDownload( Context context, Intent intent ) public static void onGameDictDownload( Context context, Intent intent )
@ -1054,6 +1076,20 @@ public class GamesList extends XWExpandableListActivity
return intent; 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 ) public static void openGame( Context context, Uri data )
{ {
Intent intent = makeSelfIntent( context ); Intent intent = makeSelfIntent( context );

View file

@ -43,7 +43,7 @@ abstract class InviteActivity extends XWListActivity
implements View.OnClickListener { implements View.OnClickListener {
public static final String DEVS = "DEVS"; 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 int m_nMissing;
protected Button m_okButton; protected Button m_okButton;

View file

@ -42,7 +42,7 @@ public class MultiService {
public static final int OWNER_SMS = 1; public static final int OWNER_SMS = 1;
public static final int OWNER_RELAY = 2; public static final int OWNER_RELAY = 2;
private BTEventListener m_li; private MultiEventListener m_li;
public enum MultiEvent { BAD_PROTO public enum MultiEvent { BAD_PROTO
, BT_ENABLED , BT_ENABLED
@ -64,14 +64,14 @@ public class MultiService {
, SMS_SEND_FAILED_NORADIO , SMS_SEND_FAILED_NORADIO
}; };
public interface BTEventListener { public interface MultiEventListener {
public void eventOccurred( MultiEvent event, Object ... args ); public void eventOccurred( MultiEvent event, Object ... args );
} }
// public interface MultiEventSrc { // public interface MultiEventSrc {
// public void setBTEventListener( BTEventListener li ); // public void setBTEventListener( BTEventListener li );
// } // }
public void setListener( BTEventListener li ) public void setListener( MultiEventListener li )
{ {
synchronized( this ) { synchronized( this ) {
m_li = li; m_li = li;

View file

@ -73,7 +73,7 @@ public class NetLaunchInfo {
if ( null != data ) { if ( null != data ) {
String scheme = data.getScheme(); String scheme = data.getScheme();
try { try {
if ( "content".equals(scheme) ) { if ( "content".equals(scheme) || "file".equals(scheme) ) {
Assert.assertNotNull( context ); Assert.assertNotNull( context );
ContentResolver resolver = context.getContentResolver(); ContentResolver resolver = context.getContentResolver();
InputStream is = resolver.openInputStream( data ); InputStream is = resolver.openInputStream( data );

View file

@ -130,8 +130,7 @@ public class NetUtils {
} }
} }
public static byte[][][] queryRelay( Context context, String[] ids, public static byte[][][] queryRelay( Context context, String[] ids )
int nBytes )
{ {
byte[][][] msgs = null; byte[][][] msgs = null;
try { try {
@ -141,6 +140,7 @@ public class NetUtils {
new DataOutputStream( socket.getOutputStream() ); new DataOutputStream( socket.getOutputStream() );
// total packet size // total packet size
int nBytes = sumStrings( ids );
outStream.writeShort( 2 + nBytes + ids.length + 1 ); outStream.writeShort( 2 + nBytes + ids.length + 1 );
outStream.writeByte( NetUtils.PROTOCOL_VERSION ); outStream.writeByte( NetUtils.PROTOCOL_VERSION );
@ -263,4 +263,16 @@ public class NetUtils {
DbgUtils.logf( "sendToRelay: null msgs" ); DbgUtils.logf( "sendToRelay: null msgs" );
} }
} // sendToRelay } // sendToRelay
private static int sumStrings( final String[] strs )
{
int len = 0;
if ( null != strs ) {
for ( String str : strs ) {
len += str.length();
}
}
return len;
}
} }

View file

@ -256,7 +256,7 @@ public class NewGameActivity extends XWActivity {
return dialog; return dialog;
} }
// BTService.BTEventListener interface // MultiService.MultiEventListener interface
@Override @Override
public void eventOccurred( MultiService.MultiEvent event, public void eventOccurred( MultiService.MultiEvent event,
final Object ... args ) final Object ... args )
@ -299,7 +299,7 @@ public class NewGameActivity extends XWActivity {
super.eventOccurred( event, args ); super.eventOccurred( event, args );
break; break;
} }
} // BTService.BTEventListener.eventOccurred } // MultiService.MultiEventListener.eventOccurred
private void makeNewGame( boolean networked, boolean launch ) private void makeNewGame( boolean networked, boolean launch )
{ {
@ -357,7 +357,7 @@ public class NewGameActivity extends XWActivity {
intent.putExtra( GameUtils.INTENT_FORRESULT_ROWID, true ); intent.putExtra( GameUtils.INTENT_FORRESULT_ROWID, true );
startActivityForResult( intent, CONFIG_FOR_BT ); startActivityForResult( intent, CONFIG_FOR_BT );
} else { } 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 ); intent.putExtra( GameUtils.INTENT_FORRESULT_ROWID, true );
startActivityForResult( intent, CONFIG_FOR_SMS ); startActivityForResult( intent, CONFIG_FOR_SMS );
} else { } else {
GameUtils.launchSMSInviter( this, 1, INVITE_FOR_SMS ); SMSInviteActivity.launchForResult( this, 1, INVITE_FOR_SMS );
} }
} }

View file

@ -41,7 +41,7 @@ public class RelayGameActivity extends XWActivity
private long m_rowid; private long m_rowid;
private CurGameInfo m_gi; private CurGameInfo m_gi;
private GameUtils.GameLock m_gameLock; private GameLock m_gameLock;
private CommsAddrRec m_car; private CommsAddrRec m_car;
private Button m_playButton; private Button m_playButton;
private Button m_configButton; private Button m_configButton;
@ -68,22 +68,28 @@ public class RelayGameActivity extends XWActivity
super.onStart(); super.onStart();
m_gi = new CurGameInfo( this ); m_gi = new CurGameInfo( this );
m_gameLock = new GameUtils.GameLock( m_rowid, true ).lock(); m_gameLock = new GameLock( m_rowid, true ).lock( 300 );
int gamePtr = GameUtils.loadMakeGame( this, m_gi, m_gameLock ); if ( null == m_gameLock ) {
m_car = new CommsAddrRec(); DbgUtils.logf( "RelayGameActivity.onStart(): unable to lock rowid %d",
if ( XwJNI.game_hasComms( gamePtr ) ) { m_rowid );
XwJNI.comms_getAddr( gamePtr, m_car ); finish();
} else { } else {
Assert.fail(); int gamePtr = GameUtils.loadMakeGame( this, m_gi, m_gameLock );
// String relayName = CommonPrefs.getDefaultRelayHost( this ); m_car = new CommsAddrRec();
// int relayPort = CommonPrefs.getDefaultRelayPort( this ); if ( XwJNI.game_hasComms( gamePtr ) ) {
// XwJNI.comms_getInitialAddr( m_carOrig, relayName, relayPort ); XwJNI.comms_getAddr( gamePtr, m_car );
} } else {
XwJNI.game_dispose( gamePtr ); 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 ); String lang = DictLangCache.getLangName( this, m_gi.dictLang );
TextView text = (TextView)findViewById( R.id.explain ); TextView text = (TextView)findViewById( R.id.explain );
text.setText( getString( R.string.relay_game_explainf, lang ) ); text.setText( getString( R.string.relay_game_explainf, lang ) );
}
} }
@Override @Override

View file

@ -75,44 +75,39 @@ 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() private void fetchAndProcess()
{ {
int[] nBytes = new int[1]; long[][] rowIDss = new long[1][];
String[] ids = collectIDs( nBytes ); String[] relayIDs = DBUtils.getRelayIDs( this, rowIDss );
if ( null != ids && 0 < ids.length ) { if ( null != relayIDs && 0 < relayIDs.length ) {
RelayMsgSink sink = new RelayMsgSink(); long[] rowIDs = rowIDss[0];
byte[][][] msgs = byte[][][] msgs = NetUtils.queryRelay( this, relayIDs );
NetUtils.queryRelay( this, ids, nBytes[0] );
if ( null != msgs ) { if ( null != msgs ) {
int nameCount = ids.length; RelayMsgSink sink = new RelayMsgSink();
int nameCount = relayIDs.length;
ArrayList<String> idsWMsgs = ArrayList<String> idsWMsgs =
new ArrayList<String>( nameCount ); new ArrayList<String>( nameCount );
for ( int ii = 0; ii < nameCount; ++ii ) { for ( int ii = 0; ii < nameCount; ++ii ) {
byte[][] forOne = msgs[ii];
// if game has messages, open it and feed 'em // if game has messages, open it and feed 'em
// to it. // to it.
if ( GameUtils.feedMessages( this, ids[ii], if ( null == forOne ) {
msgs[ii], sink ) ) { // Nothing for this relayID
idsWMsgs.add( ids[ii] ); } 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() ) { if ( 0 < idsWMsgs.size() ) {
String[] relayIDs = new String[idsWMsgs.size()]; String[] tmp = new String[idsWMsgs.size()];
idsWMsgs.toArray( relayIDs ); idsWMsgs.toArray( tmp );
setupNotification( relayIDs ); setupNotification( tmp );
} }
sink.send( this ); sink.send( this );
} }

View file

@ -32,9 +32,9 @@ import android.os.Bundle;
import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds; import android.provider.ContactsContract.CommonDataKinds;
import android.provider.ContactsContract; import android.provider.ContactsContract;
import android.text.method.DialerKeyListener;
import android.text.Editable; import android.text.Editable;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.text.method.DialerKeyListener;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.CompoundButton; import android.widget.CompoundButton;
@ -64,6 +64,14 @@ public class SMSInviteActivity extends InviteActivity {
private String m_pendingNumber; private String m_pendingNumber;
private boolean m_immobileConfirmed; 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 @Override
protected void onCreate( Bundle savedInstanceState ) protected void onCreate( Bundle savedInstanceState )
{ {

View file

@ -189,7 +189,7 @@ public class SMSService extends Service {
return result; return result;
} }
public static void setListener( MultiService.BTEventListener li ) public static void setListener( MultiService.MultiEventListener li )
{ {
if ( XWApp.SMSSUPPORTED ) { if ( XWApp.SMSSUPPORTED ) {
if ( null == s_srcMgr ) { if ( null == s_srcMgr ) {

View file

@ -40,6 +40,8 @@ import android.net.Uri;
import android.provider.ContactsContract.PhoneLookup; import android.provider.ContactsContract.PhoneLookup;
import android.telephony.TelephonyManager; import android.telephony.TelephonyManager;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.EditText; 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 ) public static boolean hasSmallScreen( Context context )
{ {
if ( null == s_hasSmallScreen ) { if ( null == s_hasSmallScreen ) {

View file

@ -31,7 +31,7 @@ import android.widget.TextView;
import junit.framework.Assert; import junit.framework.Assert;
public class XWActivity extends Activity public class XWActivity extends Activity
implements DlgDelegate.DlgClickNotify, MultiService.BTEventListener { implements DlgDelegate.DlgClickNotify, MultiService.MultiEventListener {
private DlgDelegate m_delegate; private DlgDelegate m_delegate;
@ -192,7 +192,7 @@ public class XWActivity extends Activity
Assert.fail(); Assert.fail();
} }
// BTService.BTEventListener interface // BTService.MultiEventListener interface
public void eventOccurred( MultiService.MultiEvent event, public void eventOccurred( MultiService.MultiEvent event,
final Object ... args ) final Object ... args )
{ {

View file

@ -28,12 +28,14 @@ import java.util.UUID;
import org.eehouse.android.xw4.jni.XwJNI; import org.eehouse.android.xw4.jni.XwJNI;
public class XWApp extends Application { public class XWApp extends Application {
public static final boolean DEBUG_LOCKS = false;
public static final boolean BTSUPPORTED = false; public static final boolean BTSUPPORTED = false;
public static final boolean SMSSUPPORTED = true; public static final boolean SMSSUPPORTED = true;
public static final boolean GCMSUPPORTED = 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 = 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"; public static final String SMS_PUBLIC_HEADER = "-XW4";

View file

@ -28,7 +28,7 @@ import android.os.Bundle;
import junit.framework.Assert; import junit.framework.Assert;
public class XWListActivity extends ListActivity public class XWListActivity extends ListActivity
implements DlgDelegate.DlgClickNotify, MultiService.BTEventListener { implements DlgDelegate.DlgClickNotify, MultiService.MultiEventListener {
private DlgDelegate m_delegate; private DlgDelegate m_delegate;
@ -187,7 +187,7 @@ public class XWListActivity extends ListActivity
m_delegate.launchLookup( words, lang, forceList ); m_delegate.launchLookup( words, lang, forceList );
} }
// BTService.BTEventListener interface // MultiService.MultiEventListener interface
public void eventOccurred( MultiService.MultiEvent event, public void eventOccurred( MultiService.MultiEvent event,
final Object ... args ) final Object ... args )
{ {

View file

@ -41,11 +41,14 @@ public abstract class XWListAdapter implements ListAdapter {
public boolean areAllItemsEnabled() { return true; } public boolean areAllItemsEnabled() { return true; }
public boolean isEnabled( int position ) { return true; } public boolean isEnabled( int position ) { return true; }
public int getCount() { return m_count; } public int getCount() { return m_count; }
public long getItemId(int position) { return position; } public Object getItem( int position ) { return null; }
public int getItemViewType(int position) { return 0; } public long getItemId( int position ) { return position; }
public int getItemViewType( int position ) {
return ListAdapter.IGNORE_ITEM_VIEW_TYPE;
}
public int getViewTypeCount() { return 1; } public int getViewTypeCount() { return 1; }
public boolean hasStableIds() { return true; } public boolean hasStableIds() { return true; }
public boolean isEmpty() { return getCount() == 0; } public boolean isEmpty() { return getCount() == 0; }
public void registerDataSetObserver(DataSetObserver observer) {} public void registerDataSetObserver( DataSetObserver observer ) {}
public void unregisterDataSetObserver(DataSetObserver observer) {} public void unregisterDataSetObserver( DataSetObserver observer ) {}
} }

View file

@ -83,6 +83,11 @@ public class XWPrefs {
return getPrefsBoolean( context, R.string.key_ringer_zoom, false ); 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 ) public static int getDefaultPlayerMinutes( Context context )
{ {
String value = String value =

View file

@ -34,6 +34,7 @@ import org.eehouse.android.xw4.R;
import org.eehouse.android.xw4.DbgUtils; import org.eehouse.android.xw4.DbgUtils;
import org.eehouse.android.xw4.ConnStatusHandler; import org.eehouse.android.xw4.ConnStatusHandler;
import org.eehouse.android.xw4.BoardDims; import org.eehouse.android.xw4.BoardDims;
import org.eehouse.android.xw4.GameLock;
import org.eehouse.android.xw4.GameUtils; import org.eehouse.android.xw4.GameUtils;
import org.eehouse.android.xw4.DBUtils; import org.eehouse.android.xw4.DBUtils;
import org.eehouse.android.xw4.Toolbar; 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 QUERY_ENDGAME = 4;
public static final int TOOLBAR_STATES = 5; public static final int TOOLBAR_STATES = 5;
public static final int GOT_WORDS = 6; public static final int GOT_WORDS = 6;
public static final int GAME_OVER = 7;
public class GameStateInfo implements Cloneable { public class GameStateInfo implements Cloneable {
public int visTileCount; public int visTileCount;
@ -120,7 +122,7 @@ public class JNIThread extends Thread {
private boolean m_stopped = false; private boolean m_stopped = false;
private boolean m_saveOnStop = false; private boolean m_saveOnStop = false;
private int m_jniGamePtr; private int m_jniGamePtr;
private GameUtils.GameLock m_lock; private GameLock m_lock;
private Context m_context; private Context m_context;
private CurGameInfo m_gi; private CurGameInfo m_gi;
private Handler m_handler; private Handler m_handler;
@ -142,7 +144,7 @@ public class JNIThread extends Thread {
} }
public JNIThread( int gamePtr, CurGameInfo gi, SyncedDraw drawer, 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_jniGamePtr = gamePtr;
m_gi = gi; m_gi = gi;
@ -524,8 +526,14 @@ public class JNIThread extends Thread {
case CMD_POST_OVER: case CMD_POST_OVER:
if ( XwJNI.server_getGameIsOver( m_jniGamePtr ) ) { if ( XwJNI.server_getGameIsOver( m_jniGamePtr ) ) {
sendForDialog( R.string.finalscores_title, boolean auto = 0 < args.length &&
XwJNI.server_writeFinalScores( m_jniGamePtr ) ); ((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; break;

View file

@ -30,16 +30,17 @@ function printNonAndroid($agent) {
$subject = "Android device not identified"; $subject = "Android device not identified";
$body = htmlentities("My browser is running on an android device but" $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."); . " this as an Android browser.");
print <<<EOF print <<<EOF
<div class="center"> <div class="center">
<p>This page is meant to be viewed on an Android device.</p> <p>This page is meant to be viewed on an Android device.</p>
<hr> <hr>
<p>(If you <em>are</em> viewing this on an Android device, you&apos;ve <p>(If you <em>are</em> viewing this on an Android device,
found a bug! Please <a href="mailto: you&apos;ve found a bug! Please <a href="mailto:
xwords@eehouse.org?subject=$subject&body=$body">email me</a> (and be xwords@eehouse.org?subject=$subject&body=$body">email me</a>
sure to leave the user agent string in the email body.) (and be sure to leave the user agent string in the email body.)
</p> </p>
</div> </div>
@ -73,6 +74,11 @@ invite email (or text) and tap the link again.</p>
link in your invite email (or text) again, and this time let link in your invite email (or text) again, and this time let
Crosswords handle it.</p> Crosswords handle it.</p>
<p>(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.)</p>
<p>Have fun. And as always, <a href="mailto:xwords@eehouse.org">let <p>Have fun. And as always, <a href="mailto:xwords@eehouse.org">let
me know</a> if you have problems or suggestions.</p> me know</a> if you have problems or suggestions.</p>
</div> </div>

View file

@ -93,10 +93,12 @@ game_makeNewGame( MPFORMAL XWGame* game, CurGameInfo* gi,
#endif #endif
) )
{ {
XP_U16 nPlayersHere, nPlayersTotal; #ifndef XWFEATURE_STANDALONE_ONLY
XP_U16 nPlayersHere = 0;
assertUtilOK( util ); XP_U16 nPlayersTotal = 0;
checkServerRole( gi, &nPlayersHere, &nPlayersTotal ); checkServerRole( gi, &nPlayersHere, &nPlayersTotal );
#endif
assertUtilOK( util );
gi->gameID = makeGameID( util ); gi->gameID = makeGameID( util );
@ -137,15 +139,17 @@ game_reset( MPFORMAL XWGame* game, CurGameInfo* gi,
CommonPrefs* cp, const TransportProcs* procs ) CommonPrefs* cp, const TransportProcs* procs )
{ {
XP_U16 ii; XP_U16 ii;
XP_U16 nPlayersHere, nPlayersTotal;
XP_ASSERT( !!game->model ); XP_ASSERT( !!game->model );
XP_ASSERT( !!gi ); XP_ASSERT( !!gi );
checkServerRole( gi, &nPlayersHere, &nPlayersTotal );
gi->gameID = makeGameID( util ); gi->gameID = makeGameID( util );
#ifndef XWFEATURE_STANDALONE_ONLY #ifndef XWFEATURE_STANDALONE_ONLY
XP_U16 nPlayersHere = 0;
XP_U16 nPlayersTotal = 0;
checkServerRole( gi, &nPlayersHere, &nPlayersTotal );
if ( !!game->comms ) { if ( !!game->comms ) {
if ( gi->serverRole == SERVER_STANDALONE ) { if ( gi->serverRole == SERVER_STANDALONE ) {
comms_destroy( game->comms ); comms_destroy( game->comms );

View file

@ -686,7 +686,7 @@ handleRegistrationMsg( ServerCtxt* server, XWStreamCtxt* stream )
{ {
XP_Bool success = XP_TRUE; XP_Bool success = XP_TRUE;
XP_U16 playersInMsg; XP_U16 playersInMsg;
XP_S8 clientIndex; XP_S8 clientIndex = 0; /* quiet compiler */
XP_U16 ii = 0; XP_U16 ii = 0;
LOG_FUNC(); LOG_FUNC();

View file

@ -55,13 +55,19 @@ def init():
def getPendingMsgs( con, typ ): def getPendingMsgs( con, typ ):
cur = con.cursor() cur = con.cursor()
query = """SELECT id, devid FROM msgs 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); """ AND NOT connname IN (SELECT connname FROM games WHERE dead); """
cur.execute(query % typ) cur.execute(query % typ)
result = cur.fetchall() result = cur.fetchall()
if g_debug: print "getPendingMsgs=>", result if g_debug: print "getPendingMsgs=>", result
return 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): def asGCMIds(con, devids, typ):
cur = con.cursor() cur = con.cursor()
query = "SELECT devid FROM devices WHERE devtype = %d AND id IN (%s)" \ 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 ): def notifyGCM( devids, typ ):
if typ == DEVTYPE_GCM: if typ == DEVTYPE_GCM:
instance = gcm.GCM( mykey.myKey ) instance = gcm.GCM( mykey.myKey )
data = { 'getMoves': True, data = { 'getMoves': True, }
# 'title' : 'Msg from Darth',
# 'msg' : "I am your father, Luke.",
}
response = instance.json_request( registration_ids = devids, response = instance.json_request( registration_ids = devids,
# restricted_package_name = 'org.eehouse.android.xw4',
data = data, data = data,
# collapse_key = 'NewMove',
) )
if 'errors' in response: if 'errors' in response:
response = response['errors'] response = response['errors']
if 'NotRegistered' in response: if 'NotRegistered' in response:
for id in response['NotRegistered']: for gcmid in response['NotRegistered']:
print 'need to remove "', id, '" from db' unregister( gcmid )
else: else:
print "got some kind of error" print "got some kind of error"
else: else:
@ -184,6 +185,7 @@ def main():
print "devices needing notification:", targets print "devices needing notification:", targets
notifyGCM( asGCMIds( g_con, targets, typ ), typ ) notifyGCM( asGCMIds( g_con, targets, typ ), typ )
pruneSent( devids ) pruneSent( devids )
elif g_debug: print "no targets after backoff"
else: else:
emptyCount += 1 emptyCount += 1
if (0 == (emptyCount%5)) and not g_debug: if (0 == (emptyCount%5)) and not g_debug: