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 { 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, Service>() private val resolverLock = ReentrantLock() fun startScan() { nsdManager.discoverServices(listener.serviceType, NsdManager.PROTOCOL_DNS_SD, listener) } fun stopScan() { nsdManager.stopServiceDiscovery(listener) } fun getServices(): Collection { 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") } }) } }