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 20253aa..ebe3a57 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt @@ -1,28 +1,38 @@ package com.faraphel.tasks_valider 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.ActionListener -import android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -import android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_STATE_ENABLED +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.database.Database -import java.net.InetAddress import java.net.InetSocketAddress import java.net.ServerSocket import java.net.Socket class MainActivity : ComponentActivity() { - private var p2pManager: WifiP2pManager? = null - private var p2pChannel: WifiP2pManager.Channel? = null + 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 companion object { - private lateinit var database: Database + private lateinit var database: Database ///< the database manager } @RequiresApi(Build.VERSION_CODES.O) @@ -31,17 +41,16 @@ class MainActivity : ComponentActivity() { // ----- CONNECTION - /* - val intentFilter = IntentFilter() + // TOOD: more check on permissions + 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. - intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION) - intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION) - intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION) - intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION) - - this.registerReceiver(MyBroadcastReceiver(this), intentFilter) - */ + if (this.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + // TODO(Faraphel): should be used with shouldShowRequestPermissionRationale, with a check + this.requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), PERMISSION_ACCESS_FINE_LOCATION) + } // get the WiFi-Direct manager this.p2pManager = this.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager? @@ -52,52 +61,84 @@ class MainActivity : ComponentActivity() { // get the Wifi-Direct channel this.p2pChannel = this.p2pManager!!.initialize(this, this.mainLooper, null) - - // test the current channel - this.callWithP2pPeers { devices -> - // DEBUG - Log.d("wifi-p2p", "Peers found : ${devices.size}") - devices.forEach { device -> - Log.d( - "wifi-p2p", - "peer found : [${device.deviceAddress}] ${device.deviceName}" - ) - } + if (this.p2pManager == null) { + Log.e("wifi-p2p", "cannot access the WiFi-Direct channel") + return } - this.callWithP2pGroup { group -> - Log.d("wifi-p2p", "Is group owner : ${group.isGroupOwner}") - } + // create a handler for the WiFi-Direct events + this.p2pReceiver = WifiP2pBroadcastReceiver(this.p2pManager!!, this.p2pChannel!!) - /* - // NOTE(Faraphel): it seem that this feature is deprecated and should be done manually. - // 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 - } - }) - */ + // start a demo connexion + this.demoP2pConnexion() } - private fun callWithP2pPeers(callback: (Collection) -> Unit) { - 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 ?") + private fun demoP2pConnexion() { + // Get the connection information (for the host IP) + this.p2pManager!!.requestConnectionInfo(this.p2pChannel) { connection -> - 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) { - this.p2pManager?.requestGroupInfo(this.p2pChannel) { group -> - callback(group) - } + @RequiresApi(Build.VERSION_CODES.O) + @SuppressLint("UnspecifiedRegisterReceiverFlag") + 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) } } diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/DemoClient.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/DemoClient.kt new file mode 100644 index 0000000..c33ebe0 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/DemoClient.kt @@ -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() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/DemoServer.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/DemoServer.kt new file mode 100644 index 0000000..5f0aa31 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/DemoServer.kt @@ -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() + } +} 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 new file mode 100644 index 0000000..18d1906 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pBroadcastReceiver.kt @@ -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}") + } + } + } +} \ No newline at end of file