/* * ZeroconfDiscoveryProvider * Connect SDK * * Copyright (c) 2014 LG Electronics. * Created by Hyun Kook Khang on 18 Apr 2014 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.connectsdk.discovery.provider; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import javax.jmdns.JmDNS; import javax.jmdns.ServiceEvent; import javax.jmdns.ServiceListener; import android.content.Context; import android.util.Log; import com.connectsdk.core.Util; import com.connectsdk.discovery.DiscoveryFilter; import com.connectsdk.discovery.DiscoveryProvider; import com.connectsdk.discovery.DiscoveryProviderListener; import com.connectsdk.service.config.ServiceDescription; public class ZeroconfDiscoveryProvider implements DiscoveryProvider { private static final String HOSTNAME = "connectsdk"; JmDNS jmdns; InetAddress srcAddress; private Timer scanTimer; List<DiscoveryFilter> serviceFilters; ConcurrentHashMap<String, ServiceDescription> foundServices; CopyOnWriteArrayList<DiscoveryProviderListener> serviceListeners; boolean isRunning = false; ServiceListener jmdnsListener = new ServiceListener() { @Override public void serviceResolved(ServiceEvent ev) { @SuppressWarnings("deprecation") String ipAddress = ev.getInfo().getHostAddress(); if (!Util.isIPv4Address(ipAddress)) { // Currently, we only support ipv4 return; } String friendlyName = ev.getInfo().getName(); int port = ev.getInfo().getPort(); ServiceDescription foundService = foundServices.get(ipAddress); boolean isNew = foundService == null; boolean listUpdateFlag = false; if (isNew) { foundService = new ServiceDescription(); foundService.setUUID(ipAddress); foundService.setServiceFilter(ev.getInfo().getType()); foundService.setIpAddress(ipAddress); foundService.setServiceID(serviceIdForFilter(ev.getInfo().getType())); foundService.setPort(port); foundService.setFriendlyName(friendlyName); listUpdateFlag = true; } else { if (!foundService.getFriendlyName().equals(friendlyName)) { foundService.setFriendlyName(friendlyName); listUpdateFlag = true; } } if (foundService != null) foundService.setLastDetection(new Date().getTime()); foundServices.put(ipAddress, foundService); if (listUpdateFlag) { for (DiscoveryProviderListener listener: serviceListeners) { listener.onServiceAdded(ZeroconfDiscoveryProvider.this, foundService); } } } @Override public void serviceRemoved(ServiceEvent ev) { @SuppressWarnings("deprecation") String uuid = ev.getInfo().getHostAddress(); final ServiceDescription service = foundServices.get(uuid); if (service != null) { Util.runOnUI(new Runnable() { @Override public void run() { for (DiscoveryProviderListener listener : serviceListeners) { listener.onServiceRemoved(ZeroconfDiscoveryProvider.this, service); } } }); } } @Override public void serviceAdded(ServiceEvent event) { // Required to force serviceResolved to be called again // (after the first search) jmdns.requestServiceInfo(event.getType(), event.getName(), 1); } }; public ZeroconfDiscoveryProvider(Context context) { foundServices = new ConcurrentHashMap<String, ServiceDescription>(8, 0.75f, 2); serviceListeners = new CopyOnWriteArrayList<DiscoveryProviderListener>(); serviceFilters = new CopyOnWriteArrayList<DiscoveryFilter>(); try { srcAddress = Util.getIpAddress(context); } catch (UnknownHostException e) { e.printStackTrace(); } } @Override public void start() { if (isRunning) return; isRunning = true; scanTimer = new Timer(); scanTimer.schedule(new MDNSSearchTask(), 100, RESCAN_INTERVAL); } protected JmDNS createJmDNS() throws IOException { if (srcAddress != null) return JmDNS.create(srcAddress, HOSTNAME); else return null; } private class MDNSSearchTask extends TimerTask { @Override public void run() { List<String> killKeys = new ArrayList<String>(); long killPoint = new Date().getTime() - TIMEOUT; for (String key : foundServices.keySet()) { ServiceDescription service = foundServices.get(key); if (service == null || service.getLastDetection() < killPoint) { killKeys.add(key); } } for (String key : killKeys) { final ServiceDescription service = foundServices.get(key); if (service != null) { Util.runOnUI(new Runnable() { @Override public void run() { for (DiscoveryProviderListener listener : serviceListeners) { listener.onServiceRemoved(ZeroconfDiscoveryProvider.this, service); } } }); } if (foundServices.containsKey(key)) foundServices.remove(key); } rescan(); } } @Override public void stop() { isRunning = false; if (scanTimer != null) { scanTimer.cancel(); scanTimer = null; } if (jmdns != null) { for (DiscoveryFilter searchTarget : serviceFilters) { String filter = searchTarget.getServiceFilter(); jmdns.removeServiceListener(filter, jmdnsListener); } } } @Override public void restart() { stop(); start(); } @Override public void reset() { stop(); foundServices.clear(); } @Override public void rescan() { try { if (jmdns != null) { jmdns.close(); jmdns = null; } jmdns = createJmDNS(); if (jmdns != null) { for (DiscoveryFilter searchTarget : serviceFilters) { String filter = searchTarget.getServiceFilter(); jmdns.addServiceListener(filter, jmdnsListener); } } } catch (IOException e) { e.printStackTrace(); } } @Override public void addListener(DiscoveryProviderListener listener) { serviceListeners.add(listener); } @Override public void removeListener(DiscoveryProviderListener listener) { serviceListeners.remove(listener); } @Override public void addDeviceFilter(DiscoveryFilter filter) { if (filter.getServiceFilter() == null) { Log.e(Util.T, "This device filter does not have zeroconf filter info"); } else { serviceFilters.add(filter); } } @Override public void removeDeviceFilter(DiscoveryFilter filter) { serviceFilters.remove(filter); } @Override public void setFilters(List<DiscoveryFilter> filters) { serviceFilters = filters; } @Override public boolean isEmpty() { return serviceFilters.size() == 0; } public String serviceIdForFilter(String filter) { String serviceId = ""; for (DiscoveryFilter serviceFilter : serviceFilters) { String ssdpFilter = serviceFilter.getServiceFilter(); if (ssdpFilter.equals(filter)) { return serviceFilter.getServiceId(); } } return serviceId; } }