126 lines
3.9 KiB
Kotlin
126 lines
3.9 KiB
Kotlin
|
package tech.platypush.app
|
||
|
|
||
|
import android.content.ContentValues.TAG
|
||
|
import android.content.Context
|
||
|
import android.net.nsd.NsdManager
|
||
|
import android.net.nsd.NsdServiceInfo
|
||
|
import android.util.Log
|
||
|
import java.util.*
|
||
|
import java.util.concurrent.locks.ReentrantLock
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Service data class
|
||
|
*/
|
||
|
data class Service(val host: String, val port: Int, val name: String?) {
|
||
|
fun toMap(): Map<String, Any?> {
|
||
|
return mapOf(
|
||
|
"host" to host,
|
||
|
"port" to port,
|
||
|
"name" to name
|
||
|
)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* ZeroConf/Bonjour/mDNS event listener
|
||
|
*/
|
||
|
class Listener(private val scanner: Scanner): NsdManager.DiscoveryListener {
|
||
|
val serviceType = "_platypush-http._tcp."
|
||
|
|
||
|
override fun onDiscoveryStarted(regType: String) {
|
||
|
Log.d(TAG, "Service discovery started")
|
||
|
}
|
||
|
|
||
|
override fun onServiceFound(service: NsdServiceInfo) {
|
||
|
Log.d(TAG, "Service discovery succeeded: $service")
|
||
|
if (service.serviceType != serviceType) {
|
||
|
Log.d(TAG, "Unknown service type: ${service.serviceType}")
|
||
|
return
|
||
|
}
|
||
|
|
||
|
scanner.resolveService(service)
|
||
|
}
|
||
|
|
||
|
override fun onServiceLost(service: NsdServiceInfo) {
|
||
|
Log.w(TAG, "Service lost: $service")
|
||
|
}
|
||
|
|
||
|
override fun onDiscoveryStopped(serviceType: String) {
|
||
|
Log.i(TAG, "Discovery stopped: $serviceType")
|
||
|
}
|
||
|
|
||
|
override fun onStartDiscoveryFailed(serviceType: String, errorCode: Int) {
|
||
|
Log.e(TAG, "Discovery start failed: Error code: $errorCode")
|
||
|
scanner.stopScan()
|
||
|
}
|
||
|
|
||
|
override fun onStopDiscoveryFailed(serviceType: String, errorCode: Int) {
|
||
|
Log.e(TAG, "Discovery stop failed: Error code: $errorCode")
|
||
|
scanner.stopScan()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* ZeroConf/Bonjour/mDNS service scanner and resolver
|
||
|
*/
|
||
|
class Scanner(context: Context) {
|
||
|
private val nsdManager = context.getSystemService(Context.NSD_SERVICE) as NsdManager
|
||
|
private val listener = Listener(this)
|
||
|
private val services = HashMap<Pair<String, Int>, Service>()
|
||
|
private val resolverLock = ReentrantLock()
|
||
|
|
||
|
fun startScan() {
|
||
|
nsdManager.discoverServices(listener.serviceType, NsdManager.PROTOCOL_DNS_SD, listener)
|
||
|
}
|
||
|
|
||
|
fun stopScan() {
|
||
|
nsdManager.stopServiceDiscovery(listener)
|
||
|
}
|
||
|
|
||
|
fun getServices(): Collection<Service> {
|
||
|
return services.values
|
||
|
}
|
||
|
|
||
|
fun resolveService(service: NsdServiceInfo) {
|
||
|
// Service resolution is a critical section
|
||
|
resolverLock.lock()
|
||
|
val scanner = this
|
||
|
|
||
|
nsdManager.resolveService(service, object : NsdManager.ResolveListener {
|
||
|
override fun onResolveFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
|
||
|
resolverLock.unlock()
|
||
|
val msg = "Resolve of service ${serviceInfo.serviceName} failed"
|
||
|
|
||
|
// Retry logic
|
||
|
when (errorCode) {
|
||
|
NsdManager.FAILURE_ALREADY_ACTIVE -> {
|
||
|
Thread.sleep(100)
|
||
|
scanner.resolveService(serviceInfo)
|
||
|
Log.w(TAG, "$msg: Resolver already active")
|
||
|
}
|
||
|
|
||
|
NsdManager.FAILURE_MAX_LIMIT -> {
|
||
|
Thread.sleep(5000)
|
||
|
scanner.resolveService(serviceInfo)
|
||
|
Log.e(TAG, "$msg: Maximum number of resolve requests reached")
|
||
|
}
|
||
|
|
||
|
NsdManager.FAILURE_INTERNAL_ERROR -> {
|
||
|
Log.e(TAG, "$msg: Internal error")
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
override fun onServiceResolved(serviceInfo: NsdServiceInfo) {
|
||
|
services[Pair(serviceInfo.host.hostAddress, serviceInfo.port)] = Service(
|
||
|
serviceInfo.host.hostAddress, serviceInfo.port, serviceInfo.serviceName)
|
||
|
resolverLock.unlock()
|
||
|
Log.i(TAG, "Resolve succeeded: $serviceInfo")
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|