From c2c673e93265b817e9db38092eb2f20f78a52d50 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 16 Aug 2011 19:44:37 -0700 Subject: [PATCH 1/8] improve testing of proxy delete by saving up a number of relayIDs and sending with multiple -d flags to rq. --- xwords4/linux/scripts/discon_ok2.sh | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/xwords4/linux/scripts/discon_ok2.sh b/xwords4/linux/scripts/discon_ok2.sh index 9508f9243..36bbca4a4 100755 --- a/xwords4/linux/scripts/discon_ok2.sh +++ b/xwords4/linux/scripts/discon_ok2.sh @@ -225,11 +225,17 @@ close_device() { unset ROOMS[$ID] } +OBITS="" + kill_from_log() { LOG=$1 RELAYID=$(./scripts/relayID.sh --long $LOG) if [ -n "$RELAYID" ]; then - ../relay/rq -a $HOST -d $RELAYID 2>/dev/null || true + OBITS="$OBITS -d $RELAYID" + if [ 0 -eq $(($RANDOM%2)) ]; then + ../relay/rq -a $HOST $OBITS 2>/dev/null || true + OBITS="" + fi return 0 # success fi echo "unable to send kill command for $LOG" @@ -281,10 +287,15 @@ check_game() { close_device $ID $DONEDIR "game over" done date + # XWRELAY_ERROR_DELETED may be old elif grep -q 'relay_error_curses(XWRELAY_ERROR_DELETED)' $LOG; then echo "deleting $LOG $(connName $LOG) b/c another resigned" kill_from_log $LOG || true close_device $KEY $DEADDIR "other resigned" + elif grep -q 'relay_error_curses(XWRELAY_ERROR_DEADGAME)' $LOG; then + echo "deleting $LOG $(connName $LOG) b/c another resigned" + kill_from_log $LOG || true + close_device $KEY $DEADDIR "other resigned" else maybe_resign $KEY fi @@ -334,6 +345,8 @@ run_cmds() { fi done + [ -n "$OBITS" ] && ../relay/rq -a $HOST $OBITS 2>/dev/null || true + # kill any remaining games if [ $COUNT -gt 0 ]; then mkdir -p ${LOGDIR}/not_done From 0c29a4c1ca7d98d499e2be3f631234c0e7bcf3ea Mon Sep 17 00:00:00 2001 From: Andy2 Date: Wed, 17 Aug 2011 20:11:30 -0700 Subject: [PATCH 2/8] fix NPE when sd card unmounted --- .../android/XWords4/src/org/eehouse/android/xw4/GameUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java index f1aed07e5..03b975378 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java @@ -718,7 +718,7 @@ public class GameUtils { File file = context.getFileStreamPath( name ); if ( !file.exists() ) { file = getSDPathFor( context, name ); - if ( !file.exists() ) { + if ( null != file && !file.exists() ) { file = null; } } From 455cb9a4c44fbb134ef43a203806c864f092a84a Mon Sep 17 00:00:00 2001 From: Andy2 Date: Thu, 18 Aug 2011 06:26:43 -0700 Subject: [PATCH 3/8] Include possibility of umounted sd card in missing dict explanation. Name of game too. Use single-selectable list and Substitute button instead of expecting user to tap once to select (which confused me.) --- .../android/XWords4/res/values/strings.xml | 12 ++--- .../org/eehouse/android/xw4/GamesList.java | 44 ++++++++++--------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/xwords4/android/XWords4/res/values/strings.xml b/xwords4/android/XWords4/res/values/strings.xml index f47737116..41ca0d2c1 100644 --- a/xwords4/android/XWords4/res/values/strings.xml +++ b/xwords4/android/XWords4/res/values/strings.xml @@ -389,11 +389,13 @@ Substitute dictionary (wordcount) Substitute - Unable to open game because no %s - dictionary found. - Unable to open game because - dictionary %1$s not found. You can download a replacement or - substitute another %2$s dictionary. + Unable to open game \"%1$s\" because no + %2$s dictionary found. (It may have been deleted, or stored on + an an SD card that\'s been unmounted.) + Unable to open game \"%1$s\" because + dictionary %2$s not found. (It may have been deleted, or stored + on an an SD card that\'s been unmounted.) You can download a + replacement or substitute another %3$s dictionary. Password for \"%s\": diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java index 0b77b8353..e91a13658 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java @@ -97,14 +97,16 @@ public class GamesList extends XWListActivity } }; String message; - String langName = DictLangCache.getLangName( this, - m_missingDictLang ); + String langName = + DictLangCache.getLangName( this, m_missingDictLang ); + String gameName = GameUtils.getName( this, m_rowid ); if ( WARN_NODICT == id ) { - message = String.format( getString(R.string.no_dictf), - langName ); + message = String.format( getString( R.string.no_dictf ), + gameName, langName ); } else { message = String.format( getString(R.string.no_dict_substf), - m_missingDictNames[0], langName ); + gameName, m_missingDictNames[0], + langName ); } ab = new AlertDialog.Builder( this ) @@ -127,24 +129,26 @@ public class GamesList extends XWListActivity case SHOW_SUBST: m_sameLangDicts = DictLangCache.getHaveLangCounts( this, m_missingDictLang ); - ab = new AlertDialog.Builder( this ) + lstnr = new DialogInterface.OnClickListener() { + public void onClick( DialogInterface dlg, + int which ) { + int pos = ((AlertDialog)dlg).getListView(). + getCheckedItemPosition(); + String dict = m_sameLangDicts[pos]; + dict = DictLangCache.stripCount( dict ); + GameUtils.replaceDicts( GamesList.this, + m_missingDictRowId, + m_missingDictNames[0], + dict ); + } + }; + dialog = new AlertDialog.Builder( this ) .setTitle( R.string.subst_dict_title ) + .setPositiveButton( R.string.button_substdict, lstnr ) .setNegativeButton( R.string.button_cancel, null ) - .setItems( m_sameLangDicts, - new DialogInterface.OnClickListener() { - public void onClick( DialogInterface dlg, - int which ) { - String dict = m_sameLangDicts[which]; - dict = DictLangCache.stripCount( dict ); - GameUtils. - replaceDicts( GamesList.this, - m_missingDictRowId, - m_missingDictNames[0], - dict ); - } - }) + .setSingleChoiceItems( m_sameLangDicts, 0, null ) + .create(); ; - dialog = ab.create(); // Force destruction so onCreateDialog() will get // called next time and we can insert a different // list. There seems to be no way to change the list From 7a1346e530aa2e1a24a868e1882bdce2d3691562 Mon Sep 17 00:00:00 2001 From: Andy2 Date: Thu, 18 Aug 2011 07:49:40 -0700 Subject: [PATCH 4/8] add assert to catch bad dict earlier --- xwords4/common/movestak.c | 1 + 1 file changed, 1 insertion(+) diff --git a/xwords4/common/movestak.c b/xwords4/common/movestak.c index 4bb44740b..ac2295fa2 100644 --- a/xwords4/common/movestak.c +++ b/xwords4/common/movestak.c @@ -61,6 +61,7 @@ void stack_setBitsPerTile( StackCtxt* stack, XP_U16 bitsPerTile ) { XP_ASSERT( !!stack ); + XP_ASSERT( bitsPerTile == 5 || bitsPerTile == 6 ); stack->bitsPerTile = bitsPerTile; } From fd4e6276286911182ee59256711b18a0cfba73c0 Mon Sep 17 00:00:00 2001 From: Andy2 Date: Thu, 18 Aug 2011 07:54:00 -0700 Subject: [PATCH 5/8] when sd card is umounted, android sends a message to apps. I can't figure out how to get that so I ignore it and am killed then relaunched if I have a dict file open on the SD. On relauch, don't crash. Instead, check if any dicts used by the game is unreachable and put up an alert with only one choice: Close game. Thought about using the missing-dicts stuff from GamesList, but this is a special case that should be seen only when user umounts while a BoardActivity instance is frontmost. --- .../android/XWords4/res/values/strings.xml | 11 +- .../eehouse/android/xw4/BoardActivity.java | 172 ++++++++++-------- .../org/eehouse/android/xw4/GameUtils.java | 22 ++- 3 files changed, 125 insertions(+), 80 deletions(-) diff --git a/xwords4/android/XWords4/res/values/strings.xml b/xwords4/android/XWords4/res/values/strings.xml index 41ca0d2c1..ebd8ed481 100644 --- a/xwords4/android/XWords4/res/values/strings.xml +++ b/xwords4/android/XWords4/res/values/strings.xml @@ -389,13 +389,18 @@ Substitute dictionary (wordcount) Substitute + Close game + A dictionary this game is using has + disappeared. (Usually this means it\'s on an external card that + is no longer available.) Unable to open game \"%1$s\" because no %2$s dictionary found. (It may have been deleted, or stored on - an an SD card that\'s been unmounted.) + an external card that is no longer available.) Unable to open game \"%1$s\" because dictionary %2$s not found. (It may have been deleted, or stored - on an an SD card that\'s been unmounted.) You can download a - replacement or substitute another %3$s dictionary. + on an external card that is no longer available.) You can + download a replacement or substitute another %3$s + dictionary. Password for \"%s\": diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java index a79c25951..94bfe9947 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java @@ -38,6 +38,7 @@ import android.app.Dialog; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.DialogInterface; +import android.content.DialogInterface.OnDismissListener; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.EditText; @@ -68,6 +69,7 @@ public class BoardActivity extends XWActivity private static final int QUERY_ENDGAME = DLG_OKONLY + 7; private static final int DLG_DELETED = DLG_OKONLY + 8; private static final int DLG_INVITE = DLG_OKONLY + 9; + private static final int DLG_NODICT = DLG_OKONLY + 10; private static final int CHAT_REQUEST = 1; private static final int SCREEN_ON_TIME = 10 * 60 * 1000; // 10 mins @@ -80,7 +82,7 @@ public class BoardActivity extends XWActivity private int m_jniGamePtr; private GameUtils.GameLock m_gameLock; private CurGameInfo m_gi; - CommsTransport m_xport; + private CommsTransport m_xport; private Handler m_handler = null; private TimerRunnable[] m_timers; private Runnable m_screenTimer; @@ -286,6 +288,21 @@ public class BoardActivity extends XWActivity .create(); } break; + case DLG_NODICT: + dialog = new AlertDialog.Builder( this ) + .setTitle( R.string.no_dict_title ) + .setMessage( R.string.no_dict_finish ) + .setPositiveButton( R.string.button_close_game, null ) + .create(); + OnDismissListener dlstnr; + dlstnr = new OnDismissListener() { + public void onDismiss( DialogInterface di ) { + // removeDialog( DLG_NODICT ); + finish(); + } + }; + dialog.setOnDismissListener( dlstnr ); + break; default: // just drop it; super.onCreateDialog likely failed break; @@ -1044,89 +1061,96 @@ public class BoardActivity extends XWActivity private void loadGame() { if ( 0 == m_jniGamePtr ) { - Assert.assertNull( m_gameLock ); - m_gameLock = new GameUtils.GameLock( m_rowid, true ).lock(); - - byte[] stream = GameUtils.savedGame( this, m_gameLock ); - XwJNI.gi_from_stream( m_gi, stream ); - String[] dictNames = m_gi.dictNames(); GameUtils.DictPairs pairs = GameUtils.openDicts( this, dictNames ); - String langName = m_gi.langName(); - m_jniGamePtr = XwJNI.initJNI(); - if ( m_gi.serverRole != DeviceRole.SERVER_STANDALONE ) { - m_xport = new CommsTransport( m_jniGamePtr, this, this, - m_gi.serverRole ); - } + if ( pairs.anyMissing( dictNames ) ) { + showDialog( DLG_NODICT ); + } else { - CommonPrefs cp = CommonPrefs.get( this ); - if ( null == stream || - ! XwJNI.game_makeFromStream( m_jniGamePtr, stream, - m_gi, dictNames, pairs.m_bytes, - pairs.m_paths, langName, m_utils, - m_jniu, m_view, cp, m_xport ) ) { - XwJNI.game_makeNewGame( m_jniGamePtr, m_gi, m_utils, m_jniu, - m_view, cp, m_xport, dictNames, - pairs.m_bytes, pairs.m_paths, - langName ); - } + String langName = m_gi.langName(); - m_jniThread = new - JNIThread( m_jniGamePtr, m_gi, m_view, m_gameLock, this, - new Handler() { - public void handleMessage( Message msg ) { - switch( msg.what ) { - case JNIThread.DRAW: - m_view.invalidate(); - break; - case JNIThread.DIALOG: - m_dlgBytes = (String)msg.obj; - m_dlgTitle = msg.arg1; - showDialog( DLG_OKONLY ); - break; - case JNIThread.QUERY_ENDGAME: - showDialog( QUERY_ENDGAME ); - break; - case JNIThread.TOOLBAR_STATES: - if ( null != m_jniThread ) { - m_gsi = m_jniThread.getGameStateInfo(); - updateToolbar(); + Assert.assertNull( m_gameLock ); + m_gameLock = new GameUtils.GameLock( m_rowid, true ).lock(); + + byte[] stream = GameUtils.savedGame( this, m_gameLock ); + XwJNI.gi_from_stream( m_gi, stream ); + + m_jniGamePtr = XwJNI.initJNI(); + + if ( m_gi.serverRole != DeviceRole.SERVER_STANDALONE ) { + m_xport = new CommsTransport( m_jniGamePtr, this, this, + m_gi.serverRole ); + } + + CommonPrefs cp = CommonPrefs.get( this ); + if ( null == stream || + ! XwJNI.game_makeFromStream( m_jniGamePtr, stream, + m_gi, dictNames, pairs.m_bytes, + pairs.m_paths, langName, m_utils, + m_jniu, m_view, cp, m_xport ) ) { + XwJNI.game_makeNewGame( m_jniGamePtr, m_gi, m_utils, m_jniu, + m_view, cp, m_xport, dictNames, + pairs.m_bytes, pairs.m_paths, + langName ); + } + + m_jniThread = new + JNIThread( m_jniGamePtr, m_gi, m_view, m_gameLock, this, + new Handler() { + public void handleMessage( Message msg ) { + switch( msg.what ) { + case JNIThread.DRAW: + m_view.invalidate(); + break; + case JNIThread.DIALOG: + m_dlgBytes = (String)msg.obj; + m_dlgTitle = msg.arg1; + showDialog( DLG_OKONLY ); + break; + case JNIThread.QUERY_ENDGAME: + showDialog( QUERY_ENDGAME ); + break; + case JNIThread.TOOLBAR_STATES: + if ( null != m_jniThread ) { + m_gsi = m_jniThread.getGameStateInfo(); + updateToolbar(); + } + break; } - break; } - } - } ); - // see http://stackoverflow.com/questions/680180/where-to-stop-\ - // destroy-threads-in-android-service-class - m_jniThread.setDaemon( true ); - m_jniThread.start(); + } ); + // see http://stackoverflow.com/questions/680180/where-to-stop-\ + // destroy-threads-in-android-service-class + m_jniThread.setDaemon( true ); + m_jniThread.start(); - m_view.startHandling( this, m_jniThread, m_jniGamePtr, m_gi ); - if ( null != m_xport ) { - m_xport.setReceiver( m_jniThread ); - } - m_jniThread.handle( JNICmd.CMD_START ); + m_view.startHandling( this, m_jniThread, m_jniGamePtr, m_gi ); + if ( null != m_xport ) { + m_xport.setReceiver( m_jniThread ); + } + m_jniThread.handle( JNICmd.CMD_START ); - if ( !CommonPrefs.getHideTitleBar( this ) ) { - setTitle( GameUtils.getName( this, m_rowid ) ); - } - m_toolbar = new Toolbar( this ); + if ( !CommonPrefs.getHideTitleBar( this ) ) { + setTitle( GameUtils.getName( this, m_rowid ) ); + } + m_toolbar = new Toolbar( this ); - populateToolbar(); + populateToolbar(); - int flags = DBUtils.getMsgFlags( this, m_rowid ); - if ( 0 != (GameSummary.MSG_FLAGS_CHAT & flags) ) { - startChatActivity(); - } - if ( 0 != (GameSummary.MSG_FLAGS_GAMEOVER & flags) ) { - m_jniThread.handle( JNIThread.JNICmd.CMD_POST_OVER ); - } - if ( 0 != flags ) { - DBUtils.setMsgFlags( m_rowid, GameSummary.MSG_FLAGS_NONE ); - } + int flags = DBUtils.getMsgFlags( this, m_rowid ); + if ( 0 != (GameSummary.MSG_FLAGS_CHAT & flags) ) { + startChatActivity(); + } + if ( 0 != (GameSummary.MSG_FLAGS_GAMEOVER & flags) ) { + m_jniThread.handle( JNIThread.JNICmd.CMD_POST_OVER ); + } + if ( 0 != flags ) { + DBUtils.setMsgFlags( m_rowid, GameSummary.MSG_FLAGS_NONE ); + } - trySendChats(); + trySendChats(); + } } } // loadGame @@ -1209,9 +1233,9 @@ public class BoardActivity extends XWActivity }); } // populateToolbar - private DialogInterface.OnDismissListener makeODLforBlocking( final int id ) + private OnDismissListener makeODLforBlocking( final int id ) { - return new DialogInterface.OnDismissListener() { + return new OnDismissListener() { public void onDismiss( DialogInterface di ) { releaseIfBlocking(); removeDialog( id ); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java index 03b975378..3381468af 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java @@ -158,7 +158,23 @@ public class GameUtils { public DictPairs( byte[][] bytes, String[] paths ) { m_bytes = bytes; m_paths = paths; } - } + + public boolean anyMissing( final String[] names ) + { + boolean missing = false; + for ( int ii = 0; ii < m_paths.length; ++ii ) { + if ( names[ii] != null ) { + // It's ok for there to be no dict IFF there's no + // name. That's a player using the default dict. + if ( null == m_paths[ii] && null == m_bytes[ii] ) { + missing = true; + break; + } + } + } + return missing; + } + } // DictPairs private static Object s_syncObj = new Object(); @@ -180,7 +196,7 @@ public class GameUtils { * basis for a new one. */ public static GameLock resetGame( Context context, GameLock lockSrc, - GameLock lockDest ) + GameLock lockDest ) { int gamePtr = XwJNI.initJNI(); CurGameInfo gi = new CurGameInfo( context ); @@ -897,7 +913,7 @@ public class GameUtils { // Which isn't possible right now, so make sure the old and new // dict have the same langauge code. public static void replaceDicts( Context context, long rowid, - String oldDict, String newDict ) + String oldDict, String newDict ) { GameLock lock = new GameLock( rowid, true ).lock(); byte[] stream = savedGame( context, lock ); From 8f60ca5af3e7e21842944cb831e829a141899a88 Mon Sep 17 00:00:00 2001 From: Andy2 Date: Fri, 19 Aug 2011 18:52:55 -0700 Subject: [PATCH 6/8] Need to show dict-gone-missing dialog when umount from GameConfig too, so: change loadMakeGame() to return gamePtr rather than take one in, and to return 0 when any dict no available. Move dict-gone-missing dialog into DlgDelegate. Remove DlgDelegate always adding onDismiss listener that removes the dialog, and instead add one to the dict-gone-missing dialog that, like the onClick handler, calls finish() on the activity. --- .../eehouse/android/xw4/BoardActivity.java | 18 +-- .../org/eehouse/android/xw4/DlgDelegate.java | 35 +++- .../org/eehouse/android/xw4/GameConfig.java | 153 +++++++++--------- .../org/eehouse/android/xw4/GameUtils.java | 50 +++--- .../android/xw4/RelayGameActivity.java | 3 +- .../org/eehouse/android/xw4/XWActivity.java | 15 +- .../eehouse/android/xw4/XWListActivity.java | 1 + 7 files changed, 153 insertions(+), 122 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java index 94bfe9947..436809680 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java @@ -69,7 +69,6 @@ public class BoardActivity extends XWActivity private static final int QUERY_ENDGAME = DLG_OKONLY + 7; private static final int DLG_DELETED = DLG_OKONLY + 8; private static final int DLG_INVITE = DLG_OKONLY + 9; - private static final int DLG_NODICT = DLG_OKONLY + 10; private static final int CHAT_REQUEST = 1; private static final int SCREEN_ON_TIME = 10 * 60 * 1000; // 10 mins @@ -288,21 +287,6 @@ public class BoardActivity extends XWActivity .create(); } break; - case DLG_NODICT: - dialog = new AlertDialog.Builder( this ) - .setTitle( R.string.no_dict_title ) - .setMessage( R.string.no_dict_finish ) - .setPositiveButton( R.string.button_close_game, null ) - .create(); - OnDismissListener dlstnr; - dlstnr = new OnDismissListener() { - public void onDismiss( DialogInterface di ) { - // removeDialog( DLG_NODICT ); - finish(); - } - }; - dialog.setOnDismissListener( dlstnr ); - break; default: // just drop it; super.onCreateDialog likely failed break; @@ -1065,7 +1049,7 @@ public class BoardActivity extends XWActivity GameUtils.DictPairs pairs = GameUtils.openDicts( this, dictNames ); if ( pairs.anyMissing( dictNames ) ) { - showDialog( DLG_NODICT ); + showDictGoneFinish(); } else { String langName = m_gi.langName(); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java index 67e3a3193..8f91ed2d1 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java @@ -40,7 +40,8 @@ public class DlgDelegate { public static final int DIALOG_NOTAGAIN = 3; public static final int CONFIRM_THEN = 4; public static final int TEXT_OR_HTML_THEN = 5; - public static final int DIALOG_LAST = TEXT_OR_HTML_THEN; + public static final int DLG_DICTGONE = 6; + public static final int DIALOG_LAST = DLG_DICTGONE; private int m_msgID; private String m_msg; @@ -78,6 +79,9 @@ public class DlgDelegate { case TEXT_OR_HTML_THEN: dialog = createHtmlThenDialog(); break; + case DLG_DICTGONE: + dialog = createDictGoneDialog(); + break; } return dialog; } @@ -141,6 +145,11 @@ public class DlgDelegate { m_activity.showDialog( DIALOG_OKONLY ); } + public void showDictGoneFinish() + { + m_activity.showDialog( DLG_DICTGONE ); + } + public void showAboutDialog() { m_activity.showDialog( DIALOG_ABOUT ); @@ -283,4 +292,28 @@ public class DlgDelegate { .create(); } + private Dialog createDictGoneDialog() + { + Utils.logf( "DlgDelegate.createDictGoneDialog() called" ); + Dialog dialog; + dialog = new AlertDialog.Builder( m_activity ) + .setTitle( R.string.no_dict_title ) + .setMessage( R.string.no_dict_finish ) + .setPositiveButton( R.string.button_close_game, + new DialogInterface.OnClickListener() { + public void onClick( DialogInterface dlg, int item ) { + m_activity.finish(); + } + } ) + .create(); + + dialog.setOnDismissListener( new DialogInterface.OnDismissListener() { + public void onDismiss( DialogInterface di ) { + m_activity.finish(); + } + } ); + + return dialog; + } + } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameConfig.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameConfig.java index 0f5e18f6e..b007ee2ae 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameConfig.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameConfig.java @@ -396,88 +396,91 @@ public class GameConfig extends XWActivity { super.onResume(); - int gamePtr = XwJNI.initJNI(); m_giOrig = new CurGameInfo( this ); // Lock in case we're going to config. We *could* re-get the // lock once the user decides to make changes. PENDING. m_gameLock = new GameUtils.GameLock( m_rowid, true ).lock(); - GameUtils.loadMakeGame( this, gamePtr, m_giOrig, m_gameLock ); - m_gameStarted = XwJNI.model_getNMoves( gamePtr ) > 0 - || XwJNI.comms_isConnected( gamePtr ); - - if ( m_gameStarted ) { - if ( null == m_gameLockedCheck ) { - m_gameLockedCheck = - (CheckBox)findViewById( R.id.game_locked_check ); - m_gameLockedCheck.setVisibility( View.VISIBLE ); - m_gameLockedCheck.setChecked( true ); - m_gameLockedCheck.setOnClickListener( this ); - } - handleLockedChange(); - } - - m_gi = new CurGameInfo( this, m_giOrig ); - - m_carOrig = new CommsAddrRec( this ); - if ( XwJNI.game_hasComms( gamePtr ) ) { - XwJNI.comms_getAddr( gamePtr, m_carOrig ); + int gamePtr = GameUtils.loadMakeGame( this, m_giOrig, m_gameLock ); + if ( 0 == gamePtr ) { + showDictGoneFinish(); } else { - String relayName = CommonPrefs.getDefaultRelayHost( this ); - int relayPort = CommonPrefs.getDefaultRelayPort( this ); - XwJNI.comms_getInitialAddr( m_carOrig, relayName, relayPort ); - } - XwJNI.game_dispose( gamePtr ); + m_gameStarted = XwJNI.model_getNMoves( gamePtr ) > 0 + || XwJNI.comms_isConnected( gamePtr ); - m_car = new CommsAddrRec( m_carOrig ); - - m_notNetworkedGame = DeviceRole.SERVER_STANDALONE == m_gi.serverRole; - setTitle(); - - if ( !m_notNetworkedGame ) { - m_joinPublicCheck = - (CheckBox)findViewById(R.id.join_public_room_check); - m_joinPublicCheck.setOnClickListener( this ); - m_joinPublicCheck.setChecked( m_car.ip_relay_seeksPublicRoom ); - Utils.setChecked( this, R.id.advertise_new_room_check, - m_car.ip_relay_advertiseRoom ); - m_publicRoomsSet = - (LinearLayout)findViewById(R.id.public_rooms_set ); - m_privateRoomsSet = - (LinearLayout)findViewById(R.id.private_rooms_set ); - - Utils.setText( this, R.id.room_edit, m_car.ip_relay_invite ); - - m_roomChoose = (Spinner)findViewById( R.id.room_spinner ); - - m_refreshRoomsButton = - (ImageButton)findViewById( R.id.refresh_button ); - m_refreshRoomsButton.setOnClickListener( this ); - - adjustConnectStuff(); - } - - loadPlayers(); - configLangSpinner(); - - m_phoniesSpinner.setSelection( m_gi.phoniesAction.ordinal() ); - - setSmartnessSpinner(); - - Utils.setChecked( this, R.id.hints_allowed, !m_gi.hintsNotAllowed ); - Utils.setInt( this, R.id.timer_minutes_edit, - m_gi.gameSeconds/60/m_gi.nPlayers ); - - CheckBox check = (CheckBox)findViewById( R.id.use_timer ); - CompoundButton.OnCheckedChangeListener lstnr = - new CompoundButton.OnCheckedChangeListener() { - public void onCheckedChanged( CompoundButton buttonView, - boolean checked ) { - View view = findViewById( R.id.timer_set ); - view.setVisibility( checked ? View.VISIBLE : View.GONE ); + if ( m_gameStarted ) { + if ( null == m_gameLockedCheck ) { + m_gameLockedCheck = + (CheckBox)findViewById( R.id.game_locked_check ); + m_gameLockedCheck.setVisibility( View.VISIBLE ); + m_gameLockedCheck.setChecked( true ); + m_gameLockedCheck.setOnClickListener( this ); } - }; - check.setOnCheckedChangeListener( lstnr ); - Utils.setChecked( this, R.id.use_timer, m_gi.timerEnabled ); + handleLockedChange(); + } + + m_gi = new CurGameInfo( this, m_giOrig ); + + m_carOrig = new CommsAddrRec( this ); + if ( XwJNI.game_hasComms( gamePtr ) ) { + XwJNI.comms_getAddr( gamePtr, m_carOrig ); + } else { + String relayName = CommonPrefs.getDefaultRelayHost( this ); + int relayPort = CommonPrefs.getDefaultRelayPort( this ); + XwJNI.comms_getInitialAddr( m_carOrig, relayName, relayPort ); + } + XwJNI.game_dispose( gamePtr ); + + m_car = new CommsAddrRec( m_carOrig ); + + m_notNetworkedGame = DeviceRole.SERVER_STANDALONE == m_gi.serverRole; + setTitle(); + + if ( !m_notNetworkedGame ) { + m_joinPublicCheck = + (CheckBox)findViewById(R.id.join_public_room_check); + m_joinPublicCheck.setOnClickListener( this ); + m_joinPublicCheck.setChecked( m_car.ip_relay_seeksPublicRoom ); + Utils.setChecked( this, R.id.advertise_new_room_check, + m_car.ip_relay_advertiseRoom ); + m_publicRoomsSet = + (LinearLayout)findViewById(R.id.public_rooms_set ); + m_privateRoomsSet = + (LinearLayout)findViewById(R.id.private_rooms_set ); + + Utils.setText( this, R.id.room_edit, m_car.ip_relay_invite ); + + m_roomChoose = (Spinner)findViewById( R.id.room_spinner ); + + m_refreshRoomsButton = + (ImageButton)findViewById( R.id.refresh_button ); + m_refreshRoomsButton.setOnClickListener( this ); + + adjustConnectStuff(); + } + + loadPlayers(); + configLangSpinner(); + + m_phoniesSpinner.setSelection( m_gi.phoniesAction.ordinal() ); + + setSmartnessSpinner(); + + Utils.setChecked( this, R.id.hints_allowed, !m_gi.hintsNotAllowed ); + Utils.setInt( this, R.id.timer_minutes_edit, + m_gi.gameSeconds/60/m_gi.nPlayers ); + + CheckBox check = (CheckBox)findViewById( R.id.use_timer ); + CompoundButton.OnCheckedChangeListener lstnr = + new CompoundButton.OnCheckedChangeListener() { + public void onCheckedChanged( CompoundButton buttonView, + boolean checked ) { + View view = findViewById( R.id.timer_set ); + view.setVisibility( checked ? View.VISIBLE : View.GONE ); + } + }; + check.setOnCheckedChangeListener( lstnr ); + Utils.setChecked( this, R.id.use_timer, m_gi.timerEnabled ); + } } // onResume @Override diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java index 3381468af..60e392920 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java @@ -198,13 +198,12 @@ public class GameUtils { public static GameLock resetGame( Context context, GameLock lockSrc, GameLock lockDest ) { - int gamePtr = XwJNI.initJNI(); CurGameInfo gi = new CurGameInfo( context ); CommsAddrRec addr = null; // loadMakeGame, if makinga new game, will add comms as long // as DeviceRole.SERVER_STANDALONE != gi.serverRole - loadMakeGame( context, gamePtr, gi, lockSrc ); + int gamePtr = loadMakeGame( context, gi, lockSrc ); String[] dictNames = gi.dictNames(); DictPairs pairs = openDicts( context, dictNames ); @@ -282,9 +281,8 @@ public class GameUtils { public static GameSummary summarize( Context context, GameLock lock ) { - int gamePtr = XwJNI.initJNI(); CurGameInfo gi = new CurGameInfo( context ); - loadMakeGame( context, gamePtr, gi, lock ); + int gamePtr = loadMakeGame( context, gi, lock ); return summarizeAndClose( context, lock, gamePtr, gi ); } @@ -321,33 +319,40 @@ public class GameUtils { return result; } - public static void loadMakeGame( Context context, int gamePtr, - CurGameInfo gi, GameLock lock ) + public static int loadMakeGame( Context context, CurGameInfo gi, + GameLock lock ) { - loadMakeGame( context, gamePtr, gi, null, lock ); + return loadMakeGame( context, gi, null, lock ); } - public static void loadMakeGame( Context context, int gamePtr, - CurGameInfo gi, UtilCtxt util, - GameLock lock ) + public static int loadMakeGame( Context context, CurGameInfo gi, + UtilCtxt util, GameLock lock ) { + int gamePtr = 0; + byte[] stream = savedGame( context, lock ); XwJNI.gi_from_stream( gi, stream ); String[] dictNames = gi.dictNames(); DictPairs pairs = openDicts( context, dictNames ); - String langName = gi.langName(); + if ( pairs.anyMissing( dictNames ) ) { + Utils.logf( "loadMakeGame() failing: dict unavailable" ); + } else { + gamePtr = XwJNI.initJNI(); - boolean madeGame = XwJNI.game_makeFromStream( gamePtr, stream, - JNIUtilsImpl.get(), gi, - dictNames, pairs.m_bytes, - pairs.m_paths, langName, - util, - CommonPrefs.get(context)); - if ( !madeGame ) { - XwJNI.game_makeNewGame( gamePtr, gi, JNIUtilsImpl.get(), - CommonPrefs.get(context), dictNames, - pairs.m_bytes, pairs.m_paths, langName ); + String langName = gi.langName(); + boolean madeGame = XwJNI.game_makeFromStream( gamePtr, stream, + JNIUtilsImpl.get(), gi, + dictNames, pairs.m_bytes, + pairs.m_paths, langName, + util, + CommonPrefs.get(context)); + if ( !madeGame ) { + XwJNI.game_makeNewGame( gamePtr, gi, JNIUtilsImpl.get(), + CommonPrefs.get(context), dictNames, + pairs.m_bytes, pairs.m_paths, langName ); + } } + return gamePtr; } public static long saveGame( Context context, int gamePtr, @@ -872,12 +877,11 @@ public class GameUtils { boolean draw = false; long rowid = DBUtils.getRowIDFor( context, relayID ); if ( -1 != rowid ) { - int gamePtr = XwJNI.initJNI(); CurGameInfo gi = new CurGameInfo( context ); FeedUtilsImpl feedImpl = new FeedUtilsImpl( context, rowid ); GameLock lock = new GameLock( rowid, true ); if ( lock.tryLock() ) { - loadMakeGame( context, gamePtr, gi, feedImpl, lock ); + int gamePtr = loadMakeGame( context, gi, feedImpl, lock ); for ( byte[] msg : msgs ) { draw = XwJNI.game_receiveMessage( gamePtr, msg ) || draw; diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayGameActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayGameActivity.java index 1f0ee8d09..4f23b8ede 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayGameActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayGameActivity.java @@ -67,10 +67,9 @@ public class RelayGameActivity extends XWActivity { super.onStart(); - int gamePtr = XwJNI.initJNI(); m_gi = new CurGameInfo( this ); m_gameLock = new GameUtils.GameLock( m_rowid, true ).lock(); - GameUtils.loadMakeGame( this, gamePtr, m_gi, m_gameLock ); + int gamePtr = GameUtils.loadMakeGame( this, m_gi, m_gameLock ); m_car = new CommsAddrRec( this ); if ( XwJNI.game_hasComms( gamePtr ) ) { XwJNI.comms_getAddr( gamePtr, m_car ); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWActivity.java index 7bb01a339..d094a9adf 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWActivity.java @@ -36,6 +36,7 @@ public class XWActivity extends Activity { private DlgDelegate m_delegate; + @Override protected void onCreate( Bundle savedInstanceState ) { Utils.logf( "%s.onCreate(this=%H)", getClass().getName(), this ); @@ -84,10 +85,10 @@ public class XWActivity extends Activity { @Override protected Dialog onCreateDialog( int id ) { - Utils.logf( "%s.onCreateDialog() called", getClass().getName() ); - Dialog dialog = m_delegate.onCreateDialog( id ); - if ( null != dialog ) { - setRemoveOnDismiss( dialog, id ); + Dialog dialog = super.onCreateDialog( id ); + if ( null == dialog ) { + Utils.logf( "%s.onCreateDialog() called", getClass().getName() ); + dialog = m_delegate.onCreateDialog( id ); } return dialog; } @@ -100,6 +101,7 @@ public class XWActivity extends Activity { @Override protected void onPrepareDialog( int id, Dialog dialog ) { + super.onPrepareDialog( id, dialog ); // docs say should call through m_delegate.onPrepareDialog( id, dialog ); } @@ -121,6 +123,11 @@ public class XWActivity extends Activity { m_delegate.showOKOnlyDialog( msgID ); } + protected void showDictGoneFinish() + { + m_delegate.showDictGoneFinish(); + } + protected void showConfirmThen( int msgID, DialogInterface.OnClickListener action ) { diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListActivity.java index 77fe99799..bb1206eff 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListActivity.java @@ -106,6 +106,7 @@ public class XWListActivity extends ListActivity { @Override protected void onPrepareDialog( int id, Dialog dialog ) { + super.onPrepareDialog( id, dialog ); m_delegate.onPrepareDialog( id, dialog ); } From 6ca60438a190c381e6218257740ebc967892588f Mon Sep 17 00:00:00 2001 From: Andy2 Date: Fri, 19 Aug 2011 18:54:21 -0700 Subject: [PATCH 7/8] no need for onClick listener when onDismiss listener does the same thing --- .../XWords4/src/org/eehouse/android/xw4/DlgDelegate.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java index 8f91ed2d1..abc2ee79f 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java @@ -299,12 +299,7 @@ public class DlgDelegate { dialog = new AlertDialog.Builder( m_activity ) .setTitle( R.string.no_dict_title ) .setMessage( R.string.no_dict_finish ) - .setPositiveButton( R.string.button_close_game, - new DialogInterface.OnClickListener() { - public void onClick( DialogInterface dlg, int item ) { - m_activity.finish(); - } - } ) + .setPositiveButton( R.string.button_close_game, null ) .create(); dialog.setOnDismissListener( new DialogInterface.OnDismissListener() { From 659c054696eacfa070677a7fd40467c99df565dd Mon Sep 17 00:00:00 2001 From: Andy2 Date: Sat, 20 Aug 2011 11:59:32 -0700 Subject: [PATCH 8/8] add new class that listens for SDCard mounted event (could be generalized for multiple events and map them to multiple notification callbacks), and use it from DictsActivity to redraw the list when the SD card comes back online. While the default android behavior works for the unmount case (relaunches DictsActivity which correctly draws only those dicts still available) it needs this change to redraw after the available set grows. --- .../eehouse/android/xw4/DictsActivity.java | 24 +++++-- .../eehouse/android/xw4/SDCardWatcher.java | 68 +++++++++++++++++++ 2 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 xwords4/android/XWords4/src/org/eehouse/android/xw4/SDCardWatcher.java diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictsActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictsActivity.java index 6901d3d7f..3cf294cf9 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictsActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictsActivity.java @@ -55,7 +55,8 @@ import org.eehouse.android.xw4.jni.JNIUtilsImpl; import org.eehouse.android.xw4.jni.CommonPrefs; public class DictsActivity extends ExpandableListActivity - implements View.OnClickListener, XWListItem.DeleteCallback { + implements View.OnClickListener, XWListItem.DeleteCallback, + SDCardWatcher.SDCardNotifiee { private static final String DICT_DOLAUNCH = "do_launch"; private static final String DICT_LANG_EXTRA = "use_lang"; @@ -75,9 +76,10 @@ public class DictsActivity extends ExpandableListActivity private XWListItem m_rowView; GameUtils.DictLoc m_moveFromLoc; GameUtils.DictLoc m_moveToLoc; - String m_moveName; + private SDCardWatcher m_cardWatcher; + private String m_moveName; - LayoutInflater m_factory; + private LayoutInflater m_factory; private class DictListAdapter implements ExpandableListAdapter { private Context m_context; @@ -331,10 +333,17 @@ public class DictsActivity extends ExpandableListActivity protected void onResume() { super.onResume(); + m_cardWatcher = new SDCardWatcher( this, this ); mkListAdapter(); expandGroups(); } + protected void onPause() { + m_cardWatcher.close(); + m_cardWatcher = null; + super.onPause(); + } + public void onClick( View v ) { askStartDownload( 0, null ); @@ -429,7 +438,7 @@ public class DictsActivity extends ExpandableListActivity showDialog( MOVE_DICT ); } - // DeleteCallback interface + // XWListItem.DeleteCallback interface public void deleteCalled( int myPosition, final String dict ) { int code = DictLangCache.getDictLangCode( this, dict ); @@ -457,6 +466,13 @@ public class DictsActivity extends ExpandableListActivity m_delegate.showConfirmThen( msg, action ); } + // SDCardWatcher.SDCardNotifiee interface + public void cardMounted() + { + mkListAdapter(); + expandGroups(); + } + private void deleteDict( String dict ) { GameUtils.deleteDict( this, dict ); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SDCardWatcher.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SDCardWatcher.java new file mode 100644 index 000000000..1492e9731 --- /dev/null +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SDCardWatcher.java @@ -0,0 +1,68 @@ +/* -*- compile-command: "cd ../../../../../; ant install"; -*- */ +/* + * Copyright 2009-2010 by Eric House (xwords@eehouse.org). All + * rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +package org.eehouse.android.xw4; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +public class SDCardWatcher { + public interface SDCardNotifiee { + void cardMounted(); + } + + private UmountReceiver m_rcvr; + private Context m_context; + private SDCardNotifiee m_notifiee; + + private class UmountReceiver extends BroadcastReceiver { + @Override + public void onReceive( Context context, Intent intent ) + { + if ( intent.getAction(). + equals( Intent.ACTION_MEDIA_MOUNTED ) ) { + m_notifiee.cardMounted(); + } + } + } + + public SDCardWatcher( Context context, SDCardNotifiee notifiee ) + { + m_context = context; + m_rcvr = new UmountReceiver(); + m_notifiee = notifiee; + + IntentFilter filter = + new IntentFilter( Intent.ACTION_MEDIA_MOUNTED ); + // filter.addAction( Intent.ACTION_MEDIA_UNMOUNTED ); + // filter.addAction( Intent.ACTION_MEDIA_EJECT ); + filter.addDataScheme( "file" ); + + /*Intent intent = */context.getApplicationContext(). + registerReceiver( m_rcvr, filter ); + } + + public void close() + { + m_context.getApplicationContext().unregisterReceiver( m_rcvr ); + } +}