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()