This repository contains a prototype of the P2P Sync Transport using Wifi Direct. The intention of this work is to demonstrate the use of the Android Wifi Direct APIs to exchange data between two devices. Please note that this is a prototype and NOT READY FOR PRODUCTION use.
The process involves a few steps:
- Confirming if the device supports wifi direct.
- Confirming if wifi direct is enabled.
- Granting the
ACCESS_FINE_LOCATIONpermission required by the WifiP2pManager. - Discovering peer devices.
- Connecting to a discovered peer.
- Requesting the group information, including the ip address of the group owner to establish a socket.
- Establishing a socket connection between the two devices.
- Exchanging data between the two devices.
These tasks are achieved through the use of APIs of the WifiP2pManager and the receiving of relevant intents via the WifiP2pBroadcastReceiver.
The key class from the Android SDK is the WifiP2pManager. This class enables an application to request relevant information, register for relevant information and initiate relevant actions related to establish a wifi direct connection to another device.
The key class in this prototype is the WifiP2pBroadcastReceiver. The class receives the relevant intents containing information of interest related to establishing a connection to another device via wifi direct. This class also uses the WifiP2pManager to request the relevant information at the relvant time.
An application is required to register to receive the relevant intents.
In this prototype this is achieved by registering an IntentFilter with the Context in MainActivity.onResume().
class MainActivity : AppCompatActivity() {
private val wifiP2pManager: WifiP2pManager by
lazy(LazyThreadSafetyMode.NONE) {
getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager
}
private var wifiP2pChannel: WifiP2pManager.Channel? = null
private var wifiP2pReceiver: BroadcastReceiver? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
wifiP2pChannel = wifiP2pManager.initialize(this, mainLooper, null)
wifiP2pChannel?.also { channel ->
wifiP2pReceiver = WifiP2pBroadcastReceiver(wifiP2pManager, channel, this)
}
}
override fun onResume() {
super.onResume()
listenForWifiP2pIntents()
}
override fun onPause() {
super.onPause()
wifiP2pReceiver?.also { unregisterReceiver(it) }
}
private fun listenForWifiP2pIntents() {
wifiP2pReceiver?.also {
registerReceiver(
it,
IntentFilter().apply {
addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)
addAction(WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION)
addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)
addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)
addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION)
})
}
}
}The library is published to Maven Central via a GitHub Actions workflow that triggers on version tags matching v<major>.<minor>.<patch> (release) or v<major>.<minor>.<patch>-SNAPSHOT (snapshot).
Add the following secrets to the repository (Settings → Secrets and variables → Actions):
| Secret | Description |
|---|---|
MAVEN_CENTRAL_USERNAME |
Maven Central portal token username (generate at central.sonatype.com) |
MAVEN_CENTRAL_PASSWORD |
Maven Central portal token password |
GPG_PRIVATE_KEY |
Armored GPG private key (gpg --armor --export-secret-keys <KEY_ID>) — release builds only |
GPG_PASSPHRASE |
Passphrase for the GPG key, or omit if the key has no passphrase — release builds only |
Push a tag whose version component ends with -SNAPSHOT:
git tag v0.6.11-SNAPSHOT
git push origin v0.6.11-SNAPSHOTThe artifact is published directly to https://central.sonatype.com/repository/maven-snapshots/ and is immediately available. Snapshot artifacts are not GPG-signed.
Note: Maven Central automatically deletes SNAPSHOT artifacts after 90 days. Re-publish the same tag if you need to extend availability. See the Maven Central snapshot documentation for details.
Push a tag whose version component does not end with -SNAPSHOT:
git tag v0.6.11
git push origin v0.6.11The workflow signs the artifacts with GPG, stages them locally, then uploads the bundle to the Maven Central Portal API (https://central.sonatype.com/api/v1/publisher/upload) with publishingType=AUTOMATIC so the release is validated and published without manual intervention. Allow up to 30 minutes for it to appear in Maven Central search.
The workflow strips the leading v from the tag and passes the remainder to Gradle as the versionName property. For example, tag v0.6.11-SNAPSHOT produces version 0.6.11-SNAPSHOT in the published POM.
There are a few challenges that were experienced during the development of this prototype.
The Wifi Direct APIs required the ACCESS_FINE_LOCATION permission which is a dangerous level permission.
This means that this permission must be requested just in time by the application, in newer version of the Android SDK.
In this prototype the permission is requested in the MainActivity.onCreate as well as prior to each sensitive API request.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestAccessFineLocationIfNotGranted()
} else {
handleMinimumSDKVersionNotMet(Build.VERSION_CODES.M)
}
}
@RequiresApi(Build.VERSION_CODES.M)
private fun requestAccessFineLocationIfNotGranted() {
when (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)) {
PackageManager.PERMISSION_GRANTED -> logDebug("Wifi P2P: Access fine location granted")
else -> {
logDebug("Wifi P2P: Requesting access fine location permission")
return requestPermissions(
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
accessFineLocationPermissionRequestInt)
}
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
if (requestCode == accessFineLocationPermissionRequestInt) {
val accessFineLocationPermissionIndex =
permissions.indexOfFirst { it == Manifest.permission.ACCESS_FINE_LOCATION }
if (grantResults[accessFineLocationPermissionIndex] == PackageManager.PERMISSION_GRANTED) {
return logDebug("Wifi P2P: Access fine location granted")
}
}
}
}