From 812262e41dc92a2389a0741222bc7d47d29ffc83 Mon Sep 17 00:00:00 2001 From: eehouse Date: Thu, 8 Apr 2010 04:09:50 +0000 Subject: [PATCH] add basic framework to collect metadata prior to saving open game (e.g. number of moves) and display it in game list. What to save and how to display it still not finalized but it works. --- xwords4/android/XWords4/jni/xwjni.c | 14 ++++ .../eehouse/android/xw4/BoardActivity.java | 10 +++ .../src/org/eehouse/android/xw4/DBHelper.java | 59 +++++++++++++++ .../org/eehouse/android/xw4/GameConfig.java | 9 ++- .../eehouse/android/xw4/GameListAdapter.java | 27 ++++++- .../org/eehouse/android/xw4/GamesList.java | 20 ++--- .../src/org/eehouse/android/xw4/Utils.java | 73 ++++++++++++++++++- .../eehouse/android/xw4/jni/GameSummary.java | 29 ++++++++ .../org/eehouse/android/xw4/jni/XwJNI.java | 3 +- 9 files changed, 222 insertions(+), 22 deletions(-) create mode 100644 xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java create mode 100644 xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/GameSummary.java diff --git a/xwords4/android/XWords4/jni/xwjni.c b/xwords4/android/XWords4/jni/xwjni.c index 64effdec6..d62a0d3d1 100644 --- a/xwords4/android/XWords4/jni/xwjni.c +++ b/xwords4/android/XWords4/jni/xwjni.c @@ -917,6 +917,20 @@ Java_org_eehouse_android_xw4_jni_XwJNI_game_1receiveMessage return result; } +JNIEXPORT void JNICALL +Java_org_eehouse_android_xw4_jni_XwJNI_game_1summarize +( JNIEnv* env, jclass C, jint gamePtr, jobject jsummary ) +{ + LOG_FUNC(); + XWJNI_START(); + XP_S16 nMoves = model_getNMoves( state->game.model ); + setInt( env, jsummary, "nMoves", nMoves ); + setBool( env, jsummary, "gameOver", + server_getGameIsOver( state->game.server ) ); + XWJNI_END(); + LOG_RETURN_VOID(); +} + JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1prefsChanged ( JNIEnv* env, jclass C, jint gamePtr, jobject jcp ) 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 2c8724030..083b0343e 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java @@ -351,6 +351,13 @@ public class BoardActivity extends Activity implements UtilCtxt { return super.onKeyUp( keyCode, event ); } + + // onDestroy may be the wrong place to do this. + // onWindowFocusChanged seems to be getting called in GamesList + // before this function, and so the list item corresponding to + // this game isn't necessarily up-to-date. But if move e.g. to + // onStop() then onStart() needs to be prepared to reconstitute + // everything. @Override protected void onDestroy() { @@ -366,8 +373,11 @@ public class BoardActivity extends Activity implements UtilCtxt { // This has to happen after the drawing thread is killed // to avoid the possibility of reentering the jni world. + GameSummary summary = new GameSummary(); + XwJNI.game_summarize( m_jniGamePtr, summary ); byte[] state = XwJNI.game_saveToStream( m_jniGamePtr, null ); Utils.saveGame( this, state, m_path ); + Utils.saveSummary( m_path, summary ); XwJNI.game_dispose( m_jniGamePtr ); m_jniGamePtr = 0; diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java new file mode 100644 index 000000000..436ffdb5d --- /dev/null +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DBHelper.java @@ -0,0 +1,59 @@ +/* -*- compile-command: "cd ../../../../../; ant install"; -*- */ +/* + * Copyright 2009-2010 by Eric House (xwords@eehouse.org). All + * rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +package org.eehouse.android.xw4; + +import android.content.Context; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteDatabase; + +public class DBHelper extends SQLiteOpenHelper { + + public static final String TABLE_NAME = "summaries"; + private static final String DB_NAME = "xwdb"; + private static final int DB_VERSION = 1; + + public static final String FILE_NAME = "FILE_NAME"; + public static final String NUM_MOVES = "NUM_MOVES"; + public static final String GAME_OVER = "GAME_OVER"; + public static final String SNAPSHOT = "SNAPSHOT"; + + public DBHelper( Context context ) + { + super( context, DB_NAME, null, DB_VERSION ); + } + + @Override + public void onCreate( SQLiteDatabase db ) + { + db.execSQL( "CREATE TABLE " + TABLE_NAME + " (" + + FILE_NAME + " TEXT PRIMARY KEY," + + NUM_MOVES + " INTEGER," + + GAME_OVER + " INTEGER," + + SNAPSHOT + " BLOB," + + ");" ); + } + + @Override + public void onUpgrade( SQLiteDatabase db, int oldVersion, int newVersion ) + { + Utils.logf( "onUpgrade: old: %d; new: %d", oldVersion, newVersion ); + } +} 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 e2a26abe7..9457c1f8e 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameConfig.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameConfig.java @@ -522,8 +522,8 @@ public class GameConfig extends Activity implements View.OnClickListener { boolean consumed = false; if ( keyCode == KeyEvent.KEYCODE_BACK ) { saveChanges(); - if ( 0 < m_nMoves && (m_giOrig.changesMatter(m_gi) - || m_carOrig.changesMatter(m_car) ) ) { + if ( 0 <= m_nMoves && (m_giOrig.changesMatter(m_gi) + || m_carOrig.changesMatter(m_car) ) ) { showDialog( CONFIRM_CHANGE ); consumed = true; } else { @@ -782,6 +782,11 @@ public class GameConfig extends Activity implements View.OnClickListener { } Utils.saveGame( this, gamePtr, m_gi, m_path ); + + GameSummary summary = new GameSummary(); + XwJNI.game_summarize( gamePtr, summary ); + Utils.saveSummary( m_path, summary ); + XwJNI.game_dispose( gamePtr ); } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java index 112e57554..82a9e478e 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListAdapter.java @@ -51,14 +51,35 @@ public class GameListAdapter implements ListAdapter { return Utils.gamesList(m_context).length; } - public Object getItem( int position ) { + public Object getItem( int position ) + { TextView view = new TextView(m_context); - byte[] stream = open( Utils.gamesList(m_context)[position] ); + String path = Utils.gamesList(m_context)[position]; + byte[] stream = open( path ); if ( null != stream ) { CurGameInfo gi = new CurGameInfo( m_context ); XwJNI.gi_from_stream( gi, stream ); - view.setText( gi.summary(m_context) ); + String summaryTxt = gi.summary( m_context ); + + GameSummary summary = Utils.getSummary( m_context, path ); + if ( null != summary ) { + String state = "\nState: "; + if ( summary.nMoves < 0 ) { + state += "Configured"; + } else if ( summary.gameOver ) { + state += "Game over"; + } else { + state += "In play"; + } + summaryTxt += state; + + if ( summary.nMoves >= 0 ) { + summaryTxt += String.format( " Moves played: %d", + summary.nMoves ); + } + } + view.setText( summaryTxt ); } return view; } 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 9349a4a71..2ebddeaff 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java @@ -42,8 +42,6 @@ import junit.framework.Assert; import org.eehouse.android.xw4.jni.*; -import org.eehouse.android.xw4.XWords4.Games; // for constants - public class GamesList extends ListActivity implements View.OnClickListener { private GameListAdapter m_adapter; @@ -102,13 +100,6 @@ public class GamesList extends ListActivity implements View.OnClickListener { Button newGameB = (Button)findViewById(R.id.new_game); newGameB.setOnClickListener( this ); - // If no data was given in the intent (because we were started - // as a MAIN activity), then use our default content provider. - Intent intent = getIntent(); - if (intent.getData() == null) { - intent.setData(Games.CONTENT_URI); - } - m_adapter = new GameListAdapter( this ); setListAdapter( m_adapter ); } @@ -124,7 +115,8 @@ public class GamesList extends ListActivity implements View.OnClickListener { @Override public void onCreateContextMenu( ContextMenu menu, View view, - ContextMenuInfo menuInfo ) { + ContextMenuInfo menuInfo ) + { MenuInflater inflater = getMenuInflater(); inflater.inflate( R.menu.games_list_item_menu, menu ); } @@ -147,6 +139,7 @@ public class GamesList extends ListActivity implements View.OnClickListener { int id = item.getItemId(); if ( R.id.list_item_delete == id ) { + Utils.saveSummary( path, null ); if ( ! deleteFile( path ) ) { Utils.logf( "unable to delete " + path ); } @@ -169,14 +162,17 @@ public class GamesList extends ListActivity implements View.OnClickListener { case R.id.list_item_reset: Utils.resetGame( this, path, path ); + Utils.saveSummary( path, null ); break; case R.id.list_item_new_from: - Utils.resetGame( this, path ); + String newName = Utils.resetGame( this, path ); + Utils.saveSummary( newName, null ); break; case R.id.list_item_copy: stream = Utils.savedGame( this, path ); - Utils.saveGame( this, stream ); + newName = Utils.saveGame( this, stream ); + Utils.saveSummary( newName, Utils.getSummary( this, path ) ); break; // These require some notion of predictable sort order. diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java index d7ae6b9ab..cc82b7272 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java @@ -32,6 +32,7 @@ import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.util.ArrayList; import android.content.res.AssetManager; +import android.content.ContentValues; import android.os.Environment; import java.io.InputStream; import android.widget.CheckBox; @@ -46,6 +47,9 @@ import java.util.Formatter; import android.view.LayoutInflater; import android.net.Uri; import junit.framework.Assert; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.Cursor; import org.eehouse.android.xw4.jni.*; @@ -54,8 +58,10 @@ public class Utils { static final int DIALOG_ABOUT = 1; static final int DIALOG_LAST = DIALOG_ABOUT; + static final String DB_PATH = "XW_GAMES"; private static Time s_time = new Time(); + private static SQLiteOpenHelper m_dbHelper = null; private Utils() {} @@ -164,9 +170,11 @@ public class Utils { return al.toArray( new String[al.size()] ); } - public static void resetGame( Context context, String pathIn ) + public static String resetGame( Context context, String pathIn ) { - resetGame( context, pathIn, newName( context ) ); + String newName = newName( context ); + resetGame( context, pathIn, newName ); + return newName; } public static void loadMakeGame( Context context, int gamePtr, @@ -211,9 +219,11 @@ public class Utils { } } - public static void saveGame( Context context, byte[] bytes ) + public static String saveGame( Context context, byte[] bytes ) { - saveGame( context, bytes, newName( context ) ); + String name = newName( context ); + saveGame( context, bytes, name ); + return name; } public static boolean gameDictHere( Context context, String path, @@ -436,6 +446,30 @@ public class Utils { } } + public static GameSummary getSummary( Context context, String file ) + { + initDB( context ); + + GameSummary summary = new GameSummary(); + SQLiteDatabase db = m_dbHelper.getReadableDatabase(); + + String[] columns = { DBHelper.NUM_MOVES, DBHelper.GAME_OVER }; + String selection = DBHelper.FILE_NAME + "=\"" + file + "\""; + + Cursor cursor = db.query( DBHelper.TABLE_NAME, columns, selection, + null, null, null, null ); + if ( 1 == cursor.getCount() && cursor.moveToFirst() ) { + summary = new GameSummary(); + summary.nMoves = cursor.getInt(cursor. + getColumnIndex(DBHelper.NUM_MOVES)); + int tmp = cursor.getInt(cursor. + getColumnIndex(DBHelper.GAME_OVER)); + summary.gameOver = tmp == 0 ? false : true; + } + db.close(); + return summary; + } + private static boolean isGame( String file ) { return file.endsWith( XWConstants.GAME_EXTN ); @@ -446,4 +480,35 @@ public class Utils { return file.endsWith( XWConstants.DICT_EXTN ); } + private static void initDB( Context context ) + { + if ( null == m_dbHelper ) { + m_dbHelper = new DBHelper( context ); + } + } + + public static void saveSummary( String path, GameSummary summary ) + { + SQLiteDatabase db = m_dbHelper.getWritableDatabase(); + + if ( null == summary ) { + String selection = DBHelper.FILE_NAME + "=\"" + path + "\""; + db.delete( DBHelper.TABLE_NAME, selection, null ); + } else { + ContentValues values = new ContentValues(); + values.put( DBHelper.FILE_NAME, path ); + values.put( DBHelper.NUM_MOVES, summary.nMoves ); + values.put( DBHelper.GAME_OVER, summary.gameOver ); + + Utils.logf( "saveSummary: nMoves=%d", summary.nMoves ); + + try { + long result = db.replaceOrThrow( DBHelper.TABLE_NAME, "", values ); + } catch ( Exception ex ) { + Utils.logf( "ex: %s", ex.toString() ); + } + } + db.close(); + } + } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/GameSummary.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/GameSummary.java new file mode 100644 index 000000000..a93c2cd36 --- /dev/null +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/GameSummary.java @@ -0,0 +1,29 @@ +/* -*- 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.jni; + +/** Info we want to access when the game's closed that's not available + * in CurGameInfo + */ +public class GameSummary { + public int nMoves; + public boolean gameOver; +} diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/XwJNI.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/XwJNI.java index 3a512c5ff..6ad97fa40 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/XwJNI.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/XwJNI.java @@ -84,8 +84,9 @@ public class XwJNI { public static native boolean game_receiveMessage( int gamePtr, byte[] stream ); + public static native void game_summarize( int gamePtr, GameSummary summary ); public static native byte[] game_saveToStream( int gamePtr, - CurGameInfo gi ); + CurGameInfo gi ); public static native boolean game_hasComms( int gamePtr ); public static native void game_dispose( int gamePtr );