├── AndroidManifest.xml ├── README ├── README.md ├── lib └── gson-2.1-appsonfire.jar ├── proguard.cfg ├── project.properties ├── res ├── drawable-hdpi │ └── ic_launcher.png ├── drawable-ldpi │ └── ic_launcher.png ├── drawable-mdpi │ └── ic_launcher.png └── values │ └── strings.xml └── src └── com └── appsonfire └── p2p ├── Ack.java ├── ClientConnectionCallback.java ├── Message.java ├── NetworkListener.java ├── NetworkService.java ├── NetworkServiceCallback.java ├── SeqIdGenerator.java ├── ServerReadyCallback.java ├── bluetooth ├── BluetoothClient.java ├── BluetoothServer.java ├── BluetoothService.java ├── BluetoothSocketHandler.java ├── P2PBluetooth.java └── wifi │ ├── P2PWifi.java │ ├── WifiClient.java │ ├── WifiDiscoveryClient.java │ ├── WifiDiscoveryClientListener.java │ ├── WifiDiscoveryServer.java │ ├── WifiDiscoveryServerListener.java │ ├── WifiServer.java │ └── WifiService.java └── util └── Logger.java /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | #P2P4Android 2 | ## Peer to peer messaging framework for Android. 3 | 4 | ```java 5 | P2PBluetooth p2p = new P2PBluetooth(); 6 | p2p.startBluetoothServer(this, new NetworkServiceCallback() { 7 | 8 | @Override 9 | public void onSuccess(NetworkService networkService) { 10 | getOkeyApplication().setNetworkService(networkService); 11 | networkService.registerNetworkListener(ActivityServer.this); 12 | runOnUiThread(new Runnable() { 13 | @Override 14 | public void run() { 15 | progressDialog.dismiss(); 16 | makeToast(ActivityServer.this, getString(R.string.bt_server_ready)); 17 | Button button = (Button) findViewById(R.id.start_bt_game_button); 18 | button.setEnabled(true); 19 | } 20 | }); 21 | } 22 | 23 | @Override 24 | public void onFailure(int reason) { 25 | runOnUiThread(new Runnable() { 26 | @Override 27 | public void run() { 28 | progressDialog.dismiss(); 29 | makeToast(ActivityServer.this, getString(R.string.bt_failed_to_start_server)); 30 | //ActivityDashboard'a geri don. 31 | finish(); 32 | } 33 | }); 34 | } 35 | }); 36 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #P2P4Android 2 | ## Peer to peer messaging framework for Android. 3 | 4 | * This is an android library project. 5 | * Sends and receives text messages. 6 | * Messages are decoded/encoded with json. 7 | * [Okey Mini](https://play.google.com/store/apps/details?id=com.appsonfire.okey) multiplayer bluetooth game uses this library. 8 | 9 | Bluetooth server start example: 10 | ```java 11 | P2PBluetooth p2p = new P2PBluetooth(); 12 | p2p.startBluetoothServer(this, new NetworkServiceCallback() { 13 | 14 | @Override 15 | public void onSuccess(NetworkService networkService) { 16 | //Register a network listener for receiving messages from peers. 17 | networkService.registerNetworkListener(this); 18 | 19 | //You can send messages to peers through networkService. 20 | } 21 | 22 | @Override 23 | public void onFailure(int reason) { 24 | /* 25 | * For a list of failure reasons see 26 | * https://github.com/ilkinulas/P2P4Android/blob/master/src/com/appsonfire/p2p/NetworkServiceCallback.java 27 | */ 28 | } 29 | }); 30 | ``` 31 | 32 | Bluetooth client start example: 33 | ```java 34 | P2PBluetooth p2p = new P2PBluetooth(); 35 | p2p.startBluetoothClient(this, new NetworkServiceCallback() { 36 | 37 | @Override 38 | public void onSuccess(NetworkService networkService) { 39 | //Register a network listener for receiving messages from peers. 40 | networkService.registerNetworkListener(this); 41 | 42 | //You can send messages to peers through networkService. 43 | } 44 | 45 | @Override 46 | public void onFailure(int reason) { 47 | /* 48 | * For a list of failure reasons see 49 | * https://github.com/ilkinulas/P2P4Android/blob/master/src/com/appsonfire/p2p/NetworkServiceCallback.java 50 | */ 51 | } 52 | }); 53 | ``` 54 | 55 | * Create your own messages by extending com.appsonfire.p2p.Message. 56 | * Messages are converted to JSON string before being sent to peers. 57 | * Received text messages (in JSON format) are converted to Message instances and delivered to application via NetworkListener interface. 58 | 59 | ```java 60 | package test; 61 | 62 | import com.appsonfire.p2p.Message; 63 | 64 | public class ConnectMessage extends Message { 65 | private String data; 66 | 67 | public ConnectMessage() { 68 | this.type = MessageType.CONNECT; 69 | } 70 | 71 | public void setData(String data) { 72 | this.data = data; 73 | } 74 | 75 | public String getData() { 76 | return this.data; 77 | } 78 | } 79 | ``` 80 | 81 | You should implement NetworkListener and register it to a NetworkService. 82 | 83 | ```java 84 | package com.appsonfire.p2p; 85 | public interface NetworkListener { 86 | 87 | public void messageReceived(Message message); 88 | 89 | public void connectionLost(String endPoint); 90 | 91 | } 92 | ``` 93 | 94 | P2P4Android only supports Bluetooth. WIFI support is in progress. -------------------------------------------------------------------------------- /lib/gson-2.1-appsonfire.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilkinulas/P2P4Android/ea969d6b2ca19f2e8e40f8d752a1691deda8b159/lib/gson-2.1-appsonfire.jar -------------------------------------------------------------------------------- /proguard.cfg: -------------------------------------------------------------------------------- 1 | -optimizationpasses 5 2 | -dontusemixedcaseclassnames 3 | -dontskipnonpubliclibraryclasses 4 | -dontpreverify 5 | -verbose 6 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* 7 | 8 | -keep public class * extends android.app.Activity 9 | -keep public class * extends android.app.Application 10 | -keep public class * extends android.app.Service 11 | -keep public class * extends android.content.BroadcastReceiver 12 | -keep public class * extends android.content.ContentProvider 13 | -keep public class * extends android.app.backup.BackupAgentHelper 14 | -keep public class * extends android.preference.Preference 15 | -keep public class com.android.vending.licensing.ILicensingService 16 | 17 | -keepclasseswithmembernames class * { 18 | native ; 19 | } 20 | 21 | -keepclasseswithmembers class * { 22 | public (android.content.Context, android.util.AttributeSet); 23 | } 24 | 25 | -keepclasseswithmembers class * { 26 | public (android.content.Context, android.util.AttributeSet, int); 27 | } 28 | 29 | -keepclassmembers class * extends android.app.Activity { 30 | public void *(android.view.View); 31 | } 32 | 33 | -keepclassmembers enum * { 34 | public static **[] values(); 35 | public static ** valueOf(java.lang.String); 36 | } 37 | 38 | -keep class * implements android.os.Parcelable { 39 | public static final android.os.Parcelable$Creator *; 40 | } 41 | -------------------------------------------------------------------------------- /project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | # Project target. 11 | target=android-10 12 | android.library=true 13 | -------------------------------------------------------------------------------- /res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilkinulas/P2P4Android/ea969d6b2ca19f2e8e40f8d752a1691deda8b159/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilkinulas/P2P4Android/ea969d6b2ca19f2e8e40f8d752a1691deda8b159/res/drawable-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilkinulas/P2P4Android/ea969d6b2ca19f2e8e40f8d752a1691deda8b159/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | P2P4Android 5 | Please wait 6 | Starting Server 7 | Bluetooth enabled. 8 | Searching bluetooth server... 9 | -------------------------------------------------------------------------------- /src/com/appsonfire/p2p/Ack.java: -------------------------------------------------------------------------------- 1 | package com.appsonfire.p2p; 2 | 3 | 4 | public class Ack extends Message { 5 | 6 | public Ack(Message originalMessage) { 7 | this.ack = true; 8 | this.sequenceId = originalMessage.getSequenceId(); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/com/appsonfire/p2p/ClientConnectionCallback.java: -------------------------------------------------------------------------------- 1 | package com.appsonfire.p2p; 2 | 3 | public interface ClientConnectionCallback { 4 | public void connectionEstablished(); 5 | public void connectionFailed(); 6 | } 7 | -------------------------------------------------------------------------------- /src/com/appsonfire/p2p/Message.java: -------------------------------------------------------------------------------- 1 | package com.appsonfire.p2p; 2 | 3 | import java.io.IOException; 4 | 5 | import com.google.appsonfire.gson.Gson; 6 | import com.google.appsonfire.gson.GsonBuilder; 7 | import com.google.appsonfire.gson.JsonElement; 8 | import com.google.appsonfire.gson.TypeAdapter; 9 | import com.google.appsonfire.gson.internal.Streams; 10 | import com.google.appsonfire.gson.stream.JsonReader; 11 | import com.google.appsonfire.gson.stream.JsonWriter; 12 | 13 | public abstract class Message { 14 | protected String klass = getClass().getCanonicalName(); 15 | protected int type; 16 | protected long sequenceId; 17 | protected int playerIndex = -1; 18 | protected String playerName; 19 | protected boolean ack; 20 | protected String sourceAddress; 21 | 22 | protected boolean updateTurn; 23 | 24 | private static Gson gson = null; 25 | 26 | static { 27 | GsonBuilder gsonBuilder = new GsonBuilder(); 28 | gsonBuilder.registerTypeAdapter(Message.class, new MessageTypeAdapter().nullSafe()); 29 | gson = gsonBuilder.create(); 30 | } 31 | 32 | public static Message decode(String s) { 33 | return gson.fromJson(s, Message.class); 34 | 35 | } 36 | 37 | public String encode() { 38 | return gson.toJson(this); 39 | } 40 | 41 | public boolean shouldUpdateTurn() { 42 | return this.updateTurn; 43 | } 44 | 45 | public long getSequenceId() { 46 | return sequenceId; 47 | } 48 | 49 | public void setSequenceId(long sequenceId) { 50 | this.sequenceId = sequenceId; 51 | } 52 | 53 | public boolean isAck() { 54 | return ack; 55 | } 56 | 57 | public void setAck(boolean ack) { 58 | this.ack = ack; 59 | } 60 | 61 | public String getKlass() { 62 | return klass; 63 | } 64 | 65 | public int getPlayerIndex() { 66 | return playerIndex; 67 | } 68 | 69 | public String getPlayerName() { 70 | return playerName; 71 | } 72 | 73 | public void setPlayerIndex(int playerIndex) { 74 | this.playerIndex = playerIndex; 75 | } 76 | 77 | public void setPlayerName(String playerName) { 78 | this.playerName = playerName; 79 | } 80 | 81 | public String getSourceAddress() { 82 | return sourceAddress; 83 | } 84 | 85 | public void setSourceAddress(String sourceAddress) { 86 | this.sourceAddress = sourceAddress; 87 | } 88 | 89 | public int getType() { 90 | return type; 91 | } 92 | 93 | public void setType(int type) { 94 | this.type = type; 95 | } 96 | 97 | public static class MessageTypeAdapter extends TypeAdapter { 98 | private Gson gson = new Gson(); 99 | 100 | @Override 101 | public Message read(JsonReader reader) throws IOException { 102 | JsonElement jsonElement = Streams.parse(reader); 103 | String type = jsonElement.getAsJsonObject().get("klass").getAsString(); 104 | // Logger.d("Message type is " + type); 105 | try { 106 | String jsonString = jsonElement.getAsJsonObject().toString(); 107 | // Logger.d("Json String for message is " + jsonString); 108 | return (Message) gson.fromJson(jsonString, Class.forName(type)); 109 | } catch (Exception e) { 110 | e.printStackTrace(); 111 | } 112 | return null; 113 | } 114 | 115 | @Override 116 | public void write(JsonWriter writer, Message message) throws IOException { 117 | writer.value(gson.toJson(message)); 118 | } 119 | 120 | } 121 | 122 | @Override 123 | public String toString() { 124 | StringBuilder builder = new StringBuilder(); 125 | builder.append("Message [klass="); 126 | builder.append(klass); 127 | builder.append(", type="); 128 | builder.append(type); 129 | builder.append(", sequenceId="); 130 | builder.append(sequenceId); 131 | builder.append(", playerIndex="); 132 | builder.append(playerIndex); 133 | builder.append(", playerName="); 134 | builder.append(playerName); 135 | builder.append(", ack="); 136 | builder.append(ack); 137 | builder.append(", sourceAddress="); 138 | builder.append(sourceAddress); 139 | builder.append("]"); 140 | return builder.toString(); 141 | } 142 | 143 | 144 | 145 | 146 | 147 | } 148 | -------------------------------------------------------------------------------- /src/com/appsonfire/p2p/NetworkListener.java: -------------------------------------------------------------------------------- 1 | package com.appsonfire.p2p; 2 | 3 | public interface NetworkListener { 4 | 5 | public void messageReceived(Message message); 6 | 7 | public void connectionLost(String endPoint); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/com/appsonfire/p2p/NetworkService.java: -------------------------------------------------------------------------------- 1 | package com.appsonfire.p2p; 2 | 3 | 4 | //TODO complete javadoc documentation.. 5 | public interface NetworkService { 6 | public void registerNetworkListener(NetworkListener listener); 7 | 8 | public void startServer(ServerReadyCallback callback); 9 | 10 | public void startClient(String serverAddress, ClientConnectionCallback callback); 11 | 12 | public boolean isServer(); 13 | 14 | public void sendMessageToServer(Message message); 15 | 16 | public void sendMessageToServer(Message message, long timeout) throws InterruptedException; 17 | 18 | public void sendMessageToClient(String destination, Message message); 19 | 20 | public void sendMessageToClient(String destination, Message message, long timeout) throws InterruptedException; 21 | 22 | public void sendMessageToClients(Message message); 23 | 24 | public void sendMessageToClients(Message message, String exclude); 25 | 26 | public void sendMessageToClients(Message message, long timeout) throws InterruptedException; 27 | 28 | public void sendMessageToClients(Message message, long timeout, String exclude) throws InterruptedException; 29 | 30 | public void sendAck(Message message); 31 | 32 | public void terminate(); 33 | 34 | public String getMyNetworkAddress(); 35 | } 36 | -------------------------------------------------------------------------------- /src/com/appsonfire/p2p/NetworkServiceCallback.java: -------------------------------------------------------------------------------- 1 | package com.appsonfire.p2p; 2 | 3 | public interface NetworkServiceCallback { 4 | 5 | public static final int REASON_BLUETOOTH_IS_NOT_AVAILABLE = 1; 6 | 7 | public static final int REASON_BLUETOOTH_DISCOVERY_STARTED = 2; 8 | 9 | public static final int REASON_BLUETOOTH_DISCOVERY_FINISHED = 3; 10 | 11 | public static final int REASON_BLUETOOTH_IS_NOT_ENABLED = 4; 12 | 13 | public static final int REASON_BLUETOOTH_IS_ENABLED = 5; 14 | 15 | public static final int REASON_BLUETOOTH_DISCOVERABLE_MODE_IS_ON = 6; 16 | 17 | public static final int REASON_BLUETOOTH_NOT_DISCOVERABLE = 7; 18 | 19 | public static final int REASON_BLUETOOTH_CAN_NOT_CREATE_SERVER_SOCKET = 8; 20 | 21 | public static final int REASON_BLUETOOTH_CAN_NOT_CONNECT_TO_SERVER = 9; 22 | 23 | public static final int REASON_BLUETOOTH_CAN_NOT_FIND_SERVER = 10; 24 | 25 | public static final int REASON_CANCELED_BY_USER = 11; 26 | 27 | public void onSuccess(NetworkService networkService); 28 | 29 | public void onFailure(int reason); 30 | } 31 | -------------------------------------------------------------------------------- /src/com/appsonfire/p2p/SeqIdGenerator.java: -------------------------------------------------------------------------------- 1 | package com.appsonfire.p2p; 2 | 3 | import java.util.concurrent.atomic.AtomicLong; 4 | 5 | public class SeqIdGenerator { 6 | private static final AtomicLong seqId = new AtomicLong(1); 7 | 8 | public static final long nextSeqId() { 9 | return seqId.getAndIncrement(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/com/appsonfire/p2p/ServerReadyCallback.java: -------------------------------------------------------------------------------- 1 | package com.appsonfire.p2p; 2 | 3 | public interface ServerReadyCallback { 4 | public void acceptingConnections(); 5 | public void initFailure(); 6 | } 7 | -------------------------------------------------------------------------------- /src/com/appsonfire/p2p/bluetooth/BluetoothClient.java: -------------------------------------------------------------------------------- 1 | package com.appsonfire.p2p.bluetooth; 2 | 3 | import java.io.IOException; 4 | import java.util.UUID; 5 | 6 | import android.bluetooth.BluetoothAdapter; 7 | import android.bluetooth.BluetoothDevice; 8 | import android.bluetooth.BluetoothSocket; 9 | 10 | import com.appsonfire.p2p.ClientConnectionCallback; 11 | import com.appsonfire.p2p.Message; 12 | import com.appsonfire.p2p.NetworkListener; 13 | import com.appsonfire.p2p.SeqIdGenerator; 14 | import com.appsonfire.p2p.util.Logger; 15 | 16 | public class BluetoothClient { 17 | 18 | private BluetoothAdapter bluetoothAdapter; 19 | private final String serverAddress; 20 | private NetworkListener networkListener; 21 | private BluetoothSocketHandler socketHandler; 22 | private final ClientConnectionCallback connectionEstablishedCallback; 23 | 24 | public BluetoothClient(String serverAddress, ClientConnectionCallback connectionEstablishedCallback) { 25 | this.serverAddress = serverAddress; 26 | this.connectionEstablishedCallback = connectionEstablishedCallback; 27 | this.bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 28 | } 29 | 30 | public void registerNetworkListener(NetworkListener listener) { 31 | this.networkListener = listener; 32 | if (this.socketHandler != null) { 33 | this.socketHandler.setNetworkListener(this.networkListener); 34 | } 35 | Logger.d(listener + " registered as NetworkListener (client)"); 36 | } 37 | 38 | public void start() { 39 | BluetoothSocket socket = connect(); 40 | if (socket == null) { 41 | if (connectionEstablishedCallback != null) { 42 | connectionEstablishedCallback.connectionFailed(); 43 | } 44 | return; 45 | } 46 | 47 | this.socketHandler = new BluetoothSocketHandler(socket); 48 | this.socketHandler.setNetworkListener(this.networkListener); 49 | this.socketHandler.start(); 50 | this.connectionEstablishedCallback.connectionEstablished(); 51 | } 52 | 53 | public void sendMessage(Message message) { 54 | if ( ! message.isAck()) { 55 | message.setSequenceId(SeqIdGenerator.nextSeqId()); 56 | } 57 | Logger.d("Sending message " + message + " to server"); 58 | this.socketHandler.sendMessage(message); 59 | } 60 | 61 | public void sendMessage(Message message, long timeout) throws InterruptedException { 62 | if ( ! message.isAck()) { 63 | message.setSequenceId(SeqIdGenerator.nextSeqId()); 64 | } 65 | Logger.d("Sending message " + message + " to server"); 66 | this.socketHandler.sendMessage(message, timeout); 67 | } 68 | 69 | private BluetoothSocket connect() { 70 | BluetoothDevice remoteDevice = bluetoothAdapter.getRemoteDevice(serverAddress); 71 | for (int i = 0; i < BluetoothService.UUIDs.length; i++) { 72 | BluetoothSocket bluetoothSocket = null; 73 | UUID uuid = BluetoothService.UUIDs[i]; 74 | try { 75 | bluetoothSocket = remoteDevice.createRfcommSocketToServiceRecord(uuid); 76 | } catch (IOException e) { 77 | Logger.w("IOException caught while executing createRfcommSocketToServiceRecord"); 78 | doSleep(3000L); 79 | continue; 80 | } 81 | try { 82 | bluetoothSocket.connect(); 83 | } catch (IOException e) { 84 | Logger.w("IOException caught while connecting bluetooth socket. UUID = " + uuid , e); 85 | doSleep(500L); 86 | continue; 87 | } 88 | return bluetoothSocket; 89 | } 90 | Logger.w("Can not connect to server@" + this.serverAddress); 91 | return null; 92 | } 93 | 94 | private static void doSleep(long duration) { 95 | try { 96 | Thread.sleep(duration); 97 | } catch (InterruptedException interruptedException) { 98 | Logger.e("Sleep interrupted. " , interruptedException); 99 | } 100 | } 101 | 102 | public void terminate() { 103 | Logger.i("Terminating BluetoothClient."); 104 | if (this.socketHandler != null ) { 105 | this.socketHandler.terminate(); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/com/appsonfire/p2p/bluetooth/BluetoothServer.java: -------------------------------------------------------------------------------- 1 | package com.appsonfire.p2p.bluetooth; 2 | 3 | import java.io.IOException; 4 | import java.util.Collection; 5 | import java.util.Map; 6 | import java.util.Map.Entry; 7 | import java.util.Set; 8 | import java.util.UUID; 9 | import java.util.concurrent.ConcurrentHashMap; 10 | 11 | import android.bluetooth.BluetoothAdapter; 12 | import android.bluetooth.BluetoothServerSocket; 13 | import android.bluetooth.BluetoothSocket; 14 | 15 | import com.appsonfire.p2p.Message; 16 | import com.appsonfire.p2p.NetworkListener; 17 | import com.appsonfire.p2p.SeqIdGenerator; 18 | import com.appsonfire.p2p.ServerReadyCallback; 19 | import com.appsonfire.p2p.util.Logger; 20 | 21 | public class BluetoothServer extends Thread { 22 | 23 | private boolean running = true; 24 | private BluetoothAdapter bluetoothAdapter; 25 | private NetworkListener networkListener; 26 | private Map clientSocketHandlers = new ConcurrentHashMap(); 27 | private ServerReadyCallback serverReadyCallback; 28 | private BluetoothServerSocket serverSocket; 29 | 30 | public BluetoothServer(ServerReadyCallback callback) { 31 | this.bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 32 | this.serverReadyCallback = callback; 33 | Logger.d("New Bluetooth Server instance created."); 34 | } 35 | 36 | public void registerNetworkListener(NetworkListener listener) { 37 | this.networkListener = listener; 38 | Collection handlers = this.clientSocketHandlers.values(); 39 | for (BluetoothSocketHandler handler : handlers) { 40 | handler.setNetworkListener(this.networkListener); 41 | } 42 | Logger.d(listener + " registered as NetworkListener (server)"); 43 | } 44 | 45 | 46 | @Override 47 | public void run() { 48 | boolean notifyAcceptingConnections = true; 49 | try { 50 | Logger.i("Bluetooth Server started."); 51 | for (UUID uuid : BluetoothService.UUIDs) { 52 | if (! running) { 53 | break; 54 | } 55 | serverSocket = bluetoothAdapter.listenUsingRfcommWithServiceRecord(BluetoothService.BLUETOOTH_SERVICE_NAME, uuid); 56 | if (notifyAcceptingConnections) { 57 | //before accepting first client connection notify serverReadyCallback. 58 | serverReadyCallback.acceptingConnections(); 59 | notifyAcceptingConnections = false; 60 | } 61 | Logger.d("Server socket created UUID " + uuid); 62 | BluetoothSocket bluetoothSocket = serverSocket.accept(); 63 | serverSocket.close(); 64 | 65 | BluetoothSocketHandler socketHandler = new BluetoothSocketHandler(bluetoothSocket); 66 | 67 | String destination = bluetoothSocket.getRemoteDevice().getAddress(); 68 | this.clientSocketHandlers.put(destination, socketHandler); 69 | Logger.d("Client address " + destination + " saved."); 70 | if (this.networkListener != null) { 71 | socketHandler.setNetworkListener(this.networkListener); 72 | } 73 | 74 | socketHandler.start(); 75 | } 76 | Logger.w("MAX number of clients reached. MAX = " + BluetoothService.UUIDs.length); 77 | } catch (Exception e) { 78 | Logger.e("BluetoothServer failed to listen for incomming connections.", e); 79 | serverReadyCallback.initFailure(); 80 | } 81 | Logger.i("BluetoothServer exiting..."); 82 | } 83 | 84 | 85 | /** 86 | * Waits for an acknowledgement. If ack is not received until timeout miliseconds calling thread is interrupted. 87 | * @param destination 88 | * @param message 89 | * @param timeout in miliseconds 90 | */ 91 | public void sendMessage(String destination, Message message, long timeout) throws InterruptedException { 92 | BluetoothSocketHandler clientHandler = this.clientSocketHandlers.get(destination); 93 | if (! message.isAck()) { 94 | message.setSequenceId(SeqIdGenerator.nextSeqId()); 95 | } 96 | if (clientHandler == null) { 97 | Logger.w("Client socket handler for destination " + destination + " is null"); 98 | return; 99 | } 100 | Logger.d("Sending message " + message + " to " + destination); 101 | clientHandler.sendMessage(message, timeout); 102 | } 103 | 104 | public void sendMessage(String destination, Message message) { 105 | BluetoothSocketHandler clientHandler = this.clientSocketHandlers.get(destination); 106 | if (! message.isAck()) { 107 | message.setSequenceId(SeqIdGenerator.nextSeqId()); 108 | } 109 | if (clientHandler == null) { 110 | Logger.w("Client socket handler for destination " + destination + " is null"); 111 | return; 112 | } 113 | Logger.d("Sending message " + message + " to " + destination); 114 | clientHandler.sendMessage(message); 115 | } 116 | 117 | public void sendMessage(Message message) { 118 | sendMessage(message, null); 119 | } 120 | 121 | public void sendMessage(Message message, String exclude) { 122 | Set> entrySet = this.clientSocketHandlers.entrySet(); 123 | for (Entry entry: entrySet) { 124 | String client = entry.getKey(); 125 | if ( ! client.equals(exclude)) { 126 | sendMessage(client, message); 127 | } 128 | } 129 | } 130 | 131 | public void sendMessage(Message message, long timeout) throws InterruptedException{ 132 | sendMessage(message, timeout, null); 133 | } 134 | 135 | public void sendMessage(Message message, long timeout, String exclude) throws InterruptedException { 136 | Set> entrySet = this.clientSocketHandlers.entrySet(); 137 | for (Entry entry: entrySet) { 138 | String client = entry.getKey(); 139 | if (client.equals(exclude)) { 140 | continue; 141 | } 142 | sendMessage(client, message, timeout); 143 | } 144 | } 145 | 146 | public void terminate() { 147 | Logger.i("Terminating BluetoothServer."); 148 | this.running = false; 149 | Set> entrySet = this.clientSocketHandlers.entrySet(); 150 | for (Entry entry : entrySet) { 151 | entry.getValue().terminate(); 152 | } 153 | this.clientSocketHandlers.clear(); 154 | if (this.serverSocket != null) { 155 | try { 156 | this.serverSocket.close(); 157 | } catch (IOException e) { 158 | Logger.e("Exception caught while closing server socket", e); 159 | } 160 | } 161 | } 162 | 163 | } 164 | -------------------------------------------------------------------------------- /src/com/appsonfire/p2p/bluetooth/BluetoothService.java: -------------------------------------------------------------------------------- 1 | package com.appsonfire.p2p.bluetooth; 2 | 3 | import java.util.UUID; 4 | 5 | import android.bluetooth.BluetoothAdapter; 6 | 7 | import com.appsonfire.p2p.Ack; 8 | import com.appsonfire.p2p.ClientConnectionCallback; 9 | import com.appsonfire.p2p.Message; 10 | import com.appsonfire.p2p.NetworkListener; 11 | import com.appsonfire.p2p.NetworkService; 12 | import com.appsonfire.p2p.ServerReadyCallback; 13 | import com.appsonfire.p2p.util.Logger; 14 | 15 | public class BluetoothService implements NetworkService { 16 | 17 | public static final int SERVICE_TYPE_CLIENT = 1; 18 | public static final int SERVICE_TYPE_SERVER = 2; 19 | public static final String BLUETOOTH_SERVICE_NAME = "com.appsonfire.multiplayergamefw.bluetoothservice"; 20 | 21 | private int type; 22 | 23 | public static final UUID[] UUIDs = { 24 | UUID.fromString("a60f35f0-b93a-11de-8a39-08002009c661"), 25 | UUID.fromString("a60f35f0-b93a-11de-8a39-08002009c662"), 26 | UUID.fromString("a60f35f0-b93a-11de-8a39-08002009c663"), 27 | UUID.fromString("a60f35f0-b93a-11de-8a39-08002009c664"), 28 | UUID.fromString("a60f35f0-b93a-11de-8a39-08002009c665"), 29 | UUID.fromString("a60f35f0-b93a-11de-8a39-08002009c666"), 30 | UUID.fromString("a60f35f0-b93a-11de-8a39-08002009c667"), }; 31 | 32 | private BluetoothClient bluetoothClient = null; 33 | private BluetoothServer bluetoothServer = null; 34 | private boolean bluetoothServerStarted; 35 | 36 | public boolean isBluetoothServerStarted() { 37 | return this.bluetoothServerStarted; 38 | } 39 | 40 | @Override 41 | public void registerNetworkListener(NetworkListener listener) { 42 | if (isServer()) { 43 | if (this.bluetoothServer != null) { 44 | this.bluetoothServer.registerNetworkListener(listener); 45 | } 46 | } else { 47 | if (this.bluetoothClient != null) { 48 | this.bluetoothClient.registerNetworkListener(listener); 49 | } 50 | } 51 | } 52 | 53 | @Override 54 | public void sendMessageToServer(Message message) { 55 | bluetoothClient.sendMessage(message); 56 | } 57 | 58 | @Override 59 | public void sendMessageToServer(Message message, long timeout) throws InterruptedException { 60 | bluetoothClient.sendMessage(message, timeout); 61 | } 62 | 63 | @Override 64 | public void sendMessageToClient(String destination, Message message) { 65 | bluetoothServer.sendMessage(destination, message); 66 | } 67 | 68 | @Override 69 | public void sendMessageToClient(String destination, Message message, long timeout) throws InterruptedException { 70 | bluetoothServer.sendMessage(destination, message, timeout); 71 | } 72 | 73 | 74 | @Override 75 | public void sendMessageToClients(Message message) { 76 | bluetoothServer.sendMessage(message); 77 | } 78 | 79 | @Override 80 | public void sendMessageToClients(Message message, String exclude) { 81 | bluetoothServer.sendMessage(message, exclude); 82 | } 83 | 84 | @Override 85 | public void sendMessageToClients(Message message, long timeout) throws InterruptedException { 86 | bluetoothServer.sendMessage(message, timeout); 87 | } 88 | 89 | @Override 90 | public void sendMessageToClients(Message message, long timeout, String exclude) throws InterruptedException { 91 | bluetoothServer.sendMessage(message, timeout, exclude); 92 | } 93 | 94 | @Override 95 | public void startServer(ServerReadyCallback callback) { 96 | Logger.d("Starting bluetooth server service."); 97 | this.type = SERVICE_TYPE_SERVER; 98 | if (this.bluetoothServer != null) { 99 | this.bluetoothServer.terminate(); 100 | this.bluetoothServer.interrupt(); 101 | this.bluetoothServer = null; 102 | } else { 103 | Logger.d("Bluetooth server is null. Creating a new one."); 104 | } 105 | this.bluetoothServer = new BluetoothServer(callback); 106 | this.bluetoothServer.start(); 107 | this.bluetoothServerStarted = true; 108 | } 109 | 110 | @Override 111 | public void startClient(String serverAddress, ClientConnectionCallback callback) { 112 | Logger.d("Starting bluetooth client service."); 113 | this.type = SERVICE_TYPE_CLIENT; 114 | if (this.bluetoothClient != null ) { 115 | this.bluetoothClient.terminate(); 116 | } 117 | this.bluetoothClient = new BluetoothClient(serverAddress, callback); 118 | this.bluetoothClient.start(); 119 | } 120 | 121 | @Override 122 | public boolean isServer() { 123 | return this.type == SERVICE_TYPE_SERVER; 124 | } 125 | 126 | @Override 127 | public void sendAck(Message message) { 128 | Logger.d("Sending ACK for " + message); 129 | Ack ackMessage = new Ack(message); 130 | if (isServer()) { 131 | bluetoothServer.sendMessage(message.getSourceAddress(), ackMessage); 132 | } else { 133 | bluetoothClient.sendMessage(ackMessage); 134 | } 135 | } 136 | 137 | @Override 138 | public void terminate() { 139 | if (this.bluetoothClient != null) { 140 | this.bluetoothClient.terminate(); 141 | this.bluetoothClient = null; 142 | } 143 | if (this.bluetoothServer != null) { 144 | this.bluetoothServer.terminate(); 145 | this.bluetoothServer = null; 146 | } 147 | this.bluetoothServerStarted = false; 148 | } 149 | 150 | @Override 151 | public String getMyNetworkAddress() { 152 | return BluetoothAdapter.getDefaultAdapter().getAddress(); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/com/appsonfire/p2p/bluetooth/BluetoothSocketHandler.java: -------------------------------------------------------------------------------- 1 | package com.appsonfire.p2p.bluetooth; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.InputStreamReader; 7 | import java.io.OutputStream; 8 | import java.io.PrintWriter; 9 | import java.util.concurrent.TimeUnit; 10 | import java.util.concurrent.locks.Condition; 11 | import java.util.concurrent.locks.Lock; 12 | import java.util.concurrent.locks.ReentrantLock; 13 | 14 | import android.bluetooth.BluetoothAdapter; 15 | import android.bluetooth.BluetoothSocket; 16 | import android.util.Log; 17 | 18 | import com.appsonfire.p2p.Message; 19 | import com.appsonfire.p2p.NetworkListener; 20 | import com.appsonfire.p2p.util.Logger; 21 | 22 | public class BluetoothSocketHandler extends Thread { 23 | 24 | 25 | private final BluetoothSocket socket; 26 | private final String destinationAddress; 27 | private boolean running = true; 28 | private Message ackWaitingMessage = null; 29 | private Lock ackLock = new ReentrantLock(true); 30 | private Condition ackReceivedCondition = ackLock.newCondition(); 31 | private NetworkListener networkListener; 32 | private BluetoothAdapter bluetoothAdapter; 33 | 34 | public BluetoothSocketHandler(BluetoothSocket socket) { 35 | super(); 36 | this.socket = socket; 37 | this.destinationAddress = socket.getRemoteDevice().getAddress(); 38 | this.bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 39 | setName("BluetoothSocketHandler_"+socket.getRemoteDevice().getAddress()); 40 | } 41 | 42 | 43 | public void setNetworkListener(NetworkListener networkListener) { 44 | this.networkListener = networkListener; 45 | } 46 | 47 | @Override 48 | public void run() { 49 | super.run(); 50 | Logger.d("BluetoothSocketHandler is running for client " + this.destinationAddress); 51 | try { 52 | InputStream inputStream = socket.getInputStream(); 53 | BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); 54 | 55 | while (running) { 56 | String message = reader.readLine(); 57 | Message decodedMessage = Message.decode(message); 58 | if (decodedMessage.isAck()) { 59 | notifyAckWaitingThreads(decodedMessage); 60 | } else { 61 | if (networkListener != null) { 62 | networkListener.messageReceived(decodedMessage); 63 | } 64 | } 65 | } 66 | Logger.i("BluetoothSocketHandler exiting..."); 67 | } catch (IOException e) { 68 | Logger.e("Exception caught while listening client messages. " + this.destinationAddress, e); 69 | if (networkListener != null) { 70 | networkListener.connectionLost(this.destinationAddress); 71 | } 72 | } 73 | //TODO remove this socket handler from client socket handler list 74 | //clientSocketHandlers.remove(this.destinationAddress); 75 | } 76 | 77 | private void notifyAckWaitingThreads(Message ack) { 78 | if (this.ackWaitingMessage == null) { 79 | return; 80 | } 81 | if (ack.getSequenceId() == this.ackWaitingMessage.getSequenceId()) { 82 | this.ackWaitingMessage = null; 83 | try { 84 | ackLock.lock(); 85 | ackReceivedCondition.signalAll(); 86 | } finally { 87 | ackLock.unlock(); 88 | } 89 | } 90 | 91 | } 92 | 93 | public void sendMessage(Message message, long timeout) throws InterruptedException { 94 | this.sendMessage(message); 95 | this.waitForAcknowledgement(message, timeout); 96 | } 97 | 98 | public void sendMessage(Message message) { 99 | OutputStream outStream; 100 | try { 101 | outStream = this.socket.getOutputStream(); 102 | PrintWriter writer = new PrintWriter(outStream); 103 | message.setSourceAddress(this.bluetoothAdapter.getAddress()); 104 | writer.println(message.encode()); 105 | writer.flush(); 106 | } catch (IOException e) { 107 | Log.w("Failed to send message " + message + " to destination " + destinationAddress, e); 108 | } 109 | } 110 | 111 | public void terminate() { 112 | //TODO remove this socket handler from client socket handlers list 113 | //clientSocketHandlers.remove(this.destinationAddress); 114 | this.running = false; 115 | try { 116 | this.socket.close(); 117 | } catch (IOException e) { 118 | Log.w("Exception caught while destroying BluetoothSocketHandler.", e); 119 | } 120 | this.interrupt(); 121 | } 122 | 123 | public void waitForAcknowledgement(Message message, long timeout) throws InterruptedException { 124 | this.ackWaitingMessage = message; 125 | try { 126 | ackLock.lock(); 127 | boolean ackReceived = this.ackReceivedCondition.await(timeout, TimeUnit.MILLISECONDS); 128 | if ( ! ackReceived) { 129 | throw new InterruptedException("Ack is not received for " + message + " in " + timeout + " ms."); 130 | } 131 | } finally { 132 | ackLock.unlock(); 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/com/appsonfire/p2p/bluetooth/P2PBluetooth.java: -------------------------------------------------------------------------------- 1 | package com.appsonfire.p2p.bluetooth; 2 | 3 | 4 | import android.bluetooth.BluetoothAdapter; 5 | import android.bluetooth.BluetoothDevice; 6 | import android.content.BroadcastReceiver; 7 | import android.content.Context; 8 | import android.content.Intent; 9 | import android.content.IntentFilter; 10 | import android.os.Parcelable; 11 | 12 | import com.appsonfire.p2p.ClientConnectionCallback; 13 | import com.appsonfire.p2p.NetworkServiceCallback; 14 | import com.appsonfire.p2p.ServerReadyCallback; 15 | import com.appsonfire.p2p.util.Logger; 16 | 17 | public class P2PBluetooth { 18 | 19 | private boolean clientConnectionSuccess = false; 20 | private String savedBluetoothName; 21 | private static final String MAGIC_BT_NAME_PREFIX = "=p2p="; 22 | 23 | private BroadcastReceiver bluetoothStateBroadcastReceiver; 24 | private BroadcastReceiver bluetoothClientStateBroadcastReceiver; 25 | private BroadcastReceiver bluetoothScanmodeBroadcastReceiver; 26 | 27 | private BluetoothService bluetoothService; 28 | 29 | /** 30 | * Enables bluetooth and makes the device discoverable. 31 | * @param context android context. 32 | * @param callback Callback is used to receive async updates about server start 33 | */ 34 | public void startBluetoothServer(final Context context, final NetworkServiceCallback callback) { 35 | final BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 36 | if (bluetoothAdapter == null) { 37 | callback.onFailure(NetworkServiceCallback.REASON_BLUETOOTH_IS_NOT_AVAILABLE); 38 | return; 39 | } 40 | 41 | if (bluetoothAdapter.isEnabled()) { 42 | prepareServerBluetoothAdapter(context, callback, bluetoothAdapter); 43 | } else { 44 | bluetoothStateBroadcastReceiver = new BroadcastReceiver() { 45 | 46 | @Override 47 | public void onReceive(Context context, Intent intent) { 48 | int newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF); 49 | switch (newState) { 50 | case BluetoothAdapter.STATE_TURNING_ON: 51 | Logger.i("Turning on bluetooth adapter. STATE_TURNING_ON "); 52 | break; 53 | case BluetoothAdapter.STATE_ON: 54 | Logger.i("Bluetooth adapter is enabled. STATE_ON "); 55 | doUnregisterReceiver(context, this); 56 | prepareServerBluetoothAdapter(context, callback, bluetoothAdapter); 57 | break; 58 | case BluetoothAdapter.STATE_TURNING_OFF: 59 | case BluetoothAdapter.STATE_OFF: 60 | doUnregisterReceiver(context, this); 61 | callback.onFailure(NetworkServiceCallback.REASON_BLUETOOTH_IS_NOT_ENABLED); 62 | break; 63 | default: 64 | Logger.w("Unexpected BluetoothAdapter state received. STATE : " + newState); 65 | } 66 | } 67 | }; 68 | context.registerReceiver(bluetoothStateBroadcastReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); 69 | bluetoothAdapter.enable(); 70 | } 71 | } 72 | 73 | private void prepareServerBluetoothAdapter(Context context, final NetworkServiceCallback callback, final BluetoothAdapter bluetoothAdapter) { 74 | changeBluetoothAdapterName(bluetoothAdapter); 75 | 76 | if (bluetoothAdapter.getScanMode() == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { 77 | startBluetoothServerService(callback); 78 | return; 79 | } 80 | 81 | bluetoothScanmodeBroadcastReceiver = new BroadcastReceiver() { 82 | @Override 83 | public void onReceive(Context context, Intent intent) { 84 | String action = intent.getAction(); 85 | Logger.i("Scan mode broadcast receiver received action " + action); 86 | int scanMode = intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE, BluetoothAdapter.ERROR); 87 | switch (scanMode) { 88 | case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE: 89 | Logger.i("Bluetooth scan mode is CONNECTABLE & DISCOVERABLE"); 90 | doUnregisterReceiver(context, this); 91 | startBluetoothServerService(callback); 92 | break; 93 | default: 94 | Logger.w("Unexpected scan mode received : " + scanMode); 95 | doUnregisterReceiver(context, this); 96 | callback.onFailure(NetworkServiceCallback.REASON_BLUETOOTH_NOT_DISCOVERABLE); 97 | break; 98 | } 99 | } 100 | }; 101 | 102 | context.registerReceiver(bluetoothScanmodeBroadcastReceiver, new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED)); 103 | Intent i = new Intent(); 104 | i.setAction(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); 105 | i.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 600); 106 | context.startActivity(i); 107 | 108 | cancelDiscoverableActivityWorkaround(context, callback); 109 | } 110 | 111 | /** 112 | * When the activity started by ACTION_REQUEST_DISCOVERABLE is canceled we can not receive an activity result. 113 | * (Activity is not started with startActivityForResult) 114 | * This method spawns a thread that checks every second if the bluetooth service is started. If the service is not started 115 | * for 10 seconds then we assume that user has canceled activity. 116 | * @param context 117 | * @param callback 118 | */ 119 | private void cancelDiscoverableActivityWorkaround(final Context context, final NetworkServiceCallback callback) { 120 | new Thread(new Runnable() { 121 | @Override 122 | public void run() { 123 | for (int i = 0; i < 10; i++) { 124 | if (bluetoothService != null && bluetoothService.isBluetoothServerStarted()) { 125 | Logger.d("cancelDiscoverableActivityWorkaround : Bluetooth service started."); 126 | return; 127 | } 128 | try { 129 | Thread.sleep(1000); 130 | } catch (InterruptedException e) { 131 | Logger.e("cancelDiscoverableActivityWorkaround : interrupted", e); 132 | } 133 | } 134 | Logger.d("cancelDiscoverableActivityWorkaround : BT is not discovarable"); 135 | doUnregisterReceiver(context, bluetoothScanmodeBroadcastReceiver); 136 | callback.onFailure(NetworkServiceCallback.REASON_BLUETOOTH_NOT_DISCOVERABLE); 137 | } 138 | }).start(); 139 | } 140 | 141 | private void startBluetoothServerService(final NetworkServiceCallback callback) { 142 | if (this.bluetoothService != null) { 143 | this.bluetoothService.terminate(); 144 | } 145 | this.bluetoothService = new BluetoothService(); 146 | this.bluetoothService.startServer(new ServerReadyCallback() { 147 | @Override 148 | public void initFailure() { 149 | callback.onFailure(NetworkServiceCallback.REASON_BLUETOOTH_CAN_NOT_CREATE_SERVER_SOCKET); 150 | } 151 | 152 | @Override 153 | public void acceptingConnections() { 154 | callback.onSuccess(bluetoothService); 155 | } 156 | }); 157 | } 158 | 159 | /** 160 | * changes bluetooth adapter name. Name is replaced with "MAGIC_PREFIX + Name" 161 | * Name is restored while disabling bluetooth adapter. 162 | * This magic prefix is used by clients to identify the bluetooth server. 163 | * @param bluetoothAdapter 164 | */ 165 | private void changeBluetoothAdapterName(final BluetoothAdapter bluetoothAdapter) { 166 | Logger.d("Changing bluetooth adater name..."); 167 | String btName = bluetoothAdapter.getName(); 168 | if (btName == null) { 169 | Logger.w("Failed to read bluetooth adapter name. bluetoothAdapter.getName() returns null."); 170 | return; 171 | } 172 | 173 | if (btName.startsWith(MAGIC_BT_NAME_PREFIX)) { 174 | Logger.d("No need to change bluetooth adapter name. Name : " + btName); 175 | } else { 176 | savedBluetoothName = btName; 177 | bluetoothAdapter.setName(MAGIC_BT_NAME_PREFIX + btName); 178 | Logger.d("Bluetooth name has been changed. New name is " + bluetoothAdapter.getName()); 179 | } 180 | } 181 | 182 | public void revertBluetoothAdapterName() { 183 | BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 184 | if (adapter == null) { 185 | return; 186 | } 187 | 188 | if (this.savedBluetoothName == null) { 189 | String name = adapter.getName(); 190 | if (name != null && name.startsWith(MAGIC_BT_NAME_PREFIX)) { 191 | name = name.substring(MAGIC_BT_NAME_PREFIX.length(), name.length()); 192 | if (adapter.setName(name)) { 193 | Logger.i("Bluetooth adapter name is changed to " + name); 194 | } 195 | } 196 | } else { 197 | if (adapter.setName(this.savedBluetoothName)) { 198 | Logger.i("Bluetooth adapter name is set to initial value : " + this.savedBluetoothName); 199 | } 200 | this.savedBluetoothName = null; 201 | } 202 | } 203 | 204 | public void startBluetoothClient(Context context, final NetworkServiceCallback callback) { 205 | final BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 206 | if (bluetoothAdapter == null) { 207 | callback.onFailure(NetworkServiceCallback.REASON_BLUETOOTH_IS_NOT_AVAILABLE); 208 | return; 209 | } 210 | 211 | if (bluetoothAdapter.isEnabled()) { 212 | startBluetoothDiscovery(context, callback, bluetoothAdapter); 213 | } else { 214 | bluetoothClientStateBroadcastReceiver = new BroadcastReceiver() { 215 | @Override 216 | public void onReceive(Context context, Intent intent) { 217 | int newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF); 218 | switch (newState) { 219 | case BluetoothAdapter.STATE_TURNING_ON: 220 | Logger.i("Turning on bluetooth adapter. STATE_TURNING_ON "); 221 | break; 222 | case BluetoothAdapter.STATE_ON: 223 | doUnregisterReceiver(context, this); 224 | startBluetoothDiscovery(context, callback, bluetoothAdapter); 225 | break; 226 | case BluetoothAdapter.STATE_TURNING_OFF: 227 | case BluetoothAdapter.STATE_OFF: 228 | doUnregisterReceiver(context, this); 229 | callback.onFailure(NetworkServiceCallback.REASON_BLUETOOTH_IS_NOT_ENABLED); 230 | break; 231 | default: 232 | Logger.w("Unexpected BluetoothAdapter state received. STATE : " + newState); 233 | } 234 | } 235 | }; 236 | 237 | context.registerReceiver(bluetoothClientStateBroadcastReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); 238 | bluetoothAdapter.enable(); 239 | } 240 | } 241 | 242 | private void startBluetoothDiscovery(Context context, final NetworkServiceCallback callback, 243 | final BluetoothAdapter bluetoothAdapter) { 244 | IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); 245 | filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); 246 | 247 | context.registerReceiver(new BroadcastReceiver() { 248 | 249 | @Override 250 | public void onReceive(Context context, Intent intent) { 251 | String action = intent.getAction(); 252 | if (BluetoothDevice.ACTION_FOUND.equals(action)) { 253 | Parcelable btParcel = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 254 | BluetoothDevice btDevice = (BluetoothDevice) btParcel; 255 | String btDeviceName = btDevice.getName(); 256 | Logger.d("BT Device found. " + btDevice + " name : " + btDeviceName); 257 | if (btDeviceName != null && btDeviceName.startsWith(MAGIC_BT_NAME_PREFIX)) { 258 | bluetoothAdapter.cancelDiscovery(); 259 | doUnregisterReceiver(context, this); 260 | String btServerAddress = btDevice.getAddress(); 261 | if (bluetoothService != null) { 262 | bluetoothService.terminate(); 263 | } 264 | bluetoothService = new BluetoothService(); 265 | bluetoothService.startClient(btServerAddress, new ClientConnectionCallback() { 266 | @Override 267 | public void connectionEstablished() { 268 | clientConnectionSuccess = true; 269 | callback.onSuccess(bluetoothService); 270 | } 271 | 272 | @Override 273 | public void connectionFailed() { 274 | callback.onFailure(NetworkServiceCallback.REASON_BLUETOOTH_CAN_NOT_CONNECT_TO_SERVER); 275 | } 276 | }); 277 | } 278 | } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) { 279 | Logger.d("Bluetooth device search completed. "); 280 | doUnregisterReceiver(context, this); 281 | bluetoothAdapter.cancelDiscovery(); 282 | if ( ! clientConnectionSuccess) { 283 | callback.onFailure(NetworkServiceCallback.REASON_BLUETOOTH_CAN_NOT_FIND_SERVER); 284 | } 285 | } 286 | } 287 | }, filter); 288 | bluetoothAdapter.startDiscovery(); 289 | } 290 | 291 | public boolean stopBluetoothService(Context context) { 292 | doUnregisterReceiver(context, bluetoothScanmodeBroadcastReceiver); 293 | doUnregisterReceiver(context, bluetoothStateBroadcastReceiver); 294 | doUnregisterReceiver(context, bluetoothClientStateBroadcastReceiver); 295 | revertBluetoothAdapterName(); 296 | if (this.bluetoothService!=null) { 297 | this.bluetoothService.terminate(); 298 | } 299 | disableBluetoothAdapter(); 300 | return true; 301 | 302 | } 303 | 304 | public void disableBluetoothAdapter() { 305 | BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 306 | Logger.i("Disabling bluetooth adapter..."); 307 | if (adapter != null ) { 308 | if (adapter.isEnabled()) { 309 | adapter.disable(); 310 | } 311 | } 312 | } 313 | 314 | private void doUnregisterReceiver(Context context, BroadcastReceiver receiver) { 315 | if (context != null) { 316 | try { 317 | context.unregisterReceiver(receiver); 318 | } catch (Exception e) { 319 | Logger.w("Failed to unregister " + receiver, e); 320 | } 321 | } 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /src/com/appsonfire/p2p/bluetooth/wifi/P2PWifi.java: -------------------------------------------------------------------------------- 1 | package com.appsonfire.p2p.bluetooth.wifi; 2 | 3 | import android.content.Context; 4 | 5 | import com.appsonfire.p2p.NetworkServiceCallback; 6 | 7 | public class P2PWifi { 8 | 9 | public void startWifiServer(Context context, final NetworkServiceCallback callback) { 10 | 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/com/appsonfire/p2p/bluetooth/wifi/WifiClient.java: -------------------------------------------------------------------------------- 1 | package com.appsonfire.p2p.bluetooth.wifi; 2 | 3 | public class WifiClient { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/com/appsonfire/p2p/bluetooth/wifi/WifiDiscoveryClient.java: -------------------------------------------------------------------------------- 1 | package com.appsonfire.p2p.bluetooth.wifi; 2 | 3 | import java.net.DatagramPacket; 4 | import java.net.DatagramSocket; 5 | import java.net.InetAddress; 6 | 7 | import android.content.Context; 8 | import android.net.DhcpInfo; 9 | import android.net.wifi.WifiManager; 10 | 11 | public class WifiDiscoveryClient { 12 | 13 | private DatagramPacket requestDatagramPacket; 14 | private byte[] responseData = new byte[1024]; 15 | private DatagramPacket responseDatagramPacket = new DatagramPacket(this.responseData, this.responseData.length); 16 | 17 | public void discoverServer(Context context, WifiDiscoveryClientListener listener) { 18 | startAsyncDiscovery(context, listener); 19 | } 20 | 21 | private void startAsyncDiscovery(final Context context, final WifiDiscoveryClientListener listener) { 22 | 23 | new Thread(new Runnable() { 24 | 25 | @Override 26 | public void run() { 27 | try { 28 | DatagramSocket socket = new DatagramSocket(WifiDiscoveryServer.DISCOVERY_PORT); 29 | socket.setBroadcast(true); 30 | InetAddress broadcastAddress = InetAddress.getByAddress(getBroadcastIPAddressRaw(context)); 31 | 32 | final byte[] out = WifiDiscoveryServer.REQUEST_ID; 33 | requestDatagramPacket = new DatagramPacket(out, out.length, broadcastAddress, WifiDiscoveryServer.DISCOVERY_PORT); 34 | 35 | socket.send(requestDatagramPacket); 36 | 37 | socket.receive(responseDatagramPacket); 38 | final byte[] discoveryResponseData = new byte[responseDatagramPacket.getLength()]; 39 | System.arraycopy(responseDatagramPacket.getData(),responseDatagramPacket.getOffset(), discoveryResponseData, 0, responseDatagramPacket.getLength()); 40 | listener.onDiscover(String.valueOf(discoveryResponseData)); 41 | } catch (Exception e) { 42 | listener.onException(e); 43 | } 44 | } 45 | }).start(); 46 | } 47 | 48 | public static byte[] getBroadcastIPAddressRaw(final Context context) { 49 | // TODO what if wifi manager is null... 50 | WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 51 | DhcpInfo dhcp = wifiManager.getDhcpInfo(); 52 | int broadcast = (dhcp.ipAddress & dhcp.netmask) | ~dhcp.netmask; 53 | final byte[] broadcastIP = new byte[4]; 54 | for (int k = 0; k < 4; k++) { 55 | broadcastIP[k] = (byte) ((broadcast >> k * 8) & 0xFF); 56 | } 57 | return broadcastIP; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/com/appsonfire/p2p/bluetooth/wifi/WifiDiscoveryClientListener.java: -------------------------------------------------------------------------------- 1 | package com.appsonfire.p2p.bluetooth.wifi; 2 | 3 | 4 | public interface WifiDiscoveryClientListener { 5 | 6 | public void onException(Exception e); 7 | 8 | public void onDiscover(String valueOf); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/com/appsonfire/p2p/bluetooth/wifi/WifiDiscoveryServer.java: -------------------------------------------------------------------------------- 1 | package com.appsonfire.p2p.bluetooth.wifi; 2 | 3 | import java.io.IOException; 4 | import java.net.DatagramPacket; 5 | import java.net.DatagramSocket; 6 | import java.net.SocketException; 7 | 8 | import com.appsonfire.p2p.util.Logger; 9 | 10 | public class WifiDiscoveryServer extends Thread { 11 | 12 | public static final int DISCOVERY_PORT = 11211; 13 | public static final byte[] REQUEST_ID = "com.appsonfire.p2p".getBytes(); 14 | 15 | private boolean running = true; 16 | 17 | private DatagramSocket datagramSocket; 18 | private final byte[] requestData = new byte[1024]; 19 | private final DatagramPacket datagramPacket = new DatagramPacket(this.requestData, this.requestData.length); 20 | 21 | private final WifiDiscoveryServerListener listener; 22 | 23 | private final String ipAddress; 24 | 25 | public WifiDiscoveryServer(String ipAddress, WifiDiscoveryServerListener listener) { 26 | this.ipAddress = ipAddress; 27 | this.listener = listener; 28 | } 29 | 30 | @Override 31 | public void run() { 32 | super.run(); 33 | try { 34 | datagramSocket = new DatagramSocket(DISCOVERY_PORT); 35 | } catch (SocketException e) { 36 | Logger.e("Failed to start wifi discovery server", e); 37 | if (listener != null) { 38 | listener.onException(e); 39 | } 40 | return; 41 | } 42 | while (running && !Thread.interrupted()) { 43 | try { 44 | this.datagramSocket.receive(this.datagramPacket); 45 | byte[] data = this.datagramPacket.getData(); 46 | int aStart = 0; 47 | int bStart = this.datagramPacket.getOffset(); 48 | boolean isDiscoveryMessage = true; 49 | for (int i = aStart, j = bStart; i < REQUEST_ID.length; i++, j++) { 50 | if (REQUEST_ID[i] != data[j]) { 51 | isDiscoveryMessage = false; 52 | break; 53 | } 54 | } 55 | if (isDiscoveryMessage) { 56 | byte[] response = this.ipAddress.getBytes(); 57 | this.datagramSocket.send(new DatagramPacket(response, response.length, datagramPacket.getAddress(), datagramPacket.getPort())); 58 | listener.onDiscoveryMessageReceived(); 59 | } 60 | } catch (IOException e) { 61 | if (listener != null) { 62 | Logger.e("Failed to reveice datagram packet", e); 63 | listener.onException(e); 64 | } 65 | } 66 | } 67 | } 68 | 69 | public void terminate() { 70 | running = false; 71 | if (this.datagramSocket != null) { 72 | this.datagramSocket.close(); 73 | } 74 | interrupt(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/com/appsonfire/p2p/bluetooth/wifi/WifiDiscoveryServerListener.java: -------------------------------------------------------------------------------- 1 | package com.appsonfire.p2p.bluetooth.wifi; 2 | 3 | 4 | public interface WifiDiscoveryServerListener { 5 | 6 | public void onException(Exception e); 7 | 8 | public void onDiscoveryMessageReceived(); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/com/appsonfire/p2p/bluetooth/wifi/WifiServer.java: -------------------------------------------------------------------------------- 1 | package com.appsonfire.p2p.bluetooth.wifi; 2 | 3 | public class WifiServer extends Thread { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/com/appsonfire/p2p/bluetooth/wifi/WifiService.java: -------------------------------------------------------------------------------- 1 | package com.appsonfire.p2p.bluetooth.wifi; 2 | 3 | import com.appsonfire.p2p.ClientConnectionCallback; 4 | import com.appsonfire.p2p.Message; 5 | import com.appsonfire.p2p.NetworkListener; 6 | import com.appsonfire.p2p.NetworkService; 7 | import com.appsonfire.p2p.ServerReadyCallback; 8 | 9 | public class WifiService implements NetworkService { 10 | 11 | @Override 12 | public void registerNetworkListener(NetworkListener listener) { 13 | // TODO Auto-generated method stub 14 | 15 | } 16 | 17 | @Override 18 | public void startServer(ServerReadyCallback callback) { 19 | // TODO Auto-generated method stub 20 | 21 | } 22 | 23 | @Override 24 | public void startClient(String serverAddress, ClientConnectionCallback callback) { 25 | // TODO Auto-generated method stub 26 | 27 | } 28 | 29 | @Override 30 | public boolean isServer() { 31 | // TODO Auto-generated method stub 32 | return false; 33 | } 34 | 35 | @Override 36 | public void sendMessageToServer(Message message) { 37 | // TODO Auto-generated method stub 38 | 39 | } 40 | 41 | @Override 42 | public void sendMessageToServer(Message message, long timeout) throws InterruptedException { 43 | // TODO Auto-generated method stub 44 | 45 | } 46 | 47 | @Override 48 | public void sendMessageToClient(String destination, Message message) { 49 | // TODO Auto-generated method stub 50 | 51 | } 52 | 53 | @Override 54 | public void sendMessageToClient(String destination, Message message, long timeout) throws InterruptedException { 55 | // TODO Auto-generated method stub 56 | 57 | } 58 | 59 | @Override 60 | public void sendMessageToClients(Message message) { 61 | // TODO Auto-generated method stub 62 | 63 | } 64 | 65 | @Override 66 | public void sendMessageToClients(Message message, String exclude) { 67 | // TODO Auto-generated method stub 68 | 69 | } 70 | 71 | @Override 72 | public void sendMessageToClients(Message message, long timeout) throws InterruptedException { 73 | // TODO Auto-generated method stub 74 | 75 | } 76 | 77 | @Override 78 | public void sendMessageToClients(Message message, long timeout, String exclude) throws InterruptedException { 79 | // TODO Auto-generated method stub 80 | 81 | } 82 | 83 | @Override 84 | public void sendAck(Message message) { 85 | // TODO Auto-generated method stub 86 | 87 | } 88 | 89 | @Override 90 | public void terminate() { 91 | // TODO Auto-generated method stub 92 | 93 | } 94 | 95 | @Override 96 | public String getMyNetworkAddress() { 97 | // TODO Auto-generated method stub 98 | return null; 99 | } 100 | 101 | 102 | } 103 | -------------------------------------------------------------------------------- /src/com/appsonfire/p2p/util/Logger.java: -------------------------------------------------------------------------------- 1 | package com.appsonfire.p2p.util; 2 | 3 | import android.util.Log; 4 | 5 | public class Logger { 6 | public static final String TAG = "p2p4Android"; 7 | public static int logLevel = Log.INFO; 8 | 9 | 10 | public static final void d(String s) { 11 | if (Log.DEBUG >= logLevel) { 12 | Log.d(TAG, s); 13 | } 14 | } 15 | 16 | public static final void i(String s) { 17 | if (Log.INFO >= logLevel) { 18 | Log.i(TAG, s); 19 | } 20 | } 21 | 22 | public static final void w(String s) { 23 | if (Log.WARN >= logLevel) { 24 | Log.w(TAG, s); 25 | } 26 | } 27 | 28 | public static final void w(String s, Exception e) { 29 | if (Log.WARN >= logLevel) { 30 | Log.w(TAG, s, e); 31 | } 32 | } 33 | 34 | public static final void e(String s) { 35 | if (Log.ERROR >= logLevel) { 36 | Log.e(TAG, s); 37 | } 38 | } 39 | 40 | public static final void e(String s, Exception e) { 41 | if (Log.ERROR >= logLevel) { 42 | Log.e(TAG, s, e); 43 | } 44 | } 45 | } 46 | --------------------------------------------------------------------------------