//
// anyRemote android client
// a bluetooth/wi-fi remote control for Linux.
//
// Copyright (C) 2011-2016 Mikhail Fedotov <anyremote@mail.ru>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
//
package anyremote.client.android.util;
// API 16
import java.util.ArrayList;
import java.util.TimerTask;
import android.net.nsd.NsdServiceInfo;
import android.net.nsd.NsdManager;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.os.Build;
import anyremote.client.android.MainLoop;
import anyremote.client.android.anyRemote;
import anyremote.client.android.Dispatcher;
import anyremote.client.android.util.IScanner;
import anyremote.client.android.util.ScanMessage;
//
// Zeroconf scanner
//
@TargetApi(android.os.Build.VERSION_CODES.JELLY_BEAN) // API 16
public class ZCScanner implements IScanner {
static final String ZEROCONF_TCP_SERVICE_TYPE = "_anyremote._tcp";
static final String ZEROCONF_WEB_SERVICE_TYPE = "_anyremote-http._tcp";
static final String ZEROCONF_SERVICE_NAME = "anyRemote";
private NsdManager mNsdManager = null;
private ArDiscoveryListener mDiscoveryListenerTCP = null;
private ArDiscoveryListener mDiscoveryListenerWEB = null;
private NsdManager.ResolveListener resolver = null;
boolean resolving = false;
ArrayList<NsdServiceInfo> toResolve = new ArrayList<NsdServiceInfo>();
Handler searchFormHandler;
Context context;
public ZCScanner(Handler hdl, Context ctx) {
searchFormHandler = hdl;
context = ctx;
}
@TargetApi(android.os.Build.VERSION_CODES.JELLY_BEAN)
public void resolveNext() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
synchronized (toResolve) {
if (toResolve.size() > 0 && !resolving) {
NsdServiceInfo srvInfo = toResolve.remove(0);
anyRemote._log("ZCScanner","resolveNext (remains #" + toResolve.size() + ") " + srvInfo);
resolving = true;
mNsdManager.resolveService(srvInfo, resolver);
}
}
}
@TargetApi(android.os.Build.VERSION_CODES.JELLY_BEAN)
public void startScan() {
if (mNsdManager == null) {
mNsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
if (resolver == null) {
resolver = new NsdManager.ResolveListener() {
@Override
public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
anyRemote._log("ZCScanner", "resolve failed " + errorCode);
synchronized (toResolve) {
resolving = false;
}
resolveNext();
}
@Override
public void onServiceResolved(NsdServiceInfo serviceInfo) {
String service = serviceInfo.getServiceName();
String type = serviceInfo.getServiceType();
String host = serviceInfo.getHost().getHostAddress();
String port = String.valueOf(serviceInfo.getPort());
anyRemote._log("ZCScanner", "resolve succeeded " + service +"/" + type);
ScanMessage sm = new ScanMessage();
if (type.contains(ZEROCONF_TCP_SERVICE_TYPE)) {
sm.name = service + "://" + host;
sm.address = "socket://" + host + ":" + port;
} else if (type.contains(ZEROCONF_WEB_SERVICE_TYPE)) {
sm.name = "web://" + host;
sm.address = "web://" + host + ":" + port;
} else {
anyRemote._log("ZCScanner", "resolver: improper service type " + type);
return;
}
Message msg = searchFormHandler.obtainMessage(SCAN_FOUND, sm);
msg.sendToTarget();
synchronized (toResolve) {
resolving = false;
}
resolveNext();
}
};
}
if (mDiscoveryListenerTCP == null) {
anyRemote._log("ZCScanner", "startScan TCP");
mDiscoveryListenerTCP = new ArDiscoveryListener(ZEROCONF_TCP_SERVICE_TYPE);
}
if (mDiscoveryListenerWEB == null) {
anyRemote._log("ZCScanner", "startScan WEB");
mDiscoveryListenerWEB = new ArDiscoveryListener(ZEROCONF_WEB_SERVICE_TYPE);
}
anyRemote._log("ZCScanner", "startScan discoverServices");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
MainLoop.schedule(new TimerTask() {
public void run() {
stopScan();
}
}, 15000); // stop discovery after 15 seconds
mNsdManager.discoverServices(ZEROCONF_TCP_SERVICE_TYPE,
NsdManager.PROTOCOL_DNS_SD,
(NsdManager.DiscoveryListener) mDiscoveryListenerTCP);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
mNsdManager.discoverServices(ZEROCONF_WEB_SERVICE_TYPE,
NsdManager.PROTOCOL_DNS_SD,
(NsdManager.DiscoveryListener) mDiscoveryListenerWEB);
}
@TargetApi(android.os.Build.VERSION_CODES.JELLY_BEAN)
public void stopScan () {
try {
mNsdManager.stopServiceDiscovery((NsdManager.DiscoveryListener) mDiscoveryListenerTCP);
} catch (IllegalArgumentException e) {
}
try {
mNsdManager.stopServiceDiscovery((NsdManager.DiscoveryListener) mDiscoveryListenerWEB);
} catch (IllegalArgumentException e) {
}
}
private void informDiscoveryResult(int res) {
Message msg = searchFormHandler.obtainMessage(res);
msg.sendToTarget();
}
@TargetApi(android.os.Build.VERSION_CODES.JELLY_BEAN)
public class ArDiscoveryListener implements NsdManager.DiscoveryListener {
private String serviceType;
public ArDiscoveryListener(String sType) {
serviceType = sType;
anyRemote._log("ZCScanner", "ArDiscoveryListener "+serviceType);
}
// Called as soon as service discovery begins.
@Override
public void onDiscoveryStarted(String regType) {
anyRemote._log("ZCScanner", "service discovery started "+serviceType);
}
@Override
public void onServiceFound(NsdServiceInfo service) {
anyRemote._log("ZCScanner","service discovery success " + service.getServiceName() + " " + service.getServiceType());
if (!service.getServiceName().contains(ZEROCONF_SERVICE_NAME)) {
return;
}
if (service.getServiceType().contains(serviceType)) {
synchronized (toResolve) {
toResolve.add(service);
anyRemote._log("ZCScanner","resolve queue #" + toResolve.size());
}
resolveNext();
} else {
// Service type is the string containing the protocol and transport layer for this service
anyRemote._log("ZCScanner","unknown service type " + service.getServiceType());
}
}
@Override
public void onServiceLost(NsdServiceInfo service) {
// When the network service is no longer available.
// Internal bookkeeping code goes here.
anyRemote._log("ZCScanner", "service lost " + service.getServiceName());
informDiscoveryResult(SCAN_FAILED);
}
@Override
public void onDiscoveryStopped(String serviceType) {
anyRemote._log("ZCScanner","discovery stopped " + serviceType);
informDiscoveryResult(SCAN_FINISHED);
}
@Override
public void onStartDiscoveryFailed(String serviceType, int errorCode) {
anyRemote._log("ZCScanner", "discovery failed: error code " + errorCode);
mNsdManager.stopServiceDiscovery((NsdManager.DiscoveryListener) this);
informDiscoveryResult(SCAN_FAILED);
}
@Override
public void onStopDiscoveryFailed(String serviceType, int errorCode) {
anyRemote._log("ZCScanner","discovery failed: error code " + errorCode);
mNsdManager.stopServiceDiscovery((NsdManager.DiscoveryListener) this);
informDiscoveryResult(SCAN_FAILED);
}
}
}