Skip to content

Repository files navigation

Coverage Status

A Prototype Implementation of the P2P Sync Transport using Wifi Direct

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:

  1. Confirming if the device supports wifi direct.
  2. Confirming if wifi direct is enabled.
  3. Granting the ACCESS_FINE_LOCATION permission required by the WifiP2pManager.
  4. Discovering peer devices.
  5. Connecting to a discovered peer.
  6. Requesting the group information, including the ip address of the group owner to establish a socket.
  7. Establishing a socket connection between the two devices.
  8. 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.

WifiP2pManager

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.

WifiP2pBroadcastReceiver

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)
          })
    }
  }
}

Publishing

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).

Prerequisites

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

Publishing a SNAPSHOT

Push a tag whose version component ends with -SNAPSHOT:

git tag v0.6.11-SNAPSHOT
git push origin v0.6.11-SNAPSHOT

The 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.

Publishing a release

Push a tag whose version component does not end with -SNAPSHOT:

git tag v0.6.11
git push origin v0.6.11

The 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.

How versioning works

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.


Gotchas

There are a few challenges that were experienced during the development of this prototype.

ACCESS_FINE_LOCATION Permission

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")
      }
    }
  }
}

About

Wifi Direct P2P Research and Implementation for FHIR Core

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages