diff --git a/xwords4/android/XWords4/AndroidManifest.xml b/xwords4/android/XWords4/AndroidManifest.xml
index ee72f9d96..a8200f124 100644
--- a/xwords4/android/XWords4/AndroidManifest.xml
+++ b/xwords4/android/XWords4/AndroidManifest.xml
@@ -63,6 +63,9 @@
+
gameInfo->serverRole = isServer? SERVER_ISSERVER : SERVER_ISCLIENT;
- UTIL_CBK_HEADER("setIsServer", "(Z)V" );
- (*env)->CallVoidMethod( env, util->jutil, mid, isServer );
- UTIL_CBK_TAIL();
-}
-
#ifndef XWFEATURE_MINIWIN
static void
and_util_bonusSquareHeld( XW_UtilCtxt* uc, XWBonusType bonus )
@@ -473,6 +463,22 @@ and_util_cellSquareHeld( XW_UtilCtxt* uc, XWStreamCtxt* words )
#endif
#ifndef XWFEATURE_STANDALONE_ONLY
+
+static void
+and_util_informMissing(XW_UtilCtxt* uc, XP_Bool isServer,
+ CommsConnType connType, XP_U16 nMissing )
+{
+ UTIL_CBK_HEADER( "informMissing",
+ "(ZLorg/eehouse/android/xw4/jni/"
+ "CommsAddrRec$CommsConnType;I)V" );
+ jobject jtyp = intToJEnum( env, connType,
+ "org/eehouse/android/xw4/jni/"
+ "CommsAddrRec$CommsConnType" );
+ (*env)->CallVoidMethod( env, util->jutil, mid, isServer, jtyp, nMissing );
+ (*env)->DeleteLocalRef( env, jtyp );
+ UTIL_CBK_TAIL();
+}
+
static void
and_util_addrChange( XW_UtilCtxt* uc, const CommsAddrRec* oldAddr,
const CommsAddrRec* newAddr )
@@ -480,6 +486,15 @@ and_util_addrChange( XW_UtilCtxt* uc, const CommsAddrRec* oldAddr,
LOG_FUNC();
}
+static void
+and_util_setIsServer(XW_UtilCtxt* uc, XP_Bool isServer )
+{
+ /* Change both the C and Java structs, which need to stay in sync */
+ uc->gameInfo->serverRole = isServer? SERVER_ISSERVER : SERVER_ISCLIENT;
+ UTIL_CBK_HEADER("setIsServer", "(Z)V" );
+ (*env)->CallVoidMethod( env, util->jutil, mid, isServer );
+ UTIL_CBK_TAIL();
+}
#endif
#ifdef XWFEATURE_SEARCHLIMIT
@@ -557,7 +572,6 @@ makeUtil( MPFORMAL JNIEnv** envp, jobject jutil, CurGameInfo* gi,
SET_PROC(warnIllegalWord);
SET_PROC(showChat);
SET_PROC(remSelected);
- SET_PROC(setIsServer);
#ifndef XWFEATURE_MINIWIN
SET_PROC(bonusSquareHeld);
@@ -569,7 +583,9 @@ makeUtil( MPFORMAL JNIEnv** envp, jobject jutil, CurGameInfo* gi,
#endif
#ifndef XWFEATURE_STANDALONE_ONLY
+ SET_PROC(informMissing);
SET_PROC(addrChange);
+ SET_PROC(setIsServer);
#endif
#ifdef XWFEATURE_SEARCHLIMIT
SET_PROC(getTraySearchLimits);
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTInviteActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTInviteActivity.java
new file mode 100644
index 000000000..3b8a6aab4
--- /dev/null
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTInviteActivity.java
@@ -0,0 +1,207 @@
+/* -*- compile-command: "cd ../../../../../; ant debug install"; -*- */
+/*
+ * Copyright 2009-2011 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.app.Activity;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+// import android.content.res.Resources;
+// import android.net.Uri;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+// import android.view.Window;
+import android.widget.AdapterView;
+// import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.ListView;
+import android.os.Handler;
+
+// import android.widget.TextView;
+// import android.app.AlertDialog;
+// import java.util.ArrayList;
+// import android.util.AttributeSet;
+
+import junit.framework.Assert;
+
+public class BTInviteActivity extends XWListActivity
+ implements View.OnClickListener,
+ CompoundButton.OnCheckedChangeListener {
+
+ public static final String DEVS = "DEVS";
+ public static final String INTENT_KEY_NMISSING = "NMISSING";
+
+ private Button m_okButton;
+ private Button m_rescanButton;
+ private Button m_reconfigureButton;
+ private String[] m_btDevNames;
+ private int m_nMissing;
+ private Handler m_handler;
+ private int m_checkCount = 0;
+
+ @Override
+ protected void onCreate( Bundle savedInstanceState )
+ {
+ super.onCreate( savedInstanceState );
+
+ Intent intent = getIntent();
+ m_nMissing = intent.getIntExtra( INTENT_KEY_NMISSING, -1 );
+
+ setContentView( R.layout.btinviter );
+
+ // getListView().setOnItemClickListener( this );
+
+ m_okButton = (Button)findViewById( R.id.button_ok );
+ m_okButton.setOnClickListener( this );
+ m_rescanButton = (Button)findViewById( R.id.button_rescan );
+ m_rescanButton.setOnClickListener( this );
+ m_reconfigureButton = (Button)findViewById( R.id.button_reconfigure );
+ m_reconfigureButton.setOnClickListener( this );
+
+ m_handler = new Handler();
+
+ m_checkCount = 0;
+ tryEnable();
+ }
+
+ @Override
+ protected void onResume()
+ {
+ super.onResume();
+ if ( null == m_btDevNames ) {
+ rescan();
+ }
+ }
+
+ public void onClick( View view )
+ {
+ DbgUtils.logf( "onClick" );
+ if ( m_okButton == view ) {
+ DbgUtils.logf( "OK BUTTON" );
+ Intent intent = new Intent();
+ String[] devs = listSelected();
+ intent.putExtra( DEVS, devs );
+ setResult( Activity.RESULT_OK, intent );
+ finish();
+ } else if ( m_rescanButton == view ) {
+ rescan();
+ } else if ( m_reconfigureButton == view ) {
+ }
+ }
+
+ // /* AdapterView.OnItemClickListener */
+ // public void onItemClick( AdapterView> parent, View view,
+ // int position, long id )
+ // {
+ // DbgUtils.logf( "BTInviteActivity.onItemClick(position=%d)", position );
+ // }
+
+ public void onCheckedChanged( CompoundButton buttonView,
+ boolean isChecked )
+ {
+ DbgUtils.logf( "BTInviteActivity.onCheckedChanged( isChecked=%b )",
+ isChecked );
+ if ( isChecked ) {
+ ++m_checkCount;
+ } else {
+ --m_checkCount;
+ }
+ tryEnable();
+ }
+
+ // BTService.BTEventListener interface
+ @Override
+ public void eventOccurred( BTService.BTEvent event, final Object ... args )
+ {
+ switch( event ) {
+ case SCAN_DONE:
+ m_handler.post( new Runnable() {
+ public void run() {
+ synchronized( BTInviteActivity.this ) {
+ stopProgress();
+ if ( 0 < args.length ) {
+ m_btDevNames = (String[])(args[0]);
+ }
+ setListAdapter( new BTDevsAdapter( m_btDevNames ) );
+ m_checkCount = 0;
+ tryEnable();
+ }
+ }
+ } );
+ break;
+ default:
+ super.eventOccurred( event, args );
+ }
+ }
+
+ private void rescan()
+ {
+ startProgress( R.string.scan_progress );
+ BTService.rescan( this );
+ }
+
+ private String[] listSelected()
+ {
+ ListView list = (ListView)findViewById( android.R.id.list );
+ String[] result = new String[m_checkCount];
+ int count = list.getChildCount();
+ int index = 0;
+ for ( int ii = 0; ii < count; ++ii ) {
+ CheckBox box = (CheckBox)list.getChildAt( ii );
+ if ( box.isChecked() ) {
+ result[index++] = box.getText().toString();
+ }
+ }
+ return result;
+ }
+
+ private void tryEnable()
+ {
+ m_okButton.setEnabled( m_checkCount == m_nMissing );
+ }
+
+
+ private class BTDevsAdapter extends XWListAdapter {
+ private String[] m_devs;
+ public BTDevsAdapter( String[] devs )
+ {
+ super( devs.length );
+ m_devs = devs;
+ }
+
+ public Object getItem( int position) { return m_devs[position]; }
+ public View getView( final int position, View convertView,
+ ViewGroup parent ) {
+ CheckBox box = (CheckBox)
+ Utils.inflate( BTInviteActivity.this, R.layout.btinviter_item );
+ box.setText( m_devs[position] );
+ box.setOnCheckedChangeListener( BTInviteActivity.this );
+ return box;
+ }
+
+ }
+
+}
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 85ab2efd2..a4eaf4f2b 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java
@@ -75,6 +75,8 @@ public class BoardActivity extends XWActivity
private static final int PICK_TILE_REQUESTTRAY_BLK = DLG_OKONLY + 12;
private static final int CHAT_REQUEST = 1;
+ private static final int BT_INVITE_RESULT = 2;
+
private static final int SCREEN_ON_TIME = 10 * 60 * 1000; // 10 mins
private static final int UNDO_LAST_ACTION = 1;
@@ -93,6 +95,7 @@ public class BoardActivity extends XWActivity
private static final int LOOKUP_ACTION = 14;
private static final int BUTTON_BROWSE_ACTION = 15;
private static final int VALUES_ACTION = 16;
+ private static final int BT_PICK_ACTION = 17;
private static final String DLG_TITLE = "DLG_TITLE";
private static final String DLG_TITLESTR = "DLG_TITLESTR";
@@ -131,6 +134,8 @@ public class BoardActivity extends XWActivity
private boolean m_volKeysZoom;
private boolean m_inTrade; // save this in bundle?
private BoardUtilCtxt m_utils;
+ private int m_nMissingPlayers = -1;
+ private int m_invitesPending;
// call startActivityForResult synchronously
private Semaphore m_forResultWait = new Semaphore(0);
@@ -510,12 +515,24 @@ public class BoardActivity extends XWActivity
protected void onActivityResult( int requestCode, int resultCode, Intent data )
{
if ( Activity.RESULT_CANCELED != resultCode ) {
- if ( CHAT_REQUEST == requestCode ) {
+ switch ( requestCode ) {
+ case CHAT_REQUEST:
String msg = data.getStringExtra( INTENT_KEY_CHAT );
if ( null != msg && msg.length() > 0 ) {
m_pendingChats.add( msg );
trySendChats();
}
+ break;
+ case BT_INVITE_RESULT:
+ m_invitesPending = 0;
+ String[] devs = data.getStringArrayExtra( BTInviteActivity.DEVS );
+ for ( String dev : devs ) {
+ BTService.inviteRemote( this, dev, m_gi.gameID, m_gi.dictLang,
+ m_gi.nPlayers, 1 );
+ ++m_invitesPending;
+ }
+ startProgress( R.string.invite_progress );
+ break;
}
}
}
@@ -720,6 +737,10 @@ public class BoardActivity extends XWActivity
case SYNC_ACTION:
doSyncMenuitem();
break;
+ case BT_PICK_ACTION:
+ GameUtils.launchBTInviter( this, m_nMissingPlayers,
+ BT_INVITE_RESULT );
+ break;
case COMMIT_ACTION:
cmd = JNIThread.JNICmd.CMD_COMMIT;
break;
@@ -807,6 +828,18 @@ public class BoardActivity extends XWActivity
}
} );
break;
+ case NEWGAME_FAILURE:
+ DbgUtils.logf( "failed to create game" );
+ case NEWGAME_SUCCESS:
+ if ( 0 == --m_invitesPending ) {
+ m_handler.post( new Runnable() {
+ public void run() {
+ stopProgress();
+ }
+ } );
+ }
+ break;
+
default:
super.eventOccurred( event, args );
break;
@@ -1296,6 +1329,26 @@ public class BoardActivity extends XWActivity
}
} // userError
+ @Override
+ public void informMissing( boolean isServer,
+ CommsAddrRec.CommsConnType connType,
+ final int nMissingPlayers )
+ {
+ if ( isServer &&
+ CommsAddrRec.CommsConnType.COMMS_CONN_BT == connType ) {
+ post( new Runnable() {
+ public void run() {
+ DbgUtils.showf( BoardActivity.this,
+ "%d players missing",
+ nMissingPlayers );
+ m_nMissingPlayers = nMissingPlayers;
+ showConfirmThen( R.string.bt_devs_missing,
+ BT_PICK_ACTION );
+ }
+ } );
+ }
+ }
+
@Override
public void informMove( String expl, String words )
{
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 9135b52d4..ef0282aac 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java
@@ -454,6 +454,14 @@ public class GameUtils {
null, gameID, isHost );
}
+ public static void launchBTInviter( Activity activity, int nMissing,
+ int replyCode )
+ {
+ Intent intent = new Intent( activity, BTInviteActivity.class );
+ intent.putExtra( BTInviteActivity.INTENT_KEY_NMISSING, nMissing );
+ activity.startActivityForResult( intent, replyCode );
+ }
+
public static void launchInviteActivity( Context context,
boolean choseEmail,
String room, String inviteID,
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NewGameActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NewGameActivity.java
index 160d8fb3d..b89456c1b 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NewGameActivity.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NewGameActivity.java
@@ -48,13 +48,12 @@ public class NewGameActivity extends XWActivity {
private static final int NEW_GAME_ACTION = 1;
private static final String SAVE_DEVNAMES = "DEVNAMES";
- private static final int PICK_BTDEV_DLG = DlgDelegate.DIALOG_LAST + 1;
- private static final int CONFIG_BT = 1;
+ private static final int CONFIG_FOR_BT = 1;
+ private static final int INVITE_FOR_BT = 2;
private boolean m_showsOn;
private Handler m_handler = null;
private int m_chosen;
- private String[] m_btDevNames;
private int m_lang = 0;
private long m_btRowID = -1;
@@ -62,7 +61,6 @@ public class NewGameActivity extends XWActivity {
protected void onCreate( Bundle savedInstanceState )
{
super.onCreate( savedInstanceState );
- getBundledData( savedInstanceState );
m_handler = new Handler();
@@ -109,81 +107,6 @@ public class NewGameActivity extends XWActivity {
checkEnableBT( true );
}
- @Override
- protected Dialog onCreateDialog( final int id )
- {
- Dialog dialog = super.onCreateDialog( id );
- if ( null == dialog ) {
- AlertDialog.Builder ab;
- OnClickListener lstnr;
-
- switch( id ) {
- case PICK_BTDEV_DLG:
- OnClickListener scanLstnr =
- new OnClickListener() {
- public void onClick( DialogInterface dlg,
- int whichButton ) {
- startProgress( R.string.scan_progress );
- BTService.rescan( NewGameActivity.this );
- }
- };
- OnClickListener okLstnr =
- new OnClickListener() {
- public void onClick( DialogInterface dlg,
- int whichButton ) {
- if ( 0 <= m_chosen ) {
- if ( m_chosen < m_btDevNames.length ) {
- int gameID = GameUtils.newGameID();
- BTService.
- inviteRemote( NewGameActivity.this,
- m_btDevNames[m_chosen],
- gameID, m_lang, 2, 1 );
- startProgress( R.string.invite_progress );
- }
- }
- }
- };
- ab = new AlertDialog.Builder( this )
- .setTitle( R.string.bt_pick_title )
- .setPositiveButton( R.string.button_ok, okLstnr )
- .setNegativeButton( R.string.bt_pick_rescan_button,
- scanLstnr );
-
- if ( null != m_btDevNames && 0 < m_btDevNames.length ) {
- OnClickListener devChosenLstnr =
- new OnClickListener() {
- public void onClick( DialogInterface dlgi,
- int whichButton ) {
- AlertDialog dlg = (AlertDialog)dlgi;
- Button btn =
- dlg.getButton( AlertDialog.BUTTON_POSITIVE );
- btn.setEnabled( 0 <= whichButton );
-
- m_chosen = whichButton;
- }
- };
- m_chosen = -1;
- ab.setSingleChoiceItems( m_btDevNames, m_chosen,
- devChosenLstnr );
- }
- dialog = ab.create();
- Utils.setRemoveOnDismiss( this, dialog, PICK_BTDEV_DLG );
- break;
- }
- }
- return dialog;
- }
-
- @Override
- protected void onPrepareDialog( int id, Dialog dialog )
- {
- super.onPrepareDialog( id, dialog );
- if ( PICK_BTDEV_DLG == id ) {
- ((AlertDialog)dialog).getButton( AlertDialog.BUTTON_POSITIVE )
- .setEnabled( false );
- }
- }
-
// DlgDelegate.DlgClickNotify interface
@Override
public void dlgButtonClicked( int id, int which )
@@ -209,50 +132,38 @@ public class NewGameActivity extends XWActivity {
protected void onActivityResult( int requestCode, int resultCode,
Intent data )
{
- if ( CONFIG_BT == requestCode ) {
- if ( Activity.RESULT_CANCELED == resultCode ) {
- DBUtils.deleteGame( this, m_btRowID );
- } else {
- // We'll leave it up to BoardActivity to detect that
- // it's not had any remote connections yet.
- GameUtils.launchGame( this, m_btRowID );
- finish();
+ if ( Activity.RESULT_CANCELED != resultCode ) {
+ switch ( requestCode ) {
+ case CONFIG_FOR_BT:
+ if ( Activity.RESULT_CANCELED == resultCode ) {
+ DBUtils.deleteGame( this, m_btRowID );
+ } else {
+ // We'll leave it up to BoardActivity to detect that
+ // it's not had any remote connections yet.
+ GameUtils.launchGame( this, m_btRowID );
+ finish();
+ }
+ break;
+ case INVITE_FOR_BT: // user selected device
+ if ( Activity.RESULT_CANCELED != resultCode ) {
+ int gameID = GameUtils.newGameID();
+ String[] remoteDevs =
+ data.getStringArrayExtra( BTInviteActivity.DEVS );
+ DbgUtils.logf( "got %s", remoteDevs[0] );
+ BTService.inviteRemote( NewGameActivity.this, remoteDevs[0],
+ gameID, m_lang, 2, 1 );
+ startProgress( R.string.invite_progress );
+ }
+ break;
}
}
}
- @Override
- protected void onSaveInstanceState( Bundle outState )
- {
- super.onSaveInstanceState( outState );
- outState.putStringArray( SAVE_DEVNAMES, m_btDevNames );
- }
-
- private void getBundledData( Bundle bundle )
- {
- if ( null != bundle ) {
- m_btDevNames = bundle.getStringArray( SAVE_DEVNAMES );
- }
- }
-
// BTService.BTEventListener interface
@Override
public void eventOccurred( BTService.BTEvent event, final Object ... args )
{
switch( event ) {
- case SCAN_DONE:
- m_handler.post( new Runnable() {
- public void run() {
- synchronized( NewGameActivity.this ) {
- stopProgress();
- if ( 0 < args.length ) {
- m_btDevNames = (String[])(args[0]);
- }
- showDialog( PICK_BTDEV_DLG );
- }
- }
- } );
- break;
case BT_ENABLED:
case BT_DISABLED:
m_handler.post( new Runnable() {
@@ -284,7 +195,7 @@ public class NewGameActivity extends XWActivity {
} );
break;
default:
- DbgUtils.logf( "unexpected event %s", event.toString() );
+ super.eventOccurred( event, args );
break;
}
}
@@ -341,12 +252,9 @@ public class NewGameActivity extends XWActivity {
intent.setAction( Intent.ACTION_EDIT );
intent.putExtra( GameUtils.INTENT_KEY_ROWID, m_btRowID );
intent.putExtra( GameUtils.INTENT_FORRESULT_ROWID, true );
- startActivityForResult( intent, CONFIG_BT );
- } else if ( null == m_btDevNames || 0 == m_btDevNames.length ) {
- startProgress( R.string.scan_progress );
- BTService.rescan( this );
+ startActivityForResult( intent, CONFIG_FOR_BT );
} else {
- showDialog( PICK_BTDEV_DLG );
+ GameUtils.launchBTInviter( this, 1, INVITE_FOR_BT );
}
}
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 7c3533306..3d0111634 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListActivity.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListActivity.java
@@ -30,7 +30,7 @@ import junit.framework.Assert;
import org.eehouse.android.xw4.jni.CommonPrefs;
public class XWListActivity extends ListActivity
- implements DlgDelegate.DlgClickNotify {
+ implements DlgDelegate.DlgClickNotify, BTService.BTEventListener {
private DlgDelegate m_delegate;
@@ -54,6 +54,7 @@ public class XWListActivity extends ListActivity
protected void onResume()
{
DbgUtils.logf( "%s.onResume(this=%H)", getClass().getName(), this );
+ BTService.setBTEventListener( this );
super.onResume();
}
@@ -61,6 +62,7 @@ public class XWListActivity extends ListActivity
protected void onPause()
{
DbgUtils.logf( "%s.onPause(this=%H)", getClass().getName(), this );
+ BTService.setBTEventListener( null );
super.onPause();
}
@@ -157,6 +159,16 @@ public class XWListActivity extends ListActivity
m_delegate.doSyncMenuitem();
}
+ protected void startProgress( int id )
+ {
+ m_delegate.startProgress( id );
+ }
+
+ protected void stopProgress()
+ {
+ m_delegate.stopProgress();
+ }
+
// DlgDelegate.DlgClickNotify interface
public void dlgButtonClicked( int id, int which )
{
@@ -173,4 +185,10 @@ public class XWListActivity extends ListActivity
m_delegate.launchLookup( words, lang, forceList );
}
+ // BTService.BTEventListener interface
+ public void eventOccurred( BTService.BTEvent event, final Object ... args )
+ {
+ m_delegate.eventOccurred( event, args );
+ }
+
}
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java
index f2d95790b..321d9f2ca 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java
@@ -108,6 +108,9 @@ public interface UtilCtxt {
void informMove( String expl, String words );
+ void informMissing( boolean isServer, CommsAddrRec.CommsConnType connType,
+ int nMissingPlayers );
+
void notifyGameOver();
// Don't need this unless we have a scroll thumb to indicate position
//void yOffsetChange( int maxOffset, int oldOffset, int newOffset );
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxtImpl.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxtImpl.java
index 3549b5863..4d168fa2c 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxtImpl.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxtImpl.java
@@ -207,6 +207,13 @@ public class UtilCtxtImpl implements UtilCtxt {
subclassOverride( "informMove" );
}
+ public void informMissing( boolean isServer,
+ CommsAddrRec.CommsConnType connType,
+ int nMissingPlayers )
+ {
+ subclassOverride( "informMissing" );
+ }
+
// Probably want to cache the fact that the game over notification
// showed up and then display it next time game's opened.
public void notifyGameOver()