From 154e428fa802b691d1aa7c7933231a66169f5eee Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 7 Nov 2016 07:02:27 -0800 Subject: [PATCH] more work on wifi direct Now sends and receives a single packet more often than not. Disabled for non-DEBUG devices, and new permissions that are required are commented out so I don't accidentally ship before the sdk-23 permissions stuff makes additions less scary. --- xwords4/android/XWords4/AndroidManifest.xml | 6 +- .../src/org/eehouse/android/xw4/.gitignore | 1 + .../org/eehouse/android/xw4/BiDiSockWrap.java | 96 +++++++--- .../android/xw4/WifiDirectService.java | 170 +++++++++++++++--- 4 files changed, 225 insertions(+), 48 deletions(-) diff --git a/xwords4/android/XWords4/AndroidManifest.xml b/xwords4/android/XWords4/AndroidManifest.xml index aeecfc3fb..26984883d 100644 --- a/xwords4/android/XWords4/AndroidManifest.xml +++ b/xwords4/android/XWords4/AndroidManifest.xml @@ -48,7 +48,11 @@ - + + + + + mQueue; private Thread mReadThread; private Thread mWriteThread; private boolean mRunThreads; + private boolean mActive; + private InetAddress mAddress; + private int mPort; // For sockets that came from accept() on a ServerSocket public BiDiSockWrap( Socket socket, Iface iface ) { - init( socket, iface ); + mIface = iface; + init( socket ); } - // For creating sockets that will connect to a remote ServerSocket - public BiDiSockWrap( String address, int port, Iface iface ) + // For creating sockets that will connect to a remote ServerSocket. Must + // call connect() afterwards. + public BiDiSockWrap( InetAddress address, int port, Iface iface ) { - Socket socket = null; - try { - socket = new Socket( address, port ); - } catch ( java.net.UnknownHostException uhe ) { - Assert.fail(); - } catch ( IOException uhe ) { - Assert.fail(); - } - if ( null != socket ) { - init( socket, iface ); - } + mIface = iface; + mAddress = address; + mPort = port; + } + + public BiDiSockWrap connect() + { + mActive = true; + new Thread( new Runnable() { + public void run() { + long waitMillis = 1000; + while ( mActive ) { + try { + Thread.sleep( waitMillis ); + DbgUtils.logd( getClass(), "trying to connect..." ); + Socket socket = new Socket( mAddress, mPort ); + DbgUtils.logd( getClass(), "connected!!!" ); + init( socket ); + mIface.connectStateChanged( BiDiSockWrap.this, true ); + break; + } catch ( java.net.UnknownHostException uhe ) { + DbgUtils.logex( uhe ); + } catch ( IOException ioe ) { + DbgUtils.logex( ioe ); + } catch ( InterruptedException ie ) { + DbgUtils.logex( ie ); + } + waitMillis = Math.min( waitMillis * 2, 1000 * 60 ); + } + } + } ).start(); + + return this; } public Socket getSocket() { return mSocket; } + public boolean isConnected() { return null != getSocket(); } + + public void send( String packet ) + { + try { + send( packet.getBytes( "UTF-8" ) ); + } catch ( java.io.UnsupportedEncodingException uee ) { + DbgUtils.logex( uee ); + } + } + + public void send( JSONObject obj ) + { + send( obj.toString() ); + } + public void send( byte[] packet ) { Assert.assertNotNull( packet ); mQueue.add(packet); } - private void init( Socket socket, Iface iface ) + private void init( Socket socket ) { mSocket = socket; - mIface = iface; mQueue = new LinkedBlockingQueue(); startThreads(); } @@ -79,13 +125,14 @@ public class BiDiSockWrap { private void closeSocket() { mRunThreads = false; + mActive = false; try { mSocket.close(); } catch ( IOException ioe ) { DbgUtils.logex( ioe ); } - mIface.onSocketClosed( this ); - send( new byte[0] ); + mIface.connectStateChanged( this, false ); + send( (byte[])null ); } private void startThreads() @@ -99,9 +146,10 @@ public class BiDiSockWrap { = new DataOutputStream( mSocket.getOutputStream() ); while ( mRunThreads ) { byte[] packet = mQueue.take(); - DbgUtils.logd( getClass(), "write thread got packet of len %d", + DbgUtils.logd( BiDiSockWrap.class, + "write thread got packet of len %d", packet.length ); - if ( null == packet ) { + if ( null == packet || 0 == packet.length ) { closeSocket(); break; } @@ -126,11 +174,13 @@ public class BiDiSockWrap { = new DataInputStream( mSocket.getInputStream() ); while ( mRunThreads ) { short len = inStream.readShort(); + DbgUtils.logd( BiDiSockWrap.class, "got len: %d", len ); byte[] packet = new byte[len]; inStream.read( packet ); + mIface.gotPacket( BiDiSockWrap.this, packet ); } } catch( IOException ioe ) { - Assert.fail(); + DbgUtils.logex( ioe ); } } } ); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/WifiDirectService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/WifiDirectService.java index 731672c68..3cb2cc5f6 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/WifiDirectService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/WifiDirectService.java @@ -20,18 +20,17 @@ package org.eehouse.android.xw4; -import java.net.InetAddress; import android.app.Activity; -import android.net.NetworkInfo; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.net.NetworkInfo; import android.net.wifi.WpsInfo; import android.net.wifi.p2p.WifiP2pConfig; import android.net.wifi.p2p.WifiP2pDevice; -import android.net.wifi.p2p.WifiP2pInfo; import android.net.wifi.p2p.WifiP2pDeviceList; +import android.net.wifi.p2p.WifiP2pInfo; import android.net.wifi.p2p.WifiP2pManager.ActionListener; import android.net.wifi.p2p.WifiP2pManager.Channel; import android.net.wifi.p2p.WifiP2pManager.ChannelListener; @@ -42,9 +41,17 @@ import android.net.wifi.p2p.nsd.WifiP2pDnsSdServiceInfo; import android.net.wifi.p2p.nsd.WifiP2pDnsSdServiceRequest; import android.net.wifi.p2p.nsd.WifiP2pUpnpServiceInfo; import android.os.Looper; +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; +import org.json.JSONException; +import org.json.JSONObject; import junit.framework.Assert; @@ -52,6 +59,7 @@ public class WifiDirectService { private static final String SERVICE_NAME = "_xwords"; private static final String SERVICE_REG_TYPE = "_presence._tcp"; private static final boolean WIFI_DIRECT_ENABLED = true; + private static final int USE_PORT = 5432; private static Context sContext; private static Channel sChannel; @@ -60,10 +68,18 @@ public class WifiDirectService { private static WFDBroadcastReceiver sReceiver; private static boolean sDiscoveryStarted; private static boolean sEnabled; + private static boolean sAmServer; + private static Thread sAcceptThread; + private static ServerSocket sServerSock; + private static BiDiSockWrap.Iface sIface; + private static BiDiSockWrap sSocketWrap; + private static Set sPendingDevs = new HashSet(); + public static Activity sActivity; public static void activityResumed( Activity activity ) { if ( WIFI_DIRECT_ENABLED ) { + sActivity = activity; initListeners( activity ); activity.registerReceiver( sReceiver, sIntentFilter); DbgUtils.logd( WifiDirectService.class, "activityResumed() done" ); @@ -74,6 +90,7 @@ public class WifiDirectService { public static void activityPaused( Activity activity ) { if ( WIFI_DIRECT_ENABLED ) { + sActivity = null; activity.unregisterReceiver( sReceiver ); DbgUtils.logd( WifiDirectService.class, "activityPaused() done" ); } @@ -84,6 +101,25 @@ public class WifiDirectService { sContext = context; if ( WIFI_DIRECT_ENABLED ) { if ( null == sListener ) { + sIface = new BiDiSockWrap.Iface() { + public void gotPacket( BiDiSockWrap socket, byte[] bytes ) + { + DbgUtils.logd( WifiDirectService.class, "got packet!!!" ); + processPacket( socket, bytes ); + } + + public void connectStateChanged( BiDiSockWrap wrap, boolean nowConnected ) + { + if ( nowConnected ) { + try { + wrap.send( new JSONObject().put( "cmd", "ping" ) ); + } catch ( JSONException jse ) { + DbgUtils.logex( jse ); + } + } + } + }; + sListener = new ChannelListener() { @Override public void onChannelDisconnected() { @@ -131,7 +167,7 @@ public class WifiDirectService { mgr.discoverServices( sChannel, new WDAL("discoverServices") ); // mgr.discoverPeers( sChannel, sActionListener ); - DbgUtils.logd( WifiDirectService.class, "called discoverServices" ); + DbgUtils.logd( WifiDirectService.class, "called mgr.discoverServices" ); sDiscoveryStarted = true; } } @@ -168,16 +204,100 @@ public class WifiDirectService { private static void tryConnect( WifiP2pDevice device ) { - DbgUtils.logd( WifiDirectService.class, "trying to connect to %s", - device.toString() ); - WifiP2pConfig config = new WifiP2pConfig(); - config.deviceAddress = device.deviceAddress; - config.wps.setup = WpsInfo.PBC; + final String macAddress = device.deviceAddress; + if ( ! sPendingDevs.contains( macAddress ) ) { + DbgUtils.logd( WifiDirectService.class, "trying to connect to %s", + device.toString() ); + WifiP2pConfig config = new WifiP2pConfig(); + config.deviceAddress = device.deviceAddress; + config.wps.setup = WpsInfo.PBC; - WifiP2pManager mgr = (WifiP2pManager)sContext - .getSystemService(Context.WIFI_P2P_SERVICE); + WifiP2pManager mgr = (WifiP2pManager)sContext + .getSystemService(Context.WIFI_P2P_SERVICE); - mgr.connect( sChannel, config, new WDAL("connect") ); + mgr.connect( sChannel, config, new ActionListener() { + @Override + public void onSuccess() { + DbgUtils.logd( getClass(), "onSuccess(): %s", "connect_xx" ); + sPendingDevs.add( macAddress ); + } + @Override + public void onFailure(int reason) { + DbgUtils.logd( getClass(), "onFailure(%d): %s", reason, "connect_xx"); + } + } ); + } else { + DbgUtils.logd( WifiDirectService.class, "already connected to %s", + macAddress ); + } + } + + private static void connectToOwner( InetAddress addr ) + { + DbgUtils.logd( WifiDirectService.class, "connectToOwner(%s)", addr.toString() ); + sSocketWrap = new BiDiSockWrap( addr, USE_PORT, sIface ).connect(); + } + + private static void processPacket( BiDiSockWrap wrap, byte[] bytes ) + { + String asStr = new String(bytes); + DbgUtils.logd( WifiDirectService.class, "got string: %s", asStr ); + try { + JSONObject asObj = new JSONObject( asStr ); + DbgUtils.logd( WifiDirectService.class, "got json: %s", asObj.toString() ); + final String cmd = asObj.optString( "cmd", "" ); + sActivity.runOnUiThread( new Runnable() { + public void run() { + DbgUtils.showf( sActivity, "got cmd: %s", cmd ); + } + } ); + if ( cmd.equals( "ping" ) ) { + try { + wrap.send( new JSONObject().put( "cmd", "pong" ) ); + } catch ( JSONException jse ) { + DbgUtils.logex( jse ); + } + } + } catch ( JSONException jse ) { + DbgUtils.logex( jse ); + } + } + + private static void startAcceptThread() + { + sAmServer = true; + sAcceptThread = new Thread( new Runnable() { + public void run() { + try { + sServerSock = new ServerSocket( USE_PORT ); + while ( sAmServer ) { + DbgUtils.logd( WifiDirectService.class, "calling accept()" ); + Socket socket = sServerSock.accept(); + DbgUtils.logd( WifiDirectService.class, "accept() returned!!" ); + sSocketWrap = new BiDiSockWrap( socket, sIface ); + } + } catch ( IOException ioe ) { + sAmServer = false; + DbgUtils.logex( ioe ); + } + } + } ); + sAcceptThread.start(); + } + + private static void stopAcceptThread() + { + if ( null != sAcceptThread ) { + if ( null != sServerSock ) { + try { + sServerSock.close(); + } catch ( IOException ioe ) { + DbgUtils.logex( ioe ); + } + sServerSock = null; + } + sAcceptThread = null; + } } private static class WFDBroadcastReceiver extends BroadcastReceiver { @@ -246,16 +366,17 @@ public class WifiDirectService { info.toString(), hostAddress ); // After the group negotiation, we can determine the group owner. - if (info.groupFormed && info.isGroupOwner) { - DbgUtils.logd( getClass(), "am group owner" ); - // Do whatever tasks are specific to the group owner. - // One common case is creating a server thread and accepting - // incoming connections. - } else if (info.groupFormed) { - DbgUtils.logd( getClass(), "am NOT group owner" ); - // The other device acts as the client. In this case, - // you'll want to create a client thread that connects to the group - // owner. + if (info.groupFormed ) { + if ( info.isGroupOwner ) { + DbgUtils.logd( getClass(), "am group owner" ); + startAcceptThread(); + } else { + DbgUtils.logd( getClass(), "am NOT group owner" ); + connectToOwner( info.groupOwnerAddress ); + // The other device acts as the client. In this case, + // you'll want to create a client thread that connects to the group + // owner. + } } else { Assert.fail(); } @@ -264,12 +385,13 @@ public class WifiDirectService { private static class PLL implements WifiP2pManager.PeerListListener { @Override - public void onPeersAvailable(WifiP2pDeviceList peerList) { + public void onPeersAvailable( WifiP2pDeviceList peerList ) { DbgUtils.logd( getClass(), "got list of %d peers", peerList.getDeviceList().size() ); for ( WifiP2pDevice device : peerList.getDeviceList() ) { - // tryConnect( device ); + // DbgUtils.logd( getClass(), "not connecting to: %s", device.toString() ); + tryConnect( device ); } // Out with the old, in with the new. // peers.clear();