Run JNIThread inside a Looper; use BlockingActivity to implement

dialogs requiring an immediate response.  The Looper change was made
in the hopes that a new Activity wouldn't be required and may not be
necessary.
This commit is contained in:
ehouse 2010-01-16 18:39:27 +00:00
parent 35943e8731
commit 5c183acb5e
4 changed files with 248 additions and 132 deletions

View file

@ -23,10 +23,14 @@ import android.app.AlertDialog;
import android.content.DialogInterface;
import org.eehouse.android.xw4.jni.*;
import org.eehouse.android.xw4.jni.JNIThread.*;
public class BoardActivity extends Activity implements XW_UtilCtxt, Runnable {
private static final int PICK_TILE_REQUEST = 1;
private static final int QUERY_REQUEST = 2;
private static final int INFORM_REQUEST = 3;
private BoardView m_view;
private int m_jniGamePtr;
@ -37,7 +41,6 @@ public class BoardActivity extends Activity implements XW_UtilCtxt, Runnable {
private String m_path;
private final int DLG_OKONLY = 1;
private final int DLG_QUERY = 2;
private String m_dlgBytes = null;
private int m_dlgTitle;
private boolean m_dlgResult;
@ -48,6 +51,7 @@ public class BoardActivity extends Activity implements XW_UtilCtxt, Runnable {
private Intent m_resultIntent = null;
private JNIThread m_jniThread;
private JNIThread m_jniThread_pending;
public class TimerRunnable implements Runnable {
private int m_gamePtr;
@ -61,8 +65,10 @@ public class BoardActivity extends Activity implements XW_UtilCtxt, Runnable {
}
public void run() {
m_timers[m_why] = null;
m_jniThread.handle( JNIThread.JNICmd.CMD_TIMER_FIRED,
new Object[] { m_why, m_when, m_handle } );
if ( null != m_jniThread ) {
m_jniThread.handle( JNICmd.CMD_TIMER_FIRED,
m_why, m_when, m_handle );
}
}
}
@ -152,17 +158,26 @@ public class BoardActivity extends Activity implements XW_UtilCtxt, Runnable {
m_prefs, null, dictBytes );
}
m_jniThread = new JNIThread( m_jniGamePtr,
new Handler() {
public void handleMessage( Message msg ) {
Utils.logf( "handleMessage called" );
m_view.invalidate();
}
} );
m_jniThread.start();
m_view.startHandling( m_jniThread, m_jniGamePtr, m_gi );
m_jniThread.handle( JNIThread.JNICmd.CMD_DO );
m_jniThread_pending = new
JNIThread( m_jniGamePtr,
new Handler() {
public void handleMessage( Message msg ) {
Utils.logf( "handleMessage() called" );
switch( msg.what ) {
case JNIThread.RUNNING:
m_jniThread = m_jniThread_pending;
m_view.startHandling( m_jniThread,
m_jniGamePtr,
m_gi );
m_jniThread.handle( JNICmd.CMD_DO );
break;
case JNIThread.DRAW:
m_view.invalidate();
break;
}
}
} );
m_jniThread_pending.start();
Utils.logf( "BoardActivity::onCreate() done" );
} // onCreate
@ -175,6 +190,7 @@ public class BoardActivity extends Activity implements XW_UtilCtxt, Runnable {
protected void onDestroy()
{
// what if m_jniThread is null?
m_jniThread.waitToStop();
saveGame();
XwJNI.game_dispose( m_jniGamePtr );
@ -187,9 +203,9 @@ public class BoardActivity extends Activity implements XW_UtilCtxt, Runnable {
Intent result )
{
Utils.logf( "onActivityResult called" );
this.m_resultCode = resultCode;
this.m_resultIntent = result;
this.m_forResultWait.release();
m_resultCode = resultCode;
m_resultIntent = result;
m_forResultWait.release();
}
public boolean onCreateOptionsMenu(Menu menu) {
@ -270,7 +286,9 @@ public class BoardActivity extends Activity implements XW_UtilCtxt, Runnable {
}
if ( handled && cmd != JNIThread.JNICmd.CMD_NONE ) {
m_jniThread.handle( cmd );
if ( null != m_jniThread ) {
m_jniThread.handle( cmd );
}
}
return handled;
@ -415,7 +433,7 @@ public class BoardActivity extends Activity implements XW_UtilCtxt, Runnable {
public boolean engineProgressCallback()
{
return !m_jniThread.busy();
return null != m_jniThread && !m_jniThread.busy();
}
public String getUserString( int stringCode )
@ -509,18 +527,107 @@ public class BoardActivity extends Activity implements XW_UtilCtxt, Runnable {
public boolean userQuery( int id, String query )
{
String actString = XWConstants.ACTION_QUERY;
switch( id ) {
case XW_UtilCtxt.QUERY_ROBOT_MOVE:
case XW_UtilCtxt.QUERY_ROBOT_TRADE:
actString = XWConstants.ACTION_INFORM;
break;
case XW_UtilCtxt.QUERY_COMMIT_TRADE:
query = getString( R.string.query_trade );
break;
case XW_UtilCtxt.QUERY_COMMIT_TURN:
case XW_UtilCtxt.QUERY_ROBOT_MOVE:
case XW_UtilCtxt.QUERY_ROBOT_TRADE:
break;
}
// Need to figure out how this thing can block.
return true;
Intent intent = new Intent( BoardActivity.this, BlockingActivity.class );
intent.setAction( actString );
Bundle bundle = new Bundle();
bundle.putString( XWConstants.QUERY_QUERY, query );
intent.putExtra( XWConstants.QUERY_QUERY, bundle );
boolean userConfirmed = false;
try {
startActivityForResult( intent, QUERY_REQUEST );
m_forResultWait.acquire();
Utils.logf( "userQuery back from acquire" );
userConfirmed = m_resultCode != 0;
} catch ( Exception ee ) {
Utils.logf( "userPickTile got: " + ee.toString() );
}
return userConfirmed;
}
public void userError( int code )
{
int resid = 0;
switch( code ) {
case ERR_TILES_NOT_IN_LINE:
resid = R.string.str_tiles_not_in_line;
break;
case ERR_NO_EMPTIES_IN_TURN:
resid = R.string.str_no_empties_in_turn;
break;
case ERR_TWO_TILES_FIRST_MOVE:
resid = R.string.str_two_tiles_first_move;
break;
case ERR_TILES_MUST_CONTACT:
resid = R.string.str_tiles_must_contact;
break;
case ERR_NOT_YOUR_TURN:
resid = R.string.str_not_your_turn;
break;
case ERR_NO_PEEK_ROBOT_TILES:
resid = R.string.str_no_peek_robot_tiles;
break;
case ERR_CANT_TRADE_MID_MOVE:
resid = R.string.str_cant_trade_mid_move;
break;
case ERR_TOO_FEW_TILES_LEFT_TO_TRADE:
resid = R.string.str_too_few_tiles_left_to_trade;
break;
case ERR_CANT_UNDO_TILEASSIGN:
resid = R.string.str_cant_undo_tileassign;
break;
case ERR_CANT_HINT_WHILE_DISABLED:
resid = R.string.str_cant_hint_while_disabled;
break;
case ERR_NO_PEEK_REMOTE_TILES:
resid = R.string.str_no_peek_remote_tiles;
break;
case ERR_REG_UNEXPECTED_USER:
resid = R.string.str_reg_unexpected_user;
break;
case ERR_SERVER_DICT_WINS:
resid = R.string.str_server_dict_wins;
break;
case ERR_REG_SERVER_SANS_REMOTE:
resid = R.string.str_reg_server_sans_remote;
break;
}
if ( resid != 0 ) {
String txt = getString( resid );
Intent intent = new Intent( BoardActivity.this, BlockingActivity.class );
intent.setAction( XWConstants.ACTION_INFORM );
Bundle bundle = new Bundle();
bundle.putString( XWConstants.QUERY_QUERY, txt );
intent.putExtra( XWConstants.QUERY_QUERY, bundle );
try {
startActivityForResult( intent, INFORM_REQUEST );
m_forResultWait.acquire();
} catch ( Exception ee ) {
Utils.logf( "userPickTile got: " + ee.toString() );
}
}
} // userError
} // class BoardActivity

View file

@ -10,8 +10,8 @@ public interface XWConstants {
public static final String PICK_TILE_TILES
= "org.eehouse.android.xw4.PICK_TILE_TILES";
public static final String PICK_TILE_TILE
= "org.eehouse.android.xw4.PICK_TILE_TILE";
// public static final String PICK_TILE_TILE
// = "org.eehouse.android.xw4.PICK_TILE_TILE";
// These are duplicated in AndroidManifest.xml. If change here
// must change there too to keep in sync.
@ -19,4 +19,10 @@ public interface XWConstants {
= "org.eehouse.android.xw4.action.PICK_TILE";
public final String CATEGORY_PICK_TILE
= "org.eehouse.android.xw4.category.PICK_TILE";
public final String ACTION_QUERY = "org.eehouse.android.xw4.action.QUERY";
public final String ACTION_INFORM= "org.eehouse.android.xw4.action.INFORM";
public static final String QUERY_QUERY
= "org.eehouse.android.xw4.QUERY_QUERY";
}

View file

@ -8,8 +8,8 @@ public class CommonPrefs {
public CommonPrefs() {
showBoardArrow = true;
showRobotScores = false;
showRobotScores = true;
hideTileValues = false;
skipCommitConfirm = true;
skipCommitConfirm = false;
}
}

View file

@ -9,6 +9,7 @@ import java.lang.InterruptedException;
import java.util.concurrent.LinkedBlockingQueue;
import android.os.Handler;
import android.os.Message;
import android.os.Looper;
public class JNIThread extends Thread {
@ -29,33 +30,26 @@ public class JNIThread extends Thread {
CMD_HINT,
CMD_NEXT_HINT,
CMD_VALUES,
CMD_STOP,
};
private boolean m_stopped = false;
public static final int RUNNING = 1;
public static final int DRAW = 2;
private int m_jniGamePtr;
private Handler m_handler;
LinkedBlockingQueue<QueueElem> m_queue;
private class QueueElem {
protected QueueElem( JNICmd cmd, Object[] args )
{
m_cmd = cmd; m_args = args;
}
JNICmd m_cmd;
Object[] m_args;
}
private Handler m_parentHandler;
private Handler m_loopHandler;
private boolean[] m_barr = new boolean[1]; // scratch boolean
public JNIThread( int gamePtr, Handler handler ) {
Utils.logf( "in JNIThread()" );
m_jniGamePtr = gamePtr;
m_handler = handler;
m_queue = new LinkedBlockingQueue<QueueElem>();
m_parentHandler = handler;
}
public void waitToStop() {
m_stopped = true;
handle( JNICmd.CMD_NONE ); // tickle it
handle( JNICmd.CMD_STOP );
try {
join();
} catch ( java.lang.InterruptedException ie ) {
@ -64,9 +58,10 @@ public class JNIThread extends Thread {
}
public boolean busy()
{ // synchronize this!!!
int siz = m_queue.size();
return siz > 0;
{
// HTF to I tell if my queue has anything in it. Do I have to
// keep a counter? Which means synchronizing...
return false;
}
private boolean toggleTray() {
@ -82,103 +77,111 @@ public class JNIThread extends Thread {
public void run()
{
boolean[] barr = new boolean[1]; // scratch boolean
while ( !m_stopped ) {
QueueElem elem;
Object[] args;
try {
elem = m_queue.take();
} catch ( InterruptedException ie ) {
Utils.logf( "interrupted; killing thread" );
break;
}
boolean draw = false;
args = elem.m_args;
switch( elem.m_cmd ) {
Looper.prepare();
m_loopHandler = new Handler() {
public void handleMessage( Message msg ) {
boolean draw = false;
Object[] args = (Object[])msg.obj;
switch( JNICmd.values()[msg.what] ) {
case CMD_DRAW:
draw = true;
break;
case CMD_DO:
draw = XwJNI.server_do( m_jniGamePtr );
break;
case CMD_DRAW:
draw = true;
break;
case CMD_DO:
draw = XwJNI.server_do( m_jniGamePtr );
break;
case CMD_PEN_DOWN:
draw = XwJNI.board_handlePenDown( m_jniGamePtr,
((Integer)args[0]).intValue(),
((Integer)args[1]).intValue(),
barr );
break;
case CMD_PEN_MOVE:
draw = XwJNI.board_handlePenMove( m_jniGamePtr,
((Integer)args[0]).intValue(),
((Integer)args[1]).intValue() );
break;
case CMD_PEN_UP:
draw = XwJNI.board_handlePenUp( m_jniGamePtr,
((Integer)args[0]).intValue(),
((Integer)args[1]).intValue() );
break;
case CMD_PEN_DOWN:
draw = XwJNI.board_handlePenDown( m_jniGamePtr,
((Integer)args[0]).intValue(),
((Integer)args[1]).intValue(),
m_barr );
break;
case CMD_PEN_MOVE:
draw = XwJNI.board_handlePenMove( m_jniGamePtr,
((Integer)args[0]).intValue(),
((Integer)args[1]).intValue() );
break;
case CMD_PEN_UP:
draw = XwJNI.board_handlePenUp( m_jniGamePtr,
((Integer)args[0]).intValue(),
((Integer)args[1]).intValue() );
break;
case CMD_COMMIT:
draw = XwJNI.board_commitTurn( m_jniGamePtr );
break;
case CMD_COMMIT:
draw = XwJNI.board_commitTurn( m_jniGamePtr );
break;
case CMD_JUGGLE:
draw = XwJNI.board_juggleTray( m_jniGamePtr );
break;
case CMD_FLIP:
draw = XwJNI.board_flip( m_jniGamePtr );
break;
case CMD_TOGGLE_TRAY:
draw = toggleTray();
break;
case CMD_TOGGLE_TRADE:
draw = XwJNI.board_beginTrade( m_jniGamePtr );
break;
case CMD_UNDO_CUR:
draw = XwJNI.board_replaceTiles( m_jniGamePtr );
break;
case CMD_UNDO_LAST:
XwJNI.server_handleUndo( m_jniGamePtr );
draw = true;
break;
case CMD_JUGGLE:
draw = XwJNI.board_juggleTray( m_jniGamePtr );
break;
case CMD_FLIP:
draw = XwJNI.board_flip( m_jniGamePtr );
break;
case CMD_TOGGLE_TRAY:
draw = toggleTray();
break;
case CMD_TOGGLE_TRADE:
draw = XwJNI.board_beginTrade( m_jniGamePtr );
break;
case CMD_UNDO_CUR:
draw = XwJNI.board_replaceTiles( m_jniGamePtr );
break;
case CMD_UNDO_LAST:
XwJNI.server_handleUndo( m_jniGamePtr );
draw = true;
break;
case CMD_HINT:
XwJNI.board_resetEngine( m_jniGamePtr );
// fallthru
case CMD_NEXT_HINT:
draw = XwJNI.board_requestHint( m_jniGamePtr, false, barr );
if ( barr[0] ) {
handle( JNICmd.CMD_NEXT_HINT );
case CMD_HINT:
XwJNI.board_resetEngine( m_jniGamePtr );
// fallthru
case CMD_NEXT_HINT:
draw = XwJNI.board_requestHint( m_jniGamePtr, false, m_barr );
if ( m_barr[0] ) {
handle( JNICmd.CMD_NEXT_HINT );
}
break;
case CMD_VALUES:
draw = XwJNI.board_toggle_showValues( m_jniGamePtr );
break;
case CMD_TIMER_FIRED:
draw = XwJNI.timerFired( m_jniGamePtr,
((Integer)args[0]).intValue(),
((Integer)args[1]).intValue(),
((Integer)args[2]).intValue() );
break;
case CMD_STOP:
Looper.myLooper().quit();
break;
}
if ( draw ) {
if ( !XwJNI.board_draw( m_jniGamePtr ) ) {
Utils.logf( "draw not complete" );
}
Message.obtain( m_parentHandler, DRAW ).sendToTarget();
}
}
break;
};
case CMD_VALUES:
draw = XwJNI.board_toggle_showValues( m_jniGamePtr );
break;
// Safe to use us now
Message.obtain( m_parentHandler, RUNNING ).sendToTarget();
Looper.loop();
case CMD_TIMER_FIRED:
draw = XwJNI.timerFired( m_jniGamePtr,
((Integer)args[0]).intValue(),
((Integer)args[1]).intValue(),
((Integer)args[2]).intValue() );
break;
}
if ( draw ) {
if ( !XwJNI.board_draw( m_jniGamePtr ) ) {
Utils.logf( "draw not complete" );
}
m_handler.post(null);
}
}
Utils.logf( "run exiting" );
} // run
/** Post a cmd to be handled by the JNI thread
*/
public void handle( JNICmd cmd, Object... args )
{
QueueElem elem = new QueueElem( cmd, args );
m_queue.add( elem );
Message message = Message.obtain( m_loopHandler, cmd.ordinal(), args );
message.sendToTarget();
}
}