diff --git a/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt b/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt index ebe3a57..3ecae51 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt @@ -4,32 +4,21 @@ 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.WifiP2pManager.ConnectionInfoListener -import android.net.wifi.p2p.WifiP2pManager.GroupInfoListener import android.os.Build import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent 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.connectivity.wifi_p2p.WifiP2pHelper import com.faraphel.tasks_valider.database.Database -import java.net.InetSocketAddress -import java.net.ServerSocket -import java.net.Socket +import com.faraphel.tasks_valider.ui.widgets.WifiP2pWidgetDevice class MainActivity : ComponentActivity() { - private var PERMISSION_ACCESS_FINE_LOCATION = 1001 ///< permission code for the fine location (required for WiFi-Direct) - - 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 + private var PERMISSION_ACCESS_FINE_LOCATION = 1001 ///< permission code for the fi + private var p2pHelper: WifiP2pHelper? = null ///< the Wi-Fi Direct helper companion object { private lateinit var database: Database ///< the database manager @@ -53,77 +42,40 @@ class MainActivity : ComponentActivity() { } // get the WiFi-Direct manager - this.p2pManager = this.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager? - if (this.p2pManager == null) { + val p2pManager: WifiP2pManager? = this.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager? + if (p2pManager == null) { Log.e("wifi-p2p", "cannot access the WiFi-Direct manager") return } // get the Wifi-Direct channel - this.p2pChannel = this.p2pManager!!.initialize(this, this.mainLooper, null) - if (this.p2pManager == null) { - Log.e("wifi-p2p", "cannot access the WiFi-Direct channel") - return - } + val p2pChannel: WifiP2pManager.Channel = p2pManager.initialize(this, this.mainLooper, null) - // create a handler for the WiFi-Direct events - this.p2pReceiver = WifiP2pBroadcastReceiver(this.p2pManager!!, this.p2pChannel!!) + // create a helper for handling the WiFi-Direct + this.p2pHelper = WifiP2pHelper(p2pManager, p2pChannel) - // start a demo connexion - this.demoP2pConnexion() - } + // try to display the different devices when available. + this.p2pHelper!!.registerListener( + WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION, + { + peers: WifiP2pDeviceList? -> - private fun demoP2pConnexion() { - // Get the connection information (for the host IP) - this.p2pManager!!.requestConnectionInfo(this.p2pChannel) { connection -> - - 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() + Log.d("wifi-p2p", "peers: $peers") + if (peers != null) this.setContent { + for (device in peers.deviceList) { + Log.d("wifi-p2p", "device: ${device.deviceAddress}") + WifiP2pWidgetDevice(device) + } } - } - } + }, + 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) @@ -132,13 +84,13 @@ class MainActivity : ComponentActivity() { super.onResume() // enable the WiFi-Direct events - this.registerReceiver(this.p2pReceiver, WifiP2pBroadcastReceiver.INTENT_FILTER) + this.registerReceiver(this.p2pHelper, WifiP2pHelper.ALL_INTENT_FILTER) } override fun onPause() { super.onPause() // disable the WiFi-Direct events - this.unregisterReceiver(this.p2pReceiver) + this.unregisterReceiver(this.p2pHelper) } } diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pBroadcastReceiver.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pBroadcastReceiver.kt deleted file mode 100644 index 18d1906..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pBroadcastReceiver.kt +++ /dev/null @@ -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}") - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pHelper.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pHelper.kt new file mode 100644 index 0000000..381f3a7 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pHelper.kt @@ -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 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 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 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) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pHelperListenerInfo.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pHelperListenerInfo.kt new file mode 100644 index 0000000..3c68903 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pHelperListenerInfo.kt @@ -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( + val callback: CallbackType, + val once: Boolean, +) diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/error/WifiP2pHelperException.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/error/WifiP2pHelperException.kt new file mode 100644 index 0000000..892e2b2 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/error/WifiP2pHelperException.kt @@ -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) diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/error/WifiP2pHelperInvalidActionException.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/error/WifiP2pHelperInvalidActionException.kt new file mode 100644 index 0000000..26d147f --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/error/WifiP2pHelperInvalidActionException.kt @@ -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") diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/TaskGroup.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/TaskGroup.kt index 0055489..eb3dd80 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/TaskGroup.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/TaskGroup.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.unit.dp import com.faraphel.tasks_valider.database.Database import com.faraphel.tasks_valider.database.entities.TaskGroup + @Composable fun WidgetTaskStudent(database: Database, taskStudent: TaskGroup) { val teacherDao = database.teacherDao() diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/WifiP2pWidgetDevice.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/WifiP2pWidgetDevice.kt new file mode 100644 index 0000000..dfd009a --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/WifiP2pWidgetDevice.kt @@ -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()) + } + } +} \ No newline at end of file