added very basic p2p test (small communication between two devices, a bit unstable)

This commit is contained in:
Faraphel 2024-04-28 22:48:02 +02:00
parent 10e01c3a9b
commit adab37fd49
4 changed files with 234 additions and 55 deletions

View file

@ -1,28 +1,38 @@
package com.faraphel.tasks_valider package com.faraphel.tasks_valider
import android.content.Context import android.content.Context
import android.content.pm.PackageManager
import android.Manifest
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.net.wifi.p2p.* import android.net.wifi.p2p.*
import android.net.wifi.p2p.WifiP2pManager.ActionListener import android.net.wifi.p2p.WifiP2pManager.ConnectionInfoListener
import android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION import android.net.wifi.p2p.WifiP2pManager.GroupInfoListener
import android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_STATE_ENABLED
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.compose.material3.Button
import com.faraphel.tasks_valider.connectivity.DemoClient
import com.faraphel.tasks_valider.connectivity.DemoServer
import com.faraphel.tasks_valider.connectivity.wifi_p2p.WifiP2pBroadcastReceiver
import com.faraphel.tasks_valider.database.Database import com.faraphel.tasks_valider.database.Database
import java.net.InetAddress
import java.net.InetSocketAddress import java.net.InetSocketAddress
import java.net.ServerSocket import java.net.ServerSocket
import java.net.Socket import java.net.Socket
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private var p2pManager: WifiP2pManager? = null private var PERMISSION_ACCESS_FINE_LOCATION = 1001 ///< permission code for the fine location (required for WiFi-Direct)
private var p2pChannel: WifiP2pManager.Channel? = null
private var p2pManager: WifiP2pManager? = null ///< the wifi direct manager
private var p2pChannel: WifiP2pManager.Channel? = null ///< the wifi direct channel
private var p2pReceiver: BroadcastReceiver? = null ///< the wifi direct event receiver handler
companion object { companion object {
private lateinit var database: Database private lateinit var database: Database ///< the database manager
} }
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
@ -31,17 +41,16 @@ class MainActivity : ComponentActivity() {
// ----- CONNECTION // ----- CONNECTION
/* // TOOD: more check on permissions
val intentFilter = IntentFilter() if (!this.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)) {
Log.e("wifi-p2p", "this device does not support the WiFi-Direct feature")
return
}
// Indicates a change in the Wi-Fi Direct status. if (this.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION) // TODO(Faraphel): should be used with shouldShowRequestPermissionRationale, with a check
intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION) this.requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), PERMISSION_ACCESS_FINE_LOCATION)
intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION) }
intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION)
this.registerReceiver(MyBroadcastReceiver(this), intentFilter)
*/
// get the WiFi-Direct manager // get the WiFi-Direct manager
this.p2pManager = this.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager? this.p2pManager = this.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager?
@ -52,52 +61,84 @@ class MainActivity : ComponentActivity() {
// get the Wifi-Direct channel // get the Wifi-Direct channel
this.p2pChannel = this.p2pManager!!.initialize(this, this.mainLooper, null) this.p2pChannel = this.p2pManager!!.initialize(this, this.mainLooper, null)
if (this.p2pManager == null) {
// test the current channel Log.e("wifi-p2p", "cannot access the WiFi-Direct channel")
this.callWithP2pPeers { devices -> return
// DEBUG
Log.d("wifi-p2p", "Peers found : ${devices.size}")
devices.forEach { device ->
Log.d(
"wifi-p2p",
"peer found : [${device.deviceAddress}] ${device.deviceName}"
)
}
} }
this.callWithP2pGroup { group -> // create a handler for the WiFi-Direct events
Log.d("wifi-p2p", "Is group owner : ${group.isGroupOwner}") this.p2pReceiver = WifiP2pBroadcastReceiver(this.p2pManager!!, this.p2pChannel!!)
// start a demo connexion
this.demoP2pConnexion()
} }
/* private fun demoP2pConnexion() {
// NOTE(Faraphel): it seem that this feature is deprecated and should be done manually. // Get the connection information (for the host IP)
// get the list of peers in the group this.p2pManager!!.requestConnectionInfo(this.p2pChannel) { connection ->
manager.discoverPeers(channel, object : ActionListener {
Log.v("wifi-p2p", "connection information: $connection")
// Get the group information (for the owner)
this.p2pManager!!.requestGroupInfo(this.p2pChannel) { group ->
Log.v("wifi-p2p", "group information: $group")
if (group.isGroupOwner) {
Log.d("wifi-p2p", "mode: server")
DemoServer(9876).start()
} else {
Log.d("wifi-p2p", "mode: client")
Thread { // NOTE(Faraphel): this thread is started to avoid blocking the main loop
while (true) {
// TODO(Faraphel): the thread is handled outside the connect block to prevent connection before the server started.
// This should be handled in the future by selecting the server before starting the client.
p2pManager!!.connect(
this.p2pChannel,
WifiP2pConfig().apply { deviceAddress = group.owner.deviceAddress },
object : WifiP2pManager.ActionListener {
override fun onSuccess() { override fun onSuccess() {
Log.d("wifi-p2p", "Peers discovery started") Log.d("wifi-p2p", "peer connection successful !")
Thread {
try {
DemoClient(connection.groupOwnerAddress, 9876).once()
} catch(exception: Exception) {
Log.e("wifi-p2p", "could not connect to the server", exception)
}
}.start() // NOTE(Faraphel): this thread is started to avoid networking in main thread
} }
override fun onFailure(reason: Int) { override fun onFailure(reason: Int) {
Log.e("wifi-p2p", "Error when starting peers discovery : $reason") Log.e("wifi-p2p", "could not establish a peer connection. Reason: $reason")
// NOTE: might fail if the "Location" permission is not granted
} }
}) }
*/ )
Thread.sleep(1000)
} }
private fun callWithP2pPeers(callback: (Collection<WifiP2pDevice>) -> Unit) { }.start()
this.p2pManager?.requestPeers(this.p2pChannel) { devices -> }
// NOTE(Faraphel): might be empty if the "Location" permission is not granted }
if (devices.deviceList.isEmpty())
Log.w("wifi-p2p", "No WiFi-Direct devices found. Have you enabled, granted required permissions and created the group ?")
callback(devices.deviceList)
} }
} }
private fun callWithP2pGroup(callback: (WifiP2pGroup) -> Unit) { @RequiresApi(Build.VERSION_CODES.O)
this.p2pManager?.requestGroupInfo(this.p2pChannel) { group -> @SuppressLint("UnspecifiedRegisterReceiverFlag")
callback(group) override fun onResume() {
} super.onResume()
// enable the WiFi-Direct events
this.registerReceiver(this.p2pReceiver, WifiP2pBroadcastReceiver.INTENT_FILTER)
}
override fun onPause() {
super.onPause()
// disable the WiFi-Direct events
this.unregisterReceiver(this.p2pReceiver)
} }
} }

View file

@ -0,0 +1,49 @@
package com.faraphel.tasks_valider.connectivity
import android.util.Log
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.Socket
class DemoClient(hostname: InetAddress, port: Int) {
companion object {
var SOCKET_TIMEOUT = 120 * 1000 ///< TEST(Faraphel): long timeout for testing
}
private var server = Socket()
private var address = InetSocketAddress(hostname, port)
init {
server.soTimeout = SOCKET_TIMEOUT
}
/**
* Try to contact the server once
*/
fun once() {
Log.d("demo-client", "connecting to the server...")
this.server.connect(this.address, SOCKET_TIMEOUT)
Log.d("demo-client", "connected !")
Log.d("demo-client", "sending message...")
server.getOutputStream().write(42) // send a byte
Log.d("demo-client", "message sent !")
}
/**
* Try to contact the server indefinitely
*/
fun start() {
Thread {
while (true) {
try {
this.once()
} catch (exception: Exception) {
Log.w("demo-client", "an error occured: $exception")
}
Thread.sleep(1000)
}
}.start()
}
}

View file

@ -0,0 +1,50 @@
package com.faraphel.tasks_valider.connectivity
import android.util.Log
import java.net.ServerSocket
class DemoServer(port: Int) {
companion object {
var SOCKET_TIMEOUT = 120 * 1000 ///< TEST(Faraphel): long timeout for testing
}
private var server = ServerSocket(port)
init {
server.soTimeout = SOCKET_TIMEOUT
}
/**
* Accept and treat a client once
*/
private fun once() {
// wait for a client connexion
Log.d("demo-server", "waiting for a client...")
val client = server.accept()
Log.d("demo-server", "client connected !")
// once connected, wait and read a byte from the client.
val value = client.getInputStream().read()
Log.d("demo-server", "client sent data : $value")
// TODO(Faraphel): an abstraction layer should be added in the future to receive type of packets and read the data
}
/**
* Accept and threat clients indefinitely
*/
fun start() {
Thread {
while (true) {
try {
this.once()
} catch (exception: Exception) {
Log.w("demo-server", "an error occured: $exception")
}
Thread.sleep(1000)
}
}.start()
}
}

View file

@ -0,0 +1,39 @@
package com.faraphel.tasks_valider.connectivity.wifi_p2p
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.wifi.p2p.WifiP2pManager
import android.util.Log
class WifiP2pBroadcastReceiver(
private val manager: WifiP2pManager,
private val channel: WifiP2pManager.Channel,
) : BroadcastReceiver() {
companion object {
val INTENT_FILTER = IntentFilter().apply {
addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)
addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)
addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)
addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION)
}
}
override fun onReceive(context: Context?, intent: Intent) {
when (intent.action) {
WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> {
Log.i("wifi-p2p", "Connection Changed : $context, $intent, ${intent.extras}")
}
WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> {
Log.i("wifi-p2p", "Peers Changed : $context, $intent, ${intent.extras}")
}
WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> {
Log.i("wifi-p2p", "State Changed : $context, $intent, ${intent.extras}")
}
WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION -> {
Log.i("wifi-p2p", "This Device Changed : $context, $intent, ${intent.extras}")
}
}
}
}