Http Server / Client communication #7
4 changed files with 234 additions and 55 deletions
|
@ -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
|
||||||
// NOTE(Faraphel): it seem that this feature is deprecated and should be done manually.
|
this.demoP2pConnexion()
|
||||||
// get the list of peers in the group
|
|
||||||
manager.discoverPeers(channel, object : ActionListener {
|
|
||||||
override fun onSuccess() {
|
|
||||||
Log.d("wifi-p2p", "Peers discovery started")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(reason: Int) {
|
|
||||||
Log.e("wifi-p2p", "Error when starting peers discovery : $reason")
|
|
||||||
// NOTE: might fail if the "Location" permission is not granted
|
|
||||||
}
|
|
||||||
})
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun callWithP2pPeers(callback: (Collection<WifiP2pDevice>) -> Unit) {
|
private fun demoP2pConnexion() {
|
||||||
this.p2pManager?.requestPeers(this.p2pChannel) { devices ->
|
// Get the connection information (for the host IP)
|
||||||
// NOTE(Faraphel): might be empty if the "Location" permission is not granted
|
this.p2pManager!!.requestConnectionInfo(this.p2pChannel) { connection ->
|
||||||
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)
|
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() {
|
||||||
|
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) {
|
||||||
|
Log.e("wifi-p2p", "could not establish a peer connection. Reason: $reason")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Thread.sleep(1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue