lots of changes. Respond to new-bt-game button by posting a list of

known paired-with-Crosswords devices and sending invite to the one
selected.  Includes rescan button with infinite progress indicator
(that sometimes works).  Recipient saves new game with the gameID
passed.  Then sender does too (but gameID doesn't seem to stick.)
Both games crash when you open them, but they're created.
This commit is contained in:
Eric House 2012-01-23 20:52:04 -08:00
parent f048980701
commit c425686589
4 changed files with 342 additions and 39 deletions

View file

@ -1838,4 +1838,9 @@
<string name="newgame_enable_bt">Turn Bluetooth on</string>
<string name="bt_pick_rescan_button">Rescan</string>
<string name="bt_pick_title">Opponent devices</string>
<string name="scan_progress">Scanning for Crosswords on paired devices</string>
</resources>

View file

@ -25,17 +25,27 @@ import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.bluetooth.BluetoothDevice;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Handler;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import java.util.UUID;
public class BTConnection extends BroadcastReceiver {
public static final int GOT_PONG = 1;
public static final int CONNECT_ACCEPTED = 2;
public static final int CONNECT_REFUSED = 3;
public static final int CONNECT_FAILED = 4;
public static final byte SCAN_DONE = 5;
public interface BTStateChangeListener {
public void stateChanged( boolean nowEnabled );
@ -44,16 +54,29 @@ public class BTConnection extends BroadcastReceiver {
private static final byte PING = 1;
private static final byte PONG = 2;
private static final byte INVITE = 3;
private static final byte INVITE_ACCPT = 4;
private static final byte INVITE_DECL = 5;
private static final byte MESG_SEND = 6;
private static final byte MESG_ACCPT = 7;
private static final byte MESG_DECL = 8;
private static BluetoothAdapter s_btAdapter =
BluetoothAdapter.getDefaultAdapter();
private static BluetoothServerSocket s_serverSocket;
private static HashMap<String,String> s_names =
new HashMap<String, String>();
private class BTListener extends Thread {
private Context m_context;
public BTListener( Context context )
{
m_context = context;
}
@Override
public void run() {
byte[] buffer = new byte[1024];
try {
s_serverSocket = s_btAdapter.
listenUsingRfcommWithServiceRecord( XWApp.getAppName(),
@ -66,22 +89,28 @@ public class BTConnection extends BroadcastReceiver {
while ( null != s_serverSocket ) {
BluetoothSocket socket = null;
InputStream inStream = null;
DataInputStream inStream = null;
int nRead = 0;
try {
DbgUtils.logf( "run: calling accept()" );
socket = s_serverSocket.accept(); // blocks
DbgUtils.logf( "run: accept() returned" );
inStream = socket.getInputStream();
nRead = inStream.read( buffer );
DbgUtils.logf( "read %d bytes from BT socket", nRead );
inStream = new DataInputStream( socket.getInputStream() );
// now handle what's on the socket
if ( 0 < nRead && buffer[0] == PING ) {
DbgUtils.logf( "got PING!!!" );
OutputStream os = socket.getOutputStream();
os.write( PONG );
os.flush();
short len = inStream.readShort();
if ( 1 <= len ) {
byte msg = inStream.readByte();
switch( msg ) {
case PING:
receivePing( socket );
break;
case INVITE:
receiveInvitation( m_context, inStream, socket );
break;
default:
DbgUtils.logf( "unexpected msg %d", msg );
break;
}
}
socket.close();
@ -90,7 +119,6 @@ public class BTConnection extends BroadcastReceiver {
DbgUtils.logf( "trying again..." );
continue;
}
}
if ( null != s_serverSocket ) {
@ -122,7 +150,7 @@ public class BTConnection extends BroadcastReceiver {
break;
case BluetoothAdapter.STATE_ON:
asString = "STATE_ON";
m_listener = new BTListener();
m_listener = new BTListener( context );
m_listener.start();
tellStateChanged( true );
break;
@ -159,25 +187,17 @@ public class BTConnection extends BroadcastReceiver {
for ( BluetoothDevice dev : pairedDevs ) {
BluetoothSocket socket = null;
try {
socket = dev.createRfcommSocketToServiceRecord( myUUID );
DbgUtils.logf( "PingThread: got socket to device %s",
dev.getName() );
socket.connect();
DbgUtils.logf( "PingThread: connected" );
OutputStream os = socket.getOutputStream();
os.write( PING );
DbgUtils.logf( "PingThread: wrote" );
os.flush();
InputStream is = socket.getInputStream();
byte[] buffer = new byte[128];
int nRead = is.read( buffer );
if ( 1 == nRead && buffer[0] == PONG ) {
m_handler.obtainMessage( GOT_PONG, dev.getName() )
.sendToTarget();
if ( sendPing( dev ) ) {
synchronized( s_names ) {
s_names.put( dev.getName(), dev.getAddress() );
}
if ( null != m_handler ) {
m_handler.obtainMessage( GOT_PONG, dev.getName() )
.sendToTarget();
}
}
socket.close();
} catch ( java.io.IOException ioe ) {
DbgUtils.logf( "PingThread: %s", ioe.toString() );
}
@ -201,9 +221,190 @@ public class BTConnection extends BroadcastReceiver {
s_stateChangeListener = li;
}
//
public static void enqueueFor( byte[] buf )
{
}
private static class InviteThread extends Thread {
String m_name;
int m_gameID;
Handler m_handler;
public InviteThread( String name, int gameID, Handler handler ) {
m_name = name;
m_gameID = gameID;
m_handler = handler;
}
public void run() {
String addr;
int result = CONNECT_FAILED;
synchronized( s_names ) {
addr = s_names.get( m_name );
}
if ( null != addr ) {
try {
BluetoothDevice remote = s_btAdapter.getRemoteDevice( addr );
if ( null != remote && sendInvitation( remote, m_gameID ) ) {
result = CONNECT_ACCEPTED;
}
} catch( java.io.IOException ioe ) {
DbgUtils.logf( "ioe: %s", ioe.toString() );
}
}
m_handler.obtainMessage( result, m_gameID ).sendToTarget();
}
}
public static void inviteRemote( String devName, int gameID,
Handler handler )
{
InviteThread thread = new InviteThread( devName, gameID, handler );
thread.start();
}
private static class BTScanner extends AsyncTask<Void, Void, Void> {
private Handler m_handler;
private ProgressDialog m_progress;
public BTScanner( Context context, Handler handler ) {
super();
m_handler = handler;
String msg = context.getString( R.string.scan_progress );
m_progress = ProgressDialog.show( context, msg, null, true, true );
}
@Override
protected Void doInBackground( Void... unused )
{
synchronized( s_names ) {
s_names.clear();
}
new PingThread( null ).run(); // same thread
return null;
}
@Override
protected void onPostExecute( Void unused )
{
m_progress.cancel();
m_handler.obtainMessage( SCAN_DONE ).sendToTarget();
}
}
public static void rescan( Context context, Handler handler )
{
new BTScanner( context, handler ).execute();
}
public static String[] listPairedWithXwords()
{
Set<String> names = null;
synchronized( s_names ) {
names = s_names.keySet();
}
String[] result = new String[names.size()];
Iterator<String> iter = names.iterator();
for ( int ii = 0; iter.hasNext(); ++ii ) {
result[ii] = iter.next();
}
return result;
}
private void tellStateChanged( boolean nowOn ) {
if ( null != s_stateChangeListener ) {
s_stateChangeListener.stateChanged( nowOn );
}
}
private static boolean sendInvitation( BluetoothDevice dev, int gameID )
throws java.io.IOException
{
boolean success = false;
BluetoothSocket socket =
dev.createRfcommSocketToServiceRecord( XWApp.getAppUUID() );
if ( null != socket ) {
socket.connect();
DataOutputStream outStream =
new DataOutputStream( socket.getOutputStream() );
short len = 1 // INVITE
+ 4 // gameID
;
writeHeader( outStream, len, INVITE );
// outStream.writeShort( len );
// outStream.writeByte( INVITE );
outStream.writeInt( gameID );
outStream.flush();
DataInputStream inStream =
new DataInputStream( socket.getInputStream() );
success = INVITE_ACCPT == inStream.readByte();
socket.close();
}
return success;
}
private static void receiveInvitation( Context context,
DataInputStream is,
BluetoothSocket socket )
throws java.io.IOException
{
int gameID = is.readInt();
DbgUtils.logf( "receiveInvitation: got gameID of %d", gameID );
GameUtils.makeNewBTGame( context, gameID );
// Post notification that, when selected, will create a game
// -- or ask if user wants to create one.
OutputStream os = socket.getOutputStream();
os.write( INVITE_ACCPT );
os.flush();
}
private static boolean sendPing( BluetoothDevice dev )
throws java.io.IOException
{
boolean success = false;
BluetoothSocket socket =
dev.createRfcommSocketToServiceRecord( XWApp.getAppUUID() );
if ( null != socket ) {
socket.connect();
DbgUtils.logf( "PingThread: connected" );
DataOutputStream os = new DataOutputStream( socket.getOutputStream() );
writeHeader( os, (short)1, PING );
DbgUtils.logf( "PingThread: wrote" );
os.flush();
DataInputStream is =
new DataInputStream( socket.getInputStream() );
if ( PONG == is.readByte() ) {
success = true;
}
socket.close();
}
return success;
}
private static void receivePing( BluetoothSocket socket )
throws java.io.IOException
{
DbgUtils.logf( "got PING!!!" );
OutputStream os = socket.getOutputStream();
os.write( PONG );
os.flush();
}
private static void writeHeader( DataOutputStream outStream, short len,
byte msg )
throws java.io.IOException
{
outStream.writeShort( len );
outStream.writeByte( msg );
}
}

View file

@ -381,8 +381,9 @@ public class GameUtils {
}
private static long makeNewMultiGame( Context context, CommsAddrRec addr,
int[] lang, int nPlayersT,
int nPlayersH, String inviteID )
int[] lang,
int nPlayersT, int nPlayersH,
String inviteID, int gameID )
{
long rowid = -1;
@ -391,6 +392,9 @@ public class GameUtils {
lang[0] = gi.dictLang;
gi.setNPlayers( nPlayersT, nPlayersH );
gi.juggle();
if ( 0 != gameID ) {
gi.gameID = gameID;
}
// Will need to add a setNPlayers() method to gi to make this
// work
Assert.assertTrue( gi.nPlayers == nPlayersT );
@ -414,7 +418,7 @@ public class GameUtils {
addr.ip_relay_invite = room;
return makeNewMultiGame( context, addr, lang, nPlayersT,
nPlayersH, inviteID );
nPlayersH, inviteID, 0 );
}
public static long makeNewNetGame( Context context, String room,
@ -430,7 +434,7 @@ public class GameUtils {
info.nPlayers );
}
public static long makeNewBTGame( Context context, boolean fixme )
public static long makeNewBTGame( Context context, int gameID )
{
long rowid = -1;
CommsAddrRec addr =
@ -438,7 +442,7 @@ public class GameUtils {
CommsAddrRec.CommsConnType.COMMS_CONN_BT );
int[] lang = { 1 }; // English
return makeNewMultiGame( context, addr, lang, 2, 1, null );
return makeNewMultiGame( context, addr, lang, 2, 1, null, gameID );
}
public static void launchInviteActivity( Context context,
@ -758,6 +762,16 @@ public class GameUtils {
return String.format( "%X", rint ).substring( 0, 4 );
}
public static int newGameID()
{
int rint = 0;
while ( 0 == rint ) {
rint = s_random.nextInt();
}
DbgUtils.logf( "newGameID=>%d", rint );
return rint;
}
private static void tellRelayDied( Context context, GameLock lock,
boolean informNow )
{

View file

@ -21,11 +21,16 @@
package org.eehouse.android.xw4;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
@ -42,9 +47,11 @@ public class NewGameActivity extends XWActivity
implements BTConnection.BTStateChangeListener {
private static final int NEW_GAME_ACTION = 1;
private static final int REQUEST_ENABLE_BT = 2;
private static final int PICK_BTDEV_DLG = DlgDelegate.DIALOG_LAST + 1;
private boolean m_showsOn;
private Handler m_handler = null;
@Override
protected void onCreate(Bundle savedInstanceState)
@ -93,6 +100,55 @@ public class NewGameActivity extends XWActivity
checkEnableBT( true );
}
@Override
protected Dialog onCreateDialog( final int id )
{
Dialog dialog = super.onCreateDialog( id );
if ( null == dialog ) {
AlertDialog.Builder ab;
DialogInterface.OnClickListener lstnr;
switch( id ) {
case PICK_BTDEV_DLG:
DialogInterface.OnClickListener scanLstnr =
new DialogInterface.OnClickListener() {
public void onClick( DialogInterface dlg,
int whichButton ) {
BTConnection.rescan( NewGameActivity.this,
getHandler() );
dlg.dismiss();
}
};
ab = new AlertDialog.Builder( this )
.setTitle( R.string.bt_pick_title )
.setNegativeButton( R.string.bt_pick_rescan_button,
scanLstnr );
final String[] btDevs = BTConnection.listPairedWithXwords();
if ( null != btDevs && 0 < btDevs.length ) {
DialogInterface.OnClickListener devChosenLstnr =
new DialogInterface.OnClickListener() {
public void onClick( DialogInterface dlg,
int whichButton ) {
if ( 0 <= whichButton && whichButton
< btDevs.length ) {
int gameID = GameUtils.newGameID();
BTConnection.
inviteRemote( btDevs[whichButton],
gameID,
getHandler() );
}
}
};
ab.setItems( btDevs, devChosenLstnr );
}
dialog = ab.create();
Utils.setRemoveOnDismiss( this, dialog, PICK_BTDEV_DLG );
break;
}
}
return dialog;
}
// DlgDelegate.DlgClickNotify interface
@Override
public void dlgButtonClicked( int id, int which )
@ -170,8 +226,12 @@ public class NewGameActivity extends XWActivity
private void makeNewBTGame( boolean useDefaults )
{
GameUtils.makeNewBTGame( this, useDefaults );
finish();
int gameID = GameUtils.newGameID();
if ( useDefaults ) {
showDialog( PICK_BTDEV_DLG );
} else {
Utils.notImpl( this );
}
}
private void checkEnableBT( boolean force )
@ -209,11 +269,34 @@ public class NewGameActivity extends XWActivity
public void onClick( View v ) {
Intent enableBtIntent =
new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult( enableBtIntent,
REQUEST_ENABLE_BT );
startActivityForResult( enableBtIntent, 0 );
}
} );
}
}
}
private Handler getHandler()
{
if ( null == m_handler ) {
m_handler = new Handler() {
public void handleMessage( Message msg ) {
switch( msg.what ) {
case BTConnection.CONNECT_ACCEPTED:
GameUtils.makeNewBTGame( NewGameActivity.this,
msg.arg1 );
finish();
break;
case BTConnection.CONNECT_REFUSED:
case BTConnection.CONNECT_FAILED:
break;
case BTConnection.SCAN_DONE:
showDialog( PICK_BTDEV_DLG );
break;
}
}
};
}
return m_handler;
}
}