Http Server / Client communication #7
19 changed files with 294 additions and 373 deletions
|
@ -4,27 +4,18 @@
|
||||||
<value>
|
<value>
|
||||||
<entry key="app">
|
<entry key="app">
|
||||||
<State>
|
<State>
|
||||||
<multipleDevicesSelectedInDropDown value="true" />
|
<targetSelectedWithDropDown>
|
||||||
<runningDeviceTargetsSelectedWithDialog>
|
|
||||||
<Target>
|
<Target>
|
||||||
<type value="RUNNING_DEVICE_TARGET" />
|
<type value="QUICK_BOOT_TARGET" />
|
||||||
<deviceKey>
|
<deviceKey>
|
||||||
<Key>
|
<Key>
|
||||||
<type value="SERIAL_NUMBER" />
|
<type value="VIRTUAL_DEVICE_PATH" />
|
||||||
<value value="ypee7lnbpv9x7lvs" />
|
<value value="$USER_HOME$/.android/avd/Very_Small_API_34_2.avd" />
|
||||||
</Key>
|
</Key>
|
||||||
</deviceKey>
|
</deviceKey>
|
||||||
</Target>
|
</Target>
|
||||||
<Target>
|
</targetSelectedWithDropDown>
|
||||||
<type value="RUNNING_DEVICE_TARGET" />
|
<timeTargetWasSelectedWithDropDown value="2024-05-02T14:19:57.253897049Z" />
|
||||||
<deviceKey>
|
|
||||||
<Key>
|
|
||||||
<type value="SERIAL_NUMBER" />
|
|
||||||
<value value="2XJDU17923000406" />
|
|
||||||
</Key>
|
|
||||||
</deviceKey>
|
|
||||||
</Target>
|
|
||||||
</runningDeviceTargetsSelectedWithDialog>
|
|
||||||
</State>
|
</State>
|
||||||
</entry>
|
</entry>
|
||||||
</value>
|
</value>
|
||||||
|
|
|
@ -62,6 +62,7 @@ dependencies {
|
||||||
implementation("androidx.compose.material3:material3")
|
implementation("androidx.compose.material3:material3")
|
||||||
implementation("androidx.room:room-ktx:2.6.1")
|
implementation("androidx.room:room-ktx:2.6.1")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
|
||||||
|
implementation("androidx.navigation:navigation-compose:2.7.7")
|
||||||
testImplementation("junit:junit:4.13.2")
|
testImplementation("junit:junit:4.13.2")
|
||||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<!-- SDK -->
|
<!-- SDK -->
|
||||||
|
|
||||||
<uses-sdk
|
<uses-sdk
|
||||||
android:minSdkVersion="14"
|
android:minSdkVersion="24"
|
||||||
tools:ignore="GradleOverrides" />
|
tools:ignore="GradleOverrides" />
|
||||||
|
|
||||||
<!-- Permissions -->
|
<!-- Permissions -->
|
||||||
|
|
|
@ -1,25 +1,24 @@
|
||||||
package com.faraphel.tasks_valider
|
package com.faraphel.tasks_valider
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.net.wifi.p2p.*
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.net.wifi.p2p.WifiP2pManager
|
||||||
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 com.faraphel.tasks_valider.connectivity.packets.PacketPing
|
import com.faraphel.tasks_valider.connectivity.bwf.BwfManager
|
||||||
import com.faraphel.tasks_valider.connectivity.wifi_p2p.WifiP2pHelper
|
import com.faraphel.tasks_valider.connectivity.bwf.error.BwfNotSupportedException
|
||||||
import com.faraphel.tasks_valider.database.Database
|
import com.faraphel.tasks_valider.database.Database
|
||||||
import com.faraphel.tasks_valider.ui.screen.room.RoomScreen
|
import com.faraphel.tasks_valider.ui.screen.room.RoomScreen
|
||||||
|
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
private var PERMISSION_ACCESS_FINE_LOCATION = 1001 ///< permission code for the fi
|
private var bwfManager: BwfManager? = null ///< the WiFi-Direct helper
|
||||||
private var p2pHelper: WifiP2pHelper? = null ///< the Wi-Fi Direct helper
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private lateinit var database: Database ///< the database manager
|
private lateinit var database: Database ///< the database manager
|
||||||
|
@ -29,56 +28,56 @@ class MainActivity : ComponentActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
// ----- CONNECTION
|
// TODO(Faraphel): more check on permissions
|
||||||
|
|
||||||
// TOOD: more check on permissions
|
// check if the system support WiFi-Direct
|
||||||
if (!this.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)) {
|
if (!this.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)) {
|
||||||
Log.e("wifi-p2p", "this device does not support the WiFi-Direct feature")
|
Log.e("wifi-p2p", "this device does not support the WiFi-Direct feature")
|
||||||
return
|
throw BwfNotSupportedException()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
if (
|
||||||
|
this.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED ||
|
||||||
|
this.checkSelfPermission(Manifest.permission.NEARBY_WIFI_DEVICES) != PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
// TODO(Faraphel): should be used with shouldShowRequestPermissionRationale, with a check
|
// TODO(Faraphel): should be used with shouldShowRequestPermissionRationale, with a check
|
||||||
this.requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), PERMISSION_ACCESS_FINE_LOCATION)
|
this.requestPermissions(
|
||||||
|
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
|
||||||
|
BwfManager.PERMISSION_ACCESS_FINE_LOCATION
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the WiFi-Direct manager
|
// get the WiFi-Direct manager
|
||||||
val p2pManager: WifiP2pManager? = this.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager?
|
val manager = this.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager?
|
||||||
if (p2pManager == null) {
|
if (manager == null) {
|
||||||
Log.e("wifi-p2p", "cannot access the WiFi-Direct manager")
|
Log.e("wifi-p2p", "cannot access the WiFi-Direct manager")
|
||||||
return
|
throw BwfNotSupportedException()
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the Wifi-Direct channel
|
// create a channel for the manager
|
||||||
val p2pChannel: WifiP2pManager.Channel = p2pManager.initialize(this, this.mainLooper, null)
|
val channel = manager.initialize(this, this.mainLooper, null)
|
||||||
|
|
||||||
// create a helper for handling the WiFi-Direct
|
// create a new manager for handling the WiFi-Direct
|
||||||
this.p2pHelper = WifiP2pHelper(p2pManager, p2pChannel)
|
this.bwfManager = BwfManager(manager, channel)
|
||||||
|
|
||||||
// open the room selection screen
|
// open the room selection screen
|
||||||
this.setContent {
|
this.setContent {
|
||||||
RoomScreen(this.p2pHelper!!)
|
RoomScreen(this.bwfManager!!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
// enable the WiFi-Direct events
|
// enable the WiFi-Direct events
|
||||||
this.registerReceiver(this.p2pHelper, WifiP2pHelper.ALL_INTENT_FILTER)
|
this.registerReceiver(this.bwfManager, BwfManager.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.p2pHelper)
|
this.unregisterReceiver(this.bwfManager)
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
|
||||||
super.onSaveInstanceState(outState)
|
|
||||||
// TODO(Faraphel): save the current state to reload it later
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,151 @@
|
||||||
|
package com.faraphel.tasks_valider.connectivity.bwf
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.net.wifi.p2p.*
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import com.faraphel.tasks_valider.connectivity.bwf.error.*
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 manager
|
||||||
|
* @param channel The WiFi-Direct channel
|
||||||
|
*/
|
||||||
|
class BwfManager(
|
||||||
|
private var manager: WifiP2pManager,
|
||||||
|
private var channel: WifiP2pManager.Channel,
|
||||||
|
) : BroadcastReceiver() {
|
||||||
|
companion object {
|
||||||
|
var PERMISSION_ACCESS_FINE_LOCATION = 1001 ///< permission code for the fine location
|
||||||
|
var ALL_INTENT_FILTER = IntentFilter().apply {
|
||||||
|
addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)
|
||||||
|
addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrappers
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to another device, allowing for a communication using Sockets
|
||||||
|
* @see WifiP2pManager.connect
|
||||||
|
* @throws SecurityException if the permission has not been given
|
||||||
|
*/
|
||||||
|
@Throws(SecurityException::class)
|
||||||
|
fun connect(config: WifiP2pConfig, callback: () -> Unit = {}) =
|
||||||
|
this.manager.connect(this.channel, config, object : WifiP2pManager.ActionListener {
|
||||||
|
override fun onSuccess() { callback() }
|
||||||
|
override fun onFailure(reason: Int) = throw BwfConnectException(reason)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request the list of peers after a discovery.
|
||||||
|
* @see WifiP2pManager.requestPeers
|
||||||
|
* @throws SecurityException if the permission has not been given
|
||||||
|
*/
|
||||||
|
@Throws(SecurityException::class)
|
||||||
|
fun requestPeers(callback: (WifiP2pDeviceList) -> Unit = {}) =
|
||||||
|
this.manager.requestPeers(this.channel, callback)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start discovering peers.
|
||||||
|
* Once founds, the WIFI_P2P_PEERS_CHANGED_ACTION event will be triggered.
|
||||||
|
* @see WifiP2pManager.discoverPeers
|
||||||
|
* @throws SecurityException if the permission has not been given
|
||||||
|
*/
|
||||||
|
@Throws(SecurityException::class)
|
||||||
|
fun discoverPeers(callback: () -> Unit = {}) =
|
||||||
|
this.manager.discoverPeers(this.channel, object : WifiP2pManager.ActionListener {
|
||||||
|
override fun onSuccess() { callback() }
|
||||||
|
override fun onFailure(reason: Int) = throw BwfDiscoverException(reason)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtain information about a connection with another device.
|
||||||
|
* @see WifiP2pManager.requestConnectionInfo
|
||||||
|
*/
|
||||||
|
fun requestConnectionInfo(callback: (WifiP2pInfo) -> Unit = {}) =
|
||||||
|
this.manager.requestConnectionInfo(this.channel, callback)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtain information about the current group.
|
||||||
|
* @see WifiP2pManager.requestGroupInfo
|
||||||
|
* @throws SecurityException if the permission has not been given
|
||||||
|
*/
|
||||||
|
@Throws(SecurityException::class)
|
||||||
|
fun requestGroupInfo(callback: (WifiP2pGroup?) -> Unit = {}) =
|
||||||
|
this.manager.requestGroupInfo(this.channel, callback)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new WiFi-Direct group.
|
||||||
|
* @see WifiP2pManager.createGroup
|
||||||
|
* @throws SecurityException if the permission has not been given
|
||||||
|
*/
|
||||||
|
@Throws(SecurityException::class)
|
||||||
|
fun createGroup(callback: () -> Unit = {}) =
|
||||||
|
this.manager.createGroup(this.channel, object : WifiP2pManager.ActionListener {
|
||||||
|
override fun onSuccess() { callback() }
|
||||||
|
override fun onFailure(reason: Int) = throw BwfCreateGroupException(reason)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect from the current WiFi-Direct group.
|
||||||
|
* @see WifiP2pManager.removeGroup
|
||||||
|
*/
|
||||||
|
fun removeGroup(callback: () -> Unit = {}) =
|
||||||
|
this.manager.removeGroup(this.channel, object : WifiP2pManager.ActionListener {
|
||||||
|
override fun onSuccess() { callback() }
|
||||||
|
override fun onFailure(reason: Int) = throw BwfRemoveGroupException(reason)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new WiFi-Direct group. If already connected to a group, quit it first.
|
||||||
|
*
|
||||||
|
* Note: most of the failure on removal are caused by not having a group already created, which is checked.
|
||||||
|
*
|
||||||
|
* @param listener: the createGroup listener
|
||||||
|
* @param onRemoveFailure: error handler for the removeGroup event
|
||||||
|
*
|
||||||
|
* @see WifiP2pManager.createGroup
|
||||||
|
* @see WifiP2pManager.removeGroup
|
||||||
|
*/
|
||||||
|
fun recreateGroup(callback: () -> Unit = {}) {
|
||||||
|
// get the current group information
|
||||||
|
this.requestGroupInfo { group ->
|
||||||
|
// if a group exist, quit it
|
||||||
|
if (group != null)
|
||||||
|
this.removeGroup { this@BwfManager.createGroup(callback) }
|
||||||
|
else
|
||||||
|
// create the group
|
||||||
|
this.createGroup(callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Events
|
||||||
|
|
||||||
|
val stateConnectionInfo = mutableStateOf<WifiP2pInfo?>(null)
|
||||||
|
val statePeers = mutableStateOf<WifiP2pDeviceList?>(null)
|
||||||
|
|
||||||
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
|
// ignore empty intent
|
||||||
|
if (intent == null)
|
||||||
|
return
|
||||||
|
|
||||||
|
// update the action corresponding state
|
||||||
|
when (intent.action) {
|
||||||
|
WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> this.requestConnectionInfo {
|
||||||
|
connectionInfo -> stateConnectionInfo.value = connectionInfo
|
||||||
|
}
|
||||||
|
WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> this.requestPeers {
|
||||||
|
peers -> statePeers.value = peers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO(Faraphel): implement event dispatcher
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Better WiFi-Direct (BWD)
|
||||||
|
|
||||||
|
This package contain code to improve the base WiFi-Direct implementation.
|
||||||
|
|
||||||
|
The base have some issue, like an abusive usage of listener, error code and events that make using it
|
||||||
|
very impractical.
|
||||||
|
|
||||||
|
This improved version will instead focus on asynchronous function and exception, allowing for a
|
||||||
|
cleaner linear code instead.
|
||||||
|
|
||||||
|
(Author: https://git.faraphel.fr/Faraphel)
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.faraphel.tasks_valider.connectivity.bwf.error
|
||||||
|
|
||||||
|
class BwfConnectException(
|
||||||
|
reason: Int
|
||||||
|
) : BwfException("Cannot connect to the peer. Reason: $reason")
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.faraphel.tasks_valider.connectivity.bwf.error
|
||||||
|
|
||||||
|
class BwfCreateGroupException (
|
||||||
|
reason: Int
|
||||||
|
) : BwfException("Could not create the group : $reason")
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.faraphel.tasks_valider.connectivity.bwf.error
|
||||||
|
|
||||||
|
class BwfDiscoverException(
|
||||||
|
reason: Int
|
||||||
|
) : BwfException("Could not discover peers : $reason")
|
|
@ -1,9 +1,9 @@
|
||||||
package com.faraphel.tasks_valider.connectivity.wifi_p2p.error
|
package com.faraphel.tasks_valider.connectivity.bwf.error
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base Exception for everything concerning the WifiP2pHelper class
|
* Base Exception for everything concerning the WifiP2pHelper class
|
||||||
*/
|
*/
|
||||||
open class WifiP2pHelperException(
|
open class BwfException(
|
||||||
override val message: String?
|
override val message: String?
|
||||||
) : Exception(message)
|
) : Exception(message)
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.faraphel.tasks_valider.connectivity.bwf.error
|
||||||
|
|
||||||
|
class BwfInvalidActionException(
|
||||||
|
action: String
|
||||||
|
) : BwfException("This WiFi-Direct action is not supported : $action")
|
|
@ -0,0 +1,4 @@
|
||||||
|
package com.faraphel.tasks_valider.connectivity.bwf.error
|
||||||
|
|
||||||
|
class BwfNotSupportedException :
|
||||||
|
BwfException("WiFi-Direct is not supported on this device, or it is disabled.")
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.faraphel.tasks_valider.connectivity.bwf.error
|
||||||
|
|
||||||
|
class BwfRemoveGroupException (
|
||||||
|
reason: Int
|
||||||
|
) : BwfException("Could not remove the group : $reason")
|
|
@ -1,212 +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.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
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connect to another device, allowing for a communication using Sockets
|
|
||||||
* @see WifiP2pManager.connect
|
|
||||||
*/
|
|
||||||
fun connect(config: WifiP2pConfig, listener: WifiP2pManager.ActionListener? = null) {
|
|
||||||
this.manager.connect(this.channel, config, listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request the list of peers after a discovery.
|
|
||||||
* @see WifiP2pManager.requestPeers
|
|
||||||
*/
|
|
||||||
fun requestPeers(listener: WifiP2pManager.PeerListListener? = null) {
|
|
||||||
this.manager.requestPeers(this.channel, listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start discovering peers.
|
|
||||||
* Once founds, the WIFI_P2P_PEERS_CHANGED_ACTION event will be triggered.
|
|
||||||
* @see WifiP2pManager.discoverPeers
|
|
||||||
*/
|
|
||||||
fun discoverPeers(listener: WifiP2pManager.ActionListener? = null) {
|
|
||||||
this.manager.discoverPeers(this.channel, listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtain information about a connection with another device.
|
|
||||||
* @see WifiP2pManager.requestConnectionInfo
|
|
||||||
*/
|
|
||||||
fun requestConnectionInfo(listener: WifiP2pManager.ConnectionInfoListener? = null) {
|
|
||||||
this.manager.requestConnectionInfo(this.channel, listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtain information about the current group.
|
|
||||||
* @see WifiP2pManager.requestGroupInfo
|
|
||||||
*/
|
|
||||||
fun requestGroupInfo(listener: WifiP2pManager.GroupInfoListener? = null) {
|
|
||||||
this.manager.requestGroupInfo(this.channel, listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new WiFi-Direct group.
|
|
||||||
* @see WifiP2pManager.createGroup
|
|
||||||
*/
|
|
||||||
fun createGroup(listener: WifiP2pManager.ActionListener? = null) {
|
|
||||||
this.manager.createGroup(this.channel, listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disconnect from the current WiFi-Direct group.
|
|
||||||
* @see WifiP2pManager.removeGroup
|
|
||||||
*/
|
|
||||||
fun removeGroup(listener: WifiP2pManager.ActionListener? = null) {
|
|
||||||
this.manager.removeGroup(this.channel, listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shortcuts
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new WiFi-Direct group. If already connected to a group, quit it first.
|
|
||||||
*
|
|
||||||
* Note: most of the failure on removal are caused by not having a group already created, which is checked.
|
|
||||||
*
|
|
||||||
* @param listener: the createGroup listener
|
|
||||||
* @param onRemoveFailure: error handler for the removeGroup event
|
|
||||||
*
|
|
||||||
* @see WifiP2pManager.createGroup
|
|
||||||
* @see WifiP2pManager.removeGroup
|
|
||||||
*/
|
|
||||||
fun recreateGroup(
|
|
||||||
listener: WifiP2pManager.ActionListener? = null,
|
|
||||||
onRemoveFailure: ((Int) -> Unit)? = null
|
|
||||||
) {
|
|
||||||
// get the current group information
|
|
||||||
this.requestGroupInfo { group ->
|
|
||||||
// if a group exist, quit it
|
|
||||||
if (group != null)
|
|
||||||
this.removeGroup(object : WifiP2pManager.ActionListener {
|
|
||||||
override fun onSuccess() {
|
|
||||||
// once left, create the group
|
|
||||||
this@WifiP2pHelper.createGroup(listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(reason: Int) {
|
|
||||||
// call the handler
|
|
||||||
onRemoveFailure?.invoke(reason)
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
else
|
|
||||||
// create the group
|
|
||||||
this.createGroup(listener)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
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,
|
|
||||||
)
|
|
|
@ -1,5 +0,0 @@
|
||||||
package com.faraphel.tasks_valider.connectivity.wifi_p2p.error
|
|
||||||
|
|
||||||
class WifiP2pHelperInvalidActionException(
|
|
||||||
action: String
|
|
||||||
) : WifiP2pHelperException("This WiFi-Direct action is not supported : $action")
|
|
|
@ -3,32 +3,50 @@ package com.faraphel.tasks_valider.ui.screen.room
|
||||||
import android.net.wifi.p2p.WifiP2pConfig
|
import android.net.wifi.p2p.WifiP2pConfig
|
||||||
import android.net.wifi.p2p.WifiP2pDevice
|
import android.net.wifi.p2p.WifiP2pDevice
|
||||||
import android.net.wifi.p2p.WifiP2pDeviceList
|
import android.net.wifi.p2p.WifiP2pDeviceList
|
||||||
import android.net.wifi.p2p.WifiP2pInfo
|
|
||||||
import android.net.wifi.p2p.WifiP2pManager
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.navigation.compose.NavHost
|
||||||
|
import androidx.navigation.compose.composable
|
||||||
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
import com.faraphel.tasks_valider.connectivity.bwf.BwfManager
|
||||||
import com.faraphel.tasks_valider.connectivity.room.RoomClient
|
import com.faraphel.tasks_valider.connectivity.room.RoomClient
|
||||||
import com.faraphel.tasks_valider.connectivity.wifi_p2p.WifiP2pHelper
|
|
||||||
import com.faraphel.tasks_valider.ui.screen.tasks.TaskGroupScreen
|
import com.faraphel.tasks_valider.ui.screen.tasks.TaskGroupScreen
|
||||||
import com.faraphel.tasks_valider.ui.widgets.connectivity.WifiP2pDeviceListWidget
|
import com.faraphel.tasks_valider.ui.widgets.connectivity.WifiP2pDeviceListWidget
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This screen allow the user to join a room
|
* This screen allow the user to join a room
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun RoomClientScreen(wifiP2pHelper: WifiP2pHelper) {
|
fun RoomClientScreen(bwfManager: BwfManager) {
|
||||||
val peers = remember { mutableStateOf<WifiP2pDeviceList?>(null) }
|
val navController = rememberNavController()
|
||||||
|
|
||||||
val selectedDevice = remember { mutableStateOf<WifiP2pDevice?>(null) }
|
val selectedDevice = remember { mutableStateOf<WifiP2pDevice?>(null) }
|
||||||
val connected = remember { mutableStateOf(false) }
|
val connected = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
// if the device is connected to a host, display the main screen
|
NavHost(navController = navController, startDestination = "roomSearch") {
|
||||||
if (connected.value) {
|
composable(route = "roomSearch") {
|
||||||
TaskGroupScreen()
|
// let the user pick which host he wishes to connect to
|
||||||
|
Column {
|
||||||
|
Text(text = "Find a Server")
|
||||||
|
// display all the devices that are owner of their group
|
||||||
|
WifiP2pDeviceListWidget(
|
||||||
|
peers = bwfManager.statePeers.value,
|
||||||
|
filter = { device: WifiP2pDevice -> device.isGroupOwner },
|
||||||
|
selectedDevice,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the list when a new device is detected
|
||||||
|
}
|
||||||
|
composable(route = "taskGroup") {
|
||||||
|
TaskGroupScreen()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if a device is selected, connect to it
|
// if a device is selected, connect to it
|
||||||
|
@ -37,53 +55,17 @@ fun RoomClientScreen(wifiP2pHelper: WifiP2pHelper) {
|
||||||
val config = WifiP2pConfig().apply {
|
val config = WifiP2pConfig().apply {
|
||||||
deviceAddress = selectedDevice.value!!.deviceAddress
|
deviceAddress = selectedDevice.value!!.deviceAddress
|
||||||
}
|
}
|
||||||
// try to connect
|
|
||||||
wifiP2pHelper.connect(config, object : WifiP2pManager.ActionListener {
|
|
||||||
override fun onSuccess() {
|
|
||||||
Log.i("room", "Connection successful to the host !")
|
|
||||||
// request additional information about the connection to obtain the host IP
|
|
||||||
wifiP2pHelper.registerListener(
|
|
||||||
WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION,
|
|
||||||
{ connectionInfo: WifiP2pInfo ->
|
|
||||||
val client = RoomClient(
|
|
||||||
connectionInfo.groupOwnerAddress,
|
|
||||||
9876 // TODO(Faraphel): port should be a settings
|
|
||||||
)
|
|
||||||
Thread {
|
|
||||||
client.start()
|
|
||||||
}.start()
|
|
||||||
|
|
||||||
connected.value = true
|
// try to connect to the host
|
||||||
},
|
bwfManager.connect(config) {
|
||||||
once = true
|
Log.i("room", "Connection successful to the host !")
|
||||||
)
|
|
||||||
|
bwfManager.requestConnectionInfo { connectionInfo ->
|
||||||
|
val client = RoomClient(connectionInfo.groupOwnerAddress, 9876) // TODO(Faraphel): port should be a settings
|
||||||
|
Thread { client.start() }.start()
|
||||||
|
|
||||||
|
connected.value = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
override fun onFailure(reason: Int) {
|
|
||||||
// TODO(Faraphel): for most of theses messages, shouldn't a toast be used instead ?
|
|
||||||
Log.e("room", "Could not connect to this host. Reason: $reason")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// let the user pick which host he wishes to connect to
|
|
||||||
|
|
||||||
Column {
|
|
||||||
Text(text = "Find a Server")
|
|
||||||
// display all the devices that are owner of their group
|
|
||||||
WifiP2pDeviceListWidget(
|
|
||||||
peers = peers.value,
|
|
||||||
filter = { device: WifiP2pDevice -> device.isGroupOwner },
|
|
||||||
selectedDevice,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(Faraphel): might be doubtful. Test with more phones, should it only run once ? refresh button ?
|
|
||||||
// update the list when a new device is detected
|
|
||||||
wifiP2pHelper.registerListener(
|
|
||||||
WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION,
|
|
||||||
{ newPeers: WifiP2pDeviceList? -> peers.value = newPeers },
|
|
||||||
)
|
|
||||||
wifiP2pHelper.discoverPeers()
|
|
||||||
}
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
package com.faraphel.tasks_valider.ui.screen.room
|
package com.faraphel.tasks_valider.ui.screen.room
|
||||||
|
|
||||||
import android.net.wifi.p2p.WifiP2pManager
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
@ -10,8 +9,9 @@ import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import com.faraphel.tasks_valider.connectivity.wifi_p2p.WifiP2pHelper
|
import com.faraphel.tasks_valider.connectivity.bwf.BwfManager
|
||||||
import com.faraphel.tasks_valider.ui.screen.tasks.TaskGroupScreen
|
import com.faraphel.tasks_valider.ui.screen.tasks.TaskGroupScreen
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import java.net.ServerSocket
|
import java.net.ServerSocket
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ var DEFAULT_SERVER_PORT: Int = 9876
|
||||||
* This screen allow the user to create a room
|
* This screen allow the user to create a room
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun RoomHostScreen(wifiP2pHelper: WifiP2pHelper) {
|
fun RoomHostScreen(bwfManager: BwfManager) {
|
||||||
val expanded = remember { mutableStateOf(false) }
|
val expanded = remember { mutableStateOf(false) }
|
||||||
val serverPort = remember { mutableStateOf(DEFAULT_SERVER_PORT) }
|
val serverPort = remember { mutableStateOf(DEFAULT_SERVER_PORT) }
|
||||||
val isCreated = remember { mutableStateOf(false) }
|
val isCreated = remember { mutableStateOf(false) }
|
||||||
|
@ -65,23 +65,19 @@ fun RoomHostScreen(wifiP2pHelper: WifiP2pHelper) {
|
||||||
|
|
||||||
Button(onClick = {
|
Button(onClick = {
|
||||||
// create a new WiFi-Direct group
|
// create a new WiFi-Direct group
|
||||||
wifiP2pHelper.recreateGroup(object : WifiP2pManager.ActionListener {
|
bwfManager.recreateGroup {
|
||||||
override fun onSuccess() {
|
Log.i("room", "group created !")
|
||||||
Log.i("room", "group created !")
|
|
||||||
|
|
||||||
val server = ServerSocket(serverPort.value) // TODO(Faraphel): should the port be a settings ?
|
// open a new server
|
||||||
Thread {
|
val server = ServerSocket(serverPort.value)
|
||||||
val client = server.accept() // TODO(Faraphel): should run in a thread
|
Thread {
|
||||||
client.getInputStream()
|
val client = server.accept() // TODO(Faraphel): should run in a thread
|
||||||
}
|
client.getInputStream()
|
||||||
|
}
|
||||||
|
|
||||||
// mark the group as created
|
// mark the group as created
|
||||||
isCreated.value = true
|
isCreated.value = true
|
||||||
}
|
}
|
||||||
override fun onFailure(reason: Int) {
|
|
||||||
Log.e("room", "error while creating group. Reason: $reason")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}) {
|
}) {
|
||||||
Text("Create")
|
Text("Create")
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,42 +6,35 @@ import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import com.faraphel.tasks_valider.connectivity.wifi_p2p.WifiP2pHelper
|
import androidx.navigation.compose.NavHost
|
||||||
|
import androidx.navigation.compose.composable
|
||||||
|
import androidx.navigation.compose.rememberNavController
|
||||||
enum class RoomScreenType {
|
import com.faraphel.tasks_valider.connectivity.bwf.BwfManager
|
||||||
HOST,
|
|
||||||
CLIENT
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This screen will allow the user to choose which room is he connecting to, or let him create one.
|
* This screen will allow the user to choose which room is he connecting to, or let him create one.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun RoomScreen(wifiP2pHelper: WifiP2pHelper) {
|
fun RoomScreen(bwfManager: BwfManager) {
|
||||||
val roomMode = remember { mutableStateOf<RoomScreenType?>(null) }
|
val navController = rememberNavController()
|
||||||
|
|
||||||
when (roomMode.value) {
|
NavHost(navController = navController, startDestination = "pickMode") {
|
||||||
RoomScreenType.CLIENT -> {
|
composable(route = "pickMode") {
|
||||||
// Client mode
|
|
||||||
RoomClientScreen(wifiP2pHelper)
|
|
||||||
}
|
|
||||||
RoomScreenType.HOST -> {
|
|
||||||
// Host mode
|
|
||||||
RoomHostScreen(wifiP2pHelper)
|
|
||||||
}
|
|
||||||
|
|
||||||
null -> {
|
|
||||||
// let the user decide which room mode he which to use
|
|
||||||
Column {
|
Column {
|
||||||
Button(onClick = { roomMode.value = RoomScreenType.CLIENT }) {
|
Button(onClick = { navController.navigate("client") }) {
|
||||||
Text(text = "Join a room")
|
Text(text = "Join a room")
|
||||||
}
|
}
|
||||||
Button(onClick = { roomMode.value = RoomScreenType.HOST }) {
|
Button(onClick = { navController.navigate("host") }) {
|
||||||
Text(text = "Create a room")
|
Text(text = "Create a room")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
composable(route = "client") {
|
||||||
|
RoomClientScreen(bwfManager)
|
||||||
|
}
|
||||||
|
composable(route = "host") {
|
||||||
|
RoomHostScreen(bwfManager)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue