mirror of
git://xwords.git.sourceforge.net/gitroot/xwords/xwords
synced 2025-01-15 15:41:24 +01:00
Replace most of NBSReceiver with new NBSService. Route outgoing (from
comms) and incoming (from NBSReceiver) messages through it. Since some messages are too big, add header/protocol for breaking them up and reassemble. With this commit complete robot-vs-robot games are possible between two very new Samsung phones on T-mobile. Older phones and other networks not tested.
This commit is contained in:
parent
8e3d8c20c1
commit
599d01bfc7
11 changed files with 520 additions and 217 deletions
|
@ -36,7 +36,7 @@
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="15" />
|
<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="8" />
|
||||||
|
|
||||||
<application android:icon="@drawable/icon48x48"
|
<application android:icon="@drawable/icon48x48"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
@ -170,5 +170,7 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
|
<service android:name="NBSService"/>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -52,3 +52,4 @@ XWListPreference.java
|
||||||
GameNamer.java
|
GameNamer.java
|
||||||
LookupActivity.java
|
LookupActivity.java
|
||||||
NBSInviteActivity.java
|
NBSInviteActivity.java
|
||||||
|
NBSService.java
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
<!-- <uses-permission android:name="android.permission.RECEIVE_SMS" /> -->
|
<!-- <uses-permission android:name="android.permission.RECEIVE_SMS" /> -->
|
||||||
<!-- <uses-permission android:name="android.permission.SEND_SMS" /> -->
|
<!-- <uses-permission android:name="android.permission.SEND_SMS" /> -->
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="7" />
|
<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="8" />
|
||||||
|
|
||||||
<application android:icon="@drawable/icon48x48"
|
<application android:icon="@drawable/icon48x48"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
|
|
@ -10,4 +10,4 @@
|
||||||
# Indicates whether an apk should be generated for each density.
|
# Indicates whether an apk should be generated for each density.
|
||||||
split.density=false
|
split.density=false
|
||||||
# Project target.
|
# Project target.
|
||||||
target=android-7
|
target=android-8
|
||||||
|
|
|
@ -1855,7 +1855,7 @@
|
||||||
Bluetooth play.</string>
|
Bluetooth play.</string>
|
||||||
|
|
||||||
<string name="new_btmove_title">New move via Bluetooth</string>
|
<string name="new_btmove_title">New move via Bluetooth</string>
|
||||||
<string name="new_btmove_body">One or more moves has arrived</string>
|
<string name="new_move_body">One or more moves has arrived</string>
|
||||||
|
|
||||||
<string name="bt_devs_missing">Missing connections? Invite BT
|
<string name="bt_devs_missing">Missing connections? Invite BT
|
||||||
devs? (I won\'t ask again until you reopen this game.)</string>
|
devs? (I won\'t ask again until you reopen this game.)</string>
|
||||||
|
|
|
@ -511,7 +511,7 @@ public class BTService extends Service {
|
||||||
buffer, addr,
|
buffer, addr,
|
||||||
m_btMsgSink ) ) {
|
m_btMsgSink ) ) {
|
||||||
postNotification( gameID, R.string.new_btmove_title,
|
postNotification( gameID, R.string.new_btmove_title,
|
||||||
R.string.new_btmove_body );
|
R.string.new_move_body );
|
||||||
// do nothing
|
// do nothing
|
||||||
} else {
|
} else {
|
||||||
DbgUtils.logf( "nobody took msg for gameID %X",
|
DbgUtils.logf( "nobody took msg for gameID %X",
|
||||||
|
|
|
@ -1736,9 +1736,9 @@ public class BoardActivity extends XWActivity
|
||||||
m_gi.nPlayers, 1 );
|
m_gi.nPlayers, 1 );
|
||||||
break;
|
break;
|
||||||
case COMMS_CONN_SMS:
|
case COMMS_CONN_SMS:
|
||||||
NBSReceiver.inviteRemote( this, dev, m_gi.gameID,
|
NBSService.inviteRemote( this, dev, m_gi.gameID,
|
||||||
gameName, m_gi.dictLang,
|
gameName, m_gi.dictLang,
|
||||||
m_gi.nPlayers, 1 );
|
m_gi.nPlayers, 1 );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -235,6 +235,7 @@ public class CommsTransport implements TransportProcs,
|
||||||
// // Let other know I'm here
|
// // Let other know I'm here
|
||||||
// m_jniThread.handle( JNIThread.JNICmd.CMD_RESEND );
|
// m_jniThread.handle( JNIThread.JNICmd.CMD_RESEND );
|
||||||
// break;
|
// break;
|
||||||
|
// case COMMS_CONN_SMS:
|
||||||
// default:
|
// default:
|
||||||
// DbgUtils.logf( "tickle: unexpected type %s",
|
// DbgUtils.logf( "tickle: unexpected type %s",
|
||||||
// addr.conType.toString() );
|
// addr.conType.toString() );
|
||||||
|
@ -379,33 +380,10 @@ public class CommsTransport implements TransportProcs,
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case COMMS_CONN_SMS:
|
case COMMS_CONN_SMS:
|
||||||
Assert.fail();
|
nSent = NBSService.sendPacket( m_context, addr.sms_phone,
|
||||||
// DbgUtils.logf( "sending via sms to %s:%d",
|
gameID, buf );
|
||||||
// m_addr.sms_phone, m_addr.sms_port );
|
|
||||||
// try {
|
|
||||||
// Intent intent = new Intent( m_context, StatusReceiver.class);
|
|
||||||
// PendingIntent pi
|
|
||||||
// = PendingIntent.getBroadcast( m_context, 0,
|
|
||||||
// intent, 0 );
|
|
||||||
// if ( 0 == m_addr.sms_port ) {
|
|
||||||
// SmsManager.getDefault().sendTextMessage( m_addr.sms_phone,
|
|
||||||
// null, "Hello world",
|
|
||||||
// pi, pi );
|
|
||||||
// DbgUtils.logf( "called sendTextMessage" );
|
|
||||||
// } else {
|
|
||||||
// SmsManager.getDefault().
|
|
||||||
// sendDataMessage( m_addr.sms_phone, (String)null,
|
|
||||||
// (short)m_addr.sms_port,
|
|
||||||
// buf, pi, pi );
|
|
||||||
// DbgUtils.logf( "called sendDataMessage" );
|
|
||||||
// }
|
|
||||||
// nSent = buf.length;
|
|
||||||
// } catch ( java.lang.IllegalArgumentException iae ) {
|
|
||||||
// DbgUtils.logf( iae.toString() );
|
|
||||||
// }
|
|
||||||
break;
|
break;
|
||||||
case COMMS_CONN_BT:
|
case COMMS_CONN_BT:
|
||||||
String hostName = addr.bt_hostName;
|
|
||||||
nSent = BTService.enqueueFor( m_context, buf, addr.bt_hostName,
|
nSent = BTService.enqueueFor( m_context, buf, addr.bt_hostName,
|
||||||
addr.bt_btAddr, gameID );
|
addr.bt_btAddr, gameID );
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -20,39 +20,20 @@
|
||||||
|
|
||||||
package org.eehouse.android.xw4;
|
package org.eehouse.android.xw4;
|
||||||
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.telephony.SmsManager;
|
|
||||||
import android.telephony.SmsMessage;
|
import android.telephony.SmsMessage;
|
||||||
import android.util.Base64;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import junit.framework.Assert;
|
import junit.framework.Assert;
|
||||||
|
|
||||||
import org.eehouse.android.xw4.jni.CommsAddrRec;
|
|
||||||
|
|
||||||
public class NBSReceiver extends BroadcastReceiver {
|
public class NBSReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
// All messages are base64-encoded byte arrays. The first byte is
|
|
||||||
// always one of these. What follows depends.
|
|
||||||
private enum NBS_CMS { NBS_CMD_NONE,
|
|
||||||
NBS_CMD_INVITE,
|
|
||||||
NBS_CMD_DATA,
|
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive( Context context, Intent intent )
|
public void onReceive( Context context, Intent intent )
|
||||||
{
|
{
|
||||||
DbgUtils.logf( "NBSReceiver::onReceive(intent=%s)!!!!",
|
DbgUtils.logf( "NBSReceiver::onReceive()" );
|
||||||
intent.toString() );
|
|
||||||
|
|
||||||
Bundle bundle = intent.getExtras();
|
Bundle bundle = intent.getExtras();
|
||||||
if ( null != bundle ) {
|
if ( null != bundle ) {
|
||||||
|
@ -61,167 +42,12 @@ public class NBSReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
for ( int ii = 0; ii < nbses.length; ++ii ) {
|
for ( int ii = 0; ii < nbses.length; ++ii ) {
|
||||||
nbses[ii] = SmsMessage.createFromPdu((byte[])pdus[ii]);
|
nbses[ii] = SmsMessage.createFromPdu((byte[])pdus[ii]);
|
||||||
receiveBuffer( context, nbses[ii].getUserData(),
|
String phone = nbses[ii].getOriginatingAddress();
|
||||||
nbses[ii].getOriginatingAddress() );
|
DbgUtils.logf( "NBSReceiver: post format: %s", phone );
|
||||||
|
NBSService.handleFrom( context, nbses[ii].getUserData(),
|
||||||
|
phone );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
DbgUtils.logf( "onReceive done" );
|
||||||
}
|
}
|
||||||
|
|
||||||
static void tryNBSMessage( Context context, String phoneNo )
|
|
||||||
{
|
|
||||||
byte[] data = { 'a', 'b', 'c' };
|
|
||||||
|
|
||||||
SmsManager mgr = SmsManager.getDefault();
|
|
||||||
|
|
||||||
try {
|
|
||||||
/* online comment says providing PendingIntents prevents
|
|
||||||
random crashes */
|
|
||||||
PendingIntent sent = PendingIntent.getBroadcast( context,
|
|
||||||
0, new Intent(), 0 );
|
|
||||||
PendingIntent dlvrd = PendingIntent.getBroadcast( context, 0,
|
|
||||||
new Intent(), 0 );
|
|
||||||
|
|
||||||
mgr.sendDataMessage( phoneNo, null, (short)50009,
|
|
||||||
data, sent, dlvrd );
|
|
||||||
// PendingIntent sentIntent,
|
|
||||||
// PendingIntent deliveryIntent );
|
|
||||||
DbgUtils.logf( "sendDataMessage finished" );
|
|
||||||
} catch ( IllegalArgumentException iae ) {
|
|
||||||
DbgUtils.logf( "%s", iae.toString() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void inviteRemote( Context context, String phone,
|
|
||||||
int gameID, String gameName,
|
|
||||||
int lang, int nPlayersT,
|
|
||||||
int nPlayersH )
|
|
||||||
{
|
|
||||||
ByteArrayOutputStream bas = new ByteArrayOutputStream( 128 );
|
|
||||||
DataOutputStream das = new DataOutputStream( bas );
|
|
||||||
try {
|
|
||||||
das.writeInt( gameID );
|
|
||||||
das.writeUTF( gameName );
|
|
||||||
das.writeInt( lang );
|
|
||||||
das.writeByte( nPlayersT );
|
|
||||||
das.writeByte( nPlayersH );
|
|
||||||
das.flush();
|
|
||||||
|
|
||||||
send( context, NBS_CMS.NBS_CMD_INVITE, bas.toByteArray(), phone );
|
|
||||||
} catch ( java.io.IOException ioe ) {
|
|
||||||
DbgUtils.logf( "ioe: %s", ioe.toString() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void send( Context context, NBS_CMS cmd,
|
|
||||||
byte[] data, String phone )
|
|
||||||
{
|
|
||||||
int hash = Arrays.hashCode( data );
|
|
||||||
DbgUtils.logf( "NBSReceiver: outgoing hash on %d bytes: %X",
|
|
||||||
data.length, hash );
|
|
||||||
ByteArrayOutputStream bas = new ByteArrayOutputStream( 128 );
|
|
||||||
DataOutputStream das = new DataOutputStream( bas );
|
|
||||||
try {
|
|
||||||
das.writeByte( cmd.ordinal() );
|
|
||||||
das.writeInt( hash );
|
|
||||||
das.write( data, 0, data.length );
|
|
||||||
das.flush();
|
|
||||||
|
|
||||||
byte[] as64 = Base64.encode( bas.toByteArray(), Base64.NO_WRAP );
|
|
||||||
sendBuffer( context, as64, phone );
|
|
||||||
} catch ( java.io.IOException ioe ) {
|
|
||||||
DbgUtils.logf( "ioe: %s", ioe.toString() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void receive( Context context, NBS_CMS cmd,
|
|
||||||
byte[] data, String phone )
|
|
||||||
{
|
|
||||||
switch( cmd ) {
|
|
||||||
case NBS_CMD_INVITE:
|
|
||||||
DataInputStream dis =
|
|
||||||
new DataInputStream( new ByteArrayInputStream(data) );
|
|
||||||
try {
|
|
||||||
int gameID = dis.readInt();
|
|
||||||
String gameName = dis.readUTF();
|
|
||||||
int lang = dis.readInt();
|
|
||||||
int nPlayersT = dis.readByte();
|
|
||||||
int nPlayersH = dis.readByte();
|
|
||||||
|
|
||||||
CommsAddrRec addr = new CommsAddrRec( phone );
|
|
||||||
long rowid = GameUtils.makeNewNBSGame( context, gameID, addr,
|
|
||||||
lang, nPlayersT, nPlayersH );
|
|
||||||
|
|
||||||
if ( null != gameName && 0 < gameName.length() ) {
|
|
||||||
DBUtils.setName( context, rowid, gameName );
|
|
||||||
}
|
|
||||||
String body = Utils.format( context, R.string.new_nbs_bodyf, phone );
|
|
||||||
postNotification( context, gameID, R.string.new_nbs_title, body );
|
|
||||||
} catch ( java.io.IOException ioe ) {
|
|
||||||
DbgUtils.logf( "ioe: %s", ioe.toString() );
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Assert.fail();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void receiveBuffer( Context context, byte[] as64, String senderPhone )
|
|
||||||
{
|
|
||||||
byte[] data = Base64.decode( as64, Base64.NO_WRAP );
|
|
||||||
DataInputStream dis =
|
|
||||||
new DataInputStream( new ByteArrayInputStream(data) );
|
|
||||||
try {
|
|
||||||
NBS_CMS cmd = NBS_CMS.values()[dis.readByte()];
|
|
||||||
int hashRead = dis.readInt();
|
|
||||||
DbgUtils.logf( "NBSReceiver: incoming hash: %X", hashRead );
|
|
||||||
byte[] rest = new byte[dis.available()];
|
|
||||||
dis.read( rest );
|
|
||||||
int hashComputed = Arrays.hashCode( rest );
|
|
||||||
if ( hashComputed == hashRead ) {
|
|
||||||
DbgUtils.logf( "NBSReceiver: incoming hashes on %d bytes match: %X",
|
|
||||||
rest.length, hashRead );
|
|
||||||
receive( context, cmd, rest, senderPhone );
|
|
||||||
} else {
|
|
||||||
DbgUtils.logf( "NBSReceiver: incoming hashes on %d bytes "
|
|
||||||
+ "DON'T match: read: %X; figured: %X",
|
|
||||||
rest.length, hashRead, hashComputed );
|
|
||||||
}
|
|
||||||
} catch ( java.io.IOException ioe ) {
|
|
||||||
DbgUtils.logf( "ioe: %s", ioe.toString() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void sendBuffer( Context context, byte[] data, String phone )
|
|
||||||
{
|
|
||||||
if ( XWApp.onEmulator() ) {
|
|
||||||
DbgUtils.logf( "sendBuffer(phone=%s): FAKING IT", phone );
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
PendingIntent sent =
|
|
||||||
PendingIntent.getBroadcast( context, 0, new Intent(), 0 );
|
|
||||||
PendingIntent dlvrd =
|
|
||||||
PendingIntent.getBroadcast( context, 0, new Intent(), 0 );
|
|
||||||
|
|
||||||
SmsManager mgr = SmsManager.getDefault();
|
|
||||||
mgr.sendDataMessage( phone, null, XWApp.getNBSPort(),
|
|
||||||
data, sent, dlvrd );
|
|
||||||
// PendingIntent sentIntent,
|
|
||||||
// PendingIntent deliveryIntent );
|
|
||||||
DbgUtils.logf( "send to %s finished", phone );
|
|
||||||
} catch ( IllegalArgumentException iae ) {
|
|
||||||
DbgUtils.logf( "%s", iae.toString() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void postNotification( Context context, int gameID, int title,
|
|
||||||
String body )
|
|
||||||
{
|
|
||||||
Intent intent = new Intent( context, DispatchNotify.class );
|
|
||||||
intent.putExtra( DispatchNotify.GAMEID_EXTRA, gameID );
|
|
||||||
Utils.postNotification( context, intent, R.string.new_nbsmove_title,
|
|
||||||
body );
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,492 @@
|
||||||
|
/* -*- compile-command: "cd ../../../../../; ant debug install"; -*- */
|
||||||
|
/*
|
||||||
|
* Copyright 2010 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.PendingIntent;
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.telephony.SmsManager;
|
||||||
|
import android.telephony.SmsMessage;
|
||||||
|
import android.util.Base64;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.lang.System;
|
||||||
|
import junit.framework.Assert;
|
||||||
|
|
||||||
|
import org.eehouse.android.xw4.jni.CommsAddrRec;
|
||||||
|
|
||||||
|
public class NBSService extends Service {
|
||||||
|
|
||||||
|
private static final int MAX_LEN_TEXT = 100;
|
||||||
|
private static final int HANDLE = 1;
|
||||||
|
private static final int INVITE = 2;
|
||||||
|
private static final int SEND = 3;
|
||||||
|
|
||||||
|
private static final String CMD_STR = "CMD";
|
||||||
|
private static final String BUFFER = "BUFFER";
|
||||||
|
private static final String PHONE = "PHONE";
|
||||||
|
private static final String GAMEID = "GAMEID";
|
||||||
|
private static final String GAMENAME = "GAMENAME";
|
||||||
|
private static final String LANG = "LANG";
|
||||||
|
private static final String NPLAYERST = "NPLAYERST";
|
||||||
|
private static final String NPLAYERSH = "NPLAYERSH";
|
||||||
|
|
||||||
|
// All messages are base64-encoded byte arrays. The first byte is
|
||||||
|
// always one of these. What follows depends.
|
||||||
|
private enum NBS_CMD { NONE, INVITE, DATA, };
|
||||||
|
|
||||||
|
private int m_nReceived = 0;
|
||||||
|
private static int s_nSent = 0;
|
||||||
|
private static HashMap<String, HashMap <Integer, MsgStore>> s_partialMsgs
|
||||||
|
= new HashMap<String, HashMap <Integer, MsgStore>>();
|
||||||
|
|
||||||
|
public static void handleFrom( Context context, byte[] buffer, String phone )
|
||||||
|
{
|
||||||
|
Intent intent = getIntentTo( context, HANDLE );
|
||||||
|
intent.putExtra( BUFFER, buffer );
|
||||||
|
intent.putExtra( PHONE, phone );
|
||||||
|
context.startService( intent );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void inviteRemote( Context context, String phone,
|
||||||
|
int gameID, String gameName,
|
||||||
|
int lang, int nPlayersT,
|
||||||
|
int nPlayersH )
|
||||||
|
{
|
||||||
|
DbgUtils.logf( "inviteRemote(%s)", phone );
|
||||||
|
Intent intent = getIntentTo( context, INVITE );
|
||||||
|
intent.putExtra( PHONE, phone );
|
||||||
|
intent.putExtra( GAMEID, gameID );
|
||||||
|
intent.putExtra( GAMENAME, gameName );
|
||||||
|
intent.putExtra( LANG, lang );
|
||||||
|
intent.putExtra( NPLAYERST, nPlayersT );
|
||||||
|
intent.putExtra( NPLAYERSH, nPlayersH );
|
||||||
|
context.startService( intent );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int sendPacket( Context context, String phone,
|
||||||
|
int gameID, byte[] buffer )
|
||||||
|
{
|
||||||
|
DbgUtils.logf( "NBSService.sendPacket()" );
|
||||||
|
Intent intent = getIntentTo( context, SEND );
|
||||||
|
intent.putExtra( PHONE, phone );
|
||||||
|
intent.putExtra( GAMEID, gameID );
|
||||||
|
intent.putExtra( BUFFER, buffer );
|
||||||
|
context.startService( intent );
|
||||||
|
return buffer.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Intent getIntentTo( Context context, int cmd )
|
||||||
|
{
|
||||||
|
Intent intent = new Intent( context, NBSService.class );
|
||||||
|
intent.putExtra( CMD_STR, cmd );
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate()
|
||||||
|
{
|
||||||
|
if ( XWApp.NBSSUPPORTED ) {
|
||||||
|
} else {
|
||||||
|
stopSelf();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onStartCommand( Intent intent, int flags, int startId )
|
||||||
|
{
|
||||||
|
int result;
|
||||||
|
if ( XWApp.NBSSUPPORTED ) {
|
||||||
|
int cmd = intent.getIntExtra( CMD_STR, -1 );
|
||||||
|
switch( cmd ) {
|
||||||
|
case HANDLE:
|
||||||
|
DbgUtils.showf( this, "got %dth nbs", ++m_nReceived );
|
||||||
|
byte[] buffer = intent.getByteArrayExtra( BUFFER );
|
||||||
|
String phone = intent.getStringExtra( PHONE );
|
||||||
|
receiveBuffer( buffer, phone );
|
||||||
|
break;
|
||||||
|
case INVITE:
|
||||||
|
phone = intent.getStringExtra( PHONE );
|
||||||
|
DbgUtils.logf( "INVITE(%s)", phone );
|
||||||
|
int gameID = intent.getIntExtra( GAMEID, -1 );
|
||||||
|
String gameName = intent.getStringExtra( GAMENAME );
|
||||||
|
int lang = intent.getIntExtra( LANG, -1 );
|
||||||
|
int nPlayersT = intent.getIntExtra( NPLAYERST, -1 );
|
||||||
|
int nPlayersH = intent.getIntExtra( NPLAYERSH, -1 );
|
||||||
|
inviteRemote( phone, gameID, gameName, lang, nPlayersT,
|
||||||
|
nPlayersH);
|
||||||
|
break;
|
||||||
|
case SEND:
|
||||||
|
phone = intent.getStringExtra( PHONE );
|
||||||
|
buffer = intent.getByteArrayExtra( BUFFER );
|
||||||
|
gameID = intent.getIntExtra( GAMEID, -1 );
|
||||||
|
sendPacket( phone, gameID, buffer );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = Service.START_STICKY;
|
||||||
|
} else {
|
||||||
|
result = Service.START_STICKY_COMPATIBILITY;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind( Intent intent )
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void inviteRemote( String phone, int gameID, String gameName,
|
||||||
|
int lang, int nPlayersT, int nPlayersH )
|
||||||
|
{
|
||||||
|
ByteArrayOutputStream bas = new ByteArrayOutputStream( 128 );
|
||||||
|
DataOutputStream das = new DataOutputStream( bas );
|
||||||
|
try {
|
||||||
|
das.writeInt( gameID );
|
||||||
|
das.writeUTF( gameName );
|
||||||
|
das.writeInt( lang );
|
||||||
|
das.writeByte( nPlayersT );
|
||||||
|
das.writeByte( nPlayersH );
|
||||||
|
das.flush();
|
||||||
|
|
||||||
|
send( NBS_CMD.INVITE, bas.toByteArray(), phone );
|
||||||
|
} catch ( java.io.IOException ioe ) {
|
||||||
|
DbgUtils.logf( "ioe: %s", ioe.toString() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int sendPacket( String phone, int gameID, byte[] buf )
|
||||||
|
{
|
||||||
|
DbgUtils.logf( "non-static NBSService.sendPacket()" );
|
||||||
|
int nSent = -1;
|
||||||
|
ByteArrayOutputStream bas = new ByteArrayOutputStream( 128 );
|
||||||
|
DataOutputStream das = new DataOutputStream( bas );
|
||||||
|
try {
|
||||||
|
das.writeInt( gameID );
|
||||||
|
das.write( buf, 0, buf.length );
|
||||||
|
das.flush();
|
||||||
|
if ( send( NBS_CMD.DATA, bas.toByteArray(), phone ) ) {
|
||||||
|
nSent = buf.length;
|
||||||
|
}
|
||||||
|
} catch ( java.io.IOException ioe ) {
|
||||||
|
DbgUtils.logf( "ioe: %s", ioe.toString() );
|
||||||
|
}
|
||||||
|
return nSent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean send( NBS_CMD cmd, byte[] data, String phone )
|
||||||
|
throws java.io.IOException
|
||||||
|
{
|
||||||
|
DbgUtils.logf( "non-static NBSService.sendPacket()" );
|
||||||
|
int hash = Arrays.hashCode( data );
|
||||||
|
DbgUtils.logf( "NBSService: outgoing hash on %d bytes: %X",
|
||||||
|
data.length, hash );
|
||||||
|
ByteArrayOutputStream bas = new ByteArrayOutputStream( 128 );
|
||||||
|
DataOutputStream das = new DataOutputStream( bas );
|
||||||
|
das.writeByte( 0 ); // protocol
|
||||||
|
das.writeByte( cmd.ordinal() );
|
||||||
|
das.writeInt( hash );
|
||||||
|
das.write( data, 0, data.length );
|
||||||
|
das.flush();
|
||||||
|
|
||||||
|
String as64 = Base64.encodeToString( bas.toByteArray(), Base64.NO_WRAP );
|
||||||
|
byte[][] msgs = breakAndEncode( as64 );
|
||||||
|
return sendBuffers( msgs, phone );
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[][] breakAndEncode( String msg )
|
||||||
|
throws java.io.IOException
|
||||||
|
{
|
||||||
|
// TODO: as optimization, truncate header when only one packet
|
||||||
|
// required
|
||||||
|
Assert.assertFalse( msg.contains(":") );
|
||||||
|
int count = (msg.length() + (MAX_LEN_TEXT-1)) / MAX_LEN_TEXT;
|
||||||
|
byte[][] result = new byte[count][];
|
||||||
|
int msgID = ++s_nSent % 0x000000FF;
|
||||||
|
DbgUtils.logf( "preparing %d packets for msgid %x", count, msgID );
|
||||||
|
|
||||||
|
int start = 0;
|
||||||
|
int end = 0;
|
||||||
|
for ( int ii = 0; ii < count; ++ii ) {
|
||||||
|
int len = msg.length() - end;
|
||||||
|
if ( len > MAX_LEN_TEXT ) {
|
||||||
|
len = MAX_LEN_TEXT;
|
||||||
|
}
|
||||||
|
end += len;
|
||||||
|
String out = String.format( "0:%X:%X:%X:%s", msgID, ii, count,
|
||||||
|
msg.substring( start, end ) );
|
||||||
|
DbgUtils.logf( "fragment[%d]: %s", ii, out );
|
||||||
|
result[ii] = out.getBytes();
|
||||||
|
start = end;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void receive( NBS_CMD cmd, byte[] data, String phone )
|
||||||
|
{
|
||||||
|
CommsAddrRec addr = new CommsAddrRec( phone );
|
||||||
|
DataInputStream dis =
|
||||||
|
new DataInputStream( new ByteArrayInputStream(data) );
|
||||||
|
try {
|
||||||
|
switch( cmd ) {
|
||||||
|
case INVITE:
|
||||||
|
int gameID = dis.readInt();
|
||||||
|
String gameName = dis.readUTF();
|
||||||
|
int lang = dis.readInt();
|
||||||
|
int nPlayersT = dis.readByte();
|
||||||
|
int nPlayersH = dis.readByte();
|
||||||
|
|
||||||
|
long rowid = GameUtils.makeNewNBSGame( this, gameID, addr,
|
||||||
|
lang, nPlayersT, nPlayersH );
|
||||||
|
|
||||||
|
if ( null != gameName && 0 < gameName.length() ) {
|
||||||
|
DBUtils.setName( this, rowid, gameName );
|
||||||
|
}
|
||||||
|
String body = Utils.format( this, R.string.new_nbs_bodyf, phone );
|
||||||
|
postNotification( gameID, R.string.new_nbs_title, body );
|
||||||
|
break;
|
||||||
|
case DATA:
|
||||||
|
gameID = dis.readInt();
|
||||||
|
byte[] rest = new byte[dis.available()];
|
||||||
|
dis.read( rest );
|
||||||
|
feedMessage( gameID, rest, addr );
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Assert.fail();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch ( java.io.IOException ioe ) {
|
||||||
|
DbgUtils.logf( "ioe: %s", ioe.toString() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void receiveBuffer( byte[] as64, String senderPhone )
|
||||||
|
{
|
||||||
|
String asString = new String( as64 );
|
||||||
|
DbgUtils.logf( "receiveBuffer(%s)", asString );
|
||||||
|
String[] parts = asString.split( ":" );
|
||||||
|
DbgUtils.logf( "receiveBuffer: got %d parts", parts.length );
|
||||||
|
for ( String part : parts ) {
|
||||||
|
DbgUtils.logf( "part: %s", part );
|
||||||
|
}
|
||||||
|
Assert.assertTrue( 5 == parts.length );
|
||||||
|
if ( 5 == parts.length ) {
|
||||||
|
byte proto = Byte.valueOf( parts[0], 10 );
|
||||||
|
int id = Integer.valueOf( parts[1], 16 );
|
||||||
|
int index = Integer.valueOf( parts[2], 16 );
|
||||||
|
int count = Integer.valueOf( parts[3], 16 );
|
||||||
|
tryAssemble( senderPhone, id, index, count, parts[4] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryAssemble( String senderPhone, int id, int index,
|
||||||
|
int count, String msg )
|
||||||
|
{
|
||||||
|
if ( index == 0 && count == 1 ) {
|
||||||
|
disAssemble( senderPhone, msg.getBytes() );
|
||||||
|
} else {
|
||||||
|
synchronized( s_partialMsgs ) {
|
||||||
|
HashMap <Integer, MsgStore> perPhone =
|
||||||
|
s_partialMsgs.get( senderPhone );
|
||||||
|
if ( null == perPhone ) {
|
||||||
|
perPhone = new HashMap <Integer, MsgStore>();
|
||||||
|
s_partialMsgs.put( senderPhone, perPhone );
|
||||||
|
}
|
||||||
|
MsgStore store = perPhone.get( id );
|
||||||
|
if ( null == store ) {
|
||||||
|
store = new MsgStore( id, count );
|
||||||
|
perPhone.put( id, store );
|
||||||
|
}
|
||||||
|
store.add( index, msg );
|
||||||
|
|
||||||
|
if ( store.isComplete() ) {
|
||||||
|
byte[] fullMsg = store.message();
|
||||||
|
perPhone.remove( id );
|
||||||
|
disAssemble( senderPhone, fullMsg );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void disAssemble( String senderPhone, byte[] fullMsg )
|
||||||
|
{
|
||||||
|
DbgUtils.logf( "disAssemble()" );
|
||||||
|
byte[] data = Base64.decode( fullMsg, Base64.NO_WRAP );
|
||||||
|
DataInputStream dis =
|
||||||
|
new DataInputStream( new ByteArrayInputStream(data) );
|
||||||
|
try {
|
||||||
|
byte proto = dis.readByte();
|
||||||
|
if ( 0 != proto ) {
|
||||||
|
DbgUtils.logf( "NBSService.disAssemble: bad proto %d; dropping",
|
||||||
|
proto );
|
||||||
|
} else {
|
||||||
|
NBS_CMD cmd = NBS_CMD.values()[dis.readByte()];
|
||||||
|
int hashRead = dis.readInt();
|
||||||
|
DbgUtils.logf( "NBSService: incoming hash: %X", hashRead );
|
||||||
|
byte[] rest = new byte[dis.available()];
|
||||||
|
dis.read( rest );
|
||||||
|
int hashComputed = Arrays.hashCode( rest );
|
||||||
|
if ( hashComputed == hashRead ) {
|
||||||
|
DbgUtils.logf( "NBSService: incoming hashes on %d " +
|
||||||
|
"bytes match: %X", rest.length, hashRead );
|
||||||
|
receive( cmd, rest, senderPhone );
|
||||||
|
} else {
|
||||||
|
DbgUtils.logf( "NBSService: incoming hashes on %d bytes "
|
||||||
|
+ "DON'T match: read: %X; figured: %X",
|
||||||
|
rest.length, hashRead, hashComputed );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch ( java.io.IOException ioe ) {
|
||||||
|
DbgUtils.logf( "ioe: %s", ioe.toString() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean sendBuffers( byte[][] fragments, String phone )
|
||||||
|
{
|
||||||
|
DbgUtils.logf( "NBSService.sendBuffers()" );
|
||||||
|
boolean success = false;
|
||||||
|
if ( XWApp.onEmulator() ) {
|
||||||
|
DbgUtils.logf( "sendBuffer(phone=%s): FAKING IT", phone );
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
PendingIntent sent =
|
||||||
|
PendingIntent.getBroadcast( this, 0, new Intent(), 0 );
|
||||||
|
PendingIntent dlvrd =
|
||||||
|
PendingIntent.getBroadcast( this, 0, new Intent(), 0 );
|
||||||
|
|
||||||
|
SmsManager mgr = SmsManager.getDefault();
|
||||||
|
short port = XWApp.getNBSPort();
|
||||||
|
for ( byte[] fragment : fragments ) {
|
||||||
|
String tmp = new String(fragment);
|
||||||
|
DbgUtils.logf( "sending len %d packet: %s", tmp.length(), tmp );
|
||||||
|
mgr.sendDataMessage( phone, null, port, fragment, sent, dlvrd );
|
||||||
|
DbgUtils.logf( "sendDataMessage of %d bytes to %s on %d "
|
||||||
|
+ "finished", fragment.length, phone, port );
|
||||||
|
}
|
||||||
|
DbgUtils.showf( this, "send %dth msg", ++s_nSent );
|
||||||
|
success = true;
|
||||||
|
} catch ( IllegalArgumentException iae ) {
|
||||||
|
DbgUtils.logf( "%s", iae.toString() );
|
||||||
|
} catch ( Exception ee ) {
|
||||||
|
DbgUtils.logf( "sendDataMessage message failed: %s",
|
||||||
|
ee.toString() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void feedMessage( int gameID, byte[] msg, CommsAddrRec addr )
|
||||||
|
{
|
||||||
|
if ( BoardActivity.feedMessage( gameID, msg, addr ) ) {
|
||||||
|
// do nothing
|
||||||
|
} else {
|
||||||
|
long rowid = DBUtils.getRowIDFor( this, gameID );
|
||||||
|
if ( DBUtils.ROWID_NOTFOUND != rowid ) {
|
||||||
|
NBSMsgSink sink = new NBSMsgSink( this );
|
||||||
|
if ( GameUtils.feedMessage( this, rowid, msg, addr, sink ) ) {
|
||||||
|
postNotification( gameID, R.string.new_nbsmove_title,
|
||||||
|
getString(R.string.new_move_body)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void postNotification( int gameID, int title, String body )
|
||||||
|
{
|
||||||
|
Intent intent = new Intent( this, DispatchNotify.class );
|
||||||
|
intent.putExtra( DispatchNotify.GAMEID_EXTRA, gameID );
|
||||||
|
Utils.postNotification( this, intent, R.string.new_nbsmove_title,
|
||||||
|
body );
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NBSMsgSink extends MultiMsgSink {
|
||||||
|
private Context m_context;
|
||||||
|
public NBSMsgSink( Context context ) {
|
||||||
|
super();
|
||||||
|
m_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***** TransportProcs interface *****/
|
||||||
|
public int transportSend( byte[] buf, final CommsAddrRec addr, int gameID )
|
||||||
|
{
|
||||||
|
DbgUtils.logf( "NBSMsgSink.transportSend()" );
|
||||||
|
return sendPacket( addr.sms_phone, gameID, buf );
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean relayNoConnProc( byte[] buf, String relayID )
|
||||||
|
{
|
||||||
|
Assert.fail();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MsgStore {
|
||||||
|
String[] m_msgs;
|
||||||
|
int m_msgID;
|
||||||
|
int m_haveCount;
|
||||||
|
int m_fullLength;
|
||||||
|
|
||||||
|
public MsgStore( int id, int count )
|
||||||
|
{
|
||||||
|
m_msgID = id;
|
||||||
|
m_msgs = new String[count];
|
||||||
|
m_fullLength = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add( int index, String msg )
|
||||||
|
{
|
||||||
|
if ( null == m_msgs[index] ) {
|
||||||
|
++m_haveCount;
|
||||||
|
m_fullLength += msg.length();
|
||||||
|
}
|
||||||
|
m_msgs[index] = msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isComplete()
|
||||||
|
{
|
||||||
|
boolean complete = m_msgs.length == m_haveCount;
|
||||||
|
DbgUtils.logf( "isComplete(msg %d)=>%b", m_msgID, complete );
|
||||||
|
return complete;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] message()
|
||||||
|
{
|
||||||
|
byte[] result = new byte[m_fullLength];
|
||||||
|
int offset = 0;
|
||||||
|
for ( int ii = 0; ii < m_msgs.length; ++ii ) {
|
||||||
|
byte[] src = m_msgs[ii].getBytes();
|
||||||
|
System.arraycopy( src, 0, result, offset, src.length );
|
||||||
|
offset += src.length;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -193,9 +193,9 @@ public class NewGameActivity extends XWActivity {
|
||||||
m_gameID = GameUtils.newGameID();
|
m_gameID = GameUtils.newGameID();
|
||||||
m_gameName = Utils.format( this, R.string.dft_nbs_namef,
|
m_gameName = Utils.format( this, R.string.dft_nbs_namef,
|
||||||
m_gameID & 0xFFFF );
|
m_gameID & 0xFFFF );
|
||||||
NBSReceiver.inviteRemote( NewGameActivity.this, phones[0],
|
NBSService.inviteRemote( NewGameActivity.this, phones[0],
|
||||||
m_gameID, m_gameName,
|
m_gameID, m_gameName,
|
||||||
m_lang, 2, 1 );
|
m_lang, 2, 1 );
|
||||||
long rowid =
|
long rowid =
|
||||||
GameUtils.makeNewNBSGame( NewGameActivity.this,
|
GameUtils.makeNewNBSGame( NewGameActivity.this,
|
||||||
m_gameID, null, m_lang,
|
m_gameID, null, m_lang,
|
||||||
|
@ -355,6 +355,10 @@ public class NewGameActivity extends XWActivity {
|
||||||
m_newRowID = GameUtils.makeNewNBSGame( NewGameActivity.this,
|
m_newRowID = GameUtils.makeNewNBSGame( NewGameActivity.this,
|
||||||
gameID, null, m_lang,
|
gameID, null, m_lang,
|
||||||
2, 1 ); // initial defaults
|
2, 1 ); // initial defaults
|
||||||
|
String name = Utils.format( this, R.string.dft_nbs_namef,
|
||||||
|
gameID & 0xFFFF );
|
||||||
|
DBUtils.setName( this, m_newRowID, name );
|
||||||
|
|
||||||
Intent intent = new Intent( this, GameConfig.class );
|
Intent intent = new Intent( this, GameConfig.class );
|
||||||
intent.setAction( Intent.ACTION_EDIT );
|
intent.setAction( Intent.ACTION_EDIT );
|
||||||
intent.putExtra( GameUtils.INTENT_KEY_ROWID, m_newRowID );
|
intent.putExtra( GameUtils.INTENT_KEY_ROWID, m_newRowID );
|
||||||
|
|
Loading…
Reference in a new issue