diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java index bad336321..4792eab61 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java @@ -530,6 +530,8 @@ public class BoardDelegate extends DelegateBase Assert.assertTrue( 0 < state.nMissing ); if ( state.summary.hasRematchInfo() ) { tryRematchInvites( true ); + } else if ( state.summary.hasInviteInfo() ) { + tryOtherInvites( true ); } else { callInviteChoices( sentInfo[0] ); } @@ -1770,6 +1772,8 @@ public class BoardDelegate extends DelegateBase } else if ( nMissing > 0 ) { if ( m_summary.hasRematchInfo() ) { skipDismiss = !tryRematchInvites( false ); + } else if ( m_summary.hasInviteInfo() ) { + skipDismiss = !tryOtherInvites( false ); } else if ( !m_haveInvited ) { m_haveInvited = true; showInviteAlertIf(); @@ -2770,6 +2774,8 @@ public class BoardDelegate extends DelegateBase { if ( 0 < m_mySIS.nMissing && m_summary.hasRematchInfo() ) { tryRematchInvites( false ); + } else if ( 0 < m_mySIS.nMissing && m_summary.hasInviteInfo() ) { + tryOtherInvites( false ); } else if ( null != m_missingDevs ) { Assert.assertNotNull( m_missingMeans ); String gameName = GameUtils.getName( m_activity, m_rowid ); @@ -3136,6 +3142,15 @@ public class BoardDelegate extends DelegateBase } } + private NetLaunchInfo nliForMe() + { + int numHere = 1; + int forceChannel = 1; + NetLaunchInfo nli = new NetLaunchInfo( m_activity, m_summary, m_gi, + numHere, forceChannel ); + return nli; + } + // Return true if anything sent private boolean tryRematchInvites( boolean force ) { @@ -3148,10 +3163,7 @@ public class BoardDelegate extends DelegateBase Assert.assertNotNull( m_summary ); Assert.assertNotNull( m_gi ); // only supports a single invite for now! - int numHere = 1; - int forceChannel = 1; - NetLaunchInfo nli = new NetLaunchInfo( m_activity, m_summary, m_gi, - numHere, forceChannel ); + NetLaunchInfo nli = nliForMe(); String value; value = m_summary.getStringExtra( GameSummary.EXTRA_REMATCH_PHONE ); @@ -3184,6 +3196,47 @@ public class BoardDelegate extends DelegateBase return force; } + private boolean tryOtherInvites( boolean force ) + { + boolean result = true; + Assert.assertNotNull( m_summary ); + Assert.assertNotNull( m_gi ); + String str64 = m_summary.getStringExtra( GameSummary.EXTRA_REMATCH_ADDR ); + try { + CommsAddrRec addr = (CommsAddrRec)Utils.string64ToSerializable( str64 ); + NetLaunchInfo nli = nliForMe(); + CommsConnTypeSet conTypes = addr.conTypes; + for ( CommsConnType typ : conTypes ) { + switch ( typ ) { + case COMMS_CONN_MQTT: + MQTTUtils.inviteRemote( m_activity, addr.mqtt_devID, nli ); + recordInviteSent( InviteMeans.MQTT, addr.mqtt_devID ); + break; + case COMMS_CONN_BT: + BTService.inviteRemote( m_activity, addr.bt_btAddr, nli ); + recordInviteSent( InviteMeans.BLUETOOTH, addr.bt_btAddr ); + break; + // case COMMS_CONN_RELAY: + // RelayService.inviteRemote( m_activity, m_jniGamePtr, 0, value, nli ); + // recordInviteSent( InviteMeans.RELAY ); + // break; + case COMMS_CONN_SMS: + sendNBSInviteIf( addr.sms_phone, nli, true ); + recordInviteSent( InviteMeans.SMS_DATA, addr.sms_phone ); + break; + + default: + Log.d( TAG, "not inviting using addr type %s", typ ); + } + } + } catch ( Exception ex ) { + Log.ex( TAG, ex ); + Assert.failDbg(); + result = false; + } + return result; + } + private void sendNBSInviteIf( String phone, NetLaunchInfo nli, boolean askOk ) { diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBUtils.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBUtils.java index c11bf06ca..e98ecb1ab 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBUtils.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBUtils.java @@ -38,6 +38,7 @@ import android.text.TextUtils; import org.eehouse.android.xw4.DBHelper.TABLE_NAMES; import org.eehouse.android.xw4.DictUtils.DictLoc; import org.eehouse.android.xw4.DlgDelegate.DlgClickNotify.InviteMeans; +import org.eehouse.android.xw4.jni.CommsAddrRec; import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType; import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnTypeSet; import org.eehouse.android.xw4.jni.CurGameInfo; @@ -369,6 +370,22 @@ public class DBUtils { } } // saveSummary + public static void addRematchInfo( Context context, long rowid, CommsAddrRec addr ) + { + try ( GameLock lock = GameLock.tryLock(rowid) ) { + if ( null != lock ) { + String as64 = Utils.serializableToString64( addr ); + GameSummary summary = getSummary( context, lock ) + .putStringExtra( GameSummary.EXTRA_REMATCH_ADDR, as64 ) + ; + saveSummary( context, lock, summary ); + } else { + Assert.failDbg(); + Log.e( TAG, "addRematchInfo(%d): unable to lock game" ); + } + } + } + public static void addRematchInfo( Context context, long rowid, String btAddr, String phone, String relayID, String p2pAddr, String mqttDevID ) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java index efefbc9ec..8c042661a 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java @@ -62,6 +62,7 @@ import org.eehouse.android.xw4.jni.CommsAddrRec; import org.eehouse.android.xw4.jni.CurGameInfo; import org.eehouse.android.xw4.jni.GameSummary; import org.eehouse.android.xw4.jni.LastMoveInfo; +import org.eehouse.android.xw4.jni.XwJNI; import org.eehouse.android.xw4.loc.LocUtils; import java.io.File; @@ -89,6 +90,7 @@ public class GamesListDelegate extends ListDelegateBase private static final String RELAYIDS_EXTRA = "relayids"; private static final String ROWID_EXTRA = "rowid"; private static final String GAMEID_EXTRA = "gameid"; + private static final String INVITEE_REC_EXTRA = "invitee_rec"; private static final String REMATCH_ROWID_EXTRA = "rm_rowid"; private static final String REMATCH_GROUPID_EXTRA = "rm_groupid"; private static final String REMATCH_DICT_EXTRA = "rm_dict"; @@ -864,49 +866,14 @@ public class GamesListDelegate extends ListDelegateBase } break; - case GAMES_LIST_NEWGAME: { + case GAMES_LIST_NEWGAME: boolean solo = (Boolean)params[0]; - final LinearLayout view = (LinearLayout) - LocUtils.inflate( m_activity, R.layout.msg_label_and_edit ); - final EditWClear edit = (EditWClear)view.findViewById( R.id.edit ); - edit.setText( GameUtils.makeDefaultName( m_activity ) ); - - boolean canDoDefaults = solo || - 0 < XWPrefs.getAddrTypes( m_activity ).size(); - int iconResID = solo ? R.drawable.ic_sologame : R.drawable.ic_multigame; - int titleID = solo ? R.string.new_game : R.string.new_game_networked; - - String msg = getString( canDoDefaults ? R.string.new_game_message - : R.string.new_game_message_nodflt ); - if ( !solo ) { - msg += "\n\n" + getString( R.string.new_game_message_net ); + boolean forceConfig = 2 <= params.length && (Boolean)params[1]; + if ( !solo && !forceConfig && XwJNI.hasKnownPlayers() ) { + dialog = mkNewWithKnowns(); + } else { + dialog = mkNewGameDialog( solo ); } - TextView tmpEdit = (TextView)view.findViewById( R.id.msg ); - tmpEdit.setText( msg ); - - lstnr = new OnClickListener() { - public void onClick( DialogInterface dlg, int item ) { - String name = edit.getText().toString(); - curThis().makeThenLaunchOrConfigure( name, true, false ); - } - }; - - ab = makeAlertBuilder() - .setView( view ) - .setTitle( titleID ) - .setIcon( iconResID ) - .setPositiveButton( R.string.newgame_configure_first, lstnr ); - if ( canDoDefaults ) { - lstnr2 = new OnClickListener() { - public void onClick( DialogInterface dlg, int item ) { - String name = edit.getText().toString(); - curThis().makeThenLaunchOrConfigure( name, false, false ); - } - }; - ab.setNegativeButton( R.string.use_defaults, lstnr2 ); - } - dialog = ab.create(); - } break; case GAMES_LIST_NAME_REMATCH: { @@ -916,7 +883,7 @@ public class GamesListDelegate extends ListDelegateBase if ( null != m_rematchExtras ) { EditWClear edit = (EditWClear)view.findViewById( R.id.edit ); edit.setText( m_rematchExtras.getString( REMATCH_NEWNAME_EXTRA )); - boolean solo = m_rematchExtras.getBoolean( REMATCH_IS_SOLO, true ); + solo = m_rematchExtras.getBoolean( REMATCH_IS_SOLO, true ); if ( !solo ) { iconResID = R.drawable.ic_multigame; } @@ -947,6 +914,81 @@ public class GamesListDelegate extends ListDelegateBase return dialog; } // makeDialog + private Dialog mkNewGameDialog( boolean solo ) + { + final LinearLayout view = (LinearLayout) + LocUtils.inflate( m_activity, R.layout.msg_label_and_edit ); + final EditWClear edit = (EditWClear)view.findViewById( R.id.edit ); + edit.setText( GameUtils.makeDefaultName( m_activity ) ); + + boolean canDoDefaults = solo || + 0 < XWPrefs.getAddrTypes( m_activity ).size(); + int iconResID = solo ? R.drawable.ic_sologame : R.drawable.ic_multigame; + int titleID = solo ? R.string.new_game : R.string.new_game_networked; + + String msg = getString( canDoDefaults ? R.string.new_game_message + : R.string.new_game_message_nodflt ); + if ( !solo ) { + msg += "\n\n" + getString( R.string.new_game_message_net ); + } + TextView tmpEdit = (TextView)view.findViewById( R.id.msg ); + tmpEdit.setText( msg ); + + OnClickListener lstnr = new OnClickListener() { + public void onClick( DialogInterface dlg, int item ) { + String name = edit.getText().toString(); + curThis().makeThenLaunchOrConfigure( name, true, false ); + } + }; + + AlertDialog.Builder ab = makeAlertBuilder() + .setView( view ) + .setTitle( titleID ) + .setIcon( iconResID ) + .setPositiveButton( R.string.newgame_configure_first, lstnr ); + if ( canDoDefaults ) { + OnClickListener lstnr2 = new OnClickListener() { + public void onClick( DialogInterface dlg, int item ) { + String name = edit.getText().toString(); + curThis().makeThenLaunchOrConfigure( name, false, false ); + } + }; + ab.setNegativeButton( R.string.use_defaults, lstnr2 ); + } + return ab.create(); + } + + private Dialog mkNewWithKnowns() + { + String[] names = XwJNI.kplr_getPlayers(); + final NewWithKnowns view = (NewWithKnowns) + LocUtils.inflate( m_activity, R.layout.new_game_with_knowns ); + view.setNames( names, GameUtils.makeDefaultName( m_activity ) ); + AlertDialog.Builder ab = makeAlertBuilder() + .setView( view ) + .setTitle( R.string.new_game_networked ) + .setIcon( R.drawable.ic_multigame ) + .setPositiveButton( "Play Now", new OnClickListener() { + @Override + public void onClick( DialogInterface dlg, int item ) { + String player = view.getSelPlayer(); + CommsAddrRec rec = XwJNI.kplr_getAddr( player ); + String gameName = view.gameName(); + launchLikeRematch( rec, gameName ); + } + } ) + .setNegativeButton( "Configure First", new OnClickListener() { + @Override + public void onClick( DialogInterface dlg, int item ) { + String name = view.gameName(); + curThis().makeThenLaunchOrConfigure( name, true, false ); + } + } ) + ; + + return ab.create(); + } + private void enableMoveGroupButton( DialogInterface dlgi ) { ((AlertDialog)dlgi) @@ -2332,6 +2374,22 @@ public class GamesListDelegate extends ListDelegateBase return result; } + private boolean startWithInvitee( Intent intent ) + { + boolean result = false; + try { + CommsAddrRec rec = (CommsAddrRec)intent.getSerializableExtra( INVITEE_REC_EXTRA ); + if ( null != rec ) { + String name = intent.getStringExtra( REMATCH_NEWNAME_EXTRA ); + makeThenLaunchOrConfigure( name, false, false, rec ); + } + } catch ( Exception ex ) { + Log.ex( TAG, ex ); + } + + return result; + } + private boolean startNewNetGame( NetLaunchInfo nli ) { boolean handled = false; @@ -2749,6 +2807,7 @@ public class GamesListDelegate extends ListDelegateBase { Log.d( TAG, "tryStartsFromIntent(extras={%s})", DbgUtils.extrasToString( intent ) ); boolean handled = startFirstHasDict( intent ) + || startWithInvitee( intent ) || startNewNetGame( intent ) || startHasGameID( intent ) || startRematch( intent ) @@ -2890,6 +2949,12 @@ public class GamesListDelegate extends ListDelegateBase private void makeThenLaunchOrConfigure( String name, boolean doConfigure, boolean skipAsk ) + { + makeThenLaunchOrConfigure( name, doConfigure, skipAsk, null ); + } + + private void makeThenLaunchOrConfigure( String name, boolean doConfigure, + boolean skipAsk, CommsAddrRec rec ) { if ( skipAsk || !askingChangeName( name, doConfigure ) ) { long rowID; @@ -2906,6 +2971,10 @@ public class GamesListDelegate extends ListDelegateBase rowID = GameUtils.makeNewMultiGame( m_activity, groupID, name ); } + if ( null != rec ) { + DBUtils.addRematchInfo( m_activity, rowID, rec ); + } + if ( doConfigure ) { // configure it GameConfigDelegate.editForResult( getDelegator(), @@ -2963,6 +3032,15 @@ public class GamesListDelegate extends ListDelegateBase return intent; } + private void launchLikeRematch( CommsAddrRec rec, String name ) + { + Intent intent = makeSelfIntent( m_activity ) + .putExtra( INVITEE_REC_EXTRA, (Serializable)rec ) + .putExtra( REMATCH_NEWNAME_EXTRA, name ) + ; + startActivity( intent ); + } + public static Intent makeRematchIntent( Context context, long rowid, long groupID, CurGameInfo gi, CommsConnTypeSet addrTypes, diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NewWithKnowns.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NewWithKnowns.java new file mode 100644 index 000000000..433416cf1 --- /dev/null +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NewWithKnowns.java @@ -0,0 +1,61 @@ +/* -*- compile-command: "find-and-gradle.sh inXw4dDeb"; -*- */ +/* + * Copyright 2020 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.util.AttributeSet; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.Spinner; + +public class NewWithKnowns extends LinearLayout { + + public NewWithKnowns( Context cx, AttributeSet as ) + { + super( cx, as ); + } + + void setNames( String[] knowns, String gameName ) + { + final ArrayAdapter adapter = + new ArrayAdapter<>( getContext(), android.R.layout.simple_spinner_item ); + for ( String msg : knowns ) { + adapter.add( msg ); + } + Spinner spinner = (Spinner)findViewById( R.id.names ); + spinner.setAdapter( adapter ); + + EditText et = (EditText)findViewById( R.id.name_edit ); + et.setText( gameName ); + } + + String getSelPlayer() + { + Spinner spinner = (Spinner)findViewById( R.id.names ); + return spinner.getSelectedItem().toString(); + } + + String gameName() + { + EditText et = (EditText)findViewById( R.id.name_edit ); + return et.getText().toString(); + } +} diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/CommsAddrRec.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/CommsAddrRec.java index 50f4cc45a..51b09d2dc 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/CommsAddrRec.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/CommsAddrRec.java @@ -43,7 +43,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; -public class CommsAddrRec { +public class CommsAddrRec implements java.io.Serializable { private static final String TAG = CommsAddrRec.class.getSimpleName(); public enum CommsConnType { diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/GameSummary.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/GameSummary.java index 62f4db96c..8261f8f1f 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/GameSummary.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/GameSummary.java @@ -51,6 +51,7 @@ public class GameSummary implements Serializable { public static final String EXTRA_REMATCH_RELAY = "rm_relay"; public static final String EXTRA_REMATCH_P2P = "rm_p2p"; public static final String EXTRA_REMATCH_MQTT = "rm_mqtt"; + public static final String EXTRA_REMATCH_ADDR = "rm_addr"; public static final int MSG_FLAGS_NONE = 0; public static final int MSG_FLAGS_TURN = 1; @@ -538,6 +539,12 @@ public class GameSummary implements Serializable { return found; } + public boolean hasInviteInfo() + { + boolean found = null != getStringExtra( EXTRA_REMATCH_ADDR ); + return found; + } + private static boolean localTurnNextImpl( int flags, int turn ) { int flag = 2 << (turn * 2); diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/XwJNI.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/XwJNI.java index 83e1aa356..a8465d12f 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/XwJNI.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/XwJNI.java @@ -165,6 +165,12 @@ public class XwJNI { dvc_parseMQTTPacket( getJNI().m_ptrGlobals, buf ); } + public static boolean hasKnownPlayers() + { + String[] players = kplr_getPlayers(); + return null != players && 0 < players.length; + } + public static String[] kplr_getPlayers() { return kplr_getPlayers( getJNI().m_ptrGlobals ); diff --git a/xwords4/android/app/src/main/res/layout/new_game_with_knowns.xml b/xwords4/android/app/src/main/res/layout/new_game_with_knowns.xml new file mode 100644 index 000000000..2601158ad --- /dev/null +++ b/xwords4/android/app/src/main/res/layout/new_game_with_knowns.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + diff --git a/xwords4/common/knownplyr.c b/xwords4/common/knownplyr.c index e1aa252c9..9fb182fcd 100644 --- a/xwords4/common/knownplyr.c +++ b/xwords4/common/knownplyr.c @@ -33,6 +33,7 @@ typedef struct _KPState { KnownPlayer* players; XP_U16 nPlayers; XP_Bool dirty; + XP_Bool inUse; } KPState; /* enum { STREAM_VERSION_KP_1, /\* initial *\/ */ @@ -75,6 +76,8 @@ loadState( XW_DUtilCtxt* dutil, XWEnv xwe ) stream_destroy( stream, xwe ); } + XP_ASSERT( !state->inUse ); + state->inUse = XP_TRUE; return state; } @@ -95,6 +98,14 @@ saveState( XW_DUtilCtxt* dutil, XWEnv xwe, KPState* state ) } } +static void +releaseState( XW_DUtilCtxt* dutil, XWEnv xwe, KPState* state ) +{ + XP_ASSERT( state->inUse ); + saveState( dutil, xwe, state ); + state->inUse = XP_FALSE; +} + static const XP_UCHAR* figureNameFor( XP_U16 posn, const CurGameInfo* gi ) { @@ -158,7 +169,7 @@ kplr_addAddrs( XW_DUtilCtxt* dutil, XWEnv xwe, const CurGameInfo* gi, XP_LOGFF( "unable to find %dth name", ii ); } } - saveState( dutil, xwe, state ); + releaseState( dutil, xwe, state ); } return canUse; @@ -169,6 +180,7 @@ kplr_havePlayers( XW_DUtilCtxt* dutil, XWEnv xwe ) { KPState* state = loadState( dutil, xwe ); XP_Bool result = 0 < state->nPlayers; + releaseState( dutil, xwe, state ); LOG_RETURNF( "%s", boolToStr(result) ); return result; } @@ -185,6 +197,7 @@ kplr_getPlayers( XW_DUtilCtxt* dutil, XWEnv xwe, } } *nFound = state->nPlayers; + releaseState( dutil, xwe, state ); } XP_Bool @@ -199,6 +212,7 @@ kplr_getAddr( XW_DUtilCtxt* dutil, XWEnv xwe, const XP_UCHAR* name, *addr = kp->addr; } } + releaseState( dutil, xwe, state ); LOG_RETURNF( "%s", boolToStr(found) ); return found; } @@ -208,6 +222,7 @@ kplr_cleanup( XW_DUtilCtxt* dutil ) { KPState** state = (KPState**)&dutil->kpCtxt; if ( !!*state ) { + XP_ASSERT( !(*state)->inUse ); for ( KnownPlayer* kp = (*state)->players; !!kp; kp = kp->next ) { XP_FREEP( dutil->mpool, &kp->name ); XP_FREE( dutil->mpool, kp );