added a helper to improve the WiFi-Direct handling and prototyped the connection screen

This commit is contained in:
Faraphel 2024-04-29 22:41:54 +02:00
parent adab37fd49
commit 23e39c026a
8 changed files with 252 additions and 118 deletions

View file

@ -4,32 +4,21 @@ import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.Manifest import android.Manifest
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.net.wifi.p2p.* import android.net.wifi.p2p.*
import android.net.wifi.p2p.WifiP2pManager.ConnectionInfoListener
import android.net.wifi.p2p.WifiP2pManager.GroupInfoListener
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.activity.compose.setContent
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.compose.material3.Button import com.faraphel.tasks_valider.connectivity.wifi_p2p.WifiP2pHelper
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.InetSocketAddress import com.faraphel.tasks_valider.ui.widgets.WifiP2pWidgetDevice
import java.net.ServerSocket
import java.net.Socket
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private var PERMISSION_ACCESS_FINE_LOCATION = 1001 ///< permission code for the fine location (required for WiFi-Direct) private var PERMISSION_ACCESS_FINE_LOCATION = 1001 ///< permission code for the fi
private var p2pHelper: WifiP2pHelper? = null ///< the Wi-Fi Direct helper
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 ///< the database manager private lateinit var database: Database ///< the database manager
@ -53,77 +42,40 @@ class MainActivity : ComponentActivity() {
} }
// get the WiFi-Direct manager // get the WiFi-Direct manager
this.p2pManager = this.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager? val p2pManager: WifiP2pManager? = this.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager?
if (this.p2pManager == null) { if (p2pManager == null) {
Log.e("wifi-p2p", "cannot access the WiFi-Direct manager") Log.e("wifi-p2p", "cannot access the WiFi-Direct manager")
return return
} }
// get the Wifi-Direct channel // get the Wifi-Direct channel
this.p2pChannel = this.p2pManager!!.initialize(this, this.mainLooper, null) val p2pChannel: WifiP2pManager.Channel = p2pManager.initialize(this, this.mainLooper, null)
if (this.p2pManager == null) {
Log.e("wifi-p2p", "cannot access the WiFi-Direct channel")
return
}
// create a handler for the WiFi-Direct events // create a helper for handling the WiFi-Direct
this.p2pReceiver = WifiP2pBroadcastReceiver(this.p2pManager!!, this.p2pChannel!!) this.p2pHelper = WifiP2pHelper(p2pManager, p2pChannel)
// start a demo connexion // try to display the different devices when available.
this.demoP2pConnexion() this.p2pHelper!!.registerListener(
} WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION,
{
peers: WifiP2pDeviceList? ->
private fun demoP2pConnexion() { Log.d("wifi-p2p", "peers: $peers")
// Get the connection information (for the host IP) if (peers != null) this.setContent {
this.p2pManager!!.requestConnectionInfo(this.p2pChannel) { connection -> for (device in peers.deviceList) {
Log.d("wifi-p2p", "device: ${device.deviceAddress}")
Log.v("wifi-p2p", "connection information: $connection") WifiP2pWidgetDevice(device)
}
// 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()
} }
} },
} once = true
)
// discovers the peers to trigger the peer changed event
this.p2pHelper!!.discoverPeers(object : WifiP2pManager.ActionListener {
override fun onSuccess() { Log.i("wifi-p2p", "discovering successful !") }
override fun onFailure(reason: Int) { Log.e("wifi-p2p", "discovering failed. Reason: $reason") }
})
} }
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
@ -132,13 +84,13 @@ class MainActivity : ComponentActivity() {
super.onResume() super.onResume()
// enable the WiFi-Direct events // enable the WiFi-Direct events
this.registerReceiver(this.p2pReceiver, WifiP2pBroadcastReceiver.INTENT_FILTER) this.registerReceiver(this.p2pHelper, WifiP2pHelper.ALL_INTENT_FILTER)
} }
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
// disable the WiFi-Direct events // disable the WiFi-Direct events
this.unregisterReceiver(this.p2pReceiver) this.unregisterReceiver(this.p2pHelper)
} }
} }

View file

@ -1,39 +0,0 @@
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}")
}
}
}
}

View file

@ -0,0 +1,165 @@
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.WifiP2pConfig
import android.net.wifi.p2p.WifiP2pDeviceList
import android.net.wifi.p2p.WifiP2pManager
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import com.faraphel.tasks_valider.connectivity.wifi_p2p.error.WifiP2pHelperInvalidActionException
/**
* A helper to wrap the WiFi-Direct manager, the channel and the events.
*
* This avoids certain annoying features such as always specifying the channel as the first argument or
* handling all the events with the base event system.
*
* @param manager: the WiFi-Direct base manager
* @param channel: the WiFi-Direct channel
*/
class WifiP2pHelper(
private val manager: WifiP2pManager,
private val channel: WifiP2pManager.Channel,
) : BroadcastReceiver() {
companion object {
/**
* This IntentFilter can be used to subscribe the helper to every supported WiFi-Direct events.
*/
val ALL_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)
}
}
// Events
/**
* Contains the listeners to call when the peers are updated.
*/
private val listenersPeersChanged = mutableListOf<WifiP2pHelperListenerInfo<(WifiP2pDeviceList?) -> Unit>>()
/**
* Event receiver for the WiFi-Direct events.
* Should not be called manually.
* @param context: the WiFi-Direct context
* @param intent: the event information
*/
@RequiresApi(Build.VERSION_CODES.N)
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> {
Log.v("wifi-p2p", "Connection Changed : $context, $intent, ${intent.extras}")
}
WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> {
Log.v("wifi-p2p", "Peers Changed : $context, $intent, ${intent.extras}")
this.requestPeers { peers ->
this@WifiP2pHelper.listenersPeersChanged.forEach { listenerInfo -> listenerInfo.callback(peers) }
this@WifiP2pHelper.listenersPeersChanged.removeIf { listenerInfo -> listenerInfo.once }
}
}
WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> {
Log.v("wifi-p2p", "State Changed : $context, $intent, ${intent.extras}")
}
WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION -> {
Log.v("wifi-p2p", "This Device Changed : $context, $intent, ${intent.extras}")
}
}
}
/**
* Register a listener to a specific WiFi-Direct event.
* @param action: the name of the WiFi-Direct event
* @param callback: the callback function
* @param once: should the event be only called once ?
*/
fun <CallbackType> registerListener(action: String, callback: CallbackType, once: Boolean = false) {
when (action) {
WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> {
@Suppress("UNCHECKED_CAST")
this.listenersPeersChanged.add(WifiP2pHelperListenerInfo(
callback as ((WifiP2pDeviceList?) -> Unit),
once
))
}
else -> {
// raise an exception if the action is invalid
throw WifiP2pHelperInvalidActionException(action)
}
}
}
@RequiresApi(Build.VERSION_CODES.N)
fun <CallbackType> unregisterListener(action: String, callback: CallbackType) {
when (action) {
WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> {
this.listenersPeersChanged.removeIf { listenerInfo -> listenerInfo.callback == callback }
}
else -> {
// raise an exception if the action is invalid
throw WifiP2pHelperInvalidActionException(action)
}
}
}
// Wrappers
/**
* Wrapper around WifiP2pManager::connect
*
* Connect to another device, allowing for a communication using Sockets
*/
fun connect(deviceAddress: String, listener: WifiP2pManager.ActionListener? = null) {
val config = WifiP2pConfig().apply {
this.deviceAddress = deviceAddress
}
this.manager.connect(this.channel, config, listener)
}
/**
* Wrapper around WifiP2pManager::requestPeers
*
* Request the list of peers after a discovery.
*/
fun requestPeers(listener: WifiP2pManager.PeerListListener? = null) {
this.manager.requestPeers(this.channel, listener)
}
/**
* Wrapper around WifiP2pManager::discoverPeers
*
* Start discovering peers.
* Once founds, the WIFI_P2P_PEERS_CHANGED_ACTION event will be triggered.
*/
fun discoverPeers(listener: WifiP2pManager.ActionListener? = null) {
this.manager.discoverPeers(this.channel, listener)
}
/**
* Wrapper around WifiP2pManager::requestConnectionInfo
*
* Obtain information about a connection with another device.
*/
fun requestConnectionInfo(listener: WifiP2pManager.ConnectionInfoListener? = null) {
this.manager.requestConnectionInfo(this.channel, listener)
}
/**
* Wrapper around WifiP2pManager::requestGroupInfo
*
* Obtain information about the current group.
*/
fun requestGroupInfo(listener: WifiP2pManager.GroupInfoListener? = null) {
this.manager.requestGroupInfo(this.channel, listener)
}
}

View file

@ -0,0 +1,15 @@
package com.faraphel.tasks_valider.connectivity.wifi_p2p
import java.util.concurrent.Callable
/**
* This class contain the information required to store a listener in the WifiP2pHelper class to correctly handle events.
* @param CallbackType: the type of the callback function
* @param callback: the function to call when the event is triggered
* @param once: should the listener be called only once ?
*/
class WifiP2pHelperListenerInfo<CallbackType>(
val callback: CallbackType,
val once: Boolean,
)

View file

@ -0,0 +1,9 @@
package com.faraphel.tasks_valider.connectivity.wifi_p2p.error
/**
* Base Exception for everything concerning the WifiP2pHelper class
*/
open class WifiP2pHelperException(
override val message: String?
) : Exception(message)

View file

@ -0,0 +1,5 @@
package com.faraphel.tasks_valider.connectivity.wifi_p2p.error
class WifiP2pHelperInvalidActionException(
action: String
) : WifiP2pHelperException("This WiFi-Direct action is not supported : $action")

View file

@ -14,6 +14,7 @@ import androidx.compose.ui.unit.dp
import com.faraphel.tasks_valider.database.Database import com.faraphel.tasks_valider.database.Database
import com.faraphel.tasks_valider.database.entities.TaskGroup import com.faraphel.tasks_valider.database.entities.TaskGroup
@Composable @Composable
fun WidgetTaskStudent(database: Database, taskStudent: TaskGroup) { fun WidgetTaskStudent(database: Database, taskStudent: TaskGroup) {
val teacherDao = database.teacherDao() val teacherDao = database.teacherDao()

View file

@ -0,0 +1,26 @@
package com.faraphel.tasks_valider.ui.widgets
import android.net.wifi.p2p.WifiP2pDevice
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@Composable
fun WifiP2pWidgetDevice(device: WifiP2pDevice) {
Column {
Row {
Text(text = "Name: ")
Text(text = device.deviceName)
}
Row {
Text(text = "Address: ")
Text(text = device.deviceAddress)
}
Row {
Text(text = "Is Owner: ")
Text(text = device.isGroupOwner.toString())
}
}
}