add mqtt as a new transport

This is meant to replace the relay eventually, but for now it's a new
option, like BT or SMS, to be chosen. Protocol is handled in common/
code for the first time, meaning that linux and android interact without
the need to keep two platforms in sync. Linux uses lib-mosquitto, and
Android uses eclipse's Paho client (the generic java version, not the
one that uses four-year-old Service patterns and so crashes for SDK >=
26.)
This commit is contained in:
Eric House 2020-05-20 13:58:53 -07:00
parent 996e0d62c7
commit f6d7eed84d
72 changed files with 2766 additions and 1098 deletions

View file

@ -37,6 +37,9 @@ if ( FABRIC_API_KEY && hasProperty('useCrashlytics') ) { // rm-for-fdroid
repositories {
google()
maven { url 'https://maven.fabric.io/public' } // rm-for-fdroid
maven {
url "https://repo.eclipse.org/content/repositories/paho-releases/"
}
}
android {
@ -87,6 +90,7 @@ android {
buildConfigField "boolean", "LOG_LIFECYLE", "false"
buildConfigField "String", "KEY_FCMID", "\"FBMService_fcmid\""
buildConfigField "boolean", "ATTACH_SUPPORTED", "false"
buildConfigField "boolean", "OFFER_MQTT", "false"
}
xw4NoSMS {
@ -119,6 +123,7 @@ android {
externalNativeBuild.ndkBuild.cFlags += ['-DVARIANT_xw4fdroid']
externalNativeBuild.ndkBuild.arguments += ['XW_BT_UUID=' + XW_UUID]
}
xw4d {
dimension "variant"
buildConfigField "String", "DB_NAME", "\"xwddb\""
@ -133,6 +138,7 @@ android {
buildConfigField "String", "KEY_FCMID", "\"FBMService_fcmid1\""
buildConfigField "String", "NFC_AID", "\"${NFC_AID_XW4d}\""
resValue "string", "nfc_aid", "$NFC_AID_XW4d"
buildConfigField "boolean", "OFFER_MQTT", "true"
externalNativeBuild.ndkBuild.cFlags += ['-DVARIANT_xw4d']
externalNativeBuild.ndkBuild.arguments += ['XW_BT_UUID=' + XWD_UUID]
}
@ -150,6 +156,7 @@ android {
buildConfigField "boolean", "REPORT_LOCKS", "true"
buildConfigField "String", "NFC_AID", "\"${NFC_AID_XW4d}\""
resValue "string", "nfc_aid", "$NFC_AID_XW4d"
buildConfigField "boolean", "OFFER_MQTT", "true"
externalNativeBuild.ndkBuild.cFlags += ['-DVARIANT_xw4dNoSMS']
externalNativeBuild.ndkBuild.arguments += ['XW_BT_UUID=' + XWD_UUID]
}
@ -331,6 +338,8 @@ dependencies {
implementation 'com.google.firebase:firebase-core:16.0.6' // rm-for-fdroid
implementation 'com.github.eehouse:nbsproxy:v0.2.2'
implementation "org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.+"
}
task mkImages(type: Exec) {

View file

@ -106,6 +106,9 @@
<activity android:name="RelayInviteActivity"
android:label="@string/relay_invite_title"
/>
<activity android:name="MQTTInviteActivity"
android:label="@string/mqtt_invite_title"
/>
<activity android:name="GameConfigActivity"
android:screenOrientation="sensor"

View file

@ -749,6 +749,9 @@ public class BoardDelegate extends DelegateBase
case RELAY_INVITE_RESULT:
missingMeans = InviteMeans.RELAY;
break;
case MQTT_INVITE_RESULT:
missingMeans = InviteMeans.MQTT;
break;
case P2P_INVITE_RESULT:
missingMeans = InviteMeans.WIFIDIRECT;
break;
@ -1351,6 +1354,10 @@ public class BoardDelegate extends DelegateBase
RelayInviteDelegate.launchForResult( m_activity, m_mySIS.nMissing, info,
RequestCode.RELAY_INVITE_RESULT );
break;
case MQTT:
MQTTInviteDelegate.launchForResult( m_activity, m_mySIS.nMissing, info,
RequestCode.MQTT_INVITE_RESULT );
break;
case WIFIDIRECT:
WiDirInviteDelegate.launchForResult( m_activity,
m_mySIS.nMissing, info,
@ -1631,6 +1638,9 @@ public class BoardDelegate extends DelegateBase
case COMMS_CONN_NFC:
nli.addNFCInfo();
break;
case COMMS_CONN_MQTT:
nli.addMQTTInfo();
break;
default:
Log.w( TAG, "Not doing NFC join for conn type %s",
typ.toString() );
@ -2512,6 +2522,7 @@ public class BoardDelegate extends DelegateBase
case COMMS_CONN_SMS:
case COMMS_CONN_P2P:
case COMMS_CONN_NFC:
case COMMS_CONN_MQTT:
break;
default:
Log.w( TAG, "tickle: unexpected type %s", typ.toString() );
@ -2808,6 +2819,9 @@ public class BoardDelegate extends DelegateBase
case WIFIDIRECT:
WiDirService.inviteRemote( m_activity, dev, nli );
break;
case MQTT:
MQTTUtils.inviteRemote( m_activity, dev, nli );
break;
}
if ( null != dev ) {
@ -2993,6 +3007,7 @@ public class BoardDelegate extends DelegateBase
supported = connTypes.contains( CommsConnType.COMMS_CONN_BT )
|| connTypes.contains( CommsConnType.COMMS_CONN_SMS )
|| connTypes.contains( CommsConnType.COMMS_CONN_RELAY )
|| connTypes.contains( CommsConnType.COMMS_CONN_MQTT )
|| connTypes.contains( CommsConnType.COMMS_CONN_P2P );
}
} else if ( null != context ) {
@ -3027,6 +3042,7 @@ public class BoardDelegate extends DelegateBase
String btAddr = null;
String relayID = null;
String p2pMacAddress = null;
String mqttDevID = null;
if ( DeviceRole.SERVER_STANDALONE == gi.serverRole ) {
// nothing to do??
} else if ( 2 != gi.nPlayers ) {
@ -3057,6 +3073,10 @@ public class BoardDelegate extends DelegateBase
Assert.assertNull( p2pMacAddress );
p2pMacAddress = addr.p2p_addr;
}
if ( addr.contains( CommsConnType.COMMS_CONN_MQTT ) ) {
Assert.assertNull( mqttDevID );
mqttDevID = addr.mqtt_devID;
}
}
}
@ -3065,7 +3085,8 @@ public class BoardDelegate extends DelegateBase
String newName = summary.getRematchName( activity );
Intent intent = GamesListDelegate
.makeRematchIntent( activity, rowid, groupID, gi, connTypes,
btAddr, phone, relayID, p2pMacAddress, newName );
btAddr, phone, relayID, p2pMacAddress,
mqttDevID, newName );
if ( null != intent ) {
activity.startActivity( intent );
}
@ -3150,6 +3171,11 @@ public class BoardDelegate extends DelegateBase
WiDirService.inviteRemote( m_activity, value, nli );
recordInviteSent( InviteMeans.WIFIDIRECT, value );
}
value = m_summary.getStringExtra( GameSummary.EXTRA_REMATCH_MQTT );
if ( null != value ) {
MQTTUtils.inviteRemote( m_activity, value, nli );
recordInviteSent( InviteMeans.MQTT, value );
}
showToast( R.string.rematch_sent_toast );
}

View file

@ -446,6 +446,9 @@ public class CommsTransport implements TransportProcs,
case COMMS_CONN_NFC:
nSent = NFCUtils.addMsgFor( buf, gameID );
break;
case COMMS_CONN_MQTT:
nSent = MQTTUtils.send( context, addr.mqtt_devID, gameID, buf );
break;
default:
Assert.failDbg();
break;

View file

@ -168,6 +168,7 @@ public class ConnStatusHandler {
private static final CommsConnType[] sDisplayOrder = {
CommsConnType.COMMS_CONN_RELAY,
CommsConnType.COMMS_CONN_MQTT,
CommsConnType.COMMS_CONN_BT,
CommsConnType.COMMS_CONN_IR,
CommsConnType.COMMS_CONN_IP_DIRECT,
@ -688,6 +689,9 @@ public class ConnStatusHandler {
case COMMS_CONN_NFC:
result = NFCUtils.nfcAvail( context )[1];
break;
case COMMS_CONN_MQTT:
result = BuildConfig.OFFER_MQTT;
break;
default:
Log.w( TAG, "connTypeEnabled: %s not handled", connType.toString() );
break;
@ -717,6 +721,9 @@ public class ConnStatusHandler {
XWPrefs.getDefaultRelayHost(context),
fcmMsg );
break;
case COMMS_CONN_MQTT:
result = String.format("DevID: %s", XwJNI.dvc_getMQTTDevID(null));
break;
case COMMS_CONN_P2P:
result = WiDirService.formatNetStateInfo();
break;

View file

@ -130,6 +130,9 @@ public class ConnViaViewLayout extends LinearLayout {
case COMMS_CONN_P2P:
enabled = WiDirWrapper.enabled();
break;
case COMMS_CONN_MQTT:
enabled = BuildConfig.OFFER_MQTT;
break;
default:
Assert.failDbg();
break;
@ -169,6 +172,10 @@ public class ConnViaViewLayout extends LinearLayout {
msgID = R.string.not_again_comms_p2p;
keyID = R.string.key_na_comms_p2p;
break;
case COMMS_CONN_MQTT:
msgID = R.string.not_again_comms_mqtt;
keyID = R.string.key_na_comms_mqtt;
break;
default:
Assert.failDbg();
break;

View file

@ -377,23 +377,18 @@ public class DBUtils {
} // saveSummary
public static void addRematchInfo( Context context, long rowid, String btAddr,
String phone, String relayID, String p2pAddr )
String phone, String relayID, String p2pAddr,
String mqttDevID )
{
try ( GameLock lock = GameLock.tryLock(rowid) ) {
if ( null != lock ) {
GameSummary summary = getSummary( context, lock );
if ( null != btAddr ) {
summary.putStringExtra( GameSummary.EXTRA_REMATCH_BTADDR, btAddr );
}
if ( null != phone ) {
summary.putStringExtra( GameSummary.EXTRA_REMATCH_PHONE, phone );
}
if ( null != relayID ) {
summary.putStringExtra( GameSummary.EXTRA_REMATCH_RELAY, relayID );
}
if ( null != p2pAddr ) {
summary.putStringExtra( GameSummary.EXTRA_REMATCH_P2P, p2pAddr );
}
GameSummary summary = getSummary( context, lock )
.putStringExtra( GameSummary.EXTRA_REMATCH_BTADDR, btAddr )
.putStringExtra( GameSummary.EXTRA_REMATCH_PHONE, phone )
.putStringExtra( GameSummary.EXTRA_REMATCH_RELAY, relayID )
.putStringExtra( GameSummary.EXTRA_REMATCH_P2P, p2pAddr )
.putStringExtra( GameSummary.EXTRA_REMATCH_MQTT, mqttDevID )
;
saveSummary( context, lock, summary );
} else {
Assert.failDbg();

View file

@ -0,0 +1,298 @@
/* -*- compile-command: "find-and-gradle.sh inXw4dDeb"; -*- */
/*
* Copyright 2012 - 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.app.Activity;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.Button;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Collections;
import java.util.Set;
import java.util.Iterator;
import org.eehouse.android.xw4.DlgDelegate.Action;
abstract class DevIDInviteDelegate extends InviteDelegate {
private static final String TAG = DevIDInviteDelegate.class.getSimpleName();
private static int[] BUTTONIDS = {
R.id.button_relay_add,
R.id.manual_add_button,
R.id.button_clear,
R.id.button_edit,
};
protected ArrayList<DevIDRec> m_devIDRecs;
private Activity m_activity;
private boolean m_immobileConfirmed; // WTF is this?
abstract String getRecsKey();
abstract String getMeDevID();
public DevIDInviteDelegate( Delegator delegator, Bundle savedInstanceState )
{
super( delegator, savedInstanceState );
m_activity = delegator.getActivity();
}
static class DevIDRec implements InviterItem, Serializable {
public String m_devID;
public String m_opponent;
public int m_nPlayers;
public DevIDRec( String opponent, String devID )
{
m_devID = devID;
m_nPlayers = 1;
m_opponent = opponent;
}
public String getDev() { return m_devID; }
public boolean equals( InviterItem item )
{
return item != null
&& ((DevIDRec)item).m_devID == m_devID;
}
}
void saveAndRebuild()
{
DBUtils.setSerializableFor( m_activity, getRecsKey(), m_devIDRecs );
rebuildList( false );
}
void rebuildList( boolean checkIfAll )
{
Collections.sort( m_devIDRecs, new Comparator<DevIDRec>() {
public int compare( DevIDRec rec1, DevIDRec rec2 ) {
return rec1.m_opponent.compareTo(rec2.m_opponent);
}
});
addSelf();
updateList( m_devIDRecs );
tryEnable();
}
@Override
protected void init( Bundle savedInstanceState )
{
super.init( savedInstanceState );
String msg = getString( R.string.button_invite );
msg = getQuantityString( R.plurals.invite_relay_desc_fmt, m_nMissing,
m_nMissing, msg );
super.init( msg, R.string.empty_relay_inviter );
addButtonBar( R.layout.relay_buttons, BUTTONIDS );
getSavedState();
rebuildList( true );
}
@Override
protected void onBarButtonClicked( int id )
{
switch( id ) {
case R.id.button_relay_add:
Utils.notImpl( getActivity() );
break;
case R.id.manual_add_button:
showDialogFragment( DlgID.GET_NUMBER );
break;
case R.id.button_clear:
int count = getChecked().size();
String msg = getQuantityString( R.plurals.confirm_clear_relay_fmt,
count, count );
makeConfirmThenBuilder( msg, Action.CLEAR_ACTION ).show();
break;
case R.id.button_edit:
Object obj = getChecked().iterator().next();
Log.d( TAG, "passing %s", obj );
showDialogFragment( DlgID.GET_NUMBER, obj );
break;
}
}
@Override
protected Dialog makeDialog( DBAlert alert, Object[] params )
{
Dialog dialog = null;
DialogInterface.OnClickListener lstnr;
switch( alert.getDlgID() ) {
case GET_NUMBER: {
final DevIDRec curRec =
1 <= params.length && params[0] instanceof String
? getHasID((String)params[0]) : null;
Log.d( TAG, "curRec: %s", curRec );
final View getNumView = inflate( R.layout.get_relay );
final EditText numField = (EditText)
getNumView.findViewById( R.id.num_field );
final EditText nameField = (EditText)
getNumView.findViewById( R.id.name_field );
if ( null != curRec ) {
numField.setText( curRec.m_devID );
nameField.setText( curRec.m_opponent );
}
lstnr = new DialogInterface.OnClickListener() {
public void onClick( DialogInterface dlg, int item ) {
String number = numField.getText().toString();
if ( null != number && 0 < number.length() ) {
String name = nameField.getText().toString();
if ( curRec != null ) {
curRec.m_opponent = name;
curRec.m_devID = number;
} else {
DevIDRec rec = new DevIDRec( name, number );
m_devIDRecs.add( rec );
clearChecked();
onItemChecked( rec, true );
}
saveAndRebuild();
}
}
};
dialog = makeAlertBuilder()
.setTitle( R.string.get_sms_title )
.setView( getNumView )
.setPositiveButton( android.R.string.ok, lstnr )
.setNegativeButton( android.R.string.cancel, null )
.create();
}
break;
default:
dialog = super.makeDialog( alert, params );
break;
}
return dialog;
}
@Override
protected void onChildAdded( View child, InviterItem data )
{
DevIDRec rec = (DevIDRec)data;
((TwoStrsItem)child).setStrings( rec.m_opponent, rec.m_devID );
}
@Override
protected void tryEnable()
{
super.tryEnable();
Button button = (Button)findViewById( R.id.button_clear );
if ( null != button ) { // may not be there yet
button.setEnabled( 0 < getChecked().size() );
}
button = (Button)findViewById( R.id.button_edit );
if ( null != button ) {
button.setEnabled( 1 == getChecked().size() );
}
}
// DlgDelegate.DlgClickNotify interface
@Override
public boolean onPosButton( Action action, Object[] params )
{
boolean handled = true;
switch( action ) {
case CLEAR_ACTION:
clearSelectedImpl();
break;
case USE_IMMOBILE_ACTION:
m_immobileConfirmed = true;
break;
default:
handled = super.onPosButton( action, params );
break;
}
return handled;
}
@Override
public boolean onDismissed( Action action, Object[] params )
{
boolean handled = true;
if ( Action.USE_IMMOBILE_ACTION == action && m_immobileConfirmed ) {
makeConfirmThenBuilder( R.string.warn_unlimited,
Action.POST_WARNING_ACTION )
.setPosButton( R.string.button_yes )
.show();
} else {
handled = false;
}
return handled;
}
void addSelf()
{
boolean hasSelf = false;
String me = getMeDevID();
for ( DevIDRec rec : m_devIDRecs ) {
if ( rec.m_devID.equals( me ) ) {
hasSelf = true;
break;
}
}
if ( !hasSelf ) {
DevIDRec rec = new DevIDRec( "self", me );
m_devIDRecs.add( rec );
}
}
void getSavedState()
{
m_devIDRecs = (ArrayList<DevIDRec>)DBUtils.getSerializableFor( m_activity, getRecsKey() );
if ( null == m_devIDRecs ) {
m_devIDRecs = new ArrayList<>();
}
}
void clearSelectedImpl()
{
Set<String> checked = getChecked();
for ( Iterator<DevIDRec> iter = m_devIDRecs.iterator(); iter.hasNext(); ) {
if ( checked.contains( iter.next().getDev() ) ) {
iter.remove();
}
}
clearChecked();
saveAndRebuild();
}
DevIDRec getHasID( String id )
{
DevIDRec result = null;
for ( DevIDRec rec : m_devIDRecs ) {
if ( rec.m_devID.equals(id) ) {
result = rec;
break;
}
}
return result;
}
}

View file

@ -300,6 +300,7 @@ public class DlgDelegate {
SMS_DATA, // classic NBS-based data sms
EMAIL, NFC, BLUETOOTH, CLIPBOARD, RELAY, WIFIDIRECT,
SMS_USER, // just launch the SMS app, as with email
MQTT,
};
boolean onPosButton( Action action, Object... params );
boolean onNegButton( Action action, Object... params );

View file

@ -735,9 +735,10 @@ public class GameUtils {
if ( null != message ) {
Intent intent = new Intent();
intent.setAction( Intent.ACTION_SEND );
String subject =
String subject = null != nli.room ?
LocUtils.getString( activity, R.string.invite_subject_fmt,
nli.room );
nli.room )
: LocUtils.getString( activity, R.string.invite_subject );
intent.putExtra( Intent.EXTRA_SUBJECT, subject );
intent.putExtra( Intent.EXTRA_TEXT, Html.fromHtml(message) );
@ -1323,6 +1324,9 @@ public class GameUtils {
case COMMS_CONN_P2P:
WiDirService.gameDied( addr.p2p_addr, gameID );
break;
case COMMS_CONN_MQTT:
MQTTUtils.gameDied( addr.mqtt_devID, gameID );
break;
}
}
}

View file

@ -100,6 +100,9 @@ public class GamesListDelegate extends ListDelegateBase
private static final String REMATCH_RELAYID_EXTRA = "rm_relayid";
private static final String REMATCH_P2PADDR_EXTRA = "rm_p2pma";
private static final String INVITE_ACTION = "org.eehouse.action_invite";
private static final String INVITE_DATA = "data_invite";
private static final String ALERT_MSG = "alert_msg";
private static final String WITH_EMAIL = "with_email";
@ -2417,12 +2420,13 @@ public class GamesListDelegate extends ListDelegateBase
String p2pMacAddress = extras.getString( REMATCH_P2PADDR_EXTRA );
String dict = extras.getString( REMATCH_DICT_EXTRA );
int lang = extras.getInt( REMATCH_LANG_EXTRA, -1 );
String mqttDevID = extras.getString( GameSummary.EXTRA_REMATCH_MQTT );
String json = extras.getString( REMATCH_PREFS_EXTRA );
newid = GameUtils.makeNewMultiGame( m_activity, groupID, dict,
lang, json, addrs, gameName );
DBUtils.addRematchInfo( m_activity, newid, btAddr, phone,
relayID, p2pMacAddress );
relayID, p2pMacAddress, mqttDevID );
}
launchGame( newid );
}
@ -2446,10 +2450,10 @@ public class GamesListDelegate extends ListDelegateBase
return handled;
}
private boolean tryNFCIntent( Intent intent )
private boolean tryInviteIntent( Intent intent )
{
boolean result = false;
byte[] data = NFCUtils.getFromIntent( intent );
byte[] data = getFromIntent( intent );
if ( null != data ) {
NetLaunchInfo nli = NetLaunchInfo.makeFrom( m_activity, data );
if ( null != nli && nli.isValid() ) {
@ -2681,7 +2685,7 @@ public class GamesListDelegate extends ListDelegateBase
|| startHasGameID( intent )
|| startRematch( intent )
|| tryAlert( intent )
|| tryNFCIntent( intent )
|| tryInviteIntent( intent )
;
Log.d( TAG, "tryStartsFromIntent() => handled: %b", handled );
}
@ -2895,7 +2899,7 @@ public class GamesListDelegate extends ListDelegateBase
CommsConnTypeSet addrTypes,
String btAddr, String phone,
String relayID, String p2pMacAddress,
String newName )
String mqttDevID, String newName )
{
Intent intent = null;
boolean isSolo = gi.serverRole == CurGameInfo.DeviceRole.SERVER_STANDALONE;
@ -2926,6 +2930,9 @@ public class GamesListDelegate extends ListDelegateBase
Assert.assertTrue( addrTypes.contains( CommsConnType.COMMS_CONN_P2P ) );
intent.putExtra( REMATCH_P2PADDR_EXTRA, p2pMacAddress );
}
if ( null != mqttDevID ) {
intent.putExtra( GameSummary.EXTRA_REMATCH_MQTT, mqttDevID );
}
}
return intent;
}
@ -2944,15 +2951,40 @@ public class GamesListDelegate extends ListDelegateBase
;
}
public static void postNFCInvite( Context context, byte[] data )
public static void postReceivedInvite( Context context, byte[] data )
{
Intent intent = makeSelfIntent( context )
.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK )
;
NFCUtils.populateIntent( context, intent, data );
populateInviteIntent( context, intent, data );
context.startActivity( intent );
}
private static void populateInviteIntent( Context context, Intent intent,
byte[] data )
{
NetLaunchInfo nli = NetLaunchInfo.makeFrom( context, data );
if ( null != nli ) {
intent.setAction( INVITE_ACTION )
.putExtra( INVITE_DATA, data );
} else {
Assert.failDbg();
}
}
private byte[] getFromIntent( Intent intent )
{
byte[] result = null;
String action = intent.getAction();
if ( INVITE_ACTION.equals( action ) ) {
result = intent.getByteArrayExtra( INVITE_DATA );
}
// Log.d( TAG, "getFromIntent() => %s", result );
return result;
}
public static void openGame( Context context, Uri data )
{
Intent intent = makeSelfIntent( context )

View file

@ -68,6 +68,9 @@ public class InviteChoicesAlert extends DlgDelegateAlert {
if ( BuildConfig.DEBUG || !BuildConfig.IS_TAGGED_BUILD ) {
add( items, means, R.string.invite_choice_relay, InviteMeans.RELAY );
}
if ( BuildConfig.OFFER_MQTT ) {
add( items, means, R.string.invite_choice_mqtt, InviteMeans.MQTT );
}
if ( WiDirWrapper.enabled() ) {
add( items, means, R.string.invite_choice_p2p, InviteMeans.WIFIDIRECT );
}

View file

@ -0,0 +1,34 @@
/* -*- compile-command: "find-and-gradle.sh inXw4dDeb"; -*- */
/*
* Copyright 2015 - 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.os.Bundle;
public class MQTTInviteActivity extends InviteActivity {
private MQTTInviteDelegate m_dlgt;
@Override
protected void onCreate( Bundle savedInstanceState )
{
m_dlgt = new MQTTInviteDelegate( this, savedInstanceState );
super.onCreate( savedInstanceState, m_dlgt );
}
}

View file

@ -0,0 +1,69 @@
/* -*- compile-command: "find-and-gradle.sh inXw4dDeb"; -*- */
/*
* Copyright 2012 - 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.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import org.eehouse.android.xw4.DBUtils.SentInvitesInfo;
import org.eehouse.android.xw4.jni.XwJNI;
public class MQTTInviteDelegate extends DevIDInviteDelegate {
private static final String TAG = MQTTInviteDelegate.class.getSimpleName();
private static final String RECS_KEY = TAG + "/recs";
private static final boolean MQTTINVITE_SUPPORTED
= BuildConfig.DEBUG || !BuildConfig.IS_TAGGED_BUILD;
private String m_devIDStr;
public static void launchForResult( Activity activity, int nMissing,
SentInvitesInfo info,
RequestCode requestCode )
{
if ( MQTTINVITE_SUPPORTED ) {
Intent intent =
InviteDelegate.makeIntent( activity, MQTTInviteActivity.class,
nMissing, info );
activity.startActivityForResult( intent, requestCode.ordinal() );
}
}
public MQTTInviteDelegate( Delegator delegator, Bundle savedInstanceState )
{
super( delegator, savedInstanceState );
}
@Override
String getRecsKey()
{
return RECS_KEY;
}
@Override
String getMeDevID()
{
if ( null == m_devIDStr ) {
m_devIDStr = XwJNI.dvc_getMQTTDevID(null);
}
return m_devIDStr;
}
}

View file

@ -0,0 +1,432 @@
/* -*- compile-command: "find-and-gradle.sh inXw4dDeb"; -*- */
/*
* Copyright 2009 - 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 org.eclipse.paho.client.mqttv3.IMqttActionListener;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.IMqttToken;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicReference;
import org.eehouse.android.xw4.jni.CommsAddrRec;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
import org.eehouse.android.xw4.jni.XwJNI;
import org.eehouse.android.xw4.loc.LocUtils;
public class MQTTUtils extends Thread implements IMqttActionListener, MqttCallbackExtended {
private static final String TAG = MQTTUtils.class.getSimpleName();
private static AtomicReference<MQTTUtils> sInstance = new AtomicReference<>();
private MqttAsyncClient mClient;
private long mPauseTime = 0L;
private String mDevID;
private String mTopic;
private Context mContext;
private MsgThread mMsgThread;
private LinkedBlockingQueue<MessagePair> mOutboundQueue = new LinkedBlockingQueue<>();
private boolean mShouldExit = false;
public static void init( Context context )
{
Log.d( TAG, "init(OFFER_MQTT:%b)", BuildConfig.OFFER_MQTT );
getOrStart( context );
}
public static void onResume( Context context )
{
Log.d( TAG, "onResume()" );
getOrStart( context );
}
static void onConfigChanged( Context context )
{
MQTTUtils instance = sInstance.get();
if ( null != instance ) {
clearInstance( instance );
}
}
private static MQTTUtils getOrStart( Context context )
{
MQTTUtils result = null;
if ( BuildConfig.OFFER_MQTT ) {
result = sInstance.get();
if ( null == result ) {
try {
result = new MQTTUtils(context);
setInstance( result );
result.start();
} catch ( MqttException me ) {
result = null;
}
}
}
return result;
}
private static class MessagePair {
byte[] mPacket;
String mTopic;
MessagePair( String topic, byte[] packet ) {
mPacket = packet;
mTopic = topic;
}
}
@Override
public void run()
{
setup();
while ( !mShouldExit ) {
try {
// this thread can be fed before the connection is
// established. Wait for that before removing packets from the
// queue.
if ( !mClient.isConnected() ) {
Log.d( TAG, "not connected; sleeping..." );
Thread.sleep(500);
continue;
}
MessagePair pair = mOutboundQueue.take();
MqttMessage message = new MqttMessage( pair.mPacket );
mClient.publish( pair.mTopic, message );
} catch ( MqttException me ) {
me.printStackTrace();
break;
} catch ( InterruptedException ie ) {
ie.printStackTrace();
break;
}
}
clearInstance();
}
private void enqueue( String topic, byte[] packet )
{
mOutboundQueue.add( new MessagePair( topic, packet ) );
}
private static void setInstance( MQTTUtils instance )
{
MQTTUtils oldInstance = sInstance.getAndSet(instance);
if ( null != oldInstance ) {
oldInstance.disconnect();
}
}
private static void clearInstance( MQTTUtils curInstance )
{
MQTTUtils oldInstance = sInstance.getAndSet(null);
if ( curInstance == oldInstance ) {
oldInstance.disconnect();
}
// if ( sResumed ) {
// Log.d( TAG, "clearInstance(); looks like I could start another!!" );
// }
}
public static void onPause()
{
// Log.d( TAG, "onPause()" );
// MQTTUtils instance = sInstance.get();
// if ( null != instance ) {
// instance.setPaused(true);
// }
// DbgUtils.assertOnUIThread();
// // sResumed = false;
}
private MQTTUtils( Context context ) throws MqttException
{
Log.d( TAG, "%H.<init>()", this );
mContext = context;
String[] topic = {null};
mDevID = XwJNI.dvc_getMQTTDevID( topic );
mTopic = topic[0];
mMsgThread = new MsgThread();
String host = XWPrefs.getPrefsString( context, R.string.key_mqtt_host );
Log.d( TAG, "host: %s", host );
int port = XWPrefs.getPrefsInt( context, R.string.key_mqtt_port, 1883 );
String url = String.format( java.util.Locale.US, "tcp://%s:%d", host, port );
Log.d( TAG, "using url: %s", url );
mClient = new MqttAsyncClient( url, mDevID, new MemoryPersistence() );
mClient.setCallback( this );
}
private void setup()
{
Log.d( TAG, "setup()" );
MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();
mqttConnectOptions.setAutomaticReconnect(true);
mqttConnectOptions.setCleanSession(false);
final int qos = XWPrefs.getPrefsInt( mContext, R.string.key_mqtt_qos, 2 );
try {
mClient.connect( mqttConnectOptions, null, new IMqttActionListener() {
@Override
public void onSuccess( IMqttToken asyncActionToken ) {
Log.d( TAG, "onSuccess()" );
try {
mClient.subscribe( mTopic, qos, null, MQTTUtils.this );
Log.d( TAG, "subscribed to %s", mTopic );
mMsgThread.start();
} catch ( MqttException ex ) {
ex.printStackTrace();
} catch ( Exception ex ) {
ex.printStackTrace();
clearInstance();
}
}
@Override
public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
Log.d( TAG, "onFailure(%s, %s)", asyncActionToken, exception );
ConnStatusHandler.updateStatus( mContext, null,
CommsConnType.COMMS_CONN_MQTT,
false );
clearInstance();
}
} );
} catch ( MqttException ex ) {
ex.printStackTrace();
} catch ( java.lang.IllegalStateException ise ) {
ise.printStackTrace();
} catch ( Exception ise ) {
ise.printStackTrace();
clearInstance();
}
}
// private void setPaused( boolean paused )
// {
// if ( paused ) {
// if ( 0 == mPauseTime ) {
// mPauseTime = System.currentTimeMillis();
// Log.d( TAG, "setPaused() called for first time" );
// }
// } else {
// long diff = System.currentTimeMillis() - mPauseTime;
// Log.d( TAG, "unpausing after %d seconds", diff/1000);
// mPauseTime = 0;
// }
// }
private void disconnect()
{
if ( 0 == mPauseTime ) {
Log.d( TAG, "disconnect()" );
} else {
long diff = System.currentTimeMillis() - mPauseTime;
Log.d( TAG, "disconnect() called %d seconds after app paused", diff/1000 );
}
try {
mShouldExit = true;
mClient.unsubscribe( mDevID );
mClient.disconnect();
Log.d( TAG, "disconnect() succeeded" );
mMsgThread.interrupt();
} catch (MqttException ex){
ex.printStackTrace();
} catch (Exception ex){
ex.printStackTrace();
clearInstance();
}
}
private void clearInstance() { clearInstance( this ); }
public static void inviteRemote( Context context, String invitee, NetLaunchInfo nli )
{
String[] topic = {invitee};
byte[] packet = XwJNI.dvc_makeMQTTInvite( nli, topic );
addToSendQueue( context, topic[0], packet );
}
private static void notifyNotHere( Context context, String addressee, int gameID )
{
String[] topic = {addressee};
byte[] packet = XwJNI.dvc_makeMQTTNoSuchGame( gameID, topic );
addToSendQueue( context, topic[0], packet );
}
public static int send( Context context, String addressee, int gameID, byte[] buf )
{
Log.d( TAG, "send(to:%s, len: %d)", addressee, buf.length );
String[] topic = {addressee};
byte[] packet = XwJNI.dvc_makeMQTTMessage( gameID, buf, topic );
addToSendQueue( context, topic[0], packet );
return buf.length;
}
private static void addToSendQueue( Context context, String topic, byte[] packet )
{
MQTTUtils instance = getOrStart( context );
if ( null != instance ) {
instance.enqueue( topic, packet );
}
}
public static void gameDied( String devID, int gameID )
{
Log.e( TAG, "gameDied() not handled" );
}
@Override
public void connectComplete(boolean reconnect, String serverURI)
{
Log.d( TAG, "%H.connectComplete(reconnect=%b, serverURI=%s)", this,
reconnect, serverURI );
}
@Override
public void connectionLost( Throwable cause )
{
Log.d( TAG, "%H.connectionLost(%s)", this, cause );
clearInstance();
}
@Override
public void messageArrived( String topic, MqttMessage message) throws Exception
{
Log.d( TAG, "messageArrived(topic=%s, message=%s)", topic, message );
Assert.assertTrueNR( topic.equals(mTopic) );
mMsgThread.add( message.getPayload() );
ConnStatusHandler
.updateStatusIn( mContext, CommsConnType.COMMS_CONN_MQTT, true );
}
@Override
public void deliveryComplete(IMqttDeliveryToken token)
{
Log.d( TAG, "deliveryComplete(token=%s)", token );
ConnStatusHandler
.updateStatusOut( mContext, CommsConnType.COMMS_CONN_MQTT, true );
}
@Override
public void onSuccess(IMqttToken asyncActionToken)
{
Log.d( TAG, "onSuccess(%s)", asyncActionToken );
}
@Override
public void onFailure(IMqttToken asyncActionToken, Throwable exception)
{
Log.d( TAG, "onFailure(%s, %s)", asyncActionToken, exception );
}
private class MsgThread extends Thread {
private LinkedBlockingQueue<byte[]> mQueue = new LinkedBlockingQueue<>();
void add( byte[] msg ) {
mQueue.add( msg );
}
@Override
public void run()
{
for ( ; ; ) {
try {
byte[] packet = mQueue.take();
XwJNI.dvc_parseMQTTPacket( packet );
} catch ( InterruptedException ie ) {
// Assert.failDbg();
break;
}
}
Log.d( TAG, "%H.run() exiting", this );
}
}
public static void handleMessage( Context context, CommsAddrRec from,
int gameID, byte[] data )
{
Log.d( TAG, "handleMessage(gameID=%d): got message", gameID );
MQTTServiceHelper helper = new MQTTServiceHelper( context, from );
long[] rowids = DBUtils.getRowIDsFor( context, gameID );
Log.d( TAG, "got %d rows for gameID %d", rowids == null ? 0 : rowids.length, gameID );
if ( 0 == rowids.length ) {
notifyNotHere( context, from.mqtt_devID, gameID );
} else {
for ( long rowid : rowids ) {
MQTTMsgSink sink = new MQTTMsgSink( context, rowid );
helper.receiveMessage( rowid, sink, data );
}
}
}
public static void handleGameGone( Context context, CommsAddrRec from, int gameID )
{
new MQTTServiceHelper( context, from )
.postEvent( MultiService.MultiEvent.MESSAGE_NOGAME, gameID );
}
private static class MQTTServiceHelper extends XWServiceHelper {
private CommsAddrRec mReturnAddr;
private Context mContext;
MQTTServiceHelper( Context context, CommsAddrRec from )
{
super( context );
mContext = context;
mReturnAddr = from;
}
@Override
protected MultiMsgSink getSink( long rowid )
{
Context context = getContext();
return new MQTTMsgSink( context, rowid );
}
@Override
void postNotification( String device, int gameID, long rowid )
{
Assert.failDbg();
// Context context = getContext();
// String body = LocUtils.getString( mContext, R.string.new_relay_body );
// GameUtils.postInvitedNotification( mContext, gameID, body, rowid );
}
private void receiveMessage( long rowid, MQTTMsgSink sink, byte[] msg )
{
Log.d( TAG, "receiveMessage(rowid=%d, len=%d)", rowid, msg.length );
receiveMessage( rowid, sink, msg, mReturnAddr );
}
}
private static class MQTTMsgSink extends MultiMsgSink {
MQTTMsgSink( Context context, long rowid )
{
super( context, rowid );
}
}
}

View file

@ -84,6 +84,11 @@ public class MultiMsgSink implements TransportProcs {
return NFCUtils.addMsgFor( buf, gameID );
}
int sendViaMQTT( String addressee, byte[] buf, int gameID )
{
return MQTTUtils.send( m_context, addressee, gameID, buf );
}
public int numSent()
{
return m_sentSet.size();
@ -114,6 +119,9 @@ public class MultiMsgSink implements TransportProcs {
case COMMS_CONN_NFC:
nSent = sendViaNFC( buf, gameID );
break;
case COMMS_CONN_MQTT:
nSent = sendViaMQTT( addr.mqtt_devID, buf, gameID );
break;
default:
Assert.failDbg();
break;

View file

@ -50,6 +50,7 @@ public class MultiService {
public static final String BT_NAME = "BT_NAME";
public static final String BT_ADDRESS = "BT_ADDRESS";
public static final String P2P_MAC_ADDRESS = "P2P_MAC_ADDRESS";
public static final String MQTT_DEVID = "MQTT_DEVID";
private static final String NLI_DATA = "nli";
public static final String DUPEMODE = "du";

View file

@ -59,9 +59,6 @@ public class NFCUtils {
private static final String TAG = NFCUtils.class.getSimpleName();
private static final boolean USE_BIGINTEGER = true;
private static final String NFC_TO_SELF_ACTION = "org.eehouse.nfc_to_self";
private static final String NFC_TO_SELF_DATA = "nfc_data";
static final byte VERSION_1 = (byte)0x01;
private static final byte MESSAGE = 0x01;
@ -91,31 +88,6 @@ public class NFCUtils {
return s_nfcAvail;
}
public static byte[] getFromIntent( Intent intent )
{
byte[] result = null;
String action = intent.getAction();
if ( NFC_TO_SELF_ACTION.equals( action ) ) {
result = intent.getByteArrayExtra( NFC_TO_SELF_DATA );
}
// Log.d( TAG, "getFromIntent() => %s", result );
return result;
}
public static void populateIntent( Context context, Intent intent,
byte[] data )
{
NetLaunchInfo nli = NetLaunchInfo.makeFrom( context, data );
if ( null != nli ) {
intent.setAction( NFC_TO_SELF_ACTION )
.putExtra( NFC_TO_SELF_DATA, data );
} else {
Assert.failDbg();
}
}
public static Dialog makeEnableNFCDialog( final Activity activity )
{
DialogInterface.OnClickListener lstnr
@ -415,7 +387,7 @@ public class NFCUtils {
}
break;
case INVITE:
GamesListDelegate.postNFCInvite( context, body );
GamesListDelegate.postReceivedInvite( context, body );
break;
case REPLY:
switch( body[0] ) {

View file

@ -1,6 +1,6 @@
/* -*- compile-command: "find-and-gradle.sh inXw4Deb"; -*- */
/*
* Copyright 2009 - 2018 by Eric House (xwords@eehouse.org). All rights
* Copyright 2009 - 2020 by Eric House (xwords@eehouse.org). All rights
* reserved.
*
* This program is free software; you can redistribute it and/or
@ -45,6 +45,7 @@ import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnTypeSet;
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.XwJNI;
import org.eehouse.android.xw4.loc.LocUtils;
public class NetLaunchInfo implements Serializable {
@ -65,8 +66,11 @@ public class NetLaunchInfo implements Serializable {
private static final String FORCECHANNEL_KEY = "fc";
private static final String NAME_KEY = "nm";
private static final String P2P_MAC_KEY = "p2";
private static final String MQTT_DEVID_KEY = "r2id";
private static final String DUPMODE_KEY = "du";
private static final int EMPTY_SET = new CommsConnTypeSet().toInt();
protected String gameName;
protected String dict;
protected int lang;
@ -83,17 +87,18 @@ public class NetLaunchInfo implements Serializable {
protected boolean isGSM;
protected int osVers;
private int _conTypes; // for syncing with the c version only!!
// MQTT
protected String mqttDevID;
private int _conTypes;
private int gameID = 0;
private CommsConnTypeSet m_addrs;
private boolean m_valid;
private String inviteID;
private boolean dupeMode;
public NetLaunchInfo()
{
m_addrs = new CommsConnTypeSet();
// Give it a random number. It may be overwritten, but so what.
_conTypes = EMPTY_SET;
inviteID = GameUtils.formatGameID( Utils.nextRandomInt() );
}
@ -117,8 +122,9 @@ public class NetLaunchInfo implements Serializable {
btName = bundle.getString( MultiService.BT_NAME );
btAddress = bundle.getString( MultiService.BT_ADDRESS );
p2pMacAddress = bundle.getString( MultiService.P2P_MAC_ADDRESS );
mqttDevID = bundle.getString( MultiService.MQTT_DEVID );
m_addrs = new CommsConnTypeSet( bundle.getInt( ADDRS_KEY ) );
_conTypes = bundle.getInt( ADDRS_KEY );
Utils.testSerialization( this );
}
@ -183,14 +189,15 @@ public class NetLaunchInfo implements Serializable {
String val = data.getQueryParameter( ADDRS_KEY );
boolean hasAddrs = null != val;
if ( hasAddrs ) {
m_addrs = new CommsConnTypeSet( Integer.decode( val ) );
_conTypes = Integer.decode( val );
} else {
m_addrs = new CommsConnTypeSet();
_conTypes = EMPTY_SET;
}
List<CommsConnType> supported = CommsConnTypeSet.getSupported( context );
CommsConnTypeSet addrs = new CommsConnTypeSet( _conTypes );
for ( CommsConnType typ : supported ) {
if ( hasAddrs && !m_addrs.contains( typ ) ) {
if ( hasAddrs && !addrs.contains( typ ) ) {
continue;
}
boolean doAdd;
@ -222,15 +229,20 @@ public class NetLaunchInfo implements Serializable {
case COMMS_CONN_NFC:
doAdd = true;
break;
case COMMS_CONN_MQTT:
mqttDevID = data.getQueryParameter( MQTT_DEVID_KEY );
doAdd = !hasAddrs && null != mqttDevID;
break;
default:
doAdd = false;
Log.d( TAG, "unexpected type: %s", typ );
Assert.failDbg();
}
if ( doAdd ) {
m_addrs.add( typ );
addrs.add( typ );
}
}
_conTypes = addrs.toInt();
removeUnsupported( supported );
@ -307,6 +319,9 @@ public class NetLaunchInfo implements Serializable {
case COMMS_CONN_NFC:
addNFCInfo();
break;
case COMMS_CONN_MQTT:
addMQTTInfo();
break;
default:
Assert.failDbg();
break;
@ -316,12 +331,14 @@ public class NetLaunchInfo implements Serializable {
public boolean contains( CommsConnType typ )
{
return m_addrs.contains( typ );
return new CommsConnTypeSet( _conTypes ).contains( typ );
}
public void removeAddress( CommsConnType typ )
{
m_addrs.remove( typ );
CommsConnTypeSet addrs = new CommsConnTypeSet( _conTypes );
addrs.remove( typ );
_conTypes = addrs.toInt();
}
public String inviteID()
@ -365,12 +382,12 @@ public class NetLaunchInfo implements Serializable {
bundle.putString( MultiService.BT_ADDRESS, btAddress );
bundle.putString( MultiService.P2P_MAC_ADDRESS, p2pMacAddress );
bundle.putInt( MultiService.FORCECHANNEL, forceChannel );
bundle.putString( MultiService.MQTT_DEVID, mqttDevID );
if ( dupeMode ) {
bundle.putBoolean( MultiService.DUPEMODE, true );
}
int flags = m_addrs.toInt();
bundle.putInt( ADDRS_KEY, flags );
bundle.putInt( ADDRS_KEY, _conTypes );
}
@Override
@ -391,14 +408,14 @@ public class NetLaunchInfo implements Serializable {
&& TextUtils.equals( room, other.room )
&& TextUtils.equals( btName, other.btName )
&& TextUtils.equals( btAddress, other.btAddress )
&& TextUtils.equals( mqttDevID, other.mqttDevID )
&& TextUtils.equals( p2pMacAddress, other.p2pMacAddress )
&& TextUtils.equals( phone, other.phone )
&& isGSM == other. isGSM
&& osVers == other.osVers
&& _conTypes == other._conTypes
&& gameID == other.gameID
&& ((null == m_addrs ? (null == other.m_addrs)
: m_addrs.equals(other.m_addrs)))
&& _conTypes == other._conTypes
&& m_valid == other.m_valid
&& TextUtils.equals( inviteID, other.inviteID )
;
@ -411,7 +428,7 @@ public class NetLaunchInfo implements Serializable {
String result = null;
try {
JSONObject obj = new JSONObject()
.put( ADDRS_KEY, m_addrs.toInt() )
.put( ADDRS_KEY, _conTypes )
.put( MultiService.LANG, lang )
.put( MultiService.DICT, dict )
.put( MultiService.GAMENAME, gameName )
@ -426,23 +443,28 @@ public class NetLaunchInfo implements Serializable {
obj.put( MultiService.DUPEMODE, dupeMode );
}
if ( m_addrs.contains( CommsConnType.COMMS_CONN_RELAY ) ) {
CommsConnTypeSet addrs = new CommsConnTypeSet( _conTypes );
if ( addrs.contains( CommsConnType.COMMS_CONN_RELAY ) ) {
obj.put( MultiService.ROOM, room )
.put( MultiService.INVITEID, inviteID );
}
if ( m_addrs.contains( CommsConnType.COMMS_CONN_BT ) ) {
if ( addrs.contains( CommsConnType.COMMS_CONN_BT ) ) {
obj.put( MultiService.BT_NAME, btName )
.put( MultiService.BT_ADDRESS, btAddress );
}
if ( m_addrs.contains( CommsConnType.COMMS_CONN_SMS ) ) {
if ( addrs.contains( CommsConnType.COMMS_CONN_SMS ) ) {
obj.put( PHONE_KEY, phone )
.put( GSM_KEY, isGSM )
.put( OSVERS_KEY, osVers );
}
if ( m_addrs.contains( CommsConnType.COMMS_CONN_P2P ) ) {
if ( addrs.contains( CommsConnType.COMMS_CONN_P2P ) ) {
obj.put( P2P_MAC_KEY, p2pMacAddress );
}
if ( addrs.contains( CommsConnType.COMMS_CONN_MQTT ) ) {
obj.put( MQTT_DEVID_KEY, mqttDevID );
}
result = obj.toString();
} catch ( org.json.JSONException jse ) {
@ -455,7 +477,8 @@ public class NetLaunchInfo implements Serializable {
public CommsAddrRec makeAddrRec( Context context )
{
CommsAddrRec result = new CommsAddrRec();
for ( CommsConnType typ : m_addrs.getTypes() ) {
CommsConnTypeSet addrs = new CommsConnTypeSet( _conTypes );
for ( CommsConnType typ : addrs.getTypes() ) {
result.conTypes.add( typ );
switch( typ ) {
case COMMS_CONN_RELAY:
@ -474,6 +497,9 @@ public class NetLaunchInfo implements Serializable {
break;
case COMMS_CONN_NFC:
break;
case COMMS_CONN_MQTT:
result.setMQTTParams( mqttDevID );
break;
default:
Assert.failDbg();
break;
@ -490,8 +516,7 @@ public class NetLaunchInfo implements Serializable {
int flags = json.optInt(ADDRS_KEY, -1);
boolean hasAddrs = -1 != flags;
m_addrs = hasAddrs ?
new CommsConnTypeSet( flags ) : new CommsConnTypeSet();
_conTypes = hasAddrs ? flags : EMPTY_SET;
lang = json.optInt( MultiService.LANG, -1 );
forceChannel = json.optInt( MultiService.FORCECHANNEL, 0 );
@ -504,8 +529,9 @@ public class NetLaunchInfo implements Serializable {
gameID = json.optInt( MultiService.GAMEID, 0 );
// Try each type
CommsConnTypeSet addrs = new CommsConnTypeSet( _conTypes );
for ( CommsConnType typ : supported ) {
if ( hasAddrs && !m_addrs.contains( typ ) ) {
if ( hasAddrs && !addrs.contains( typ ) ) {
continue;
}
boolean doAdd;
@ -533,16 +559,21 @@ public class NetLaunchInfo implements Serializable {
case COMMS_CONN_NFC:
doAdd = NFCUtils.nfcAvail( context )[0];
break;
case COMMS_CONN_MQTT:
mqttDevID = json.optString( MQTT_DEVID_KEY );
doAdd = BuildConfig.OFFER_MQTT && null != mqttDevID;
break;
default:
doAdd = false;
Assert.failDbg();
}
if ( doAdd ) {
m_addrs.add( typ );
addrs.add( typ );
}
}
removeUnsupported( supported );
_conTypes = addrs.toInt();
calcValid();
}
@ -554,7 +585,6 @@ public class NetLaunchInfo implements Serializable {
public Uri makeLaunchUri( Context context )
{
int addrs = m_addrs.toInt();
String host = LocUtils.getString( context, R.string.invite_host );
host = NetUtils.forceHost( host );
Uri.Builder ub = new Uri.Builder()
@ -566,7 +596,7 @@ public class NetLaunchInfo implements Serializable {
appendInt( ub, HEREPLAYERS_KEY, nPlayersH );
appendInt( ub, GID_KEY, gameID() );
appendInt( ub, FORCECHANNEL_KEY, forceChannel );
appendInt( ub, ADDRS_KEY, addrs );
appendInt( ub, ADDRS_KEY, _conTypes );
ub.appendQueryParameter( NAME_KEY, gameName );
if ( dupeMode ) {
appendInt( ub, DUPMODE_KEY, 1 );
@ -576,22 +606,26 @@ public class NetLaunchInfo implements Serializable {
ub.appendQueryParameter( WORDLIST_KEY, dict );
}
if ( m_addrs.contains( CommsConnType.COMMS_CONN_RELAY ) ) {
CommsConnTypeSet addrs = new CommsConnTypeSet( _conTypes );
if ( addrs.contains( CommsConnType.COMMS_CONN_RELAY ) ) {
ub.appendQueryParameter( ROOM_KEY, room );
ub.appendQueryParameter( ID_KEY, inviteID );
}
if ( m_addrs.contains( CommsConnType.COMMS_CONN_BT ) ) {
if ( addrs.contains( CommsConnType.COMMS_CONN_BT ) ) {
ub.appendQueryParameter( BTADDR_KEY, btAddress );
ub.appendQueryParameter( BTNAME_KEY, btName );
}
if ( m_addrs.contains( CommsConnType.COMMS_CONN_SMS ) ) {
if ( addrs.contains( CommsConnType.COMMS_CONN_SMS ) ) {
ub.appendQueryParameter( PHONE_KEY, phone );
appendInt( ub, GSM_KEY, (isGSM? 1 : 0) );
appendInt( ub, OSVERS_KEY, osVers );
}
if ( m_addrs.contains( CommsConnType.COMMS_CONN_P2P ) ) {
if ( addrs.contains( CommsConnType.COMMS_CONN_P2P ) ) {
ub.appendQueryParameter( P2P_MAC_KEY, p2pMacAddress );
}
if ( addrs.contains( CommsConnType.COMMS_CONN_MQTT ) ) {
ub.appendQueryParameter( MQTT_DEVID_KEY, mqttDevID );
}
Uri result = ub.build();
if ( BuildConfig.DEBUG ) { // Test...
@ -603,11 +637,18 @@ public class NetLaunchInfo implements Serializable {
return result;
}
private void add( CommsConnType typ )
{
CommsConnTypeSet addrs = new CommsConnTypeSet( _conTypes );
addrs.add( typ );
_conTypes = addrs.toInt();
}
public void addRelayInfo( String aRoom, String inviteID )
{
room = aRoom;
inviteID = inviteID;
m_addrs.add( CommsConnType.COMMS_CONN_RELAY );
add( CommsConnType.COMMS_CONN_RELAY );
}
public void addBTInfo()
@ -616,7 +657,7 @@ public class NetLaunchInfo implements Serializable {
if ( null != got ) {
btName = got[0];
btAddress = got[1];
m_addrs.add( CommsConnType.COMMS_CONN_BT );
add( CommsConnType.COMMS_CONN_BT );
} else {
Log.w( TAG, "addBTInfo(): no BT info available" );
}
@ -631,19 +672,25 @@ public class NetLaunchInfo implements Serializable {
osVers = Integer.valueOf( android.os.Build.VERSION.SDK );
m_addrs.add( CommsConnType.COMMS_CONN_SMS );
add( CommsConnType.COMMS_CONN_SMS );
}
}
public void addP2PInfo( Context context )
{
p2pMacAddress = WiDirService.getMyMacAddress( context );
m_addrs.add( CommsConnType.COMMS_CONN_P2P );
add( CommsConnType.COMMS_CONN_P2P );
}
public void addNFCInfo()
{
m_addrs.add( CommsConnType.COMMS_CONN_NFC );
add( CommsConnType.COMMS_CONN_NFC );
}
public void addMQTTInfo()
{
add( CommsConnType.COMMS_CONN_MQTT );
mqttDevID = XwJNI.dvc_getMQTTDevID( null );
}
public boolean isValid()
@ -686,16 +733,6 @@ public class NetLaunchInfo implements Serializable {
Assert.failDbg();
}
public void freezeAddrs()
{
_conTypes = m_addrs.toInt();
}
public void unfreezeAddrs()
{
m_addrs = new CommsConnTypeSet( _conTypes, true );
}
private boolean hasCommon()
{
return null != dict
@ -706,20 +743,25 @@ public class NetLaunchInfo implements Serializable {
private void removeUnsupported( List<CommsConnType> supported )
{
for ( Iterator<CommsConnType> iter = m_addrs.iterator();
CommsConnTypeSet addrs = new CommsConnTypeSet( _conTypes );// , true );
for ( Iterator<CommsConnType> iter = addrs.iterator();
iter.hasNext(); ) {
if ( !supported.contains( iter.next() ) ) {
CommsConnType typ = iter.next();
if ( !supported.contains( typ ) ) {
Log.d( TAG, "removing %s", typ );
iter.remove();
}
}
_conTypes = addrs.toInt();
}
private void calcValid()
{
boolean valid = hasCommon() && null != m_addrs;
boolean valid = hasCommon();
// Log.d( TAG, "calcValid(%s); valid (so far): %b", this, valid );
if ( valid ) {
for ( Iterator<CommsConnType> iter = m_addrs.iterator();
for ( Iterator<CommsConnType> iter
= new CommsConnTypeSet( _conTypes ).iterator();
valid && iter.hasNext(); ) {
CommsConnType typ = iter.next();
switch ( typ ) {
@ -732,6 +774,9 @@ public class NetLaunchInfo implements Serializable {
case COMMS_CONN_SMS:
valid = null != phone && 0 < osVers;
break;
case COMMS_CONN_MQTT:
valid = null != mqttDevID;
break;
}
}
}

View file

@ -62,6 +62,9 @@ public class PrefsDelegate extends DelegateBase
R.string.key_disable_nag_solo,
R.string.key_disable_relay,
R.string.key_force_tablet,
R.string.key_mqtt_host,
R.string.key_mqtt_port,
R.string.key_mqtt_qos,
};
private static Map<String, Integer> s_keysHash = null;
@ -236,6 +239,11 @@ public class PrefsDelegate extends DelegateBase
case R.string.key_force_tablet:
makeOkOnlyBuilder( R.string.after_restart ).show();
break;
case R.string.key_mqtt_host:
case R.string.key_mqtt_port:
case R.string.key_mqtt_qos:
MQTTUtils.onConfigChanged( m_activity );
break;
default:
Assert.failDbg();
break;

View file

@ -1,6 +1,6 @@
/* -*- compile-command: "find-and-gradle.sh inXw4dDeb"; -*- */
/*
* Copyright 2012 - 2015 by Eric House (xwords@eehouse.org). All rights
* Copyright 2012 - 2020 by Eric House (xwords@eehouse.org). All rights
* reserved.
*
* This program is free software; you can redistribute it and/or
@ -21,59 +21,19 @@
package org.eehouse.android.xw4;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.method.DigitsKeyListener;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.Spinner;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.json.JSONArray;
import org.json.JSONObject;
import org.eehouse.android.xw4.DBUtils.SentInvitesInfo;
import org.eehouse.android.xw4.DlgDelegate.Action;
public class RelayInviteDelegate extends InviteDelegate {
public class RelayInviteDelegate extends DevIDInviteDelegate {
private static final String TAG = RelayInviteDelegate.class.getSimpleName();
private static final String RECS_KEY = "TAG" + "/recs";
private static final String RECS_KEY = TAG + "/recs";
private static final boolean RELAYINVITE_SUPPORTED
= BuildConfig.DEBUG || !BuildConfig.IS_TAGGED_BUILD;
private static int[] BUTTONIDS = {
R.id.button_relay_add,
R.id.manual_add_button,
R.id.button_clear,
R.id.button_edit,
};
private static final String SAVE_NAME = "SAVE_NAME";
private static final String SAVE_NUMBER = "SAVE_NUMBER";
private ArrayList<DevIDRec> m_devIDRecs;
private boolean m_immobileConfirmed;
private Activity m_activity;
private String m_devIDStr;
private String mRelayDevIDStr;
public static void launchForResult( Activity activity, int nMissing,
SentInvitesInfo info,
@ -90,633 +50,20 @@ public class RelayInviteDelegate extends InviteDelegate {
public RelayInviteDelegate( Delegator delegator, Bundle savedInstanceState )
{
super( delegator, savedInstanceState );
m_activity = delegator.getActivity();
}
@Override
protected void init( Bundle savedInstanceState )
String getRecsKey()
{
if ( RELAYINVITE_SUPPORTED ) {
super.init( savedInstanceState );
String msg = getString( R.string.button_invite );
msg = getQuantityString( R.plurals.invite_relay_desc_fmt, m_nMissing,
m_nMissing, msg );
super.init( msg, R.string.empty_relay_inviter );
addButtonBar( R.layout.relay_buttons, BUTTONIDS );
m_devIDStr = String.format( "%d", DevID.getRelayDevIDInt(m_activity) );
// getBundledData( savedInstanceState );
// m_addButton = (ImageButton)findViewById( R.id.manual_add_button );
// m_addButton.setOnClickListener( new View.OnClickListener() {
// public void onClick( View view )
// {
// showDialog( DlgID.GET_NUMBER );
// }
// } );
// if ( XWPrefs.getRelayInviteToSelfEnabled( m_activity ) ) {
// ImageButton addMe = (ImageButton)findViewById( R.id.add_self_button );
// addMe.setVisibility( View.VISIBLE );
// addMe.setOnClickListener( new View.OnClickListener() {
// public void onClick( View view ) {
// int devIDInt = DevID.getRelayDevIDInt( m_activity );
// String devID = String.format( "%d", devIDInt );
// DevIDRec rec = new DevIDRec( "self", devID );
// addChecked( rec );
// saveAndRebuild();
// }
// } );
// }
getSavedState();
rebuildList( true );
}
return RECS_KEY;
}
@Override
protected void onBarButtonClicked( int id )
String getMeDevID()
{
if ( RELAYINVITE_SUPPORTED ) {
switch( id ) {
case R.id.button_relay_add:
Utils.notImpl( m_activity );
break;
case R.id.manual_add_button:
showDialogFragment( DlgID.GET_NUMBER );
break;
case R.id.button_clear:
int count = getChecked().size();
String msg = getQuantityString( R.plurals.confirm_clear_relay_fmt,
count, count );
makeConfirmThenBuilder( msg, Action.CLEAR_ACTION ).show();
break;
case R.id.button_edit:
showDialogFragment( DlgID.GET_NUMBER, getChecked().iterator().next() );
break;
}
if ( null == mRelayDevIDStr ) {
mRelayDevIDStr = String.format( "%d", DevID.getRelayDevIDInt(getActivity()) );
}
return mRelayDevIDStr;
}
// protected void onSaveInstanceState( Bundle outState )
// {
// outState.putString( SAVE_NAME, m_pendingName );
// outState.putString( SAVE_NUMBER, m_pendingNumber );
// }
// private void getBundledData( Bundle bundle )
// {
// if ( null != bundle ) {
// m_pendingName = bundle.getString( SAVE_NAME );
// m_pendingNumber = bundle.getString( SAVE_NUMBER );
// }
// }
// protected void onActivityResult( int requestCode, int resultCode,
// Intent data )
// {
// // super.onActivityResult( requestCode, resultCode, data );
// if ( Activity.RESULT_CANCELED != resultCode && data != null ) {
// switch (requestCode) {
// case GET_CONTACT:
// addPhoneNumbers( data );
// break;
// }
// }
// }
@Override
protected Dialog makeDialog( DBAlert alert, Object[] params )
{
Dialog dialog = null;
if ( RELAYINVITE_SUPPORTED ) {
DialogInterface.OnClickListener lstnr;
switch( alert.getDlgID() ) {
case GET_NUMBER: {
final DevIDRec curRec =
1 <= params.length && params[0] instanceof DevIDRec
? (DevIDRec)params[0] : null;
final View getNumView = inflate( R.layout.get_relay );
final EditText numField = (EditText)
getNumView.findViewById( R.id.num_field );
numField.setKeyListener(DigitsKeyListener.getInstance());
final EditText nameField = (EditText)
getNumView.findViewById( R.id.name_field );
if ( null != curRec ) {
numField.setText( curRec.m_devID );
nameField.setText( curRec.m_opponent );
}
lstnr = new DialogInterface.OnClickListener() {
public void onClick( DialogInterface dlg, int item ) {
String number = numField.getText().toString();
if ( null != number && 0 < number.length() ) {
String name = nameField.getText().toString();
if ( curRec != null ) {
curRec.m_opponent = name;
curRec.m_devID = number;
} else {
DevIDRec rec = new DevIDRec( name, number );
m_devIDRecs.add( rec );
clearChecked();
onItemChecked( rec, true );
}
saveAndRebuild();
}
}
};
dialog = makeAlertBuilder()
.setTitle( R.string.get_sms_title )
.setView( getNumView )
.setPositiveButton( android.R.string.ok, lstnr )
.setNegativeButton( android.R.string.cancel, null )
.create();
}
break;
default:
dialog = super.makeDialog( alert, params );
break;
}
}
return dialog;
}
// protected Dialog onCreateDialog( int id )
// {
// Dialog dialog = super.onCreateDialog( id );
// if ( null == dialog ) {
// DialogInterface.OnClickListener lstnr;
// DlgID dlgID = DlgID.values()[id];
// switch( dlgID ) {
// case GET_NUMBER:
// final GameNamer namerView =
// (GameNamer)inflate( R.layout.rename_game );
// namerView.setLabel( R.string.get_relay_number );
// namerView.setKeyListener(DialerKeyListener.getInstance());
// lstnr = new DialogInterface.OnClickListener() {
// public void onClick( DialogInterface dlg, int item ) {
// String devID = namerView.getName();
// if ( 0 < devID.length() ) {
// DevIDRec rec = new DevIDRec( devID );
// addChecked( new DevIDRec( devID ) );
// saveAndRebuild();
// }
// }
// };
// dialog = makeAlertBuilder()
// .setNegativeButton( android.R.string.cancel, null )
// .setPositiveButton( android.R.string.ok, lstnr )
// .setView( namerView )
// .create();
// break;
// }
// setRemoveOnDismiss( dialog, dlgID );
// }
// return dialog;
// }
@Override
protected void onChildAdded( View child, InviterItem data )
{
DevIDRec rec = (DevIDRec)data;
((TwoStrsItem)child).setStrings( rec.m_opponent, rec.m_devID );
}
// We want to present user with list of previous opponents and devices. We
// can easily get list of relayIDs. The relay, if reachable, can convert
// that to a (likely shorter) list of devices. Then for each deviceID,
// open the newest game with a relayID mapping to it and get the name of
// the opponent?
// protected void scan()
// {
// long[][] rowIDss = new long[1][];
// String[] relayIDs = DBUtils.getRelayIDs( m_activity, rowIDss );
// if ( null != relayIDs && 0 < relayIDs.length ) {
// new ListOpponentsTask( m_activity, relayIDs, rowIDss[0] ).execute();
// }
// // Intent intent = new Intent( Intent.ACTION_PICK,
// // ContactsContract.Contacts.CONTENT_URI );
// // intent.setType( Phone.CONTENT_TYPE );
// // startActivityForResult( intent, GET_CONTACT );
// }
// @Override
// protected void clearSelected( Integer[] selected )
// {
// makeConfirmThenBuilder( R.string.confirm_clear_relay, Action.CLEAR_ACTION )
// .show();
// }
// protected void listSelected( String[][] devsP, int[][] countsP )
// {
// // int count = m_adapter.getCount();
// // String[] result = new String[countChecks()];
// // int[] counts = new int[result.length];
// // int index = 0;
// // Iterator<DevIDRec> iter = m_devIDRecs.iterator();
// // for ( int ii = 0; iter.hasNext(); ++ii ) {
// // DevIDRec rec = iter.next();
// // if ( rec.m_isChecked ) {
// // counts[index] = rec.m_nPlayers;
// // result[index] = ((SMSListItem)m_adapter.getItem(ii)).getNumber();
// // index++;
// // }
// // }
// // devsP[0] = result;
// // if ( null != countsP ) {
// // countsP[0] = counts;
// // }
// }
@Override
protected void tryEnable()
{
if ( RELAYINVITE_SUPPORTED ) {
super.tryEnable();
Button button = (Button)findViewById( R.id.button_clear );
if ( null != button ) { // may not be there yet
button.setEnabled( 0 < getChecked().size() );
}
button = (Button)findViewById( R.id.button_edit );
if ( null != button ) {
button.setEnabled( 1 == getChecked().size() );
}
}
}
// DlgDelegate.DlgClickNotify interface
@Override
public boolean onPosButton( Action action, Object[] params )
{
boolean handled = true;
if ( RELAYINVITE_SUPPORTED ) {
switch( action ) {
case CLEAR_ACTION:
clearSelectedImpl();
break;
case USE_IMMOBILE_ACTION:
m_immobileConfirmed = true;
break;
default:
handled = super.onPosButton( action, params );
break;
}
}
return handled;
}
@Override
public boolean onDismissed( Action action, Object[] params )
{
boolean handled = true;
if ( RELAYINVITE_SUPPORTED ) {
if ( Action.USE_IMMOBILE_ACTION == action && m_immobileConfirmed ) {
makeConfirmThenBuilder( R.string.warn_unlimited,
Action.POST_WARNING_ACTION )
.setPosButton( R.string.button_yes )
.show();
} else {
handled = false;
}
}
return handled;
}
// private int countChecks()
// {
// int count = 0;
// if ( null != m_devIDRecs ) {
// Iterator<DevIDRec> iter = m_devIDRecs.iterator();
// while ( iter.hasNext() ) {
// if ( iter.next().m_isChecked ) {
// ++count;
// }
// }
// }
// return count;
// }
// private void addPhoneNumbers( Intent intent )
// {
// Uri data = intent.getData();
// Cursor cursor = m_activity
// .managedQuery( data,
// new String[] { Phone.DISPLAY_NAME,
// Phone.NUMBER,
// Phone.TYPE },
// null, null, null );
// // Have seen a crash reporting
// // "android.database.StaleDataException: Attempted to access a
// // cursor after it has been closed." when the query takes a
// // long time to return. Be safe.
// if ( null != cursor && !cursor.isClosed() ) {
// if ( cursor.moveToFirst() ) {
// String name =
// cursor.getString( cursor.
// getColumnIndex( Phone.DISPLAY_NAME));
// String number =
// cursor.getString( cursor.
// getColumnIndex( Phone.NUMBER ) );
// int type = cursor.getInt( cursor.
// getColumnIndex( Phone.TYPE ) );
// // m_pendingName = name;
// // m_pendingNumber = number;
// if ( Phone.TYPE_MOBILE == type ) {
// showConfirmThen( R.string.warn_unlimited,
// R.string.button_yes,
// Action.POST_WARNING_ACTION );
// } else {
// m_immobileConfirmed = false;
// String msg = getString( R.string.warn_nomobile_fmt,
// number, name );
// showConfirmThen( msg, R.string.button_yes,
// Action.USE_IMMOBILE_ACTION );
// }
// }
// }
// } // addPhoneNumbers
private void addSelf()
{
boolean hasSelf = false;
for ( DevIDRec rec : m_devIDRecs ) {
if ( rec.m_devID.equals( m_devIDStr ) ) {
hasSelf = true;
break;
}
}
if ( !hasSelf ) {
DevIDRec rec = new DevIDRec( "me", m_devIDStr );
m_devIDRecs.add( rec );
}
}
private void rebuildList( boolean checkIfAll )
{
Collections.sort( m_devIDRecs, new Comparator<DevIDRec>() {
public int compare( DevIDRec rec1, DevIDRec rec2 ) {
return rec1.m_opponent.compareTo(rec2.m_opponent);
}
});
addSelf();
updateList( m_devIDRecs );
tryEnable();
}
private void getSavedState()
{
m_devIDRecs = (ArrayList<DevIDRec>)DBUtils.getSerializableFor( m_activity, RECS_KEY );
if ( null == m_devIDRecs ) {
m_devIDRecs = new ArrayList<>();
}
}
private void saveAndRebuild()
{
DBUtils.setSerializableFor( m_activity, RECS_KEY, m_devIDRecs );
rebuildList( false );
}
// private void addChecked( DevIDRec rec )
// {
// if ( m_nMissing <= countChecks() ) {
// Iterator<DevIDRec> iter = m_devIDRecs.iterator();
// while ( iter.hasNext() ) {
// iter.next().m_isChecked = false;
// }
// }
// rec.m_isChecked = true;
// m_devIDRecs.add( rec );
// }
private void clearSelectedImpl()
{
Set<String> checked = getChecked();
for ( Iterator<DevIDRec> iter = m_devIDRecs.iterator(); iter.hasNext(); ) {
if ( checked.contains( iter.next().getDev() ) ) {
iter.remove();
}
}
clearChecked();
saveAndRebuild();
}
private static class DevIDRec implements InviterItem, Serializable {
public String m_devID;
public String m_opponent;
public int m_nPlayers;
// public DevIDRec( String name, String devID )
// {
// this( name, devID, false );
// }
// public DevIDRec( String devID )
// {
// this( null, devID, false );
// }
public DevIDRec( String opponent, String devID )
{
m_devID = devID;
m_nPlayers = 1;
m_opponent = opponent;
}
public String getDev() { return m_devID; }
public boolean equals( InviterItem item )
{
return item != null
&& ((DevIDRec)item).m_devID == m_devID;
}
}
// private class RelayDevsAdapter extends XWListAdapter {
// private SMSListItem[] m_items;
// public RelayDevsAdapter()
// {
// super( m_devIDRecs.size() );
// m_items = new SMSListItem[m_devIDRecs.size()];
// }
// public Object getItem( final int position )
// {
// // For some reason I can't cache items to be returned.
// // Checking/unchecking breaks for some but not all items,
// // with some relation to whether they were scrolled into
// // view. So build them anew each time (but still cache
// // for by-index access.)
// SMSListItem item =
// (SMSListItem)inflate( R.layout.smsinviter_item );
// // item.setChecked( m_devIDRecs.get(position).m_isChecked );
// CompoundButton.OnCheckedChangeListener lstnr =
// new CompoundButton.OnCheckedChangeListener() {
// public void onCheckedChanged( CompoundButton bv,
// boolean isChecked ) {
// m_devIDRecs.get(position).m_isChecked = isChecked;
// tryEnable();
// }
// };
// item.setOnCheckedChangeListener( lstnr );
// final DevIDRec rec = m_devIDRecs.get( position );
// item.setContents( rec.m_opponent, rec.m_devID );
// m_items[position] = item;
// // Set up spinner
// Assert.assertTrue( 1 == rec.m_nPlayers );
// if ( XWPrefs.getCanInviteMulti( m_activity ) && 1 < m_nMissing ) {
// Spinner spinner = (Spinner)
// item.findViewById(R.id.nperdev_spinner);
// ArrayAdapter<String> adapter =
// new ArrayAdapter<>( m_activity, android.R.layout
// .simple_spinner_item );
// for ( int ii = 1; ii <= m_nMissing; ++ii ) {
// String str = getQuantityString( R.plurals.nplayers_fmt, ii, ii );
// adapter.add( str );
// }
// spinner.setAdapter( adapter );
// spinner.setVisibility( View.VISIBLE );
// spinner.setOnItemSelectedListener( new OnItemSelectedListener() {
// public void onItemSelected( AdapterView<?> parent,
// View view, int pos,
// long id )
// {
// rec.m_nPlayers = 1 + pos;
// tryEnable();
// }
// public void onNothingSelected( AdapterView<?> parent ) {}
// } );
// }
// return item;
// }
// public View getView( final int position, View convertView,
// ViewGroup parent ) {
// return (View)getItem( position );
// }
// }
// private class ListOpponentsTask extends AsyncTask<Void, Void, Set<String>> {
// private Context m_context;
// private String[] m_relayIDs;
// private long[] m_rowIDs;
// public ListOpponentsTask( Context context, String[] relayIDs, long[] rowIDs ) {
// m_context = context;
// m_relayIDs = relayIDs;
// m_rowIDs = rowIDs;
// }
// @Override protected Set<String> doInBackground( Void... unused )
// {
// Set<String> result = null;
// JSONObject reply = null;
// try {
// startProgress( R.string.fetching_from_relay );
// JSONArray ids = new JSONArray();
// for ( String id : m_relayIDs ) {
// ids.put( id );
// }
// JSONObject params = new JSONObject();
// params.put( "relayIDs", ids );
// params.put( "me", DevID.getRelayDevIDInt( m_activity ) );
// Log.i( TAG, "sending to server: %s", params.toString() );
// HttpURLConnection conn = NetUtils.makeHttpConn( m_context, "opponentIDsFor" );
// if ( null != conn ) {
// String str = NetUtils.runConn( conn, params );
// Log.i( TAG, "got json from server: %s", str );
// reply = new JSONObject( str );
// }
// if ( null != reply ) {
// result = new HashSet<>();
// setProgressMsg( R.string.processing_games );
// JSONArray objs = reply.getJSONArray("devIDs");
// for ( int ii = 0; ii < objs.length(); ++ii ) {
// JSONObject obj = objs.getJSONObject( ii );
// Iterator<String> keys = obj.keys();
// Assert.assertTrue( keys.hasNext() );
// String key = keys.next();
// Assert.assertFalse( keys.hasNext() );
// JSONArray devIDs2 = obj.getJSONArray( key );
// for ( int jj = 0; jj < devIDs2.length(); ++jj ) {
// result.add( devIDs2.getString(jj) );
// }
// }
// }
// } catch ( org.json.JSONException je ) {
// Log.ex( TAG, je );
// }
// stopProgress();
// return result;
// }
// @Override protected void onPostExecute( Set<String> devIDs )
// {
// if ( null == devIDs ) {
// Log.w( TAG, "onPostExecute: no results from server?" );
// } else {
// m_devIDRecs = new ArrayList<>(devIDs.size());
// Iterator<String> iter = devIDs.iterator();
// while ( iter.hasNext() ) {
// String devID = iter.next();
// DevIDRec rec = new DevIDRec( "name", devID );
// m_devIDRecs.add( rec );
// }
// // m_adapter = new RelayDevsAdapter();
// // setListAdapter( m_adapter );
// // // m_checked.clear();
// // tryEnable();
// }
// }
// private void startProgress( final int msgID )
// {
// runOnUiThread( new Runnable() {
// public void run() {
// RelayInviteDelegate.this
// .startProgress( R.string.rel_invite_title, msgID );
// }
// } );
// }
// private void setProgressMsg( final int id )
// {
// runOnUiThread( new Runnable() {
// public void run() {
// RelayInviteDelegate.this.setProgressMsg( id );
// }
// } );
// }
// private void stopProgress()
// {
// runOnUiThread( new Runnable() {
// public void run() {
// RelayInviteDelegate.this.stopProgress();
// }
// } );
// }
// }
}

View file

@ -30,6 +30,7 @@ public enum RequestCode {
SMS_DATA_INVITE_RESULT,
RELAY_INVITE_RESULT,
P2P_INVITE_RESULT,
MQTT_INVITE_RESULT,
// PermUtils
PERM_REQUEST,

View file

@ -101,6 +101,8 @@ public class XWApp extends Application
NBSProxy.register( this, mPort, BuildConfig.APPLICATION_ID, this );
DupeModeTimer.init( this );
MQTTUtils.init( this );
}
@OnLifecycleEvent(ON_ANY)
@ -109,12 +111,16 @@ public class XWApp extends Application
Log.d( TAG, "onAny(%s)", event );
switch( event ) {
case ON_RESUME:
MQTTUtils.onResume( this );
// Do here what checkForMoves does
if ( null != DBUtils.getRelayIDs( this, null ) ) {
RelayService.timerFired( this );
}
GameUtils.resendAllIf( this, null );
break;
case ON_PAUSE:
MQTTUtils.onPause();
break;
}
}

View file

@ -205,7 +205,19 @@ public class XWPrefs {
String key = context.getString( keyID );
SharedPreferences sp = PreferenceManager
.getDefaultSharedPreferences( context );
return sp.getInt( key, defaultValue );
int result;
try {
result = sp.getInt( key, defaultValue );
// If it's in a pref, it'll be a string (editable) So will get CCE
} catch ( ClassCastException cce ) {
String asStr = sp.getString( key, String.format( "%d", defaultValue ) );
try {
result = Integer.parseInt( asStr );
} catch ( Exception ex ) {
result = defaultValue;
}
}
return result;
}
public static void setPrefsInt( Context context, int keyID, int newValue )
@ -521,6 +533,9 @@ public class XWPrefs {
if ( RelayService.relayEnabled( context ) ) {
result.add( CommsConnType.COMMS_CONN_RELAY );
}
if ( BuildConfig.OFFER_MQTT ) {
result.add( CommsConnType.COMMS_CONN_MQTT );
}
if ( BTService.BTEnabled() ) {
result.add( CommsConnType.COMMS_CONN_BT );
}

View file

@ -1,6 +1,6 @@
/* -*- compile-command: "find-and-gradle.sh inXw4dDeb"; -*- */
/*
* Copyright 2009-2010 by Eric House (xwords@eehouse.org). All
* Copyright 2009-2020 by Eric House (xwords@eehouse.org). All
* rights reserved.
*
* This program is free software; you can redistribute it and/or
@ -54,7 +54,8 @@ public class CommsAddrRec {
COMMS_CONN_BT,
COMMS_CONN_SMS,
COMMS_CONN_P2P,
COMMS_CONN_NFC(false);
COMMS_CONN_NFC(false),
COMMS_CONN_MQTT;
private boolean mIsSelectable = true;
@ -82,6 +83,8 @@ public class CommsAddrRec {
id = R.string.invite_choice_p2p; break;
case COMMS_CONN_NFC:
id = R.string.invite_choice_nfc; break;
case COMMS_CONN_MQTT:
id = R.string.invite_choice_mqtt; break;
default:
Assert.failDbg();
}
@ -101,17 +104,16 @@ public class CommsAddrRec {
public CommsConnTypeSet() { this(BIT_VECTOR_MASK); }
public CommsConnTypeSet( int bits, boolean isVector )
public CommsConnTypeSet( final int inBits )
{
this( bits | BIT_VECTOR_MASK );
Assert.assertTrue( isVector );
}
public CommsConnTypeSet( int bits )
{
boolean isVector = 0 != (BIT_VECTOR_MASK & bits);
bits &= ~BIT_VECTOR_MASK;
boolean isVector = 0 != (BIT_VECTOR_MASK & inBits);
int bits = inBits & ~BIT_VECTOR_MASK;
CommsConnType[] values = CommsConnType.values();
// Deal with games saved before I added the BIT_VECTOR_MASK back
// in. This should be removable before ship. Or later of course.
if ( !isVector && bits >= values.length ) {
isVector = true;
}
if ( isVector ) {
for ( CommsConnType value : values ) {
int ord = value.ordinal();
@ -119,8 +121,10 @@ public class CommsAddrRec {
add( value );
}
}
} else {
} else if ( bits < values.length ) { // don't crash
add( values[bits] );
} else {
Log.e( TAG, "<init>: bad bits value: 0x%x", inBits );
}
}
@ -147,6 +151,9 @@ public class CommsAddrRec {
{
List<CommsConnType> supported = new ArrayList<>();
supported.add( CommsConnType.COMMS_CONN_RELAY );
if ( BuildConfig.OFFER_MQTT ) {
supported.add( CommsConnType.COMMS_CONN_MQTT );
}
if ( BTService.BTAvailable() ) {
supported.add( CommsConnType.COMMS_CONN_BT );
}
@ -216,9 +223,6 @@ public class CommsAddrRec {
private static final CommsConnType[] s_hint = new CommsConnType[0];
}
// The C equivalent of this struct uses a union for the various
// data sets below. So don't assume that any fields will be valid
// except those for the current conType.
public CommsConnTypeSet conTypes;
// relay case
@ -240,6 +244,9 @@ public class CommsAddrRec {
// wifi-direct
public String p2p_addr;
// mqtt
public String mqtt_devID;
public CommsAddrRec( CommsConnType cTyp )
{
this();
@ -318,6 +325,12 @@ public class CommsAddrRec {
return this;
}
public CommsAddrRec setMQTTParams( String devID )
{
mqtt_devID = devID;
return this;
}
public void populate( Context context, CommsConnTypeSet newTypes )
{
for ( CommsConnType typ : newTypes.getTypes() ) {
@ -405,6 +418,9 @@ public class CommsAddrRec {
case COMMS_CONN_P2P:
p2p_addr = WiDirService.getMyMacAddress( context );
break;
case COMMS_CONN_MQTT:
mqtt_devID = XwJNI.dvc_getMQTTDevID( null );
break;
case COMMS_CONN_NFC:
break;
default:

View file

@ -24,7 +24,10 @@ import android.content.Context;
import android.content.Intent;
import android.telephony.PhoneNumberUtils;
import java.util.Arrays;
import org.eehouse.android.xw4.Assert;
import org.eehouse.android.xw4.BuildConfig;
import org.eehouse.android.xw4.Channels;
import org.eehouse.android.xw4.DBUtils;
import org.eehouse.android.xw4.DevID;
@ -33,6 +36,8 @@ import org.eehouse.android.xw4.FBMService;
import org.eehouse.android.xw4.GameUtils;
import org.eehouse.android.xw4.GamesListDelegate;
import org.eehouse.android.xw4.Log;
import org.eehouse.android.xw4.MQTTUtils;
import org.eehouse.android.xw4.NetLaunchInfo;
import org.eehouse.android.xw4.R;
import org.eehouse.android.xw4.Utils;
import org.eehouse.android.xw4.XWApp;
@ -253,21 +258,19 @@ public class DUtilCtxt {
// Log.d( TAG, "store(key=%s)", key );
if ( null != data ) {
DBUtils.setBytesFor( m_context, key, data );
if ( BuildConfig.DEBUG ) {
byte[] tmp = load( key );
Assert.assertTrue( Arrays.equals( tmp, data ) );
}
}
}
public byte[] load( String key )
{
byte[] result = null;
int resultLen = 0;
Log.d( TAG, "load(key=%s)", key );
byte[] result = DBUtils.getBytesFor( m_context, key );
result = DBUtils.getBytesFor( m_context, key );
if ( result != null ) {
resultLen = result.length;
}
Log.d( TAG, "load(%s) returning %d bytes", key, resultLen );
// Log.d( TAG, "load(%s) returning %d bytes", key,
// null == result ? 0 : result.length );
return result;
}
@ -337,4 +340,23 @@ public class DUtilCtxt {
{
DupeModeTimer.timerChanged( m_context, gameID, newVal );
}
public void onInviteReceived( NetLaunchInfo nli )
{
Log.d( TAG, "onInviteReceived(%s)", nli );
GamesListDelegate.postReceivedInvite( m_context, nli.asByteArray() );
}
public void onMessageReceived( int gameID, CommsAddrRec from, byte[] msg )
{
Log.d( TAG, "onMessageReceived()" );
Assert.assertTrueNR( from.contains( CommsAddrRec.CommsConnType.COMMS_CONN_MQTT ) );
MQTTUtils.handleMessage( m_context, from, gameID, msg );
}
public void onGameGoneReceived( int gameID, CommsAddrRec from )
{
Assert.assertTrueNR( from.contains( CommsAddrRec.CommsConnType.COMMS_CONN_MQTT ) );
MQTTUtils.handleGameGone( m_context, from, gameID );
}
}

View file

@ -50,6 +50,7 @@ public class GameSummary implements Serializable {
public static final String EXTRA_REMATCH_PHONE = "rm_phone";
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 int MSG_FLAGS_NONE = 0;
public static final int MSG_FLAGS_TURN = 1;
@ -279,7 +280,8 @@ public class GameSummary implements Serializable {
// Otherwise, use BT or SMS
if ( null == result ) {
if ( conTypes.contains( CommsConnType.COMMS_CONN_BT )
|| ( conTypes.contains( CommsConnType.COMMS_CONN_SMS))){
|| conTypes.contains( CommsConnType.COMMS_CONN_SMS)
|| conTypes.contains( CommsConnType.COMMS_CONN_MQTT )) {
if ( 0 < missing ) {
if ( DeviceRole.SERVER_ISSERVER == serverRole ) {
fmtID = R.string.summary_wait_host;
@ -469,21 +471,24 @@ public class GameSummary implements Serializable {
m_extras = data;
}
public void putStringExtra( String key, String value )
public GameSummary putStringExtra( String key, String value )
{
String extras = (null == m_extras) ? "{}" : m_extras;
try {
JSONObject asObj = new JSONObject( extras );
if ( null == value ) {
asObj.remove( key );
} else {
asObj.put( key, value );
if ( null != value ) {
String extras = (null == m_extras) ? "{}" : m_extras;
try {
JSONObject asObj = new JSONObject( extras );
if ( null == value ) {
asObj.remove( key );
} else {
asObj.put( key, value );
}
m_extras = asObj.toString();
} catch( org.json.JSONException ex ) {
Log.ex( TAG, ex );
}
m_extras = asObj.toString();
} catch( org.json.JSONException ex ) {
Log.ex( TAG, ex );
Log.i( TAG, "putStringExtra(%s,%s) => %s", key, value, m_extras );
}
Log.i( TAG, "putStringExtra(%s,%s) => %s", key, value, m_extras );
return this;
}
public String getStringExtra( String key )
@ -511,6 +516,7 @@ public class GameSummary implements Serializable {
EXTRA_REMATCH_PHONE,
EXTRA_REMATCH_RELAY,
EXTRA_REMATCH_P2P,
EXTRA_REMATCH_MQTT,
};
for ( String key : keys ) {
found = null != getStringExtra( key );

View file

@ -127,6 +127,32 @@ public class XwJNI {
cleanGlobals();
}
public static String dvc_getMQTTDevID( String[] topic )
{
return dvc_getMQTTDevID( getJNI().m_ptrGlobals, topic );
}
public static byte[] dvc_makeMQTTInvite( NetLaunchInfo nli, String[] addrToTopic )
{
return dvc_makeMQTTInvite( getJNI().m_ptrGlobals, nli, addrToTopic );
}
public static byte[] dvc_makeMQTTMessage( int gameID, byte[] buf,
String[] addrToTopic )
{
return dvc_makeMQTTMessage( getJNI().m_ptrGlobals, gameID, buf, addrToTopic );
}
public static byte[] dvc_makeMQTTNoSuchGame( int gameID, String[] addrToTopic )
{
return dvc_makeMQTTNoSuchGame( getJNI().m_ptrGlobals, gameID, addrToTopic );
}
public static void dvc_parseMQTTPacket( byte[] buf )
{
dvc_parseMQTTPacket( getJNI().m_ptrGlobals, buf );
}
private static void cleanGlobals()
{
synchronized( XwJNI.class ) { // let's be safe here
@ -174,7 +200,6 @@ public class XwJNI {
public static byte[] nliToStream( NetLaunchInfo nli )
{
nli.freezeAddrs();
return nli_to_stream( getJNI().m_ptrGlobals, nli );
}
@ -182,7 +207,6 @@ public class XwJNI {
{
NetLaunchInfo nli = new NetLaunchInfo();
nli_from_stream( getJNI().m_ptrGlobals, nli, stream );
nli.unfreezeAddrs();
return nli;
}
@ -533,6 +557,15 @@ public class XwJNI {
// Private methods -- called only here
private static native long initGlobals( DUtilCtxt dutil, JNIUtils jniu );
private static native String dvc_getMQTTDevID( long jniState, String[] topic );
private static native byte[] dvc_makeMQTTInvite( long jniState, NetLaunchInfo nli,
String[] addrToTopic );
private static native byte[] dvc_makeMQTTMessage( long jniState, int gameID, byte[] buf,
String[] addrToTopic );
private static native byte[] dvc_makeMQTTNoSuchGame( long jniState, int gameID,
String[] addrToTopic );
private static native void dvc_parseMQTTPacket( long jniState, byte[] buf );
private static native void cleanGlobals( long jniState );
private static native byte[] gi_to_stream( long jniState, CurGameInfo gi );
private static native void gi_from_stream( long jniState, CurGameInfo gi,

View file

@ -39,6 +39,9 @@
<string name="key_relay_via_http_first">key_relay_via_http_first</string>
<string name="key_update_url">key_update_url2</string>
<string name="key_relay_url">key_relay_url2</string>
<string name="key_mqtt_host">key_mqtt_host</string>
<string name="key_mqtt_port">key_mqtt_port</string>
<string name="key_mqtt_qos">key_mqtt_qos</string>
<string name="key_update_prerel">key_update_prerel</string>
<string name="key_proxy_port">key_proxy_port</string>
<string name="key_sms_port">key_sms_port</string>
@ -143,6 +146,7 @@
<string name="key_notagain_nbsGamesOnUpgrade">key_notagain_nbsGamesOnUpgrade</string>
<string name="key_na_comms_bt">key_na_comms_bt</string>
<string name="key_na_comms_p2p">key_na_comms_p2p</string>
<string name="key_na_comms_mqtt">key_na_comms_mqtt</string>
<string name="key_na_comms_sms">key_na_comms_sms</string>
<string name="key_na_comms_relay">key_na_comms_relay</string>
<string name="key_na_bt_badproto">key_na_bt_badproto</string>
@ -339,6 +343,12 @@
<item>@string/relay_poll_name_both</item>
</string-array>
<string-array name="mqtt_qos_values">
<item>0</item>
<item>1</item>
<item>2</item>
</string-array>
<string-array name="force_tablet_names">
<item>@string/force_tablet_default</item>
<item>@string/force_tablet_phone</item>

View file

@ -1048,6 +1048,7 @@
<!-- <string name="button_nfc">NFC</string> -->
<!-- This is the subject line of the email/text sent to invite
someone to join a game. -->
<string name="invite_subject">Let\'s play CrossWords</string>
<string name="invite_subject_fmt">Let\'s play CrossWords (room %1$s)</string>
<!-- This is the body of the html version of the invitation. A URL
is created with parameters describing the game and
@ -1602,7 +1603,7 @@
<string name="invite_progress_title">Connecting…</string>
<string name="invite_progress_fmt">Sending invitation to CrossWords on %1$s</string>
<!-- -->
<string name="summary_wait_host">Waiting for connection[s]</string>
<string name="summary_wait_host">Waiting for guest[s]</string>
<!-- -->
<string name="summary_wait_guest">Unconnected</string>
<!-- -->
@ -2195,6 +2196,7 @@
<string name="advanced">For debugging</string>
<string name="advanced_summary">You should never need these…</string>
<string name="relay_host">Relay host</string>
<string name="mqtt_host">MQTT host</string>
<string name="relay_via_http_first">Use Web APIs first</string>
<string name="relay_via_http_first_summary">(instead of as fallback for custom protocol)</string>
<string name="dict_host">Wordlist download URL</string>
@ -2207,6 +2209,8 @@
<string name="git_rev_title">Source version id</string>
<string name="devid_title">Device ID (on relay)</string>
<string name="relay_port">Relay game port</string>
<string name="mqtt_port">MQTT port</string>
<string name="mqtt_qos">MQTT QOS</string>
<string name="proxy_port">Relay device port</string>
<string name="name_dict_fmt">%1$s/%2$s</string>
<string name="gamel_menu_storedb">Write games to SD card</string>
@ -2551,4 +2555,10 @@
<string name="pick_tiles_title">Pick a tile \"spelling\"</string>
<string name="no_tiles_exist">\"%1$s\" cannot be spelled with tiles in %2$s.</string>
<!-- MQTT stuff. May not see the light of day -->
<string name="invite_choice_mqtt">Internet/MQTT</string>
<string name="mqtt_invite_title">MQTT Invitation</string>
<string name="not_again_comms_mqtt">I\'m experimenting with this
as a replacement for the relay.</string>
</resources>

View file

@ -456,6 +456,26 @@
android:defaultValue="10998"
android:numeric="decimal"
/>
<org.eehouse.android.xw4.XWEditTextPreference
android:key="@string/key_mqtt_host"
android:title="@string/mqtt_host"
android:defaultValue="@string/default_host"
/>
<org.eehouse.android.xw4.XWEditTextPreference
android:key="@string/key_mqtt_port"
android:title="@string/mqtt_port"
android:defaultValue="1883"
android:numeric="decimal"
/>
<org.eehouse.android.xw4.XWListPreference
android:key="@string/key_mqtt_qos"
android:title="@string/mqtt_qos"
android:entries="@array/mqtt_qos_values"
android:entryValues="@array/mqtt_qos_values"
android:defaultValue="2"
/>
</PreferenceScreen>
<PreferenceScreen android:title="@string/pref_group_sms_title"

View file

@ -86,6 +86,7 @@ COMMON_SRC_FILES += \
$(COMMON_PATH)/dbgutil.c \
$(COMMON_PATH)/nli.c \
$(COMMON_PATH)/smsproto.c \
$(COMMON_PATH)/device.c \
LOCAL_CFLAGS+=$(LOCAL_C_INCLUDES) $(LOCAL_DEFINES) -Wall -std=c99
LOCAL_SRC_FILES := $(linux_SRC_FILES) $(LOCAL_SRC_FILES) $(COMMON_SRC_FILES)

View file

@ -150,8 +150,8 @@ setInts( JNIEnv* env, jobject jobj, void* cobj, const SetInfo* sis, XP_U16 nSis
XP_ASSERT(0);
}
setInt( env, jobj, si->name, val );
/* XP_LOGF( "%s: read int %s of size %d with val %d from offset %d", */
/* __func__, si->name, si->siz, val, si->offset ); */
/* XP_LOGFF( "read int %s of size %d with val %d/0x%x from offset %d", */
/* si->name, si->siz, val, val, si->offset ); */
}
}
@ -185,7 +185,7 @@ setBools( JNIEnv* env, jobject jobj, void* cobj, const SetInfo* sis, XP_U16 nSis
bool
setString( JNIEnv* env, jobject obj, const char* name, const XP_UCHAR* value )
{
// XP_LOGF( "%s(%s)", __func__, name );
/* XP_LOGFF( "(name=%s, val=%s)", name, value ); */
bool success = false;
jclass cls = (*env)->GetObjectClass( env, obj );
jfieldID fid = (*env)->GetFieldID( env, cls, name, "Ljava/lang/String;" );
@ -226,7 +226,7 @@ void
getString( JNIEnv* env, jobject obj, const char* name, XP_UCHAR* buf,
int bufLen )
{
// XP_LOGF( "%s(%s)", __func__, name );
/* XP_LOGFF( "(name=%s, bufLen=%d)", name, bufLen ); */
jclass cls = (*env)->GetObjectClass( env, obj );
XP_ASSERT( !!cls );
jfieldID fid = (*env)->GetFieldID( env, cls, name, "Ljava/lang/String;" );
@ -509,8 +509,27 @@ setTypeSetFieldIn( JNIEnv* env, const CommsAddrRec* addr, jobject jTarget,
deleteLocalRef( env, jtypset );
}
jobject
makeJAddr( JNIEnv* env, const CommsAddrRec* addr )
{
jobject jaddr = NULL;
if ( NULL != addr ) {
jclass clazz
= (*env)->FindClass(env, PKG_PATH("jni/CommsAddrRec") );
XP_ASSERT( !!clazz );
jmethodID mid = getMethodID( env, clazz, "<init>", "()V" );
XP_ASSERT( !!mid );
jaddr = (*env)->NewObject( env, clazz, mid );
setJAddrRec( env, jaddr, addr );
deleteLocalRef( env, clazz );
}
return jaddr;
}
/* Copy C object data into Java object */
void
jobject
setJAddrRec( JNIEnv* env, jobject jaddr, const CommsAddrRec* addr )
{
XP_ASSERT( !!addr );
@ -543,10 +562,17 @@ setJAddrRec( JNIEnv* env, jobject jaddr, const CommsAddrRec* addr )
break;
case COMMS_CONN_NFC:
break;
case COMMS_CONN_MQTT: {
XP_UCHAR buf[32];
formatMQTTDevID( &addr->u.mqtt.devID, buf, VSIZE(buf) );
setString( env, jaddr, "mqtt_devID", buf );
}
break;
default:
XP_ASSERT(0);
}
}
return jaddr;
}
jobject
@ -636,6 +662,12 @@ getJAddrRec( JNIEnv* env, CommsAddrRec* addr, jobject jaddr )
break;
case COMMS_CONN_NFC:
break;
case COMMS_CONN_MQTT: {
XP_UCHAR buf[32];
getString( env, jaddr, "mqtt_devID", buf, VSIZE(buf) );
sscanf( buf, MQTTDevID_FMT, &addr->u.mqtt.devID );
}
break;
default:
XP_ASSERT(0);
}
@ -723,6 +755,48 @@ jEnumToInt( JNIEnv* env, jobject jenum )
return (*env)->CallIntMethod( env, jenum, mid );
}
static const SetInfo nli_ints[] = {
ARR_MEMBER( NetLaunchInfo, _conTypes ),
ARR_MEMBER( NetLaunchInfo, lang ),
ARR_MEMBER( NetLaunchInfo, forceChannel ),
ARR_MEMBER( NetLaunchInfo, nPlayersT ),
ARR_MEMBER( NetLaunchInfo, nPlayersH ),
ARR_MEMBER( NetLaunchInfo, gameID ),
ARR_MEMBER( NetLaunchInfo, osVers ),
};
static const SetInfo nli_bools[] = {
ARR_MEMBER( NetLaunchInfo, isGSM ),
ARR_MEMBER( NetLaunchInfo, remotesAreRobots ),
};
static const SetInfo nli_strs[] = {
ARR_MEMBER( NetLaunchInfo, dict ),
ARR_MEMBER( NetLaunchInfo, gameName ),
ARR_MEMBER( NetLaunchInfo, room ),
ARR_MEMBER( NetLaunchInfo, btName ),
ARR_MEMBER( NetLaunchInfo, btAddress ),
ARR_MEMBER( NetLaunchInfo, phone ),
ARR_MEMBER( NetLaunchInfo, inviteID ),
ARR_MEMBER( NetLaunchInfo, mqttDevID ),
};
void
loadNLI( JNIEnv* env, NetLaunchInfo* nli, jobject jnli )
{
getInts( env, (void*)nli, jnli, AANDS(nli_ints) );
getBools( env, (void*)nli, jnli, AANDS(nli_bools) );
getStrings( env, (void*)nli, jnli, AANDS(nli_strs) );
}
void
setNLI( JNIEnv* env, jobject jnli, const NetLaunchInfo* nli )
{
setInts( env, jnli, (void*)nli, AANDS(nli_ints) );
setBools( env, jnli, (void*)nli, AANDS(nli_bools) );
setStrings( env, jnli, (void*)nli, AANDS(nli_strs) );
}
XWStreamCtxt*
and_empty_stream( MPFORMAL AndGameGlobals* globals )
{
@ -785,7 +859,7 @@ passToJava( const char* tag, const char* msg )
releaseEnvFromGlobals( env );
} else {
RAW_LOG( "env is NULL; dropping" );
// RAW_LOG( "env is NULL; dropping" );
}
}

View file

@ -92,7 +92,8 @@ jbyteArray streamToBArray( JNIEnv *env, XWStreamCtxt* stream );
jmethodID getMethodID( JNIEnv* env, jobject obj, const char* proc,
const char* sig );
void setJAddrRec( JNIEnv* env, jobject jaddr, const CommsAddrRec* addr );
jobject makeJAddr( JNIEnv* env, const CommsAddrRec* addr );
jobject setJAddrRec( JNIEnv* env, jobject jaddr, const CommsAddrRec* addr );
void getJAddrRec( JNIEnv* env, CommsAddrRec* addr, jobject jaddr );
void setTypeSetFieldIn( JNIEnv* env, const CommsAddrRec* addr, jobject jTarget,
const char* fldName );
@ -104,6 +105,10 @@ void intToJenumField( JNIEnv* env, jobject jobj, int val, const char* field,
jobject intToJEnum( JNIEnv* env, int val, const char* enumSig );
jint jEnumToInt( JNIEnv* env, jobject jenum );
#define AANDS(a) (a), VSIZE(a)
void loadNLI( JNIEnv* env, NetLaunchInfo* nli, jobject jnli );
void setNLI( JNIEnv* env, jobject jnli, const NetLaunchInfo* nli );
XP_U32 getCurSeconds( JNIEnv* env );
void deleteLocalRef( JNIEnv* env, jobject jobj );

View file

@ -210,12 +210,14 @@ makeDSI( AndDraw* draw, XWEnv xwe, int indx, const DrawScoreInfo* dsi )
}
#endif
#define DRAW_CBK_HEADER(nam,sig) \
#define DRAW_CBK_HEADER(nam,sig) { \
JNIEnv* env = xwe; \
AndDraw* draw = (AndDraw*)dctx; \
ASSERT_ENV( draw->ti, env ); \
XP_ASSERT( !!draw->jdraw ); \
jmethodID mid = getMethodID( xwe, draw->jdraw, nam, sig );
jmethodID mid = getMethodID( xwe, draw->jdraw, nam, sig ) \
#define DRAW_CBK_HEADER_END() }
static XP_Bool
and_draw_scoreBegin( DrawCtx* dctx, XWEnv xwe, const XP_Rect* rect,
@ -238,6 +240,7 @@ and_draw_scoreBegin( DrawCtx* dctx, XWEnv xwe, const XP_Rect* rect,
returnJRect( draw, JCACHE_RECT0, jrect );
deleteLocalRef( env, jscores );
DRAW_CBK_HEADER_END();
return result;
}
@ -255,6 +258,7 @@ and_draw_drawRemText( DrawCtx* dctx, XP_S16 nTilesLeft,
readJRect( env, rect, jrect );
}
returnJRect( draw, JCACHE_RECT0, jrect );
DRAW_CBK_HEADER_END();
return result;
}
@ -277,6 +281,7 @@ and_draw_score_drawPlayers( DrawCtx* dctx, XWEnv xwe, const XP_Rect* scoreRect,
readJRect( env, &playerRects[ii], jrect );
}
returnJRect( draw, JCACHE_RECT0, jrect );
DRAW_CBK_HEADER_END();
}
#else
@ -286,15 +291,16 @@ and_draw_measureRemText( DrawCtx* dctx, XWEnv xwe, const XP_Rect* rect,
XP_S16 nTilesLeft,
XP_U16* width, XP_U16* height )
{
jboolean result;
DRAW_CBK_HEADER("measureRemText", "(Landroid/graphics/Rect;I[I[I)Z" );
jintArray widthArray = (*env)->NewIntArray( env, 1 );
jintArray heightArray = (*env)->NewIntArray( env, 1 );
jobject jrect = makeJRect( draw, xwe, JCACHE_RECT0, rect );
jboolean result = (*env)->CallBooleanMethod( env, draw->jdraw, mid, jrect,
nTilesLeft, widthArray,
heightArray );
result = (*env)->CallBooleanMethod( env, draw->jdraw, mid, jrect,
nTilesLeft, widthArray,
heightArray );
if ( result ) {
int tmp;
getIntsFromArray( env, &tmp, widthArray, 1, true );
@ -303,6 +309,7 @@ and_draw_measureRemText( DrawCtx* dctx, XWEnv xwe, const XP_Rect* rect,
*height = tmp;
}
returnJRect( draw, JCACHE_RECT0, jrect );
DRAW_CBK_HEADER_END();
return result;
} /* and_draw_measureRemText */
@ -321,6 +328,7 @@ and_draw_drawRemText( DrawCtx* dctx, XWEnv xwe, const XP_Rect* rInner,
nTilesLeft, focussed );
returnJRect( draw, JCACHE_RECT0, jrinner );
returnJRect( draw, JCACHE_RECT1, jrouter );
DRAW_CBK_HEADER_END();
}
static void
@ -347,6 +355,7 @@ and_draw_measureScoreText( DrawCtx* dctx, XWEnv xwe, const XP_Rect* r,
*width = tmp;
getIntsFromArray( env, &tmp, heightArray, 1, true );
*height = tmp;
DRAW_CBK_HEADER_END();
} /* and_draw_measureScoreText */
static void
@ -366,6 +375,7 @@ and_draw_score_drawPlayer( DrawCtx* dctx, XWEnv xwe, const XP_Rect* rInner,
jdsi );
returnJRect( draw, JCACHE_RECT0, jrinner );
returnJRect( draw, JCACHE_RECT1, jrouter );
DRAW_CBK_HEADER_END();
} /* and_draw_score_drawPlayer */
#endif
@ -382,6 +392,7 @@ and_draw_drawTimer( DrawCtx* dctx, XWEnv xwe, const XP_Rect* rect, XP_U16 player
(*env)->CallVoidMethod( env, draw->jdraw, mid,
jrect, player, secondsLeft, inDuplicateMode );
returnJRect( draw, JCACHE_RECT0, jrect );
DRAW_CBK_HEADER_END();
}
}
@ -408,6 +419,7 @@ and_draw_drawCell( DrawCtx* dctx, XWEnv xwe, const XP_Rect* rect,
XP_S16 owner, XWBonusType bonus, HintAtts hintAtts,
CellFlags flags )
{
jboolean result;
DRAW_CBK_HEADER("drawCell",
"(Landroid/graphics/Rect;Ljava/lang/String;IIIIII)Z" );
jobject jrect = makeJRect( draw, xwe, JCACHE_RECT0, rect );
@ -419,13 +431,14 @@ and_draw_drawCell( DrawCtx* dctx, XWEnv xwe, const XP_Rect* rect,
jtext = (*env)->NewStringUTF( env, text );
}
jboolean result = (*env)->CallBooleanMethod( env, draw->jdraw, mid,
jrect, jtext, tile, value,
owner, bonus, hintAtts,
flags );
result = (*env)->CallBooleanMethod( env, draw->jdraw, mid,
jrect, jtext, tile, value,
owner, bonus, hintAtts,
flags );
returnJRect( draw, JCACHE_RECT0, jrect );
deleteLocalRef( env, jtext );
DRAW_CBK_HEADER_END();
return result;
}
@ -440,6 +453,7 @@ and_draw_drawBoardArrow( DrawCtx* dctx, XWEnv xwe, const XP_Rect* rect,
(*env)->CallVoidMethod( env, draw->jdraw, mid,
jrect, bonus, vert, hintAtts, flags );
returnJRect( draw, JCACHE_RECT0, jrect );
DRAW_CBK_HEADER_END();
}
static XP_Bool
@ -456,13 +470,14 @@ static XP_Bool
and_draw_trayBegin( DrawCtx* dctx, XWEnv xwe, const XP_Rect* rect, XP_U16 owner,
XP_S16 score, DrawFocusState XP_UNUSED(dfs) )
{
jboolean result;
DRAW_CBK_HEADER( "trayBegin", "(Landroid/graphics/Rect;II)Z" );
jobject jrect = makeJRect( draw, xwe, JCACHE_RECT0, rect );
jboolean result = (*env)->CallBooleanMethod( env, draw->jdraw, mid,
jrect, owner, score );
result = (*env)->CallBooleanMethod( env, draw->jdraw, mid, jrect, owner, score );
returnJRect( draw, JCACHE_RECT0, jrect );
DRAW_CBK_HEADER_END();
return result;
}
@ -484,6 +499,7 @@ and_draw_drawTile( DrawCtx* dctx, XWEnv xwe, const XP_Rect* rect,
jrect, jtext, val, flags );
returnJRect( draw, JCACHE_RECT0, jrect );
deleteLocalRef( env, jtext );
DRAW_CBK_HEADER_END();
return result;
}
@ -506,18 +522,21 @@ and_draw_drawTileMidDrag( DrawCtx* dctx, XWEnv xwe, const XP_Rect* rect,
jrect, jtext, val, owner, flags );
returnJRect( draw, JCACHE_RECT0, jrect );
deleteLocalRef( env, jtext );
DRAW_CBK_HEADER_END();
return result;
}
static XP_Bool
and_draw_drawTileBack( DrawCtx* dctx, XWEnv xwe, const XP_Rect* rect, CellFlags flags )
{
XP_Bool result;
DRAW_CBK_HEADER( "drawTileBack", "(Landroid/graphics/Rect;I)Z" );
jobject jrect = makeJRect( draw, xwe, JCACHE_RECT0, rect );
XP_Bool result = (*env)->CallBooleanMethod( env, draw->jdraw, mid, jrect, flags );
result = (*env)->CallBooleanMethod( env, draw->jdraw, mid, jrect, flags );
returnJRect( draw, JCACHE_RECT0, jrect );
DRAW_CBK_HEADER_END();
return result;
}
@ -531,6 +550,7 @@ and_draw_drawTrayDivider( DrawCtx* dctx, XWEnv xwe, const XP_Rect* rect, CellFla
(*env)->CallVoidMethod( env, draw->jdraw, mid,
jrect, flags );
returnJRect( draw, JCACHE_RECT0, jrect );
DRAW_CBK_HEADER_END();
}
static void
@ -545,6 +565,7 @@ and_draw_score_pendingScore( DrawCtx* dctx, XWEnv xwe, const XP_Rect* rect,
(*env)->CallVoidMethod( env, draw->jdraw, mid,
jrect, score, playerNum, curTurn, flags );
returnJRect( draw, JCACHE_RECT0, jrect );
DRAW_CBK_HEADER_END();
}
static void
@ -559,6 +580,7 @@ and_draw_objFinished( DrawCtx* dctx, XWEnv xwe, BoardObjectType typ,
(*env)->CallVoidMethod( env, draw->jdraw, mid,
(jint)typ, jrect );
returnJRect( draw, JCACHE_RECT0, jrect );
DRAW_CBK_HEADER_END();
#endif
}
@ -568,7 +590,7 @@ and_draw_dictChanged( DrawCtx* dctx, XWEnv xwe, XP_S16 playerNum,
{
AndDraw* draw = (AndDraw*)dctx;
if ( !!dict && !!draw->jdraw ) {
XP_LOGF( "%s(dict=%p); code=%x", __func__, dict, andDictID(dict) );
XP_LOGFF( "(dict=%p/%s); code=%x", dict, dict_getName(dict), andDictID(dict) );
XP_LangCode code = 0; /* A null dict means no-lang */
if ( NULL != dict ) {
code = dict_getLangCode( dict );
@ -587,6 +609,7 @@ and_draw_dictChanged( DrawCtx* dctx, XWEnv xwe, XP_S16 playerNum,
/* jobject jdict = (*env)->NewObject( env, rclass, initId, (int)dict ); */
(*env)->CallVoidMethod( env, draw->jdraw, mid, (jlong)dict );
DRAW_CBK_HEADER_END();
}
}
}
@ -602,6 +625,7 @@ and_draw_getMiniWText( DrawCtx* dctx, XWEnv xwe, XWMiniTextType textHint )
snprintf( draw->miniTextBuf, VSIZE(draw->miniTextBuf), "%s", str );
(*env)->ReleaseStringUTFChars( env, jstr, str );
deleteLocalRef( env, jstr );
DRAW_CBK_HEADER_END();
return draw->miniTextBuf;
}
@ -625,6 +649,7 @@ and_draw_measureMiniWText( DrawCtx* dctx, XWEnv xwe, const XP_UCHAR* textP,
*width = tmp;
getIntsFromArray( env, &tmp, heightArray, 1, true );
*height = tmp;
DRAW_CBK_HEADER_END();
}
static void
@ -641,6 +666,7 @@ and_draw_drawMiniWindow( DrawCtx* dctx, XWEnv xwe, const XP_UCHAR* text,
jstr, jrect );
returnJRect( draw, JCACHE_RECT0, jrect );
deleteLocalRef( env, jstr );
DRAW_CBK_HEADER_END();
}
#endif

View file

@ -28,6 +28,7 @@
#include "paths.h"
#include "LocalizedStrIncludes.h"
#include "dbgutil.h"
#include "nli.h"
#define MAX_QUANTITY_STRS 4
@ -523,16 +524,17 @@ and_dutil_loadPtr( XW_DUtilCtxt* duc, XWEnv xwe, const XP_UCHAR* key,
{
JNIEnv* env = xwe;
jbyteArray jvalue = loadToByteArray( duc, env, key );
jsize len = 0;
if ( jvalue != NULL ) {
jsize len = (*env)->GetArrayLength( env, jvalue );
len = (*env)->GetArrayLength( env, jvalue );
if ( len <= *lenp ) {
jbyte* jelems = (*env)->GetByteArrayElements( env, jvalue, NULL );
XP_MEMCPY( data, jelems, len );
(*env)->ReleaseByteArrayElements( env, jvalue, jelems, 0 );
}
*lenp = len;
deleteLocalRef( env, jvalue );
}
*lenp = len;
}
static void
@ -847,6 +849,64 @@ and_dutil_onDupTimerChanged( XW_DUtilCtxt* duc, XWEnv xwe, XP_U32 gameID,
DUTIL_CBK_TAIL();
}
static void
and_dutil_onInviteReceived( XW_DUtilCtxt* duc, XWEnv xwe, const NetLaunchInfo* nli )
{
LOGNLI( nli );
DUTIL_CBK_HEADER( "onInviteReceived", "(L" PKG_PATH("NetLaunchInfo") ";)V" );
/* Allocate a new NetLaunchInfo */
jclass cls = (*env)->FindClass( env, PKG_PATH("NetLaunchInfo") );
XP_ASSERT( !!cls );
jmethodID initId = (*env)->GetMethodID( env, cls, "<init>", "()V" );
XP_ASSERT( !!initId );
jobject jnli = (*env)->NewObject( env, cls, initId );
XP_ASSERT( !!jnli );
setNLI( env, jnli, nli );
(*env)->CallVoidMethod( env, dutil->jdutil, mid, jnli );
deleteLocalRefs( env, jnli, cls, DELETE_NO_REF );
DUTIL_CBK_TAIL();
}
static void
and_dutil_onMessageReceived( XW_DUtilCtxt* duc, XWEnv xwe, XP_U32 gameID,
const CommsAddrRec* from, XWStreamCtxt* stream )
{
LOG_FUNC();
DUTIL_CBK_HEADER( "onMessageReceived",
"(IL" PKG_PATH("jni/CommsAddrRec") ";[B)V" );
XP_U16 len = stream_getSize( stream );
XP_U8 data[len];
stream_getBytes( stream, data, len );
jbyteArray jmsg = makeByteArray( env, len, (jbyte*)data );
jobject jaddr = makeJAddr( env, from );
(*env)->CallVoidMethod( env, dutil->jdutil, mid, gameID, jaddr, jmsg );
deleteLocalRefs( env, jmsg, jaddr, DELETE_NO_REF );
DUTIL_CBK_TAIL();
LOG_RETURN_VOID();
}
static void
and_dutil_onGameGoneReceived( XW_DUtilCtxt* duc, XWEnv xwe, XP_U32 gameID,
const CommsAddrRec* from )
{
DUTIL_CBK_HEADER( "onGameGoneReceived",
"(IL" PKG_PATH("jni/CommsAddrRec") ";)V" );
jobject jaddr = makeJAddr( env, from );
(*env)->CallVoidMethod( env, dutil->jdutil, mid, gameID, jaddr );
deleteLocalRefs( env, jaddr, DELETE_NO_REF );
DUTIL_CBK_TAIL();
}
XW_UtilCtxt*
makeUtil( MPFORMAL JNIEnv* env,
#ifdef MAP_THREAD_TO_ENV
@ -994,6 +1054,10 @@ makeDUtil( MPFORMAL JNIEnv* env,
SET_DPROC(notifyPause);
SET_DPROC(onDupTimerChanged);
SET_DPROC(onInviteReceived);
SET_DPROC(onMessageReceived);
SET_DPROC(onGameGoneReceived);
#undef SET_DPROC
assertTableFull( vtable, sizeof(*vtable), "dutil" );

View file

@ -32,27 +32,6 @@ typedef struct _AndTransportProcs {
MPSLOT
} AndTransportProcs;
static jobject
makeJAddr( JNIEnv* env, const CommsAddrRec* addr )
{
jobject jaddr = NULL;
if ( NULL != addr ) {
jclass clazz
= (*env)->FindClass(env, PKG_PATH("jni/CommsAddrRec") );
XP_ASSERT( !!clazz );
jmethodID mid = getMethodID( env, clazz, "<init>", "()V" );
XP_ASSERT( !!mid );
jaddr = (*env)->NewObject( env, clazz, mid );
XP_ASSERT( !!jaddr );
setJAddrRec( env, jaddr, addr );
deleteLocalRef( env, clazz );
}
return jaddr;
}
static XP_U32
and_xport_getFlags( XWEnv xwe, void* closure )
{

View file

@ -35,6 +35,7 @@
#include "dictmgr.h"
#include "nli.h"
#include "smsproto.h"
#include "device.h"
#include "utilwrapper.h"
#include "drawwrapper.h"
@ -434,9 +435,6 @@ static const SetInfo pl_ints[] = {
,ARR_MEMBER( LocalPlayer, secondsUsed )
};
#define AANDS(a) \
(a), VSIZE(a)
static CurGameInfo*
makeGI( MPFORMAL JNIEnv* env, jobject jgi )
{
@ -498,47 +496,6 @@ makeGI( MPFORMAL JNIEnv* env, jobject jgi )
return gi;
} /* makeGI */
static const SetInfo nli_ints[] = {
ARR_MEMBER( NetLaunchInfo, _conTypes ),
ARR_MEMBER( NetLaunchInfo, lang ),
ARR_MEMBER( NetLaunchInfo, forceChannel ),
ARR_MEMBER( NetLaunchInfo, nPlayersT ),
ARR_MEMBER( NetLaunchInfo, nPlayersH ),
ARR_MEMBER( NetLaunchInfo, gameID ),
ARR_MEMBER( NetLaunchInfo, osVers ),
};
static const SetInfo nli_bools[] = {
ARR_MEMBER( NetLaunchInfo, isGSM ),
ARR_MEMBER( NetLaunchInfo, remotesAreRobots ),
};
static const SetInfo nli_strs[] = {
ARR_MEMBER( NetLaunchInfo, dict ),
ARR_MEMBER( NetLaunchInfo, gameName ),
ARR_MEMBER( NetLaunchInfo, room ),
ARR_MEMBER( NetLaunchInfo, btName ),
ARR_MEMBER( NetLaunchInfo, btAddress ),
ARR_MEMBER( NetLaunchInfo, phone ),
ARR_MEMBER( NetLaunchInfo, inviteID ),
};
static void
loadNLI( JNIEnv* env, NetLaunchInfo* nli, jobject jnli )
{
getInts( env, (void*)nli, jnli, AANDS(nli_ints) );
getBools( env, (void*)nli, jnli, AANDS(nli_bools) );
getStrings( env, (void*)nli, jnli, AANDS(nli_strs) );
}
static void
setNLI( JNIEnv* env, jobject jnli, const NetLaunchInfo* nli )
{
setInts( env, jnli, (void*)nli, AANDS(nli_ints) );
setBools( env, jnli, (void*)nli, AANDS(nli_bools) );
setStrings( env, jnli, (void*)nli, AANDS(nli_strs) );
}
static void
setJGI( JNIEnv* env, jobject jgi, const CurGameInfo* gi )
{
@ -653,8 +610,153 @@ streamFromJStream( MPFORMAL JNIEnv* env, VTableMgr* vtMgr, jbyteArray jstream )
} /* streamFromJStream */
/****************************************************
* These three methods are stateless: no gamePtr
* These methods are stateless: no gamePtr
****************************************************/
#define DVC_HEADER(PTR) { \
JNIGlobalState* globalState = (JNIGlobalState*)(PTR); \
#define DVC_HEADER_END() } \
JNIEXPORT jstring JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_dvc_1getMQTTDevID
( JNIEnv* env, jclass C, jlong jniGlobalPtr, jobjectArray jTopicOut )
{
jstring result;
DVC_HEADER(jniGlobalPtr);
MQTTDevID devID;
dvc_getMQTTDevID( globalState->dutil, env, &devID );
XP_UCHAR buf[64];
if ( !!jTopicOut ) {
formatMQTTTopic( &devID, buf, VSIZE(buf) );
jstring jtopic = (*env)->NewStringUTF( env, buf );
XP_ASSERT( 1 == (*env)->GetArrayLength( env, jTopicOut ) ); /* fired */
(*env)->SetObjectArrayElement( env, jTopicOut, 0, jtopic );
deleteLocalRef( env, jtopic );
}
formatMQTTDevID( &devID, buf, VSIZE(buf) );
result = (*env)->NewStringUTF( env, buf );
DVC_HEADER_END();
return result;
}
static void
addrToTopic( JNIEnv* env, jobjectArray jAddrToTopic )
{
XP_ASSERT( 1 == (*env)->GetArrayLength( env, jAddrToTopic ) );
jstring jaddr = (*env)->GetObjectArrayElement( env, jAddrToTopic, 0 );
const char* addr = (*env)->GetStringUTFChars( env, jaddr, NULL );
MQTTDevID devID;
#ifdef DEBUG
XP_Bool success =
#endif
strToMQTTCDevID( addr, &devID );
XP_ASSERT( success );
XP_UCHAR buf[64];
formatMQTTTopic( &devID, buf, VSIZE(buf) );
jstring jTopic = (*env)->NewStringUTF( env, buf );
(*env)->SetObjectArrayElement( env, jAddrToTopic, 0, jTopic );
(*env)->ReleaseStringUTFChars( env, jaddr, addr );
deleteLocalRefs( env, jaddr, jTopic, DELETE_NO_REF );
}
JNIEXPORT jbyteArray JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_dvc_1makeMQTTInvite
( JNIEnv* env, jclass C, jlong jniGlobalPtr, jobject jnli,
jobjectArray jAddrToTopic )
{
jbyteArray result;
DVC_HEADER(jniGlobalPtr);
NetLaunchInfo nli = {0};
loadNLI( env, &nli, jnli );
LOGNLI( &nli );
XWStreamCtxt* stream = mem_stream_make( MPPARM(globalState->mpool)
globalState->vtMgr,
NULL, 0, NULL );
dvc_makeMQTTInvite( stream, &nli );
result = streamToBArray( env, stream );
stream_destroy( stream, env );
addrToTopic( env, jAddrToTopic );
DVC_HEADER_END();
return result;
}
JNIEXPORT jbyteArray JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_dvc_1makeMQTTMessage
( JNIEnv* env, jclass C, jlong jniGlobalPtr, jint jGameID,
jbyteArray jmsg, jobjectArray jAddrToTopic )
{
jbyteArray result;
LOG_FUNC();
DVC_HEADER(jniGlobalPtr);
XWStreamCtxt* stream = mem_stream_make( MPPARM(globalState->mpool)
globalState->vtMgr,
NULL, 0, NULL );
XP_U16 len = (*env)->GetArrayLength( env, jmsg );
jbyte* buf = (*env)->GetByteArrayElements( env, jmsg, NULL );
dvc_makeMQTTMessage( globalState->dutil, env, stream, jGameID,
(const XP_U8*)buf, len );
(*env)->ReleaseByteArrayElements( env, jmsg, buf, 0 );
result = streamToBArray( env, stream );
stream_destroy( stream, env );
addrToTopic( env, jAddrToTopic );
DVC_HEADER_END();
return result;
}
JNIEXPORT jbyteArray JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_dvc_1makeMQTTNoSuchGame
( JNIEnv* env, jclass C, jlong jniGlobalPtr, jint jgameid, jobjectArray jAddrToTopic )
{
jbyteArray result;
DVC_HEADER(jniGlobalPtr);
XWStreamCtxt* stream = mem_stream_make( MPPARM(globalState->mpool)
globalState->vtMgr,
NULL, 0, NULL );
dvc_makeMQTTNoSuchGame( globalState->dutil, env, stream, jgameid );
result = streamToBArray( env, stream );
stream_destroy( stream, env );
addrToTopic( env, jAddrToTopic );
DVC_HEADER_END();
LOG_RETURN_VOID();
return result;
}
JNIEXPORT void JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_dvc_1parseMQTTPacket
( JNIEnv* env, jclass C, jlong jniGlobalPtr, jbyteArray jmsg )
{
DVC_HEADER(jniGlobalPtr);
XP_U16 len = (*env)->GetArrayLength( env, jmsg );
jbyte* buf = (*env)->GetByteArrayElements( env, jmsg, NULL );
dvc_parseMQTTPacket( globalState->dutil, env, (XP_U8*)buf, len );
(*env)->ReleaseByteArrayElements( env, jmsg, buf, 0 );
DVC_HEADER_END();
}
JNIEXPORT jbyteArray JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_gi_1to_1stream
( JNIEnv* env, jclass C, jlong jniGlobalPtr, jobject jgi )
@ -705,7 +807,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_gi_1from_1stream
JNIEXPORT jbyteArray JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_nli_1to_1stream
( JNIEnv* env, jclass C, jlong jniGlobalPtr, jobject njli )
( JNIEnv* env, jclass C, jlong jniGlobalPtr, jobject jnli )
{
LOG_FUNC();
JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr;
@ -715,7 +817,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_nli_1to_1stream
jbyteArray result;
NetLaunchInfo nli = {0};
loadNLI( env, &nli, njli );
loadNLI( env, &nli, jnli );
/* CurGameInfo* gi = makeGI( MPPARM(mpool) env, jgi ); */
XWStreamCtxt* stream = mem_stream_make( MPPARM(mpool) globalState->vtMgr,
NULL, 0, NULL );
@ -1877,10 +1979,8 @@ Java_org_eehouse_android_xw4_jni_XwJNI_comms_1getAddrs
jclass clas = (*env)->FindClass( env, PKG_PATH("jni/CommsAddrRec") );
result = (*env)->NewObjectArray( env, count, clas, NULL );
jmethodID initId = (*env)->GetMethodID( env, clas, "<init>", "()V" );
for ( int ii = 0; ii < count; ++ii ) {
jobject jaddr = (*env)->NewObject( env, clas, initId );
setJAddrRec( env, jaddr, &addrs[ii] );
jobject jaddr = makeJAddr( env, &addrs[ii] );
(*env)->SetObjectArrayElement( env, result, ii, jaddr );
deleteLocalRef( env, jaddr );
}
@ -1976,6 +2076,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_game_1summarize
}
break;
case COMMS_CONN_NFC:
case COMMS_CONN_MQTT:
break;
#if defined XWFEATURE_BLUETOOTH || defined XWFEATURE_SMS || defined XWFEATURE_P2P
case COMMS_CONN_BT:

View file

@ -27,9 +27,6 @@
#include "draw.h"
#include "xwstream.h"
/* typedef struct BoardVTable { */
/* } BoardVTable; */
#ifdef CPLUS
extern "C" {
#endif

View file

@ -622,6 +622,9 @@ addrFromStreamOne( CommsAddrRec* addrP, XWStreamCtxt* stream, CommsConnType typ
break;
case COMMS_CONN_NFC:
break;
case COMMS_CONN_MQTT:
stream_getBytes( stream, &addrP->u.mqtt.devID, sizeof(addrP->u.mqtt.devID) );
break;
default:
/* shut up, compiler */
break;
@ -872,6 +875,9 @@ addrToStreamOne( XWStreamCtxt* stream, CommsConnType typ, const CommsAddrRec* ad
break;
case COMMS_CONN_NFC:
break;
case COMMS_CONN_MQTT:
stream_putBytes( stream, &addrP->u.mqtt.devID, sizeof(addrP->u.mqtt.devID) );
break;
default:
XP_ASSERT(0);
break;
@ -2021,6 +2027,7 @@ preProcess( CommsCtxt* comms, XWEnv xwe, const CommsAddrRec* useAddr,
case COMMS_CONN_P2P:
break; /* nothing to grab?? */
case COMMS_CONN_NFC:
case COMMS_CONN_MQTT:
break; /* nothing to grab?? */
default:
XP_ASSERT(0);
@ -2109,8 +2116,8 @@ getRecordFor( CommsCtxt* comms, XWEnv xwe, const CommsAddrRec* addr,
}
}
XP_LOGF( "%s(%s, maskChannel=%s) => %p", __func__,
cbuf, maskChannel? "true":"false", rec );
XP_LOGFF( "(%s, maskChannel=%s) => %p", cbuf,
maskChannel? "true":"false", rec );
return rec;
} /* getRecordFor */
@ -2503,17 +2510,24 @@ comms_isConnected( const CommsCtxt* const comms )
XP_Bool result = XP_FALSE;
CommsConnType typ;
for ( XP_U32 st = 0; !result && addr_iter( &comms->addr, &typ, &st ); ) {
XP_Bool expected = XP_FALSE;
switch ( typ ) {
case COMMS_CONN_RELAY:
result = 0 != comms->rr.connName[0];
expected = XP_TRUE;
break;
case COMMS_CONN_SMS:
case COMMS_CONN_BT:
case COMMS_CONN_P2P:
result = comms->connID != CONN_ID_NONE;
case COMMS_CONN_MQTT:
expected = XP_TRUE;
default:
result = comms->connID != CONN_ID_NONE;
break;
}
if ( ! expected ) {
XP_LOGFF( "unexpected type %s", ConnType2Str(typ) );
}
}
return result;
}
@ -2666,6 +2680,7 @@ ConnType2Str( CommsConnType typ )
CASESTR( COMMS_CONN_P2P );
CASESTR( COMMS_CONN_NTYPES );
CASESTR( COMMS_CONN_NFC );
CASESTR( COMMS_CONN_MQTT );
default:
XP_ASSERT(0);
}
@ -2819,6 +2834,13 @@ logAddr( const CommsCtxt* comms, XWEnv xwe,
break;
case COMMS_CONN_NFC:
break;
case COMMS_CONN_MQTT: {
stream_catString( stream, "mqtt devID: " );
XP_UCHAR buf[32];
XP_SNPRINTF( buf, VSIZE(buf), MQTTDevID_FMT, addr->u.mqtt.devID );
stream_catString( stream, buf );
}
break;
default:
XP_ASSERT(0);
}
@ -2899,6 +2921,11 @@ augmentAddr( CommsAddrRec* destAddr, const CommsAddrRec* srcAddr )
#endif
case COMMS_CONN_NFC:
break;
case COMMS_CONN_MQTT:
dest = &destAddr->u.mqtt;
src = &srcAddr->u.mqtt;
siz = sizeof(destAddr->u.mqtt);
break;
default:
XP_ASSERT(0);
break;

View file

@ -22,6 +22,7 @@
#define _COMMS_H_
#include "comtypes.h"
#include "commstyp.h"
#include "mempool.h"
#include "xwrelay.h"
#include "server.h"
@ -43,6 +44,7 @@ typedef enum {
,COMMS_CONN_SMS
,COMMS_CONN_P2P /* a.k.a. Wifi direct */
,COMMS_CONN_NFC
,COMMS_CONN_MQTT
,COMMS_CONN_NTYPES
} CommsConnType;
@ -66,57 +68,12 @@ typedef enum {
# define XW_BT_NAME "CrossWords"
#endif
/* on Palm BtLibDeviceAddressType is a 48-bit quantity. Linux's typeis the
same size. Goal is something all platforms support */
typedef struct XP_BtAddr { XP_U8 bits[6]; } XP_BtAddr;
typedef struct XP_BtAddrStr { XP_UCHAR chars[18]; } XP_BtAddrStr;
#ifdef COMMS_HEARTBEAT
# define IF_CH(a) a,
#else
# define IF_CH(a)
#endif
#define MAX_HOSTNAME_LEN 63
#define MAX_PHONE_LEN 31
#define MAX_P2P_MAC_LEN 17
typedef struct _CommsAddrRec {
XP_U16 _conTypes;
struct {
struct {
XP_UCHAR hostName_ip[MAX_HOSTNAME_LEN + 1];
XP_U32 ipAddr_ip; /* looked up from above */
XP_U16 port_ip;
} ip;
struct {
XP_UCHAR invite[MAX_INVITE_LEN + 1]; /* room!!!! */
XP_UCHAR hostName[MAX_HOSTNAME_LEN + 1];
XP_U32 ipAddr; /* looked up from above */
XP_U16 port;
XP_Bool seeksPublicRoom;
XP_Bool advertiseRoom;
} ip_relay;
struct {
/* nothing? */
XP_UCHAR foo; /* wince doesn't like nothing here */
} ir;
struct {
/* guests can browse for the host to connect to */
XP_UCHAR hostName[MAX_HOSTNAME_LEN + 1];
XP_BtAddrStr btAddr;
} bt;
struct {
XP_UCHAR phone[MAX_PHONE_LEN + 1];
XP_U16 port;
} sms;
struct {
XP_UCHAR mac_addr[MAX_P2P_MAC_LEN + 1];
} p2p;
} u;
} CommsAddrRec;
typedef XP_S16 (*TransportSend)( XWEnv xwe, const XP_U8* buf, XP_U16 len,
const XP_UCHAR* msgNo,
const CommsAddrRec* addr,

75
xwords4/common/commstyp.h Normal file
View file

@ -0,0 +1,75 @@
/* -*- compile-command: "cd ../linux && make MEMDEBUG=TRUE -j3"; -*- */
/*
* Copyright 2001 - 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.
*/
#ifndef _COMMSTYP_H_
#define _COMMSTYP_H_
#include "comtypes.h"
#include "xwrelay.h"
#define MAX_HOSTNAME_LEN 63
#define MAX_PHONE_LEN 31
#define MAX_P2P_MAC_LEN 17
/* on Palm BtLibDeviceAddressType is a 48-bit quantity. Linux's typeis the
same size. Goal is something all platforms support */
typedef struct XP_BtAddr { XP_U8 bits[6]; } XP_BtAddr;
typedef struct XP_BtAddrStr { XP_UCHAR chars[18]; } XP_BtAddrStr;
typedef struct _CommsAddrRec {
XP_U16 _conTypes;
struct {
struct {
XP_UCHAR hostName_ip[MAX_HOSTNAME_LEN + 1];
XP_U32 ipAddr_ip; /* looked up from above */
XP_U16 port_ip;
} ip;
struct {
XP_UCHAR invite[MAX_INVITE_LEN + 1]; /* room!!!! */
XP_UCHAR hostName[MAX_HOSTNAME_LEN + 1];
XP_U32 ipAddr; /* looked up from above */
XP_U16 port;
XP_Bool seeksPublicRoom;
XP_Bool advertiseRoom;
} ip_relay;
struct {
/* nothing? */
XP_UCHAR foo; /* wince doesn't like nothing here */
} ir;
struct {
/* guests can browse for the host to connect to */
XP_UCHAR hostName[MAX_HOSTNAME_LEN + 1];
XP_BtAddrStr btAddr;
} bt;
struct {
XP_UCHAR phone[MAX_PHONE_LEN + 1];
XP_U16 port;
} sms;
struct {
MQTTDevID devID;
} mqtt;
struct {
XP_UCHAR mac_addr[MAX_P2P_MAC_LEN + 1];
} p2p;
} u;
} CommsAddrRec;
#endif

View file

@ -246,6 +246,47 @@ typedef struct _PlayerDicts {
DictionaryCtxt* dicts[MAX_NUM_PLAYERS];
} PlayerDicts;
typedef uint64_t MQTTDevID;
#if __WORDSIZE == 64
# define MQTTDevID_FMT "%lX"
#elif __WORDSIZE == 32
# define MQTTDevID_FMT "%llX"
#endif
# define MQTTTopic_FMT "xw4/device/" MQTTDevID_FMT
/* Used by scoring code and engine as fast representation of moves. */
typedef struct _MoveInfoTile {
XP_U8 varCoord; /* 5 bits ok (0-16 for 17x17 board) */
Tile tile; /* 6 bits will do */
} MoveInfoTile;
typedef struct MoveInfo {
XP_U8 nTiles; /* 4 bits: 0-7 */
XP_U8 commonCoord; /* 5 bits: 0-16 if 17x17 possible */
XP_Bool isHorizontal; /* 1 bit */
/* If this is to go on an undo stack, we need player num here, or the code
has to keep track of it *and* there must be exactly one entry per
player per turn. */
MoveInfoTile tiles[MAX_TRAY_TILES];
} MoveInfo;
typedef struct _LastMoveInfo {
const XP_UCHAR* names[MAX_NUM_PLAYERS];
XP_U16 nWinners; /* >1 possible in duplicate case only */
XP_U16 score;
XP_U16 nTiles;
XP_UCHAR word[MAX_COLS * 2]; /* be safe */
XP_U8 moveType;
XP_Bool inDuplicateMode;
} LastMoveInfo;
typedef XP_U8 TrayTile;
typedef struct _TrayTileSet {
XP_U8 nTiles;
TrayTile tiles[MAX_TRAY_TILES];
} TrayTileSet;
#ifdef XWFEATURE_BLUETOOTH
/* temporary debugging hack */

View file

@ -21,14 +21,8 @@
#include "comtypes.h"
#include "memstream.h"
#include "xwstream.h"
#ifdef XWFEATURE_DEVICE
# define KEY_DEVSTATE PERSIST_KEY("devState")
typedef struct _DevCtxt {
XP_U16 devCount;
} DevCtxt;
#include "strutils.h"
#include "nli.h"
static XWStreamCtxt*
mkStream( XW_DUtilCtxt* dutil )
@ -38,6 +32,13 @@ mkStream( XW_DUtilCtxt* dutil )
return stream;
}
#ifdef XWFEATURE_DEVICE
# define KEY_DEVSTATE PERSIST_KEY("devState")
typedef struct _DevCtxt {
XP_U16 devCount;
} DevCtxt;
static DevCtxt*
load( XW_DUtilCtxt* dutil, XWEnv xwe )
{
@ -64,7 +65,7 @@ load( XW_DUtilCtxt* dutil, XWEnv xwe )
}
void
device_store( XW_DUtilCtxt* dutil, XWEnv xwe )
dvc_store( XW_DUtilCtxt* dutil, XWEnv xwe )
{
LOG_FUNC();
DevCtxt* state = load( dutil, xwe );
@ -77,3 +78,100 @@ device_store( XW_DUtilCtxt* dutil, XWEnv xwe )
}
#endif
#define MQTT_DEVID_KEY "mqtt_devid_key"
void
dvc_getMQTTDevID( XW_DUtilCtxt* dutil, XWEnv xwe, MQTTDevID* devID )
{
MQTTDevID tmp = 0;
XP_U16 len = sizeof(tmp);
dutil_loadPtr( dutil, xwe, MQTT_DEVID_KEY, &tmp, &len );
// XP_LOGFF( "len: %d; sizeof(tmp): %d", len, sizeof(tmp) );
if ( len != sizeof(tmp) ) { /* we have it!!! */
tmp = XP_RANDOM();
tmp <<= 32;
tmp |= XP_RANDOM();
dutil_storePtr( dutil, xwe, MQTT_DEVID_KEY, &tmp, sizeof(tmp) );
#ifdef DEBUG
XP_UCHAR buf[32];
formatMQTTDevID( &tmp, buf, VSIZE(buf) );
/* This log statement is required by discon_ok2.py!!! (keep in sync) */
XP_LOGFF( "generated id: %s", buf );
#endif
}
*devID = tmp;
// LOG_RETURNF( MQTTDevID_FMT, *devID );
}
typedef enum { CMD_INVITE, CMD_MSG, CMD_DEVGONE, } MQTTCmd;
void
dvc_makeMQTTInvite( XWStreamCtxt* stream, const NetLaunchInfo* nli )
{
stream_putU8( stream, CMD_INVITE );
nli_saveToStream( nli, stream );
}
static void
addCmdAddrAndGameID( XW_DUtilCtxt* dutil, XWEnv xwe, MQTTCmd cmd, XP_U32 gameID,
XWStreamCtxt* stream)
{
stream_putU8( stream, cmd );
MQTTDevID myID;
dvc_getMQTTDevID( dutil, xwe, &myID );
stream_putBytes( stream, &myID, sizeof(myID) );
stream_putU32( stream, gameID );
}
void
dvc_makeMQTTMessage( XW_DUtilCtxt* dutil, XWEnv xwe, XWStreamCtxt* stream,
XP_U32 gameID, const XP_U8* buf, XP_U16 len )
{
addCmdAddrAndGameID( dutil, xwe, CMD_MSG, gameID, stream);
stream_putBytes( stream, buf, len );
}
void
dvc_makeMQTTNoSuchGame( XW_DUtilCtxt* dutil, XWEnv xwe,
XWStreamCtxt* stream, XP_U32 gameID )
{
addCmdAddrAndGameID( dutil, xwe, CMD_DEVGONE, gameID, stream);
}
void
dvc_parseMQTTPacket( XW_DUtilCtxt* dutil, XWEnv xwe, const XP_U8* buf, XP_U16 len )
{
XWStreamCtxt* stream = mkStream( dutil );
stream_putBytes( stream, buf, len );
MQTTCmd cmd = stream_getU8( stream );
switch ( cmd ) {
case CMD_INVITE: {
NetLaunchInfo nli = {0};
if ( nli_makeFromStream( &nli, stream ) ) {
dutil_onInviteReceived( dutil, xwe, &nli );
}
}
break;
case CMD_DEVGONE:
case CMD_MSG: {
CommsAddrRec from = {0};
addr_addType( &from, COMMS_CONN_MQTT );
stream_getBytes( stream, &from.u.mqtt.devID, sizeof(from.u.mqtt.devID) );
XP_U32 gameID = stream_getU32( stream );
if ( CMD_MSG == cmd ) {
dutil_onMessageReceived( dutil, xwe, gameID, &from, stream );
} else if ( CMD_DEVGONE == cmd ) {
dutil_onGameGoneReceived( dutil, xwe, gameID, &from );
}
}
break;
default:
XP_LOGFF( "unknown command %d; dropping message", cmd );
XP_ASSERT(0);
}
stream_destroy( stream, xwe );
}

View file

@ -25,9 +25,17 @@
// void device_load( XW_DUtilCtxt dctxt );
# ifdef XWFEATURE_DEVICE
void device_store( XW_DUtilCtxt* dctxt, XWEnv xwe );
void dvc_store( XW_DUtilCtxt* dctxt, XWEnv xwe );
# else
# define device_store(dctxt, xwe)
# define dvc_store(dctxt, xwe)
# endif
void dvc_getMQTTDevID( XW_DUtilCtxt* dutil, XWEnv xwe, MQTTDevID* devID );
void dvc_makeMQTTInvite( XWStreamCtxt* stream, const NetLaunchInfo* nli);
void dvc_makeMQTTMessage( XW_DUtilCtxt* dutil, XWEnv xwe, XWStreamCtxt* stream,
XP_U32 gameID, const XP_U8* buf, XP_U16 len );
void dvc_makeMQTTNoSuchGame( XW_DUtilCtxt* dutil, XWEnv xwe,
XWStreamCtxt* stream, XP_U32 gameID );
void dvc_parseMQTTPacket( XW_DUtilCtxt* dutil, XWEnv xwe, const XP_U8* buf, XP_U16 len );
#endif

View file

@ -21,10 +21,12 @@
#ifndef _DEVUTIL_H_
#define _DEVUTIL_H_
#include "mempool.h"
#include "comtypes.h"
#include "mempool.h"
#include "xwrelay.h"
#include "vtabmgr.h"
#include "commstyp.h"
#include "nlityp.h"
typedef enum { UNPAUSED,
PAUSED,
@ -68,6 +70,13 @@ typedef struct _DUtilVtable {
const XP_UCHAR* name, const XP_UCHAR* msg );
void (*m_dutil_onDupTimerChanged)( XW_DUtilCtxt* duc, XWEnv xwe, XP_U32 gameID,
XP_U32 oldVal, XP_U32 newVal );
void (*m_dutil_onInviteReceived)( XW_DUtilCtxt* duc, XWEnv xwe,
const NetLaunchInfo* nli );
void (*m_dutil_onMessageReceived)( XW_DUtilCtxt* duc, XWEnv xwe, XP_U32 gameID,
const CommsAddrRec* from, XWStreamCtxt* stream );
void (*m_dutil_onGameGoneReceived)( XW_DUtilCtxt* duc, XWEnv xwe, XP_U32 gameID,
const CommsAddrRec* from );
} DUtilVtable;
struct XW_DUtilCtxt {
@ -119,4 +128,13 @@ struct XW_DUtilCtxt {
#define dutil_onDupTimerChanged(duc, e, id, ov, nv) \
(duc)->vtable.m_dutil_onDupTimerChanged( (duc), (e), (id), (ov), (nv))
#define dutil_onInviteReceived(duc, xwe, nli) \
(duc)->vtable.m_dutil_onInviteReceived( (duc), (xwe), (nli) )
#define dutil_onMessageReceived(duc, xwe, gameID, from, stream) \
(duc)->vtable.m_dutil_onMessageReceived((duc),(xwe),(gameID),(from),(stream))
#define dutil_onGameGoneReceived(duc, xwe, gameID, from) \
(duc)->vtable.m_dutil_onGameGoneReceived((duc),(xwe),(gameID),(from))
#endif

View file

@ -56,37 +56,6 @@ extern "C" {
#define MAX_UNIQUE_TILES 64 /* max tile non-blank faces */
#define MAX_NUM_BLANKS 4
/* Used by scoring code and engine as fast representation of moves. */
typedef struct _MoveInfoTile {
XP_U8 varCoord; /* 5 bits ok (0-16 for 17x17 board) */
Tile tile; /* 6 bits will do */
} MoveInfoTile;
typedef struct MoveInfo {
XP_U8 nTiles; /* 4 bits: 0-7 */
XP_U8 commonCoord; /* 5 bits: 0-16 if 17x17 possible */
XP_Bool isHorizontal; /* 1 bit */
/* If this is to go on an undo stack, we need player num here, or the code
has to keep track of it *and* there must be exactly one entry per
player per turn. */
MoveInfoTile tiles[MAX_TRAY_TILES];
} MoveInfo;
typedef struct _LastMoveInfo {
const XP_UCHAR* names[MAX_NUM_PLAYERS];
XP_U16 nWinners; /* >1 possible in duplicate case only */
XP_U16 score;
XP_U16 nTiles;
XP_UCHAR word[MAX_COLS * 2]; /* be safe */
XP_U8 moveType;
XP_Bool inDuplicateMode;
} LastMoveInfo;
typedef XP_U8 TrayTile;
typedef struct _TrayTileSet {
XP_U8 nTiles;
TrayTile tiles[MAX_TRAY_TILES];
} TrayTileSet;
typedef struct BlankQueue {
XP_U16 nBlanks;

View file

@ -55,6 +55,9 @@ nli_init( NetLaunchInfo* nli, const CurGameInfo* gi, const CommsAddrRec* addr,
XP_STRCAT( nli->phone, addr->u.sms.phone );
// nli->port = addr->u.sms.port; <-- I wish
break;
case COMMS_CONN_MQTT:
nli_setMQTTDevID( nli, &addr->u.mqtt.devID );
break;
default:
XP_ASSERT(0);
break;
@ -86,6 +89,13 @@ nli_setInviteID( NetLaunchInfo* nli, const XP_UCHAR* inviteID )
XP_STRCAT( nli->inviteID, inviteID );
}
void
nli_setMQTTDevID( NetLaunchInfo* nli, const MQTTDevID* mqttDevID )
{
types_addType( &nli->_conTypes, COMMS_CONN_MQTT );
formatMQTTDevID( mqttDevID, nli->mqttDevID, VSIZE(nli->mqttDevID) );
}
void
nli_saveToStream( const NetLaunchInfo* nli, XWStreamCtxt* stream )
{
@ -118,6 +128,9 @@ nli_saveToStream( const NetLaunchInfo* nli, XWStreamCtxt* stream )
stream_putU8( stream, nli->osType );
stream_putU32( stream, nli->osVers );
}
if ( types_hasType( nli->_conTypes, COMMS_CONN_MQTT ) ) {
stringToStream( stream, nli->mqttDevID );
}
if ( NLI_VERSION > 0 ) {
stream_putBits( stream, 1, nli->remotesAreRobots ? 1 : 0 );
@ -160,6 +173,9 @@ nli_makeFromStream( NetLaunchInfo* nli, XWStreamCtxt* stream )
nli->osType= stream_getU8( stream );
nli->osVers = stream_getU32( stream );
}
if ( types_hasType( nli->_conTypes, COMMS_CONN_MQTT ) ) {
stringFromStreamHere( stream, nli->mqttDevID, sizeof(nli->mqttDevID) );
}
if ( version > 0 && 0 < stream_getSize( stream ) ) {
nli->remotesAreRobots = 0 != stream_getBits( stream, 1 );
@ -198,6 +214,16 @@ nli_makeAddrRec( const NetLaunchInfo* nli, CommsAddrRec* addr )
XP_STRCAT( addr->u.sms.phone, nli->phone );
addr->u.sms.port = 1; /* BAD, but 0 is worse */
break;
case COMMS_CONN_MQTT: {
#ifdef DEBUG
XP_Bool success =
#endif
strToMQTTCDevID( nli->mqttDevID, &addr->u.mqtt.devID );
XP_ASSERT( success );
}
break;
case COMMS_CONN_NFC:
break;
default:
XP_ASSERT(0);
break;
@ -212,9 +238,10 @@ logNLI( const NetLaunchInfo* nli, const char* callerFunc, const int callerLine )
XP_LOGFF( "called by %s(), line %d", callerFunc, callerLine );
XP_UCHAR buf[256];
XP_SNPRINTF( buf, VSIZE(buf), "{nPlayersT: %d; nPlayersH: %d; "
"gameID: %d; inviteID: %s}",
nli->nPlayersT, nli->nPlayersH, nli->gameID, nli->inviteID );
XP_SNPRINTF( buf, VSIZE(buf), "{ctyps: %x, nPlayersT: %d; nPlayersH: %d; "
"gameID: %d; inviteID: %s, mqttid: %s}", nli->_conTypes,
nli->nPlayersT, nli->nPlayersH, nli->gameID, nli->inviteID,
nli->mqttDevID );
XP_LOGF( "%s", buf );
}
# endif

View file

@ -22,63 +22,30 @@
#define _INVIT_H_
// #include "comms.h"
#include "nlityp.h"
#include "xwstream.h"
#include "game.h"
#define MAX_GAME_NAME_LEN 64
#define MAX_DICT_NAME_LEN 32
typedef enum {OSType_NONE, OSType_LINUX, OSType_ANDROID, } XP_OSType;
/* InviteInfo
*
* A representation of return addresses sent with an invitation so that the
* recipient has all it needs to create a game and connect back.
*/
typedef struct _InviteInfo {
XP_U16 _conTypes;
XP_UCHAR gameName[MAX_GAME_NAME_LEN];
XP_UCHAR dict[MAX_DICT_NAME_LEN];
XP_LangCode lang;
XP_U8 forceChannel;
XP_U8 nPlayersT;
XP_U8 nPlayersH;
XP_Bool remotesAreRobots;
XP_Bool inDuplicateMode;
/* Relay */
XP_UCHAR room[MAX_INVITE_LEN + 1];
XP_U32 devID; /* not used on android; remove?? */
/* BT */
XP_UCHAR btName[32];
XP_UCHAR btAddress[32];
// SMS
XP_UCHAR phone[32];
XP_Bool isGSM;
XP_OSType osType;
XP_U32 osVers;
XP_U32 gameID;
XP_UCHAR inviteID[32];
} NetLaunchInfo;
void
nli_init( NetLaunchInfo* invit, const CurGameInfo* gi, const CommsAddrRec* addr,
nli_init( NetLaunchInfo* nli, const CurGameInfo* gi, const CommsAddrRec* addr,
XP_U16 nPlayers, XP_U16 forceChannel );
XP_Bool nli_makeFromStream( NetLaunchInfo* invit, XWStreamCtxt* stream );
void nli_saveToStream( const NetLaunchInfo* invit, XWStreamCtxt* stream );
XP_Bool nli_makeFromStream( NetLaunchInfo* nli, XWStreamCtxt* stream );
void nli_saveToStream( const NetLaunchInfo* nli, XWStreamCtxt* stream );
void nli_makeAddrRec( const NetLaunchInfo* invit, CommsAddrRec* addr );
void nli_makeAddrRec( const NetLaunchInfo* nli, CommsAddrRec* addr );
void nli_setDevID( NetLaunchInfo* invit, XP_U32 devID );
void nli_setInviteID( NetLaunchInfo* invit, const XP_UCHAR* inviteID );
void nli_setGameName( NetLaunchInfo* invit, const XP_UCHAR* gameName );
void nli_setDevID( NetLaunchInfo* nli, XP_U32 devID );
void nli_setInviteID( NetLaunchInfo* nli, const XP_UCHAR* inviteID );
void nli_setGameName( NetLaunchInfo* nli, const XP_UCHAR* gameName );
void nli_setMQTTDevID( NetLaunchInfo* nli, const MQTTDevID* mqttDevID );
# ifdef DEBUG
void logNLI( const NetLaunchInfo* nli, const char* callerFunc, const int callerLine );

65
xwords4/common/nlityp.h Normal file
View file

@ -0,0 +1,65 @@
/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */
/*
* Copyright 2018 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.
*/
#ifndef _NLITYP_H_
#define _NLITYP_H_
#include "comtypes.h"
#include "xwrelay.h"
#define MAX_GAME_NAME_LEN 64
#define MAX_DICT_NAME_LEN 32
typedef enum {OSType_NONE, OSType_LINUX, OSType_ANDROID, } XP_OSType;
typedef struct _NetLaunchInfo {
XP_U16 _conTypes;
XP_UCHAR gameName[MAX_GAME_NAME_LEN];
XP_UCHAR dict[MAX_DICT_NAME_LEN];
XP_LangCode lang;
XP_U8 forceChannel;
XP_U8 nPlayersT;
XP_U8 nPlayersH;
XP_Bool remotesAreRobots;
XP_Bool inDuplicateMode;
/* Relay */
XP_UCHAR room[MAX_INVITE_LEN + 1];
XP_U32 devID; /* not used on android; remove?? */
/* BT */
XP_UCHAR btName[32];
XP_UCHAR btAddress[32];
// SMS
XP_UCHAR phone[32];
XP_Bool isGSM;
XP_OSType osType;
XP_U32 osVers;
XP_U32 gameID;
XP_UCHAR inviteID[32];
/* MQTT */
XP_UCHAR mqttDevID[17];
} NetLaunchInfo;
#endif

View file

@ -555,6 +555,33 @@ smsToBin( XP_U8* out, XP_U16* outlenp, const XP_UCHAR* sms, XP_U16 smslen )
#endif
const XP_UCHAR*
formatMQTTDevID( const MQTTDevID* devid, XP_UCHAR* buf, XP_U16 bufLen )
{
XP_SNPRINTF( buf, bufLen, MQTTDevID_FMT, *devid );
return buf;
}
const XP_UCHAR*
formatMQTTTopic( const MQTTDevID* devid, XP_UCHAR* buf, XP_U16 bufLen )
{
XP_SNPRINTF( buf, bufLen, MQTTTopic_FMT, *devid );
// LOG_RETURNF( "%s", buf );
return buf;
}
XP_Bool
strToMQTTCDevID( const XP_UCHAR* str, MQTTDevID* result )
{
MQTTDevID tmp;
int nMatched = sscanf( str, MQTTDevID_FMT, &tmp );
XP_Bool success = nMatched == 1;
if ( success ) {
*result = tmp;
}
return success;
}
#ifdef DEBUG
#define NUM_PER_LINE 8
void

View file

@ -108,6 +108,10 @@ void binToSms( XP_UCHAR* out, XP_U16* outlen, const XP_U8* in, XP_U16 inlen );
XP_Bool smsToBin( XP_U8* out, XP_U16* outlen, const XP_UCHAR* in, XP_U16 inlen );
#endif
const XP_UCHAR* formatMQTTTopic( const MQTTDevID* devid, XP_UCHAR* buf, XP_U16 bufLen );
const XP_UCHAR* formatMQTTDevID( const MQTTDevID* devid, XP_UCHAR* buf, XP_U16 bufLen );
XP_Bool strToMQTTCDevID( const XP_UCHAR* str, MQTTDevID* result );
#ifdef DEBUG
void assertSorted( const MoveInfo* mi );
void log_hex( const XP_U8* memp, XP_U16 len, const char* tag );

View file

@ -234,12 +234,14 @@ OBJ = \
$(BUILD_PLAT_DIR)/linuxutl.o \
$(BUILD_PLAT_DIR)/gamesdb.o \
$(BUILD_PLAT_DIR)/relaycon.o \
$(BUILD_PLAT_DIR)/mqttcon.o \
$(BUILD_PLAT_DIR)/lindutil.o \
$(CURSES_OBJS) $(GTK_OBJS) $(MAIN_OBJS)
LIBS = -lm -lpthread -luuid -lcurl -ljson-c $(GPROFFLAG)
ifdef USE_SQLITE
LIBS += -lsqlite3
LIBS += -lmosquitto
DEFINES += -DUSE_SQLITE
endif
# Turn this off for now. I apparently have a memory problem, but it

View file

@ -24,6 +24,8 @@
#include "curgamlistwin.h"
#include "linuxmain.h"
#include "device.h"
#include "strutils.h"
struct CursGameList {
WINDOW* window;
@ -295,8 +297,12 @@ cgl_draw( CursGameList* cgl )
XP_U32 relayID = linux_getDevIDRelay( cgl->params );
char buf[cgl->width + 1];
snprintf( buf, VSIZE(buf), "pid: %d; nGames: %d, relayid: %d",
cgl->pid, nGames, relayID );
MQTTDevID devID;
dvc_getMQTTDevID( cgl->params->dutil, NULL_XWE, &devID );
XP_UCHAR didBuf[32];
snprintf( buf, VSIZE(buf), "pid: %d; nGames: %d, relayid: %d, mqttid: %s",
cgl->pid, nGames, relayID, formatMQTTDevID( &devID, didBuf, VSIZE(didBuf) ) );
mvwaddstr( win, 0, 0, buf );
wrefresh( win );

View file

@ -25,6 +25,7 @@
#include "linuxmain.h"
#include "linuxutl.h"
#include "relaycon.h"
#include "mqttcon.h"
#include "cursesask.h"
#include "cursesmenu.h"
#include "cursesletterask.h"
@ -161,7 +162,8 @@ inviteIdle( gpointer data )
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)data;
LaunchParams* params = bGlobals->cGlobals.params;
if ( !!params->connInfo.relay.inviteeRelayIDs
|| !!params->connInfo.sms.inviteePhones ) {
|| !!params->connInfo.sms.inviteePhones
|| !!params->connInfo.mqtt.inviteeDevIDs ) {
handleInvite( bGlobals, 0 );
}
return FALSE;
@ -1223,7 +1225,7 @@ handleReplace( void* closure, int XP_UNUSED(key) )
static bool
inviteList( CommonGlobals* cGlobals, CommsAddrRec* addr, GSList* invitees,
bool useRelay )
CommsConnType typ )
{
bool haveAddressees = !!invitees;
if ( haveAddressees ) {
@ -1235,14 +1237,31 @@ inviteList( CommonGlobals* cGlobals, CommsAddrRec* addr, GSList* invitees,
nPlayersH, forceChannel, ii );
NetLaunchInfo nli = {0};
nli_init( &nli, cGlobals->gi, addr, nPlayersH, forceChannel );
if ( useRelay ) {
switch ( typ ) {
case COMMS_CONN_RELAY: {
uint64_t inviteeRelayID = (uint64_t)g_slist_nth_data( invitees, ii );
relaycon_invite( params, (XP_U32)inviteeRelayID, NULL, &nli );
} else {
}
break;
case COMMS_CONN_SMS: {
const gchar* inviteePhone = (const gchar*)g_slist_nth_data( invitees, ii );
linux_sms_invite( params, &nli, inviteePhone,
params->connInfo.sms.port );
}
break;
case COMMS_CONN_MQTT: {
MQTTDevID devID;
const gchar* str = g_slist_nth_data( invitees, ii );
if ( strToMQTTCDevID( str, &devID ) ) {
mqttc_invite( params, &nli, &devID );
} else {
XP_LOGFF( "unable to convert devid %s", str );
}
}
break;
default:
XP_ASSERT(0);
}
}
}
return haveAddressees;
@ -1270,20 +1289,24 @@ handleInvite( void* closure, int XP_UNUSED(key) )
/* Invite first based on an invitee provided. Otherwise, fall back to
doing a send-to-self. Let the recipient code reject a duplicate if
the user so desires. */
} else if ( inviteList( cGlobals, &addr, params->connInfo.sms.inviteePhones, false ) ) {
} else if ( inviteList( cGlobals, &addr, params->connInfo.sms.inviteePhones, COMMS_CONN_SMS ) ) {
/* do nothing */
} else if ( inviteList( cGlobals, &addr, params->connInfo.relay.inviteeRelayIDs, true ) ) {
} else if ( inviteList( cGlobals, &addr, params->connInfo.relay.inviteeRelayIDs, COMMS_CONN_RELAY ) ) {
/* do nothing */
} else if ( inviteList( cGlobals, &addr, params->connInfo.mqtt.inviteeDevIDs, COMMS_CONN_MQTT ) ) {
/* do nothing */
/* Try sending to self, using the phone number or relayID of this device */
} else if ( addr_hasType( &addr, COMMS_CONN_SMS ) ) {
linux_sms_invite( params, &nli, addr.u.sms.phone, addr.u.sms.port );
} else if ( addr_hasType( &addr, COMMS_CONN_MQTT ) ) {
mqttc_invite( params, &nli, mqttc_getDevID( params ) );
} else if ( addr_hasType( &addr, COMMS_CONN_RELAY ) ) {
XP_U32 relayID = linux_getDevIDRelay( params );
if ( 0 != relayID ) {
relaycon_invite( params, relayID, NULL, &nli );
}
} else {
ca_inform( bGlobals->boardWin, "Cannot invite via relayID or by \"sms phone\"." );
ca_inform( bGlobals->boardWin, "Cannot invite via relayID, MQTT or by \"sms phone\"." );
}
LOG_RETURNF( "%s", "TRUE" );
return XP_TRUE;

View file

@ -64,6 +64,7 @@
#include "linuxudp.h"
#include "gamesdb.h"
#include "relaycon.h"
#include "mqttcon.h"
#include "smsproto.h"
#include "device.h"
#include "cursesmenu.h"
@ -1152,10 +1153,11 @@ onJoined( void* closure, const XP_UCHAR* connname, XWHostID hid )
/* } */
#endif
static void
inviteReceivedCurses( CursesAppGlobals* aGlobals, const NetLaunchInfo* invite,
void
inviteReceivedCurses( void* closure, const NetLaunchInfo* invite,
const CommsAddrRec* returnAddr )
{
CursesAppGlobals* aGlobals = (CursesAppGlobals*)closure;
sqlite3_int64 rowids[1];
int nRowIDs = VSIZE(rowids);
getRowsForGameID( aGlobals->cag.params->pDb, invite->gameID, rowids, &nRowIDs );
@ -1176,7 +1178,7 @@ inviteReceivedCurses( CursesAppGlobals* aGlobals, const NetLaunchInfo* invite,
}
static void
relayInviteReceivedCurses( void* closure, NetLaunchInfo* invite )
relayInviteReceivedCurses( void* closure, const NetLaunchInfo* invite )
{
CursesAppGlobals* aGlobals = (CursesAppGlobals*)closure;
CommsAddrRec addr = {0};
@ -1228,6 +1230,21 @@ smsMsgReceivedCurses( void* closure, const CommsAddrRec* from, XP_U32 gameID,
cb_feedGame( aGlobals->cbState, gameID, buf, len, from );
}
void
mqttMsgReceivedCurses( void* closure, const CommsAddrRec* from,
XP_U32 gameID, const XP_U8* buf, XP_U16 len )
{
CursesAppGlobals* aGlobals = (CursesAppGlobals*)closure;
cb_feedGame( aGlobals->cbState, gameID, buf, len, from );
}
void
gameGoneCurses( void* XP_UNUSED(closure), const CommsAddrRec* XP_UNUSED(from),
XP_U32 XP_UNUSED_DBG(gameID) )
{
XP_LOGFF( "(gameID=%d)", gameID );
}
static void
cursesGotForRow( void* XP_UNUSED(closure), const CommsAddrRec* XP_UNUSED(from),
sqlite3_int64 XP_UNUSED(rowid), const XP_U8* XP_UNUSED(buf),
@ -1401,6 +1418,7 @@ cursesmain( XP_Bool XP_UNUSED(isServer), LaunchParams* params )
{
memset( &g_globals, 0, sizeof(g_globals) );
g_globals.cag.params = params;
params->appGlobals = &g_globals;
initCurses( &g_globals );
if ( !params->closeStdin ) {
@ -1494,6 +1512,8 @@ cursesmain( XP_Bool XP_UNUSED(isServer), LaunchParams* params )
linux_doInitialReg( params, idIsNew );
}
mqttc_init( params );
#ifdef XWFEATURE_SMS
gchar* myPhone = NULL;
XP_U16 myPort = 0;
@ -1535,12 +1555,13 @@ cursesmain( XP_Bool XP_UNUSED(isServer), LaunchParams* params )
endwin();
device_store( params->dutil, NULL_XWE );
dvc_store( params->dutil, NULL_XWE );
if ( params->useUdp ) {
relaycon_cleanup( params );
}
linux_sms_cleanup( params );
mqttc_cleanup( params );
} /* cursesmain */
#endif /* PLATFORM_NCURSES */

View file

@ -75,5 +75,10 @@ void cursesDrawCtxtFree( DrawCtx* dctx );
void cursesmain( XP_Bool isServer, LaunchParams* params );
bool handleQuit( void* closure, int unused_key );
void inviteReceivedCurses( void* aGlobals, const NetLaunchInfo* invite,
const CommsAddrRec* returnAddr );
void mqttMsgReceivedCurses( void* closure, const CommsAddrRec* from,
XP_U32 gameID, const XP_U8* buf, XP_U16 len );
void gameGoneCurses( void* closure, const CommsAddrRec* from, XP_U32 gameID );
#endif

View file

@ -372,6 +372,12 @@ summarize( CommonGlobals* cGlobals )
case COMMS_CONN_IP_DIRECT:
strcat( connvia, "IP" );
break;
case COMMS_CONN_MQTT:
strcat( connvia, "MQTT" );
break;
case COMMS_CONN_NFC: /* this should be filtered out!!! */
strcat( connvia, "NFC" );
break;
default:
XP_ASSERT(0);
break;
@ -703,7 +709,7 @@ FetchResult
db_fetch( sqlite3* pDb, const gchar* key, gchar* buf, gint* buflen )
{
XP_ASSERT( !!pDb );
FetchResult result = NOT_THERE;
FetchResult fetchRes = NOT_THERE;
char query[256];
#ifdef DEBUG
int len =
@ -715,20 +721,20 @@ db_fetch( sqlite3* pDb, const gchar* key, gchar* buf, gint* buflen )
int sqlResult = sqlite3_prepare_v2( pDb, query, -1, &ppStmt, NULL );
XP_Bool found = SQLITE_OK == sqlResult;
if ( found ) {
result = sqlite3_step( ppStmt );
found = SQLITE_ROW == result;
sqlResult = sqlite3_step( ppStmt );
found = SQLITE_ROW == sqlResult;
if ( found ) {
if ( getColumnText( ppStmt, 0, buf, buflen ) ) {
result = SUCCESS;
fetchRes = SUCCESS;
} else {
result = BUFFER_TOO_SMALL;
fetchRes = BUFFER_TOO_SMALL;
}
} else if ( !!buf ) {
buf[0] = '\0';
}
}
sqlite3_finalize( ppStmt );
return result;
return fetchRes;
}
XP_Bool

View file

@ -72,6 +72,7 @@
#include "filestream.h"
#include "gamesdb.h"
#include "relaycon.h"
#include "mqttcon.h"
/* static guint gtkSetupClientSocket( GtkGameGlobals* globals, int sock ); */
static void setCtrlsForTray( GtkGameGlobals* globals );
@ -86,6 +87,7 @@ static void gtkShowFinalScores( const GtkGameGlobals* globals,
XP_Bool ignoreTimeout );
static void send_invites( CommonGlobals* cGlobals, XP_U16 nPlayers,
XP_U32 relayDevID, const XP_UCHAR* relayID,
MQTTDevID* mqttInviteeID,
const CommsAddrRec* addrs );
#define GTK_TRAY_HT_ROWS 3
@ -677,7 +679,7 @@ on_board_window_shown( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals )
CommsAddrRec addr = {0};
addrFromStream( &addr, stream );
send_invites( cGlobals, 1, 0, relayID, NULL );
send_invites( cGlobals, 1, 0, relayID, &addr.u.mqtt.devID, &addr );
}
}
stream_destroy( stream, NULL_XWE );
@ -1403,18 +1405,20 @@ handle_invite_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals )
CommsAddrRec inviteAddr = {0};
gint nPlayers = nMissing;
XP_U32 relayDevID = 0;
MQTTDevID mqttInviteeID;
XP_Bool confirmed = gtkInviteDlg( globals, &inviteAddr, &nPlayers,
&relayDevID );
&relayDevID, &mqttInviteeID );
XP_LOGF( "%s: inviteDlg => %d", __func__, confirmed );
if ( confirmed ) {
send_invites( cGlobals, nPlayers, relayDevID, NULL, &inviteAddr );
send_invites( cGlobals, nPlayers, relayDevID, NULL, &mqttInviteeID, &inviteAddr );
}
} /* handle_invite_button */
static void
send_invites( CommonGlobals* cGlobals, XP_U16 nPlayers,
XP_U32 relayDevID, const XP_UCHAR* relayID,
MQTTDevID* mqttInviteeID,
const CommsAddrRec* addrs )
{
CommsAddrRec addr = {0};
@ -1433,6 +1437,11 @@ send_invites( CommonGlobals* cGlobals, XP_U16 nPlayers,
}
// nli_setDevID( &nli, linux_getDevIDRelay( cGlobals->params ) );
if ( addr_hasType( &addr, COMMS_CONN_MQTT ) ) {
const MQTTDevID* devid = mqttc_getDevID( cGlobals->params );
nli_setMQTTDevID( &nli, devid );
}
#ifdef DEBUG
{
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool)
@ -1458,6 +1467,10 @@ send_invites( CommonGlobals* cGlobals, XP_U16 nPlayers,
relaycon_invite( cGlobals->params, relayDevID, relayID, &nli );
}
if ( addr_hasType( addrs, COMMS_CONN_MQTT ) ) {
mqttc_invite( cGlobals->params, &nli, mqttInviteeID );
}
/* while ( gtkaskm( "Invite how many and how?", infos, VSIZE(infos) ) ) { */
/* int nPlayers = atoi( countStr ); */
/* if ( 0 >= nPlayers || nPlayers > nMissing ) { */
@ -1955,7 +1968,7 @@ gtk_util_makeStreamFromAddr(XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe), XP_PlayerAddr
static void
gtk_util_showChat( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe),
const XP_UCHAR* const msg, XP_S16 from,
XP_U32 timestamp )
XP_U32 tsSecs )
{
GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure;
XP_UCHAR buf[1024];
@ -1963,7 +1976,13 @@ gtk_util_showChat( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe),
if ( 0 <= from ) {
name = globals->cGlobals.gi->players[from].name;
}
XP_SNPRINTF( buf, VSIZE(buf), "quoth %s at %d: %s", name, timestamp, msg );
GDateTime* dt = g_date_time_new_from_unix_utc( tsSecs );
gchar* tsStr = g_date_time_format( dt, "%T" );
XP_SNPRINTF( buf, VSIZE(buf), "Quoth %s at %s: \"%s\"", name, tsStr, msg );
g_free( tsStr );
g_date_time_unref (dt);
(void)gtkask( globals->window, buf, GTK_BUTTONS_OK, NULL );
}
#endif

View file

@ -125,6 +125,8 @@ handle_ok( GtkWidget* XP_UNUSED(widget), gpointer closure )
txt = gtk_entry_get_text( GTK_ENTRY(state->smsport) );
state->addr->u.sms.port = atoi( txt );
break;
case COMMS_CONN_MQTT:
break;
default:
XP_ASSERT( 0 ); /* keep compiler happy */
break;
@ -321,7 +323,14 @@ makeSMSPage( GtkConnsState* state, PageData* data )
gtk_widget_show( vbox );
return vbox;
} /* makeBTPage */
} /* makeSMSPage */
static GtkWidget*
makeMQTTPage( GtkConnsState* state, PageData* data )
{
GtkWidget* vbox = boxWithUseCheck( state, data );
return vbox;
} /* makeMQTTPage */
static PageData*
getNextData( GtkConnsState* state, CommsConnType typ, gchar* label )
@ -352,6 +361,10 @@ gtkConnsDlg( GtkGameGlobals* globals, CommsAddrRec* addr, DeviceRole role,
state.notebook = gtk_notebook_new();
PageData* data;
data = getNextData( &state, COMMS_CONN_MQTT, "MQTT" );
(void)gtk_notebook_append_page( GTK_NOTEBOOK(state.notebook),
makeMQTTPage( &state, data ),
data->label );
#ifdef XWFEATURE_RELAY
data = getNextData( &state, COMMS_CONN_RELAY, "Relay" );
(void)gtk_notebook_append_page( GTK_NOTEBOOK(state.notebook),

View file

@ -23,6 +23,8 @@
#include "gtkutils.h"
#include "linuxbt.h"
#include "comtypes.h"
#include "mqttcon.h"
#include "strutils.h"
typedef struct _PageData {
CommsConnType pageType;
@ -39,6 +41,7 @@ typedef struct _GtkInviteState {
CommsAddrRec* addr;
gint* nPlayersP;
XP_U32* relayDevIDp;
MQTTDevID* mqttDevIDp;
gint maxPlayers;
GtkWidget* nPlayersCombo;
@ -50,6 +53,8 @@ typedef struct _GtkInviteState {
GtkWidget* smsphone;
GtkWidget* smsport;
GtkWidget* mqttDevID;
GtkWidget* bgScanButton;
GtkWidget* okButton;
@ -107,12 +112,17 @@ handle_ok( GtkWidget* XP_UNUSED(widget), gpointer closure )
break;
#endif
case COMMS_CONN_SMS:
txt = gtk_entry_get_text( GTK_ENTRY(state->smsphone) );
XP_STRNCPY( state->addr->u.sms.phone, txt,
sizeof(state->addr->u.sms.phone) );
txt = gtk_entry_get_text( GTK_ENTRY(state->smsport) );
state->addr->u.sms.port = atoi( txt );
break;
case COMMS_CONN_MQTT:
txt = gtk_entry_get_text( GTK_ENTRY(state->mqttDevID) );
if ( strToMQTTCDevID( txt, &state->addr->u.mqtt.devID ) ) {
*state->mqttDevIDp = state->addr->u.mqtt.devID;
} else {
XP_ASSERT(0);
}
break;
default:
XP_ASSERT( 0 ); /* keep compiler happy */
break;
@ -253,6 +263,23 @@ makeSMSPage( GtkInviteState* state, PageData* data )
return vbox;
} /* makeBTPage */
static GtkWidget*
makeMQTTPage( GtkInviteState* state, PageData* data )
{
data->okButtonTxt = "Invite via MQTT";
GtkWidget* vbox = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 );
GtkWidget* hbox;
hbox = makeLabeledField( "Invitee MQTT DevID", &state->mqttDevID, NULL );
// gtk_entry_set_text( GTK_ENTRY(state->mqttDevID), s_mqttIDBuf );
gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 );
gtk_widget_show( vbox );
return vbox;
}
static PageData*
getNextData( GtkInviteState* state, CommsConnType typ, gchar* label )
{
@ -275,7 +302,8 @@ onPageChanged( GtkNotebook* XP_UNUSED(notebook), gpointer XP_UNUSED(arg1),
XP_Bool
gtkInviteDlg( GtkGameGlobals* globals, CommsAddrRec* addr,
gint* nPlayersP, XP_U32* relayDevIDp )
gint* nPlayersP, XP_U32* relayDevIDp,
MQTTDevID* mqttDevIDp )
{
GtkInviteState state = {
.globals = globals,
@ -283,6 +311,7 @@ gtkInviteDlg( GtkGameGlobals* globals, CommsAddrRec* addr,
.nPlayersP = nPlayersP,
.relayDevIDp = relayDevIDp,
.maxPlayers = *nPlayersP,
.mqttDevIDp = mqttDevIDp,
};
GtkWidget* dialog;
@ -310,6 +339,10 @@ gtkInviteDlg( GtkGameGlobals* globals, CommsAddrRec* addr,
PageData* data;
data = getNextData( &state, COMMS_CONN_MQTT, "MQTT" );
(void)gtk_notebook_append_page( GTK_NOTEBOOK(state.notebook),
makeMQTTPage( &state, data ),
data->label );
#ifdef XWFEATURE_RELAY
data = getNextData( &state, COMMS_CONN_RELAY, "Relay" );
(void)gtk_notebook_append_page( GTK_NOTEBOOK(state.notebook),

View file

@ -24,7 +24,8 @@
/* return true if not cancelled */
XP_Bool gtkInviteDlg( GtkGameGlobals* globals, CommsAddrRec* addr,
/*inout*/ gint* nPlayers, /* out */ XP_U32* relayDevID );
/*inout*/ gint* nPlayers, /* out */ XP_U32* relayDevID,
MQTTDevID* mqttInviteeID );
#endif

View file

@ -29,6 +29,7 @@
#include "linuxmain.h"
#include "linuxutl.h"
#include "relaycon.h"
#include "mqttcon.h"
#include "linuxsms.h"
#include "gtkask.h"
#include "device.h"
@ -502,6 +503,13 @@ setWindowTitle( GtkAppGlobals* apg )
len = strlen( title );
snprintf( &title[len], VSIZE(title) - len, " (relayid: %d)", relayID );
#endif
len = strlen( title );
MQTTDevID devID;
dvc_getMQTTDevID( params->dutil, NULL_XWE, &devID );
XP_UCHAR didBuf[32];
snprintf( &title[len], VSIZE(title) - len, " (mqtt: %s)",
formatMQTTDevID( &devID, didBuf, VSIZE(didBuf) ) );
gtk_window_set_title( GTK_WINDOW(window), title );
}
@ -536,10 +544,19 @@ handle_relayid_to_clip( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* apg )
XP_U32 relayID = linux_getDevIDRelay( params );
gchar str[32];
snprintf( &str[0], VSIZE(str), "%d", relayID );
GtkClipboard *clipboard = gtk_clipboard_get( GDK_SELECTION_CLIPBOARD );
GtkClipboard* clipboard = gtk_clipboard_get( GDK_SELECTION_CLIPBOARD );
gtk_clipboard_set_text( clipboard, str, strlen(str) );
}
static void
handle_mqttid_to_clip( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* apg )
{
LaunchParams* params = apg->cag.params;
const gchar* devIDStr = mqttc_getDevIDStr( params );
GtkClipboard* clipboard = gtk_clipboard_get( GDK_SELECTION_CLIPBOARD );
gtk_clipboard_set_text( clipboard, devIDStr, strlen(devIDStr) );
}
static void
makeGamesWindow( GtkAppGlobals* apg )
{
@ -573,6 +590,8 @@ makeGamesWindow( GtkAppGlobals* apg )
}
(void)createAddItem( netMenu, "copy relayid",
(GCallback)handle_relayid_to_clip, apg );
(void)createAddItem( netMenu, "copy mqtt devid",
(GCallback)handle_mqttid_to_clip, apg );
gtk_widget_show( menubar );
gtk_box_pack_start( GTK_BOX(vbox), menubar, FALSE, TRUE, 0 );
@ -707,8 +726,8 @@ gameFromInvite( GtkAppGlobals* apg, const NetLaunchInfo* invite,
gi_disposePlayerInfo( MPPARM(params->mpool) &gi );
}
static void
relayInviteReceived( void* closure, NetLaunchInfo* invite )
void
relayInviteReceivedGTK( void* closure, const NetLaunchInfo* invite )
{
GtkAppGlobals* apg = (GtkAppGlobals*)closure;
@ -795,9 +814,9 @@ smsInviteReceived( void* closure, const NetLaunchInfo* nli,
gameFromInvite( apg, nli, returnAddr );
}
static void
smsMsgReceivedGTK( void* closure, const CommsAddrRec* from, XP_U32 gameID,
const XP_U8* buf, XP_U16 len )
void
msgReceivedGTK( void* closure, const CommsAddrRec* from, XP_U32 gameID,
const XP_U8* buf, XP_U16 len )
{
LOG_FUNC();
GtkAppGlobals* apg = (GtkAppGlobals*)closure;
@ -807,11 +826,24 @@ smsMsgReceivedGTK( void* closure, const CommsAddrRec* from, XP_U32 gameID,
int nRowIDs = VSIZE(rowids);
getRowsForGameID( params->pDb, gameID, rowids, &nRowIDs );
XP_LOGF( "%s: found %d rows for gameID %d", __func__, nRowIDs, gameID );
for ( int ii = 0; ii < nRowIDs; ++ii ) {
feedBufferGTK( apg, rowids[ii], buf, len, from );
if ( 0 == nRowIDs ) {
mqttc_notifyGameGone( params, &from->u.mqtt.devID, gameID );
} else {
for ( int ii = 0; ii < nRowIDs; ++ii ) {
feedBufferGTK( apg, rowids[ii], buf, len, from );
}
}
}
void
gameGoneGTK( void* closure, const CommsAddrRec* XP_UNUSED(from), XP_U32 gameID )
{
GtkAppGlobals* apg = (GtkAppGlobals*)closure;
gchar buf[64];
snprintf( buf, VSIZE(buf), "game %d has been deleted on a remote device", gameID );
gtktell( apg->window, buf );
}
static gboolean
keepalive_timer( gpointer data )
{
@ -872,6 +904,7 @@ int
gtkmain( LaunchParams* params )
{
GtkAppGlobals apg = {0};
params->appGlobals = &apg;
g_globals_for_signal = &apg;
@ -896,7 +929,7 @@ gtkmain( LaunchParams* params )
.msgNoticeReceived = gtkNoticeRcvd,
.devIDReceived = gtkDevIDReceived,
.msgErrorMsg = gtkErrorMsgRcvd,
.inviteReceived = relayInviteReceived,
.inviteReceived = relayInviteReceivedGTK,
};
relaycon_init( params, &procs, &apg,
@ -904,6 +937,8 @@ gtkmain( LaunchParams* params )
params->connInfo.relay.defaultSendPort );
linux_doInitialReg( params, idIsNew );
mqttc_init( params );
}
#ifdef XWFEATURE_SMS
@ -912,7 +947,7 @@ gtkmain( LaunchParams* params )
if ( parseSMSParams( params, &myPhone, &myPort ) ) {
SMSProcs smsProcs = {
.inviteReceived = smsInviteReceived,
.msgReceived = smsMsgReceivedGTK,
.msgReceived = msgReceivedGTK,
};
linux_sms_init( params, myPhone, myPort, &smsProcs, &apg );
} else {
@ -933,13 +968,14 @@ gtkmain( LaunchParams* params )
}
gtk_main();
device_store( params->dutil, NULL_XWE );
dvc_store( params->dutil, NULL_XWE );
/* closeGamesDB( params->pDb ); */
/* params->pDb = NULL; */
relaycon_cleanup( params );
#ifdef XWFEATURE_SMS
linux_sms_cleanup( params );
#endif
mqttc_cleanup( params );
return 0;
} /* gtkmain */

View file

@ -27,5 +27,8 @@ int gtkmain( LaunchParams* params );
void windowDestroyed( GtkGameGlobals* globals );
void gtkOnGameSaved( void* closure, sqlite3_int64 rowid, XP_Bool firstTime );
void make_rematch( GtkAppGlobals* apg, const CommonGlobals* cGlobals );
void relayInviteReceivedGTK( void* closure, const NetLaunchInfo* invite );
void msgReceivedGTK( void* closure, const CommsAddrRec* from, XP_U32 gameID,
const XP_U8* buf, XP_U16 len );
void gameGoneGTK( void* closure, const CommsAddrRec* from, XP_U32 gameID );
#endif

View file

@ -23,7 +23,11 @@
#include "linuxutl.h"
#include "linuxmain.h"
#include "gamesdb.h"
#include "dbgutil.h"
#include "LocalizedStrIncludes.h"
#include "nli.h"
#include "cursesmain.h"
#include "gtkmain.h"
static XP_U32 linux_dutil_getCurSeconds( XW_DUtilCtxt* duc, XWEnv xwe );
static const XP_UCHAR* linux_dutil_getUserString( XW_DUtilCtxt* duc, XWEnv xwe, XP_U16 code );
@ -78,6 +82,52 @@ linux_dutil_onDupTimerChanged( XW_DUtilCtxt* XP_UNUSED(duc), XWEnv XP_UNUSED(xwe
XP_LOGF( "%s(id=%d, oldVal=%d, newVal=%d)", __func__, gameID, oldVal, newVal );
}
static void
linux_dutil_onInviteReceived( XW_DUtilCtxt* duc, XWEnv XP_UNUSED(xwe),
const NetLaunchInfo* nli )
{
LaunchParams* params = (LaunchParams*)duc->closure;
if ( params->useCurses ) {
CommsAddrRec addr = {0};
nli_makeAddrRec( nli, &addr );
inviteReceivedCurses( params->appGlobals, nli, &addr );
} else {
relayInviteReceivedGTK( params->appGlobals, nli );
}
}
static void
linux_dutil_onMessageReceived( XW_DUtilCtxt* duc, XWEnv XP_UNUSED(xwe),
XP_U32 gameID, const CommsAddrRec* from,
XWStreamCtxt* stream )
{
XP_LOGFF( "(gameID=%d)", gameID );
LaunchParams* params = (LaunchParams*)duc->closure;
XP_U16 len = stream_getSize( stream );
XP_U8 buf[len];
stream_getBytes( stream, buf, len );
if ( params->useCurses ) {
mqttMsgReceivedCurses( params->appGlobals, from, gameID, buf, len );
} else {
msgReceivedGTK( params->appGlobals, from, gameID, buf, len );
}
}
static void
linux_dutil_onGameGoneReceived( XW_DUtilCtxt* duc, XWEnv XP_UNUSED(xwe),
XP_U32 gameID, const CommsAddrRec* from )
{
LaunchParams* params = (LaunchParams*)duc->closure;
if ( params->useCurses ) {
gameGoneCurses( params->appGlobals, from, gameID );
} else {
gameGoneGTK( params->appGlobals, from, gameID );
}
}
XW_DUtilCtxt*
dutils_init( MPFORMAL VTableMgr* vtMgr, void* closure )
{
@ -111,9 +161,14 @@ dutils_init( MPFORMAL VTableMgr* vtMgr, void* closure )
SET_PROC(notifyPause);
SET_PROC(onDupTimerChanged);
SET_PROC(onInviteReceived);
SET_PROC(onMessageReceived);
SET_PROC(onGameGoneReceived);
# undef SET_PROC
assertTableFull( &result->vtable, sizeof(result->vtable), "lindutil" );
MPASSIGN( result->mpool, mpool );
return result;
}

View file

@ -59,6 +59,7 @@
#include "linuxdict.h"
#include "lindutil.h"
#include "relaycon.h"
#include "mqttcon.h"
#include "smsproto.h"
#ifdef PLATFORM_NCURSES
# include "cursesmain.h"
@ -299,21 +300,37 @@ linuxOpenGame( CommonGlobals* cGlobals, const TransportProcs* procs,
/* If this is to be a relay connected game, tell it so. Otherwise
let the invitation process and receipt of messages populate
comms' addressbook */
if ( cGlobals->gi->serverRole != SERVER_STANDALONE
&& addr_hasType( &params->addr, COMMS_CONN_RELAY ) ) {
if ( cGlobals->gi->serverRole != SERVER_STANDALONE ) {
if ( addr_hasType( &params->addr, COMMS_CONN_RELAY ) ) {
if ( ! savedGame ) {
linuxSaveGame( cGlobals );
savedGame = true;
if ( ! savedGame ) {
linuxSaveGame( cGlobals );
savedGame = true;
}
CommsAddrRec addr = {0};
comms_getInitialAddr( &addr, params->connInfo.relay.relayName,
params->connInfo.relay.defaultSendPort );
XP_MEMCPY( addr.u.ip_relay.invite, params->connInfo.relay.invite,
1 + XP_STRLEN(params->connInfo.relay.invite) );
addr.u.ip_relay.seeksPublicRoom = params->connInfo.relay.seeksPublicRoom;
addr.u.ip_relay.advertiseRoom = params->connInfo.relay.advertiseRoom;
comms_augmentHostAddr( cGlobals->game.comms, NULL_XWE, &addr ); /* sends stuff */
}
if ( addr_hasType( &params->addr, COMMS_CONN_SMS ) ) {
CommsAddrRec addr = {0};
addr_addType( &addr, COMMS_CONN_SMS );
XP_STRCAT( addr.u.sms.phone, params->connInfo.sms.myPhone );
addr.u.sms.port = params->connInfo.sms.port;
comms_augmentHostAddr( cGlobals->game.comms, NULL_XWE, &addr );
}
if ( addr_hasType( &params->addr, COMMS_CONN_MQTT ) ) {
CommsAddrRec addr = {0};
addr_addType( &addr, COMMS_CONN_MQTT );
addr.u.mqtt.devID = *mqttc_getDevID( params );
comms_augmentHostAddr( cGlobals->game.comms, NULL_XWE, &addr );
}
CommsAddrRec addr = {0};
comms_getInitialAddr( &addr, params->connInfo.relay.relayName,
params->connInfo.relay.defaultSendPort );
XP_MEMCPY( addr.u.ip_relay.invite, params->connInfo.relay.invite,
1 + XP_STRLEN(params->connInfo.relay.invite) );
addr.u.ip_relay.seeksPublicRoom = params->connInfo.relay.seeksPublicRoom;
addr.u.ip_relay.advertiseRoom = params->connInfo.relay.advertiseRoom;
comms_augmentHostAddr( cGlobals->game.comms, NULL_XWE, &addr ); /* sends stuff */
}
if ( !!returnAddrP ) {
@ -882,6 +899,11 @@ typedef enum {
,CMD_INVITEE_SMSNUMBER
,CMD_SMSPORT
#endif
,CMD_WITHMQTT
,CMD_MQTTHOST
,CMD_MQTTPORT
,CMD_INVITEE_MQTTDEVID
,CMD_INVITEE_COUNTS
#ifdef XWFEATURE_RELAY
,CMD_ROOMNAME
@ -1018,6 +1040,10 @@ static CmdInfoRec CmdInfoRecs[] = {
,{ CMD_INVITEE_SMSNUMBER, true, "invitee-sms-number", "number to send any invitation to" }
,{ CMD_SMSPORT, true, "sms-port", "this devices's sms port" }
#endif
,{ CMD_WITHMQTT, false, "with-mqtt", "enable connecting via mqtt" }
,{ CMD_MQTTHOST, true, "mqtt-host", "server mosquitto is running on" }
,{ CMD_MQTTPORT, true, "mqtt-port", "port mosquitto is listening on" }
,{ CMD_INVITEE_MQTTDEVID, true, "invitee-mqtt-devid", "upper-case hex devID to send any invitation to" }
,{ CMD_INVITEE_COUNTS, true, "invitee-counts",
"When invitations sent, how many on each device? e.g. \"1:2\" for a "
"three-dev game with two players on second guest" }
@ -1586,6 +1612,15 @@ linux_send( XWEnv XP_UNUSED(xwe), const XP_U8* buf, XP_U16 buflen,
}
break;
#endif
case COMMS_CONN_MQTT:
nSent = mqttc_send( cGlobals->params, gameID, buf, buflen, &addrRec->u.mqtt.devID );
break;
case COMMS_CONN_NFC:
XP_LOGFF( "I don't do nfc! Should be filtering it on invitation receipt" );
break;
default:
XP_ASSERT(0);
}
@ -2587,6 +2622,8 @@ main( int argc, char** argv )
mainParams.connInfo.ip.port = DEFAULT_PORT;
mainParams.connInfo.ip.hostName = "localhost";
#endif
mainParams.connInfo.mqtt.hostName = "localhost";
mainParams.connInfo.mqtt.port = 1883;
#ifdef XWFEATURE_SMS
mainParams.connInfo.sms.port = 1;
#endif
@ -2795,6 +2832,22 @@ main( int argc, char** argv )
addr_addType( &mainParams.addr, COMMS_CONN_SMS );
break;
#endif
case CMD_WITHMQTT:
addr_addType( &mainParams.addr, COMMS_CONN_MQTT );
break;
case CMD_MQTTHOST:
addr_addType( &mainParams.addr, COMMS_CONN_MQTT );
mainParams.connInfo.mqtt.hostName = optarg;
break;
case CMD_MQTTPORT:
addr_addType( &mainParams.addr, COMMS_CONN_MQTT );
mainParams.connInfo.mqtt.port = atoi(optarg);
break;
case CMD_INVITEE_MQTTDEVID:
mainParams.connInfo.mqtt.inviteeDevIDs =
g_slist_append( mainParams.connInfo.mqtt.inviteeDevIDs, optarg );
addr_addType( &mainParams.addr, COMMS_CONN_MQTT );
break;
case CMD_DUPPACKETS:
mainParams.duplicatePackets = XP_TRUE;
break;

View file

@ -1,6 +1,6 @@
/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */
/*
* Copyright 2001-2013 by Eric House (xwords@eehouse.org). All rights
* Copyright 2001 - 2020 by Eric House (xwords@eehouse.org). All rights
* reserved.
*
* This program is free software; you can redistribute it and/or
@ -51,7 +51,7 @@ typedef void (*SockReceiver)( void* closure, int socket );
typedef void (*NewSocketProc)( void* closure, int newSock, int oldSock,
SockReceiver proc, void* procClosure );
typedef struct LaunchParams {
typedef struct _LaunchParams {
/* CommPipeCtxt* pipe; */
CurGameInfo pgi;
@ -67,6 +67,7 @@ typedef struct LaunchParams {
XP_U32 dbFileID;
#endif
void* relayConStorage; /* opaque outside of relaycon.c */
void* mqttConStorage;
#ifdef XWFEATURE_SMS
void* smsStorage;
#endif
@ -107,7 +108,10 @@ typedef struct LaunchParams {
XP_Bool skipGameOver;
XP_Bool useMmap;
XP_Bool closeStdin;
XP_Bool useCurses;
void* appGlobals; /* cursesmain or gtkmain sets this */
XP_Bool useUdp;
XP_Bool useHTTP;
XP_Bool runSMSTest;
@ -168,6 +172,12 @@ typedef struct LaunchParams {
int port;
} sms;
#endif
struct {
MQTTDevID devID;
GSList* inviteeDevIDs;
const char* hostName;
int port;
} mqtt;
} connInfo;
union {

275
xwords4/linux/mqttcon.c Normal file
View file

@ -0,0 +1,275 @@
/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */
/*
* 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.
*/
#include <mosquitto.h>
#include "mqttcon.h"
#include "gsrcwrap.h"
#include "device.h"
#include "strutils.h"
typedef struct _MQTTConStorage {
LaunchParams* params;
struct mosquitto* mosq;
MQTTDevID clientID;
gchar clientIDStr[32];
gchar topic[64];
int msgPipe[2];
} MQTTConStorage;
#define DEFAULT_QOS 2
static MQTTConStorage*
getStorage( LaunchParams* params )
{
MQTTConStorage* storage = (MQTTConStorage*)params->mqttConStorage;
if ( NULL == storage ) {
storage = XP_CALLOC( params->mpool, sizeof(*storage) );
params->mqttConStorage = storage;
}
return storage;
}
static void
loadClientID( LaunchParams* params, MQTTConStorage* storage )
{
dvc_getMQTTDevID( params->dutil, NULL_XWE, &storage->clientID );
formatMQTTDevID( &storage->clientID, storage->clientIDStr,
VSIZE(storage->clientIDStr) );
formatMQTTTopic( &storage->clientID, storage->topic, VSIZE(storage->topic) );
}
static void
onMessageReceived( struct mosquitto *mosq, void *userdata,
const struct mosquitto_message* message )
{
XP_LOGFF( "(len=%d)", message->payloadlen );
MQTTConStorage* storage = (MQTTConStorage*)userdata;
XP_ASSERT( storage->mosq == mosq );
// XP_ASSERT( 0 == XP_STRCMP( message->topic, storage->clientID ) );
XP_ASSERT( message->payloadlen < 0x7FFF );
short len = (short)message->payloadlen;
len = htons( len );
write( storage->msgPipe[1], &len, sizeof(len) );
write( storage->msgPipe[1], message->payload, message->payloadlen );
}
static void
connect_callback( struct mosquitto *mosq, void *userdata, int result )
{
LOG_FUNC();
XP_USE(mosq);
XP_USE(userdata);
XP_USE(result);
/* int i; */
/* if(!result){ */
/* /\* Subscribe to broker information topics on successful connect. *\/ */
/* mosquitto_subscribe(mosq, NULL, "$SYS/#", 2); */
/* }else{ */
/* fprintf(stderr, "Connect failed\n"); */
/* } */
}
static void
subscribe_callback( struct mosquitto *mosq, void *userdata, int mid,
int qos_count, const int *granted_qos)
{
XP_USE(mosq);
XP_USE(userdata);
XP_USE(mid);
XP_USE(qos_count);
XP_USE(granted_qos);
XP_LOGFF ("Subscribed (mid: %d): %d", mid, granted_qos[0]);
for( int ii=1; ii < qos_count; ii++ ) {
XP_LOGFF(", %d", granted_qos[ii]);
}
}
static void
log_callback(struct mosquitto *mosq, void *userdata, int level, const char *str )
{
XP_USE(mosq);
XP_USE(userdata);
XP_USE(level);
XP_LOGFF( "msg: %s", str );
}
static gboolean
handle_gotmsg( GIOChannel* source, GIOCondition XP_UNUSED(condition), gpointer data )
{
// XP_LOGFF( "(len=%d)", message->payloadlen );
LOG_FUNC();
MQTTConStorage* storage = (MQTTConStorage*)data;
int pipe = g_io_channel_unix_get_fd( source );
XP_ASSERT( pipe == storage->msgPipe[0] );
short len;
ssize_t nRead = read( pipe, &len, sizeof(len) );
XP_ASSERT( nRead == sizeof(len) );
len = ntohs(len);
XP_U8 buf[len];
nRead = read( pipe, buf, len );
XP_ASSERT( nRead == len );
dvc_parseMQTTPacket( storage->params->dutil, NULL_XWE, buf, len );
return TRUE;
} /* handle_gotmsg */
static bool
postMsg( MQTTConStorage* storage, XWStreamCtxt* stream, const MQTTDevID* invitee )
{
const XP_U8* bytes = stream_getPtr( stream );
XP_U16 len = stream_getSize( stream );
int mid;
#ifdef DEBUG
XP_UCHAR* sum = dutil_md5sum( storage->params->dutil, NULL_XWE, bytes, len );
XP_LOGFF( "sending %d bytes with sum %s", len, sum );
XP_FREEP( storage->params->mpool, &sum );
#endif
gchar buf[32];
int err = mosquitto_publish( storage->mosq, &mid,
formatMQTTTopic( invitee, buf, sizeof(buf) ),
len, bytes, DEFAULT_QOS, false );
XP_LOGFF( "mosquitto_publish(topic=%s) => %s; mid=%d", buf, mosquitto_strerror(err), mid );
stream_destroy( stream, NULL_XWE );
return err == 0;
}
void
mqttc_init( LaunchParams* params )
{
XP_ASSERT( !params->mqttConStorage );
MQTTConStorage* storage = getStorage( params );
storage->params = params;
loadClientID( params, storage );
int res = pipe( storage->msgPipe );
XP_ASSERT( !res );
ADD_SOCKET( storage, storage->msgPipe[0], handle_gotmsg );
int err = mosquitto_lib_init();
XP_LOGFF( "mosquitto_lib_init() => %d", err );
XP_ASSERT( 0 == err );
bool cleanSession = false;
struct mosquitto* mosq = storage->mosq =
mosquitto_new( storage->clientIDStr, cleanSession, storage );
mosquitto_log_callback_set( mosq, log_callback );
mosquitto_connect_callback_set( mosq, connect_callback );
mosquitto_message_callback_set( mosq, onMessageReceived );
mosquitto_subscribe_callback_set( mosq, subscribe_callback );
int keepalive = 60;
err = mosquitto_connect( mosq, params->connInfo.mqtt.hostName,
params->connInfo.mqtt.port, keepalive );
XP_LOGFF( "mosquitto_connect() => %s", mosquitto_strerror(err) );
if ( MOSQ_ERR_SUCCESS == err ) {
int mid;
err = mosquitto_subscribe( mosq, &mid, storage->topic, DEFAULT_QOS );
XP_LOGFF( "mosquitto_subscribe(topic=%s) => %s, mid=%d", storage->topic,
mosquitto_strerror(err), mid );
err = mosquitto_loop_start( mosq );
XP_ASSERT( !err );
} else {
XP_LOGFF( "failed to connect so not proceeding" );
}
}
void
mqttc_cleanup( LaunchParams* params )
{
MQTTConStorage* storage = getStorage( params );
int err = mosquitto_loop_stop( storage->mosq, true ); /* blocks until thread dies */
XP_LOGFF( "mosquitto_loop_stop() => %s", mosquitto_strerror(err) );
mosquitto_destroy( storage->mosq );
storage->mosq = NULL;
mosquitto_lib_cleanup();
XP_ASSERT( params->mqttConStorage == storage ); /* cheat */
XP_FREEP( params->mpool, &storage );
params->mqttConStorage = NULL;
}
const MQTTDevID*
mqttc_getDevID( LaunchParams* params )
{
MQTTConStorage* storage = getStorage( params );
return &storage->clientID;
}
const gchar*
mqttc_getDevIDStr( LaunchParams* params )
{
MQTTConStorage* storage = getStorage( params );
return storage->clientIDStr;
}
void
mqttc_invite( LaunchParams* params, NetLaunchInfo* nli, const MQTTDevID* invitee )
{
MQTTConStorage* storage = getStorage( params );
gchar buf[32];
XP_LOGFF( "need to send to %s", formatMQTTDevID(invitee, buf, sizeof(buf) ) );
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(params->mpool)
params->vtMgr );
dvc_makeMQTTInvite( stream, nli );
postMsg( storage, stream, invitee );
}
XP_S16
mqttc_send( LaunchParams* params, XP_U32 gameID, const XP_U8* buf,
XP_U16 len, const MQTTDevID* addressee )
{
XP_S16 result = -1;
MQTTConStorage* storage = getStorage( params );
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(params->mpool)
params->vtMgr );
dvc_makeMQTTMessage( params->dutil, NULL_XWE, stream,
gameID, buf, len );
if ( postMsg( storage, stream, addressee ) ) {
result = len;
}
return result;
}
void
mqttc_notifyGameGone( LaunchParams* params, const MQTTDevID* addressee, XP_U32 gameID )
{
MQTTConStorage* storage = getStorage( params );
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(params->mpool)
params->vtMgr );
dvc_makeMQTTNoSuchGame( params->dutil, NULL_XWE, stream, gameID );
postMsg( storage, stream, addressee );
}

38
xwords4/linux/mqttcon.h Normal file
View file

@ -0,0 +1,38 @@
/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */
/*
* 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.
*/
#ifndef _MQTTCON_H_
#define _MQTTCON_H_
#include "main.h"
#include "nli.h"
void mqttc_init( LaunchParams* params );
void mqttc_cleanup( LaunchParams* params );
const MQTTDevID* mqttc_getDevID( LaunchParams* params );
const gchar* mqttc_getDevIDStr( LaunchParams* params );
void mqttc_invite( LaunchParams* params, NetLaunchInfo* nli, const MQTTDevID* mqttInvitee );
XP_S16 mqttc_send( LaunchParams* params, XP_U32 gameID, const XP_U8* buf, XP_U16 len,
const MQTTDevID* addressee );
void mqttc_notifyGameGone( LaunchParams* params, const MQTTDevID* addressee, XP_U32 gameID );
bool mqttc_strToDevID( const gchar* str, MQTTDevID* result );
#endif

View file

@ -33,7 +33,7 @@ typedef struct _Procs {
void (*devIDReceived)( void* closure, const XP_UCHAR* devID,
XP_U16 maxInterval );
void (*msgErrorMsg)( void* closure, const XP_UCHAR* msg );
void (*inviteReceived)( void* closure, NetLaunchInfo* invit );
void (*inviteReceived)( void* closure, const NetLaunchInfo* invit );
} RelayConnProcs;
void relaycon_init( LaunchParams* params, const RelayConnProcs* procs,

View file

@ -165,6 +165,7 @@ class Device():
sTilesLeftTrayPat = re.compile('.*player \d+ now has (\d+) tiles')
sRelayIDPat = re.compile('.*UPDATE games.*seed=(\d+),.*relayid=\'([^\']+)\'.*')
sDevIDPat = re.compile('.*storing new devid: ([\da-fA-F]+).*')
sMQTTDevIDPat = re.compile('.*dvc_getMQTTDevID.*: generated id: ([\d[A-F]+).*')
sConnPat = re.compile('.*linux_util_informMissing\(isServer.*nMissing=0\).*')
sScoresDup = []
@ -198,6 +199,8 @@ class Device():
self.relayID = None
self.inviteeDevID = None
self.inviteeDevIDs = [] # only servers use this
self.inviteeMQTTDevID = None
self.inviteeMQTTDevIDs = []
self.connected = False
self.relaySeed = 0
self.locked = False
@ -261,6 +264,12 @@ class Device():
match = Device.sDevIDPat.match(line)
if match: self.inviteeDevID = int(match.group(1), 16)
if self.args.ADD_MQTT and not self.inviteeMQTTDevID:
match = Device.sMQTTDevIDPat.match(line)
if match:
self.inviteeMQTTDevID = int(match.group(1), 16)
# print('read mqtt devid: {:16X}'.format(self.inviteeMQTTDevID))
if not self.connected:
match = Device.sConnPat.match(line)
if match: self.connected = True
@ -308,6 +317,18 @@ class Device():
for inviteeDevID in self.inviteeDevIDs:
args += ['--invitee-relayid', str(inviteeDevID)]
if self.args.ADD_MQTT:
if self.order == 1 and not self.connected:
for peer in self.peers:
if peer.inviteeMQTTDevID and not peer == self:
if not peer.inviteeMQTTDevID in self.inviteeMQTTDevIDs:
self.inviteeMQTTDevIDs.append(peer.inviteeMQTTDevID)
if self.inviteeMQTTDevIDs:
args += [ '--force-invite' ]
for idid in self.inviteeMQTTDevIDs:
asHexStr = '{:16X}'.format(idid)
args += ['--invitee-mqtt-devid', asHexStr]
self.proc = subprocess.Popen(args, stdout = subprocess.DEVNULL,
stderr = subprocess.PIPE, universal_newlines = True)
self.pid = self.proc.pid
@ -450,6 +471,11 @@ def build_cmds(args):
for dev in range(2, NDEVS + 1):
PARAMS += [ '--invitee-sms-number', makeSMSPhoneNo(GAME, dev) ]
if args.ADD_MQTT:
PARAMS += [ '--mqtt-port', args.MQTT_PORT, '--mqtt-host', args.MQTT_HOST ]
if DEV == 1:
PARAMS += [ '--force-invite' ]
if args.UNDO_PCT > 0:
PARAMS += ['--undo-pct', args.UNDO_PCT]
PARAMS += [ '--game-dict', DICT]
@ -755,6 +781,11 @@ def mkParser():
parser.add_argument('--add-sms', dest = 'ADD_SMS', default = False, action = 'store_true')
parser.add_argument('--sms-fail-pct', dest = 'SMS_FAIL_PCT', default = 0, type = int)
parser.add_argument('--add-mqtt', dest = 'ADD_MQTT', default = False, action = 'store_true')
parser.add_argument('--mqtt-port', dest = 'MQTT_PORT', default = 1883 )
parser.add_argument('--mqtt-host', dest = 'MQTT_HOST', default = 'localhost' )
parser.add_argument('--remove-relay', dest = 'ADD_RELAY', default = True, action = 'store_false')
parser.add_argument('--core-pat', dest = 'CORE_PAT', default = os.environ.get('DISCON_COREPAT'),

View file

@ -0,0 +1,65 @@
#!/usr/bin/python3
import argparse, re
import paho.mqtt.client as mqtt
g_topics = [
'$SYS/broker/clients/disconnected',
# '$SYS/broker/+/+',
'xw4/device/#',
]
sDevIDPat = re.compile('xw4/device/([\dA-F]+)')
# Define event callbacks
def on_connect(client, userdata, flags, rc):
print("rc: " + str(rc))
def on_message(client, obj, msg):
match = sDevIDPat.match(msg.topic)
if match:
print('for: {}, len: {}'.format(match.group(1), len(msg.payload)))
def on_publish(client, obj, mid):
print("mid: " + str(mid))
def on_subscribe(client, obj, mid, granted_qos):
print("Subscribed: " + str(mid) + " " + str(granted_qos))
def on_log(client, obj, level, string):
print(string)
def makeClient():
mqttc = mqtt.Client()
# Assign event callbacks
mqttc.on_message = on_message
mqttc.on_connect = on_connect
mqttc.on_publish = on_publish
mqttc.on_subscribe = on_subscribe
return mqttc
def mkParser():
parser = argparse.ArgumentParser()
parser.add_argument('--host', dest = 'HOST', default = 'liquidsugar.net',
help = 'the host mosquitto is on')
parser.add_argument('--port', dest = 'PORT', default = 1883,
help = 'the port mosquitto is on')
return parser
def main():
args = mkParser().parse_args()
mqttc = makeClient()
mqttc.connect(args.HOST, args.PORT)
# Start subscribe, with QoS level 2
for topic in g_topics:
mqttc.subscribe(topic, 2)
while True:
err = mqttc.loop()
if 0 != err:
print('got {} from loop()'.format(err))
break
##############################################################################
if __name__ == '__main__':
main()