From 599d01bfc763bea221cca761171ad67d94a97c84 Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 19 Mar 2012 07:56:55 -0700 Subject: [PATCH] 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. --- .../android/XWords4-bt/AndroidManifest.xml | 4 +- .../src/org/eehouse/android/xw4bt/.gitignore | 1 + xwords4/android/XWords4/AndroidManifest.xml | 2 +- xwords4/android/XWords4/project.properties | 2 +- .../android/XWords4/res/values/strings.xml | 2 +- .../org/eehouse/android/xw4/BTService.java | 2 +- .../eehouse/android/xw4/BoardActivity.java | 6 +- .../eehouse/android/xw4/CommsTransport.java | 28 +- .../org/eehouse/android/xw4/NBSReceiver.java | 188 +------ .../org/eehouse/android/xw4/NBSService.java | 492 ++++++++++++++++++ .../eehouse/android/xw4/NewGameActivity.java | 10 +- 11 files changed, 520 insertions(+), 217 deletions(-) create mode 100644 xwords4/android/XWords4/src/org/eehouse/android/xw4/NBSService.java diff --git a/xwords4/android/XWords4-bt/AndroidManifest.xml b/xwords4/android/XWords4-bt/AndroidManifest.xml index bbd4af51d..61de33b4b 100644 --- a/xwords4/android/XWords4-bt/AndroidManifest.xml +++ b/xwords4/android/XWords4-bt/AndroidManifest.xml @@ -36,7 +36,7 @@ - + + + diff --git a/xwords4/android/XWords4-bt/src/org/eehouse/android/xw4bt/.gitignore b/xwords4/android/XWords4-bt/src/org/eehouse/android/xw4bt/.gitignore index 6cf79ef43..be7ccd75b 100644 --- a/xwords4/android/XWords4-bt/src/org/eehouse/android/xw4bt/.gitignore +++ b/xwords4/android/XWords4-bt/src/org/eehouse/android/xw4bt/.gitignore @@ -52,3 +52,4 @@ XWListPreference.java GameNamer.java LookupActivity.java NBSInviteActivity.java +NBSService.java diff --git a/xwords4/android/XWords4/AndroidManifest.xml b/xwords4/android/XWords4/AndroidManifest.xml index d445362cc..552315b98 100644 --- a/xwords4/android/XWords4/AndroidManifest.xml +++ b/xwords4/android/XWords4/AndroidManifest.xml @@ -35,7 +35,7 @@ - + New move via Bluetooth - One or more moves has arrived + One or more moves has arrived Missing connections? Invite BT devs? (I won\'t ask again until you reopen this game.) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTService.java index 8de82bf99..0eab0ed8e 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTService.java @@ -511,7 +511,7 @@ public class BTService extends Service { buffer, addr, m_btMsgSink ) ) { postNotification( gameID, R.string.new_btmove_title, - R.string.new_btmove_body ); + R.string.new_move_body ); // do nothing } else { DbgUtils.logf( "nobody took msg for gameID %X", diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java index 472aa247b..13bc82228 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java @@ -1736,9 +1736,9 @@ public class BoardActivity extends XWActivity m_gi.nPlayers, 1 ); break; case COMMS_CONN_SMS: - NBSReceiver.inviteRemote( this, dev, m_gi.gameID, - gameName, m_gi.dictLang, - m_gi.nPlayers, 1 ); + NBSService.inviteRemote( this, dev, m_gi.gameID, + gameName, m_gi.dictLang, + m_gi.nPlayers, 1 ); break; } } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/CommsTransport.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/CommsTransport.java index 253efccf3..584a412ac 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/CommsTransport.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/CommsTransport.java @@ -235,6 +235,7 @@ public class CommsTransport implements TransportProcs, // // Let other know I'm here // m_jniThread.handle( JNIThread.JNICmd.CMD_RESEND ); // break; + // case COMMS_CONN_SMS: // default: // DbgUtils.logf( "tickle: unexpected type %s", // addr.conType.toString() ); @@ -379,33 +380,10 @@ public class CommsTransport implements TransportProcs, } break; case COMMS_CONN_SMS: - Assert.fail(); - // DbgUtils.logf( "sending via sms to %s:%d", - // 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() ); - // } + nSent = NBSService.sendPacket( m_context, addr.sms_phone, + gameID, buf ); break; case COMMS_CONN_BT: - String hostName = addr.bt_hostName; nSent = BTService.enqueueFor( m_context, buf, addr.bt_hostName, addr.bt_btAddr, gameID ); break; diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NBSReceiver.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NBSReceiver.java index 92e1150f9..9184a6da6 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NBSReceiver.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NBSReceiver.java @@ -20,39 +20,20 @@ package org.eehouse.android.xw4; -import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.telephony.SmsManager; 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 org.eehouse.android.xw4.jni.CommsAddrRec; - 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 public void onReceive( Context context, Intent intent ) { - DbgUtils.logf( "NBSReceiver::onReceive(intent=%s)!!!!", - intent.toString() ); + DbgUtils.logf( "NBSReceiver::onReceive()" ); Bundle bundle = intent.getExtras(); if ( null != bundle ) { @@ -61,167 +42,12 @@ public class NBSReceiver extends BroadcastReceiver { for ( int ii = 0; ii < nbses.length; ++ii ) { nbses[ii] = SmsMessage.createFromPdu((byte[])pdus[ii]); - receiveBuffer( context, nbses[ii].getUserData(), - nbses[ii].getOriginatingAddress() ); + String phone = 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 ); - } - } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NBSService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NBSService.java new file mode 100644 index 000000000..9fbff597b --- /dev/null +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NBSService.java @@ -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> s_partialMsgs + = new HashMap>(); + + 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 perPhone = + s_partialMsgs.get( senderPhone ); + if ( null == perPhone ) { + perPhone = new HashMap (); + 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; + } + } +} diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NewGameActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NewGameActivity.java index 588881d7b..d7ed9e2df 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/NewGameActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/NewGameActivity.java @@ -193,9 +193,9 @@ public class NewGameActivity extends XWActivity { m_gameID = GameUtils.newGameID(); m_gameName = Utils.format( this, R.string.dft_nbs_namef, m_gameID & 0xFFFF ); - NBSReceiver.inviteRemote( NewGameActivity.this, phones[0], - m_gameID, m_gameName, - m_lang, 2, 1 ); + NBSService.inviteRemote( NewGameActivity.this, phones[0], + m_gameID, m_gameName, + m_lang, 2, 1 ); long rowid = GameUtils.makeNewNBSGame( NewGameActivity.this, m_gameID, null, m_lang, @@ -355,6 +355,10 @@ public class NewGameActivity extends XWActivity { m_newRowID = GameUtils.makeNewNBSGame( NewGameActivity.this, gameID, null, m_lang, 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.setAction( Intent.ACTION_EDIT ); intent.putExtra( GameUtils.INTENT_KEY_ROWID, m_newRowID );