Input:
import org.fourthline.cling.UpnpService;
import org.fourthline.cling.UpnpServiceConfiguration;
import org.fourthline.cling.model.DiscoveryOptions;
import org.fourthline.cling.model.ExpirationDetails;
import org.fourthline.cling.model.ServiceReference;
import org.fourthline.cling.model.gena.LocalGENASubscription;
import org.fourthline.cling.model.gena.RemoteGENASubscription;
import org.fourthline.cling.model.meta.Device;
import org.fourthline.cling.model.meta.LocalDevice;
import org.fourthline.cling.model.meta.RemoteDevice;
import org.fourthline.cling.model.meta.RemoteDeviceIdentity;
import org.fourthline.cling.model.meta.Service;
import org.fourthline.cling.model.resource.Resource;
import org.fourthline.cling.model.types.DeviceType;
import org.fourthline.cling.model.types.ServiceType;
import org.fourthline.cling.model.types.UDN;
import org.fourthline.cling.protocol.ProtocolFactory;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;

Context:
clinglibrary/src/main/java/org/fourthline/cling/registry/RegistryImpl.java

    synchronized public void addListener(RegistryListener listener) {
        registryListeners.add(listener);
    }

    synchronized public void removeListener(RegistryListener listener) {
        registryListeners.remove(listener);
    }

    synchronized public Collection<RegistryListener> getListeners() {
        return Collections.unmodifiableCollection(registryListeners);
    }

    synchronized public boolean notifyDiscoveryStart(final RemoteDevice device) {
        // Exit if we have it already, this is atomic inside this method, finally
        if (getUpnpService().getRegistry().getRemoteDevice(device.getIdentity().getUdn(), true) != null) {
            log.finer("Not notifying listeners, already registered: " + device);
            return false;
        }
        for (final RegistryListener listener : getListeners()) {
            getConfiguration().getRegistryListenerExecutor().execute(
                    new Runnable() {
                        public void run() {
                            listener.remoteDeviceDiscoveryStarted(RegistryImpl.this, device);
                        }
                    }
            );
        }
        return true;
    }

    synchronized public void notifyDiscoveryFailure(final RemoteDevice device, final Exception ex) {
        for (final RegistryListener listener : getListeners()) {
            getConfiguration().getRegistryListenerExecutor().execute(
                    new Runnable() {
                        public void run() {
                            listener.remoteDeviceDiscoveryFailed(RegistryImpl.this, device, ex);
                        }
                    }
            );
        }
    }

    // #################################################################################################

    synchronized public void addDevice(LocalDevice localDevice) {
        localItems.add(localDevice);
    }

    synchronized public void addDevice(LocalDevice localDevice, DiscoveryOptions options) {
        localItems.add(localDevice, options);
    }

    synchronized public void setDiscoveryOptions(UDN udn, DiscoveryOptions options) {
        localItems.setDiscoveryOptions(udn, options);
    }

    synchronized public DiscoveryOptions getDiscoveryOptions(UDN udn) {
        return localItems.getDiscoveryOptions(udn);
    }

    synchronized public void addDevice(RemoteDevice remoteDevice) {
        remoteItems.add(remoteDevice);
    }

    synchronized public boolean update(RemoteDeviceIdentity rdIdentity) {
        return remoteItems.update(rdIdentity);
    }

    synchronized public boolean removeDevice(LocalDevice localDevice) {
        return localItems.remove(localDevice);
    }

    synchronized public boolean removeDevice(RemoteDevice remoteDevice) {
        return remoteItems.remove(remoteDevice);
    }

    synchronized public void removeAllLocalDevices() {
        localItems.removeAll();
    }

    synchronized public void removeAllRemoteDevices() {
        remoteItems.removeAll();
    }

    synchronized public boolean removeDevice(UDN udn) {
        Device device = getDevice(udn, true);
        if (device != null && device instanceof LocalDevice)
            return removeDevice((LocalDevice) device);
        if (device != null && device instanceof RemoteDevice)
            return removeDevice((RemoteDevice) device);
        return false;
    }

    synchronized public Device getDevice(UDN udn, boolean rootOnly) {
        Device device;
        if ((device = localItems.get(udn, rootOnly)) != null) return device;
        if ((device = remoteItems.get(udn, rootOnly)) != null) return device;
        return null;
    }

    synchronized public LocalDevice getLocalDevice(UDN udn, boolean rootOnly) {
        return localItems.get(udn, rootOnly);
    }

    synchronized public RemoteDevice getRemoteDevice(UDN udn, boolean rootOnly) {
        return remoteItems.get(udn, rootOnly);
    }

    synchronized public Collection<LocalDevice> getLocalDevices() {
        return Collections.unmodifiableCollection(localItems.get());
    }

    synchronized public Collection<RemoteDevice> getRemoteDevices() {
        return Collections.unmodifiableCollection(remoteItems.get());
    }

    synchronized public Collection<Device> getDevices() {
        Set all = new HashSet<>();
        all.addAll(localItems.get());

clinglibrary/src/main/java/org/fourthline/cling/model/ServiceReference.java
public class ServiceReference {

    public static final String DELIMITER = "/";

    final private UDN udn;
    final private ServiceId serviceId;

    public ServiceReference(String s) {
        String[] split = s.split("/");
        if (split.length == 2) {
            this.udn =  UDN.valueOf(split[0]);
            this.serviceId = ServiceId.valueOf(split[1]);
        } else {
            this.udn = null;
            this.serviceId = null;
        }
    }

    public ServiceReference(UDN udn, ServiceId serviceId) {
        this.udn = udn;
        this.serviceId = serviceId;
    }

    public UDN getUdn() {
        return udn;
    }

    public ServiceId getServiceId() {
        return serviceId;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        ServiceReference that = (ServiceReference) o;

        if (!serviceId.equals(that.serviceId)) return false;
        if (!udn.equals(that.udn)) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = udn.hashCode();
        result = 31 * result + serviceId.hashCode();
        return result;
    }

    @Override
    public String toString() {
        return udn == null || serviceId == null ? "" : udn.toString() + DELIMITER + serviceId.toString();
    }

}

clinglibrary/src/main/java/org/fourthline/cling/model/meta/RemoteDevice.java
public class RemoteDevice extends Device<RemoteDeviceIdentity, RemoteDevice, RemoteService> {

    public RemoteDevice(RemoteDeviceIdentity identity) throws ValidationException {
        super(identity);
    }

    public RemoteDevice(RemoteDeviceIdentity identity, DeviceType type, DeviceDetails details,
                       RemoteService service) throws ValidationException {
        super(identity, type, details, null, new RemoteService[]{service});
    }

    public RemoteDevice(RemoteDeviceIdentity identity, DeviceType type, DeviceDetails details,
                       RemoteService service, RemoteDevice embeddedDevice) throws ValidationException {
        super(identity, type, details, null, new RemoteService[]{service}, new RemoteDevice[]{embeddedDevice});
    }

    public RemoteDevice(RemoteDeviceIdentity identity, DeviceType type, DeviceDetails details,
                       RemoteService[] services) throws ValidationException {
        super(identity, type, details, null, services);
    }

    public RemoteDevice(RemoteDeviceIdentity identity, DeviceType type, DeviceDetails details,
                       RemoteService[] services, RemoteDevice[] embeddedDevices) throws ValidationException {
        super(identity, type, details, null, services, embeddedDevices);
    }

    public RemoteDevice(RemoteDeviceIdentity identity, DeviceType type, DeviceDetails details,
                       Icon icon, RemoteService service) throws ValidationException {
        super(identity, type, details, new Icon[]{icon}, new RemoteService[]{service});
    }

    public RemoteDevice(RemoteDeviceIdentity identity, DeviceType type, DeviceDetails details,
                       Icon icon, RemoteService service, RemoteDevice embeddedDevice) throws ValidationException {
        super(identity, type, details, new Icon[]{icon}, new RemoteService[]{service}, new RemoteDevice[]{embeddedDevice});
    }

    public RemoteDevice(RemoteDeviceIdentity identity, DeviceType type, DeviceDetails details,
                       Icon icon, RemoteService[] services) throws ValidationException {
        super(identity, type, details, new Icon[]{icon}, services);
    }

    public RemoteDevice(RemoteDeviceIdentity identity, DeviceType type, DeviceDetails details,
                       Icon icon, RemoteService[] services, RemoteDevice[] embeddedDevices) throws ValidationException {
        super(identity, type, details, new Icon[]{icon}, services, embeddedDevices);
    }

    public RemoteDevice(RemoteDeviceIdentity identity, DeviceType type, DeviceDetails details,
                       Icon[] icons, RemoteService service) throws ValidationException {
        super(identity, type, details, icons, new RemoteService[]{service});
    }

    public RemoteDevice(RemoteDeviceIdentity identity, DeviceType type, DeviceDetails details,
                       Icon[] icons, RemoteService service, RemoteDevice embeddedDevice) throws ValidationException {
        super(identity, type, details, icons, new RemoteService[]{service}, new RemoteDevice[]{embeddedDevice});
    }

    public RemoteDevice(RemoteDeviceIdentity identity, DeviceType type, DeviceDetails details,
                       Icon[] icons, RemoteService[] services) throws ValidationException {
        super(identity, type, details, icons, services);
    }

    public RemoteDevice(RemoteDeviceIdentity identity, DeviceType type, DeviceDetails details,
                       Icon[] icons, RemoteService[] services, RemoteDevice[] embeddedDevices) throws ValidationException {
        super(identity, type, details, icons, services, embeddedDevices);
    }

    public RemoteDevice(RemoteDeviceIdentity identity, UDAVersion version, DeviceType type, DeviceDetails details,
                       Icon[] icons, RemoteService[] services, RemoteDevice[] embeddedDevices) throws ValidationException {
        super(identity, version, type, details, icons, services, embeddedDevices);
    }

    @Override
    public RemoteService[] getServices() {
        return this.services != null ? this.services : new RemoteService[0];
    }

    @Override
    public RemoteDevice[] getEmbeddedDevices() {
        return this.embeddedDevices != null ? this.embeddedDevices : new RemoteDevice[0];
    }

    public URL normalizeURI(URI relativeOrAbsoluteURI) {

        // TODO: I have one device (Netgear 834DG DSL Router) that sends a <URLBase>, and even that is wrong (port)!
        // This can be fixed by "re-enabling" UPnP in the upnpService after a reboot, it will then use the right port...
        // return URIUtil.createAbsoluteURL(getDescriptorURL(), relativeOrAbsoluteURI);

        if (getDetails() != null && getDetails().getBaseURL() != null) {
            // If we have an <URLBase>, all URIs are relative to it
            return URIUtil.createAbsoluteURL(getDetails().getBaseURL(), relativeOrAbsoluteURI);
        } else {
            // Otherwise, they are relative to the descriptor location
            return URIUtil.createAbsoluteURL(getIdentity().getDescriptorURL(), relativeOrAbsoluteURI);
        }

    }

    @Override
    public RemoteDevice newInstance(UDN udn, UDAVersion version, DeviceType type, DeviceDetails details,
                                    Icon[] icons, RemoteService[] services,
                                    List<RemoteDevice> embeddedDevices) throws ValidationException {
        return new RemoteDevice(
                new RemoteDeviceIdentity(udn, getIdentity()),
                version, type, details, icons,
                services,
                embeddedDevices.size() > 0 ? embeddedDevices.toArray(new RemoteDevice[embeddedDevices.size()]) : null
        );
    }

    @Override
    public RemoteService newInstance(ServiceType serviceType, ServiceId serviceId,
                                     URI descriptorURI, URI controlURI, URI eventSubscriptionURI,
                                     Action<RemoteService>[] actions, StateVariable<RemoteService>[] stateVariables) throws ValidationException {
        return new RemoteService(
                serviceType, serviceId,
                descriptorURI, controlURI, eventSubscriptionURI,
                actions, stateVariables
        );
    }

    @Override
    public RemoteDevice[] toDeviceArray(Collection<RemoteDevice> col) {
        return col.toArray(new RemoteDevice[col.size()]);
    }

    @Override
    public RemoteService[] newServiceArray(int size) {
        return new RemoteService[size];
    }

    @Override
    public RemoteService[] toServiceArray(Collection<RemoteService> col) {
        return col.toArray(new RemoteService[col.size()]);
    }

    @Override
    public Resource[] discoverResources(Namespace namespace) {
        List<Resource> discovered = new ArrayList<>();

        // Services
        for (RemoteService service : getServices()) {
        	if(service == null) continue;
            discovered.add(new ServiceEventCallbackResource(namespace.getEventCallbackPath(service), service));
        }

        // Embedded devices
        if (hasEmbeddedDevices()) {
            for (Device embeddedDevice : getEmbeddedDevices()) {
				if(embeddedDevice == null) continue;
                discovered.addAll(Arrays.asList(embeddedDevice.discoverResources(namespace)));
            }
        }

        return discovered.toArray(new Resource[discovered.size()]);
    }

    @Override
    public RemoteDevice getRoot() {
        if (isRoot()) return this;
        RemoteDevice current = this;
        while (current.getParentDevice() != null) {
            current = current.getParentDevice();
        }
        return current;
    }

    @Override
    public RemoteDevice findDevice(UDN udn) {
        return find(udn, this);
    }

}

clinglibrary/src/main/java/org/fourthline/cling/protocol/ProtocolFactory.java
public interface ProtocolFactory {

    public UpnpService getUpnpService();

    /**
     * Creates a {@link org.fourthline.cling.protocol.async.ReceivingNotification},
     * {@link org.fourthline.cling.protocol.async.ReceivingSearch},
     * or {@link org.fourthline.cling.protocol.async.ReceivingSearchResponse} protocol.
     *
     * @param message The incoming message, either {@link org.fourthline.cling.model.message.UpnpRequest} or
     *                {@link org.fourthline.cling.model.message.UpnpResponse}.
     * @return        The appropriate protocol that handles the messages or null if the message should be dropped.
     * @throws ProtocolCreationException If no protocol could be found for the message.
     */
    public ReceivingAsync createReceivingAsync(IncomingDatagramMessage message) throws ProtocolCreationException;

    /**
     * Creates a {@link org.fourthline.cling.protocol.sync.ReceivingRetrieval},
     * {@link org.fourthline.cling.protocol.sync.ReceivingAction},
     * {@link org.fourthline.cling.protocol.sync.ReceivingSubscribe},
     * {@link org.fourthline.cling.protocol.sync.ReceivingUnsubscribe}, or
     * {@link org.fourthline.cling.protocol.sync.ReceivingEvent} protocol.
     *
     * @param requestMessage The incoming message, examime {@link org.fourthline.cling.model.message.UpnpRequest.Method}
     *                       to determine the protocol.
     * @return        The appropriate protocol that handles the messages.
     * @throws ProtocolCreationException If no protocol could be found for the message.
     */
    public ReceivingSync createReceivingSync(StreamRequestMessage requestMessage) throws ProtocolCreationException;

    /**
     * Called by the {@link org.fourthline.cling.registry.Registry}, creates a protocol for announcing local devices.
     */
    public SendingNotificationAlive createSendingNotificationAlive(LocalDevice localDevice);

    /**
     * Called by the {@link org.fourthline.cling.registry.Registry}, creates a protocol for announcing local devices.
     */
    public SendingNotificationByebye createSendingNotificationByebye(LocalDevice localDevice);

    /**
     * Called by the {@link org.fourthline.cling.controlpoint.ControlPoint}, creates a protocol for a multicast search.
     */
    public SendingSearch createSendingSearch(UpnpHeader searchTarget, int mxSeconds);

    /**
     * Called by the {@link org.fourthline.cling.controlpoint.ControlPoint}, creates a protocol for executing an action.
     */
    public SendingAction createSendingAction(ActionInvocation actionInvocation, URL controlURL);

    /**
     * Called by the {@link org.fourthline.cling.controlpoint.ControlPoint}, creates a protocol for GENA subscription.
     */
    public SendingSubscribe createSendingSubscribe(RemoteGENASubscription subscription) throws ProtocolCreationException;

    /**
     * Called by the {@link org.fourthline.cling.controlpoint.ControlPoint}, creates a protocol for GENA renewal.
     */
    public SendingRenewal createSendingRenewal(RemoteGENASubscription subscription);

    /**
     * Called by the {@link org.fourthline.cling.controlpoint.ControlPoint}, creates a protocol for GENA unsubscription.
     */
    public SendingUnsubscribe createSendingUnsubscribe(RemoteGENASubscription subscription);

    /**
     * Called by the {@link org.fourthline.cling.model.gena.GENASubscription}, creates a protocol for sending GENA events.
     */
    public SendingEvent createSendingEvent(LocalGENASubscription subscription);
}

clinglibrary/src/main/java/org/fourthline/cling/model/types/ServiceType.java
public class ServiceType {

    final private static Logger log = Logger.getLogger(ServiceType.class.getName());

    public static final Pattern PATTERN =
        Pattern.compile("urn:(" + Constants.REGEX_NAMESPACE + "):service:(" + Constants.REGEX_TYPE + "):([0-9]+).*");

    // Note: 'serviceId' vs. 'service'
    public static final Pattern BROKEN_PATTERN =
        Pattern.compile("urn:(" + Constants.REGEX_NAMESPACE + "):serviceId:(" + Constants.REGEX_TYPE + "):([0-9]+).*");

    private String namespace;
    private String type;
    private int version = 1;

    public ServiceType(String namespace, String type) {
        this(namespace, type, 1);
    }

    public ServiceType(String namespace, String type, int version) {

        if (namespace != null && !namespace.matches(Constants.REGEX_NAMESPACE)) {
            throw new IllegalArgumentException("Service type namespace contains illegal characters");
        }
        this.namespace = namespace;

        if (type != null && !type.matches(Constants.REGEX_TYPE)) {
            throw new IllegalArgumentException("Service type suffix too long (64) or contains illegal characters");
        }
        this.type = type;

        this.version = version;
    }

    public String getNamespace() {
        return namespace;
    }

    public String getType() {
        return type;
    }

    public int getVersion() {
        return version;
    }

    /**
     * @return Either a {@link UDAServiceType} or a more generic {@link ServiceType}.
     */
    public static ServiceType valueOf(String s) throws InvalidValueException {

        if (s == null)
            throw new InvalidValueException("Can't parse null string");

        ServiceType serviceType = null;

        // Sometimes crazy UPnP devices deliver spaces in a URN, don't ask...
        s = s.replaceAll("\\s", "");

        // First try UDAServiceType parse
        try {
            serviceType = UDAServiceType.valueOf(s);
        } catch (Exception ex) {
            // Ignore
        }

        if (serviceType != null)
            return serviceType;

        // Now try a generic ServiceType parse
        try {
            Matcher matcher = ServiceType.PATTERN.matcher(s);
            if (matcher.matches() && matcher.groupCount() >= 3) {
                return new ServiceType(matcher.group(1), matcher.group(2), Integer.valueOf(matcher.group(3)));
            }

            matcher = ServiceType.BROKEN_PATTERN.matcher(s);
            if (matcher.matches() && matcher.groupCount() >= 3) {
                return new ServiceType(matcher.group(1), matcher.group(2), Integer.valueOf(matcher.group(3)));
            }

            // TODO: UPNP VIOLATION: EyeTV Netstream uses colons in service type token
            // urn:schemas-microsoft-com:service:pbda:tuner:1
            matcher = Pattern.compile("urn:(" + Constants.REGEX_NAMESPACE + "):service:(.+?):([0-9]+).*").matcher(s);
            if (matcher.matches() && matcher.groupCount() >= 3) {
                String cleanToken = matcher.group(2).replaceAll("[^a-zA-Z_0-9\\-]", "-");
                log.warning(
                    "UPnP specification violation, replacing invalid service type token '"
                        + matcher.group(2)
                        + "' with: "
                        + cleanToken
                );
                return new ServiceType(matcher.group(1), cleanToken, Integer.valueOf(matcher.group(3)));
            }

            // TODO: UPNP VIOLATION: Ceyton InfiniTV uses colons in service type token and 'serviceId' instead of 'service'
            // urn:schemas-opencable-com:serviceId:dri2:debug:1
            matcher = Pattern.compile("urn:(" + Constants.REGEX_NAMESPACE + "):serviceId:(.+?):([0-9]+).*").matcher(s);
            if (matcher.matches() && matcher.groupCount() >= 3) {
                String cleanToken = matcher.group(2).replaceAll("[^a-zA-Z_0-9\\-]", "-");
                log.warning(
                    "UPnP specification violation, replacing invalid service type token '"
                    + matcher.group(2)
                    + "' with: "
                    + cleanToken
                );
                return new ServiceType(matcher.group(1), cleanToken, Integer.valueOf(matcher.group(3)));
            }
        } catch (RuntimeException e) {
            throw new InvalidValueException(String.format(
                "Can't parse service type string (namespace/type/version) '%s': %s", s, e.toString()
            ));
        }

        throw new InvalidValueException("Can't parse service type string (namespace/type/version): " + s);
    }

    /**
     * @return true if this type's namespace/name matches the other type's namespace/name and
     *         this type's version is equal or higher than the given types version.
     */
    public boolean implementsVersion(ServiceType that) {
        if (that == null) return false;
        if (!namespace.equals(that.namespace)) return false;
        if (!type.equals(that.type)) return false;
        if (version < that.version) return false;
        return true;
    }

    public String toFriendlyString() {
        return getNamespace() + ":" + getType() + ":" + getVersion();
    }

    @Override
    public String toString() {
        return "urn:" + getNamespace() + ":service:" + getType() + ":" + getVersion();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || !(o instanceof ServiceType)) return false;

        ServiceType that = (ServiceType) o;

        if (version != that.version) return false;
        if (!namespace.equals(that.namespace)) return false;
        if (!type.equals(that.type)) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = namespace.hashCode();
        result = 31 * result + type.hashCode();
        result = 31 * result + version;
        return result;
    }
}

clinglibrary/src/main/java/org/fourthline/cling/model/types/UDN.java
public class UDN {

    final private static Logger log = Logger.getLogger(UDN.class.getName());

    public static final String PREFIX = "uuid:";

    private String identifierString;

    /**
     * @param identifierString The identifier string without the "uuid:" prefix.
     */
    public UDN(String identifierString) {
        this.identifierString = identifierString;
    }

    public UDN(UUID uuid) {
        this.identifierString = uuid.toString();
    }

    public boolean isUDA11Compliant() {
        try {
            UUID.fromString(identifierString);
            return true;
        } catch (IllegalArgumentException ex) {
            return false;
        }
    }

    public String getIdentifierString() {
        return identifierString;
    }

    public static UDN valueOf(String udnString) {
        return new UDN(udnString.startsWith(PREFIX) ? udnString.substring(PREFIX.length()) : udnString);
    }

    /**
     * Generates a global unique identifier that is the same every time this method is invoked on the same machine with
     * the same argument.
     *

     * This method combines the first non-loopback network interface's MAC address with given salt to generate a
     * globally unique identifier. In other words, every time you call this method with the same salt on the same
     * machine, you get the same identifier. If you use the same salt on a different machine, a different identifier
     * will be generated.
     *

     *

     * Note for Android users: This method does not generate unique identifiers on Android devices and will
     * throw an exception. We can't get details such as the hostname or MAC address on Android. Instead,
     * construct a UDN with new UDN(UUID). When your application is first started, generate all
     * UUIDs needed for your UPnP devices and store them in your Android preferences. Then, use the stored
     * UUID to create a UDN every time your application starts.
     *

     *

     * Control points can remember your device's identifier, it will and should be the same every time
     * your device is powered up.
     *

     *
     * @param salt An arbitrary string that uniquely identifies the device on the current system, e.g. "MyMediaServer".
     * @return A global unique identifier, stable for the current system and salt.
     */
    public static UDN uniqueSystemIdentifier(String salt) {
        StringBuilder systemSalt = new StringBuilder();

        // Bug: On Android, NetworkInterface.isLoopback() isn't implemented
        if (!ModelUtil.ANDROID_RUNTIME) {
            try {
                systemSalt.append(new String(ModelUtil.getFirstNetworkInterfaceHardwareAddress(), "UTF-8"));
            } catch (UnsupportedEncodingException ex) {
                // If your JVM doesn't support utf-8, you have bigger problems
                throw new RuntimeException(ex);
            }
        } else {
            throw new RuntimeException(
                "This method does not create a unique identifier on Android, see the Javadoc and " +
                    "use new UDN(UUID) instead!"
            );
        }

        try {
            byte[] hash = MessageDigest.getInstance("MD5").digest(systemSalt.toString().getBytes("UTF-8"));
            return new UDN(
                    new UUID(
                            new BigInteger(-1, hash).longValue(),
                            salt.hashCode()
                    )
            );
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    @Override
    public String toString() {
        return PREFIX + getIdentifierString();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || !(o instanceof UDN)) return false;
        UDN udn = (UDN) o;
        return identifierString.equals(udn.identifierString);
    }

    @Override
    public int hashCode() {
        return identifierString.hashCode();
    }

}

clinglibrary/src/main/java/org/fourthline/cling/model/meta/LocalDevice.java
public class LocalDevice extends Device<DeviceIdentity, LocalDevice, LocalService> {

    final private DeviceDetailsProvider deviceDetailsProvider;

    public LocalDevice(DeviceIdentity identity) throws ValidationException {
        super(identity);
        this.deviceDetailsProvider = null;
    }

    public LocalDevice(DeviceIdentity identity, DeviceType type, DeviceDetails details,
                       LocalService service) throws ValidationException {
        super(identity, type, details, null, new LocalService[]{service});
        this.deviceDetailsProvider = null;
    }

    public LocalDevice(DeviceIdentity identity, DeviceType type, DeviceDetailsProvider deviceDetailsProvider,
                       LocalService service) throws ValidationException {
        super(identity, type, null, null, new LocalService[]{service});
        this.deviceDetailsProvider = deviceDetailsProvider;
    }

    public LocalDevice(DeviceIdentity identity, DeviceType type, DeviceDetailsProvider deviceDetailsProvider,
                       LocalService service, LocalDevice embeddedDevice) throws ValidationException {
        super(identity, type, null, null, new LocalService[]{service}, new LocalDevice[]{embeddedDevice});
        this.deviceDetailsProvider = deviceDetailsProvider;
    }

    public LocalDevice(DeviceIdentity identity, DeviceType type, DeviceDetails details,
                       LocalService service, LocalDevice embeddedDevice) throws ValidationException {
        super(identity, type, details, null, new LocalService[]{service}, new LocalDevice[]{embeddedDevice});
        this.deviceDetailsProvider = null;
    }

    public LocalDevice(DeviceIdentity identity, DeviceType type, DeviceDetails details,
                       LocalService[] services) throws ValidationException {
        super(identity, type, details, null, services);
        this.deviceDetailsProvider = null;
    }

    public LocalDevice(DeviceIdentity identity, DeviceType type, DeviceDetails details,
                       LocalService[] services, LocalDevice[] embeddedDevices) throws ValidationException {
        super(identity, type, details, null, services, embeddedDevices);
        this.deviceDetailsProvider = null;
    }

    public LocalDevice(DeviceIdentity identity, DeviceType type, DeviceDetails details,
                       Icon icon, LocalService service) throws ValidationException {
        super(identity, type, details, new Icon[]{icon}, new LocalService[]{service});
        this.deviceDetailsProvider = null;
    }

    public LocalDevice(DeviceIdentity identity, DeviceType type, DeviceDetails details,
                       Icon icon, LocalService service, LocalDevice embeddedDevice) throws ValidationException {
        super(identity, type, details, new Icon[]{icon}, new LocalService[]{service}, new LocalDevice[]{embeddedDevice});
        this.deviceDetailsProvider = null;
    }

    public LocalDevice(DeviceIdentity identity, DeviceType type, DeviceDetails details,
                       Icon icon, LocalService[] services) throws ValidationException {
        super(identity, type, details, new Icon[]{icon}, services);
        this.deviceDetailsProvider = null;
    }

    public LocalDevice(DeviceIdentity identity, DeviceType type, DeviceDetailsProvider deviceDetailsProvider,
                       Icon icon, LocalService[] services) throws ValidationException {
        super(identity, type, null, new Icon[]{icon}, services);
        this.deviceDetailsProvider = deviceDetailsProvider;
    }

    public LocalDevice(DeviceIdentity identity, DeviceType type, DeviceDetails details,
                       Icon icon, LocalService[] services, LocalDevice[] embeddedDevices) throws ValidationException {
        super(identity, type, details, new Icon[]{icon}, services, embeddedDevices);
        this.deviceDetailsProvider = null;
    }

    public LocalDevice(DeviceIdentity identity, DeviceType type, DeviceDetails details,
                       Icon[] icons, LocalService service) throws ValidationException {
        super(identity, type, details, icons, new LocalService[]{service});
        this.deviceDetailsProvider = null;
    }

    public LocalDevice(DeviceIdentity identity, DeviceType type, DeviceDetails details,
                       Icon[] icons, LocalService service, LocalDevice embeddedDevice) throws ValidationException {
        super(identity, type, details, icons, new LocalService[]{service}, new LocalDevice[]{embeddedDevice});
        this.deviceDetailsProvider = null;
    }

    public LocalDevice(DeviceIdentity identity, DeviceType type, DeviceDetailsProvider deviceDetailsProvider,
                       Icon[] icons, LocalService service, LocalDevice embeddedDevice) throws ValidationException {
        super(identity, type, null, icons, new LocalService[]{service}, new LocalDevice[]{embeddedDevice});
        this.deviceDetailsProvider = deviceDetailsProvider;
    }

    public LocalDevice(DeviceIdentity identity, DeviceType type, DeviceDetails details,
                       Icon[] icons, LocalService[] services) throws ValidationException {
        super(identity, type, details, icons, services);
        this.deviceDetailsProvider = null;
    }

    public LocalDevice(DeviceIdentity identity, DeviceType type, DeviceDetails details,
                       Icon[] icons, LocalService[] services, LocalDevice[] embeddedDevices) throws ValidationException {
        super(identity, type, details, icons, services, embeddedDevices);
        this.deviceDetailsProvider = null;
    }

    public LocalDevice(DeviceIdentity identity, UDAVersion version, DeviceType type, DeviceDetails details,
                       Icon[] icons, LocalService[] services, LocalDevice[] embeddedDevices) throws ValidationException {
        super(identity, version, type, details, icons, services, embeddedDevices);
        this.deviceDetailsProvider = null;
    }

    public LocalDevice(DeviceIdentity identity, UDAVersion version, DeviceType type, DeviceDetailsProvider deviceDetailsProvider,
                       Icon[] icons, LocalService[] services, LocalDevice[] embeddedDevices) throws ValidationException {
        super(identity, version, type, null, icons, services, embeddedDevices);
        this.deviceDetailsProvider = deviceDetailsProvider;
    }

    public DeviceDetailsProvider getDeviceDetailsProvider() {
        return deviceDetailsProvider;
    }

    @Override
    public DeviceDetails getDetails(RemoteClientInfo info) {
        if (getDeviceDetailsProvider() != null) {
            return getDeviceDetailsProvider().provide(info);
        }
        return this.getDetails();
    }

    @Override
    public LocalService[] getServices() {
        return this.services != null ? this.services : new LocalService[0];
    }

    @Override
    public LocalDevice[] getEmbeddedDevices() {
        return this.embeddedDevices != null ? this.embeddedDevices : new LocalDevice[0];
    }

    @Override
    public LocalDevice newInstance(UDN udn, UDAVersion version, DeviceType type, DeviceDetails details,
                                   Icon[] icons, LocalService[] services, List<LocalDevice> embeddedDevices)
            throws ValidationException {
        return new LocalDevice(
                new DeviceIdentity(udn, getIdentity().getMaxAgeSeconds()),
                version, type, details, icons,
                services,
                embeddedDevices.size() > 0 ? embeddedDevices.toArray(new LocalDevice[embeddedDevices.size()]) : null
        );
    }

    @Override
    public LocalService newInstance(ServiceType serviceType, ServiceId serviceId,
                                    URI descriptorURI, URI controlURI, URI eventSubscriptionURI,
                                    Action<LocalService>[] actions, StateVariable<LocalService>[] stateVariables) throws ValidationException {
        return new LocalService(
                serviceType, serviceId,
                actions, stateVariables
        );
    }

    @Override
    public LocalDevice[] toDeviceArray(Collection<LocalDevice> col) {
        return col.toArray(new LocalDevice[col.size()]);
    }

    @Override
    public LocalService[] newServiceArray(int size) {
        return new LocalService[size];
    }

    @Override
    public LocalService[] toServiceArray(Collection<LocalService> col) {
        return col.toArray(new LocalService[col.size()]);
    }

    @Override
    public List<ValidationError> validate() {
        List<ValidationError> errors = new ArrayList<>();
        errors.addAll(super.validate());

        // We have special rules for local icons, the URI must always be a relative path which will
        // be added to the device base URI!
        if (hasIcons()) {
            for (Icon icon : getIcons()) {
                if (icon.getUri().isAbsolute()) {
                    errors.add(new ValidationError(
                            getClass(),
                            "icons",
                            "Local icon URI can not be absolute: " + icon.getUri()
                    ));
                }
                if (icon.getUri().toString().contains("../")) {
                    errors.add(new ValidationError(
                            getClass(),
                            "icons",
                            "Local icon URI must not contain '../': " + icon.getUri()
                    ));
                }
                if (icon.getUri().toString().startsWith("/")) {
                    errors.add(new ValidationError(
                            getClass(),
                            "icons",
                            "Local icon URI must not start with '/': " + icon.getUri()
                    ));
                }
            }
        }

        return errors;
    }

    @Override
    public Resource[] discoverResources(Namespace namespace) {
        List<Resource> discovered = new ArrayList<>();

        // Device
        if (isRoot()) {
            // This should guarantee that each logical local device tree (with all its embedded devices) has only
            // one device descriptor resource - because only one device in the tree isRoot().
            discovered.add(new DeviceDescriptorResource(namespace.getDescriptorPath(this), this));
        }

        // Services
        for (LocalService service : getServices()) {

            discovered.add(
                    new ServiceDescriptorResource(namespace.getDescriptorPath(service), service)
            );

            // Control
            discovered.add(
                    new ServiceControlResource(namespace.getControlPath(service), service)
            );

            // Event subscription
            discovered.add(
                    new ServiceEventSubscriptionResource(namespace.getEventSubscriptionPath(service), service)
            );

        }

        // Icons
        for (Icon icon : getIcons()) {
            discovered.add(new IconResource(namespace.prefixIfRelative(this, icon.getUri()), icon));
        }

        // Embedded devices
        if (hasEmbeddedDevices()) {
            for (Device embeddedDevice : getEmbeddedDevices()) {
                discovered.addAll(Arrays.asList(embeddedDevice.discoverResources(namespace)));
            }
        }

        return discovered.toArray(new Resource[discovered.size()]);
    }

    @Override
    public LocalDevice getRoot() {
        if (isRoot()) return this;
        LocalDevice current = this;
        while (current.getParentDevice() != null) {
            current = current.getParentDevice();
        }
        return current;
    }

    @Override
    public LocalDevice findDevice(UDN udn) {
        return find(udn, this);
    }

}

clinglibrary/src/main/java/org/fourthline/cling/model/meta/RemoteDeviceIdentity.java
public class RemoteDeviceIdentity extends DeviceIdentity {

    final private URL descriptorURL;
    final private byte[] interfaceMacAddress;
    final private InetAddress discoveredOnLocalAddress;

    public RemoteDeviceIdentity(UDN udn, RemoteDeviceIdentity template) {
        this(udn, template.getMaxAgeSeconds(), template.getDescriptorURL(), template.getInterfaceMacAddress(), template.getDiscoveredOnLocalAddress());
    }

    public RemoteDeviceIdentity(UDN udn, Integer maxAgeSeconds, URL descriptorURL, byte[] interfaceMacAddress, InetAddress discoveredOnLocalAddress) {
        super(udn, maxAgeSeconds);
        this.descriptorURL = descriptorURL;
        this.interfaceMacAddress = interfaceMacAddress;
        this.discoveredOnLocalAddress = discoveredOnLocalAddress;
    }

    public RemoteDeviceIdentity(IncomingNotificationRequest notificationRequest) {
        this(notificationRequest.getUDN(),
             notificationRequest.getMaxAge(),
             notificationRequest.getLocationURL(),
             notificationRequest.getInterfaceMacHeader(),
             notificationRequest.getLocalAddress()
        );
    }

    public RemoteDeviceIdentity(IncomingSearchResponse searchResponse) {
        this(searchResponse.getRootDeviceUDN(),
             searchResponse.getMaxAge(),
             searchResponse.getLocationURL(),
             searchResponse.getInterfaceMacHeader(),
             searchResponse.getLocalAddress()
        );
    }

    public URL getDescriptorURL() {
        return descriptorURL;
    }

    public byte[] getInterfaceMacAddress() {
        return interfaceMacAddress;
    }

    public InetAddress getDiscoveredOnLocalAddress() {
        return discoveredOnLocalAddress;
    }

    public byte[] getWakeOnLANBytes() {
        if (getInterfaceMacAddress() == null) return null;
        byte[] bytes = new byte[6 + 16 * getInterfaceMacAddress().length];
        for (int i = 0; i < 6; i++) {
            bytes[i] = (byte) 0xff;
        }
        for (int i = 6; i < bytes.length; i += getInterfaceMacAddress().length) {
            System.arraycopy(getInterfaceMacAddress(), 0, bytes, i, getInterfaceMacAddress().length);
        }
        return bytes;
    }

    @Override
    public String toString() {
        // Performance optimization, so we don't have to wrap all log("foo " + device) calls with isLoggable
		if(ModelUtil.ANDROID_RUNTIME) {
            return "(RemoteDeviceIdentity) UDN: " + getUdn() + ", Descriptor: " + getDescriptorURL();
        }
        return "(" + getClass().getSimpleName() + ") UDN: " + getUdn() + ", Descriptor: " + getDescriptorURL();
    }
}

clinglibrary/src/main/java/org/fourthline/cling/model/types/DeviceType.java
public class DeviceType {

    final private static Logger log = Logger.getLogger(DeviceType.class.getName());

    public static final String UNKNOWN = "UNKNOWN";

    public static final Pattern PATTERN =
            Pattern.compile("urn:(" + Constants.REGEX_NAMESPACE + "):device:(" + Constants.REGEX_TYPE + "):([0-9]+).*");

    private String namespace;
    private String type;
    private int version = 1;

    public DeviceType(String namespace, String type) {
        this(namespace, type, 1);
    }

    public DeviceType(String namespace, String type, int version) {
        if (namespace != null && !namespace.matches(Constants.REGEX_NAMESPACE)) {
            throw new IllegalArgumentException("Device type namespace contains illegal characters");
        }
        this.namespace = namespace;

        if (type != null && !type.matches(Constants.REGEX_TYPE)) {
            throw new IllegalArgumentException("Device type suffix too long (64) or contains illegal characters");
        }
        this.type = type;

        this.version = version;
    }

    public String getNamespace() {
        return namespace;
    }

    public String getType() {
        return type;
    }

    public int getVersion() {
        return version;
    }

    /**
     * @return Either a {@link UDADeviceType} or a more generic {@link DeviceType}.
     */
    public static DeviceType valueOf(String s) throws InvalidValueException {

        DeviceType deviceType = null;

        // Sometimes crazy UPnP devices deliver spaces in a URN, don't ask...
        s = s.replaceAll("\\s", "");

        // First try UDADeviceType parse
        try {
            deviceType = UDADeviceType.valueOf(s);
        } catch (Exception ex) {
            // Ignore
        }

        if (deviceType != null)
            return deviceType;

        try {
            // Now try a generic DeviceType parse
            Matcher matcher = PATTERN.matcher(s);
            if (matcher.matches()) {
                return new DeviceType(matcher.group(1), matcher.group(2), Integer.valueOf(matcher.group(3)));
            }

            // TODO: UPNP VIOLATION: Escient doesn't provide any device type token
            // urn:schemas-upnp-org:device::1
            matcher = Pattern.compile("urn:(" + Constants.REGEX_NAMESPACE + "):device::([0-9]+).*").matcher(s);
            if (matcher.matches() && matcher.groupCount() >= 2) {
                log.warning("UPnP specification violation, no device type token, defaulting to " + UNKNOWN + ": " + s);
                return new DeviceType(matcher.group(1), UNKNOWN, Integer.valueOf(matcher.group(2)));
            }

            // TODO: UPNP VIOLATION: EyeTV Netstream uses colons in device type token
            // urn:schemas-microsoft-com:service:pbda:tuner:1
            matcher = Pattern.compile("urn:(" + Constants.REGEX_NAMESPACE + "):device:(.+?):([0-9]+).*").matcher(s);
            if (matcher.matches() && matcher.groupCount() >= 3) {
                String cleanToken = matcher.group(2).replaceAll("[^a-zA-Z_0-9\\-]", "-");
                log.warning(
                    "UPnP specification violation, replacing invalid device type token '"
                        + matcher.group(2)
                        + "' with: "
                        + cleanToken
                );
                return new DeviceType(matcher.group(1), cleanToken, Integer.valueOf(matcher.group(3)));
            }
        } catch (RuntimeException e) {
            throw new InvalidValueException(String.format(
                "Can't parse device type string (namespace/type/version) '%s': %s", s, e.toString()
            ));
        }

        throw new InvalidValueException("Can't parse device type string (namespace/type/version): " + s);
    }

    public boolean implementsVersion(DeviceType that) {
        if (!namespace.equals(that.namespace)) return false;
        if (!type.equals(that.type)) return false;
        if (version < that.version) return false;
        return true;
    }

    public String getDisplayString() {
        return getType();
    }

    @Override
    public String toString() {
        return "urn:" + getNamespace() + ":device:" + getType()+ ":" + getVersion();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || !(o instanceof DeviceType)) return false;

        DeviceType that = (DeviceType) o;

        if (version != that.version) return false;
        if (!namespace.equals(that.namespace)) return false;
        if (!type.equals(that.type)) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = namespace.hashCode();
        result = 31 * result + type.hashCode();
        result = 31 * result + version;
        return result;
    }
}

clinglibrary/src/main/java/org/fourthline/cling/model/meta/Service.java
public abstract class Service<D extends Device, S extends Service> {

	final private static Logger log = Logger.getLogger(Service.class.getName());

    final private ServiceType serviceType;
    final private ServiceId serviceId;

    final private Map<String, Action> actions = new HashMap<>();
    final private Map<String, StateVariable> stateVariables = new HashMap<>();

    // Package mutable state
    private D device;

    public Service(ServiceType serviceType, ServiceId serviceId) throws ValidationException {
        this(serviceType, serviceId, null, null);
    }

    public Service(ServiceType serviceType, ServiceId serviceId,
                   Action<S>[] actions, StateVariable<S>[] stateVariables) throws ValidationException {

        this.serviceType = serviceType;
        this.serviceId = serviceId;

        if (actions != null) {
            for (Action action : actions) {
                this.actions.put(action.getName(), action);
                action.setService(this);
            }
        }

        if (stateVariables != null) {
            for (StateVariable stateVariable : stateVariables) {
                this.stateVariables.put(stateVariable.getName(), stateVariable);
                stateVariable.setService(this);
            }
        }

    }

    public ServiceType getServiceType() {
        return serviceType;
    }

    public ServiceId getServiceId() {
        return serviceId;
    }

    public boolean hasActions() {
        return getActions() != null && getActions().length > 0;
    }

    public Action<S>[] getActions() {
        return actions == null ? null : actions.values().toArray(new Action[actions.values().size()]);
    }

    public boolean hasStateVariables() {
        // TODO: Spec says always has to have at least one...
        return getStateVariables() != null && getStateVariables().length > 0;
    }

    public StateVariable<S>[] getStateVariables() {
        return stateVariables == null ? null : stateVariables.values().toArray(new StateVariable[stateVariables.values().size()]);
    }

    public D getDevice() {
        return device;
    }

    void setDevice(D device) {
        if (this.device != null)
            throw new IllegalStateException("Final value has been set already, model is immutable");
        this.device = device;
    }

    public Action<S> getAction(String name) {
        return actions == null ? null : actions.get(name);
    }

    public StateVariable<S> getStateVariable(String name) {
        // Some magic necessary for the deprecated 'query state variable' action stuff
        if (QueryStateVariableAction.VIRTUAL_STATEVARIABLE_INPUT.equals(name)) {
            return new StateVariable(
                    QueryStateVariableAction.VIRTUAL_STATEVARIABLE_INPUT,
                    new StateVariableTypeDetails(Datatype.Builtin.STRING.getDatatype())
            );
        }
        if (QueryStateVariableAction.VIRTUAL_STATEVARIABLE_OUTPUT.equals(name)) {
            return new StateVariable(
                    QueryStateVariableAction.VIRTUAL_STATEVARIABLE_OUTPUT,
                    new StateVariableTypeDetails(Datatype.Builtin.STRING.getDatatype())
            );
        }
        return stateVariables == null ? null : stateVariables.get(name);
    }

    public StateVariable<S> getRelatedStateVariable(ActionArgument argument) {
        return getStateVariable(argument.getRelatedStateVariableName());
    }

    public Datatype<S> getDatatype(ActionArgument argument) {
        return getRelatedStateVariable(argument).getTypeDetails().getDatatype();
    }

    public ServiceReference getReference() {
        return new ServiceReference(getDevice().getIdentity().getUdn(), getServiceId());
    }

    public List<ValidationError> validate() {
        List<ValidationError> errors = new ArrayList<>();

        if (getServiceType() == null) {
            errors.add(new ValidationError(
                    getClass(),
                    "serviceType",
                    "Service type/info is required"
            ));
        }

        if (getServiceId() == null) {
            errors.add(new ValidationError(
                    getClass(),
                    "serviceId",
                    "Service ID is required"
            ));
        }

        // TODO: If the service has no evented variables, it should not have an event subscription URL, which means
        // the url element in the device descriptor must be present, but empty!!!!

        /* TODO: This doesn't fit into our meta model, we don't know if a service has state variables until
         we completely hydrate it from a service descriptor
        if (getStateVariables().length == 0) {
            errors.add(new ValidationError(
                    getClass(),
                    "stateVariables",
                    "Service must have at least one state variable"
            ));
        }
        */
        if (hasStateVariables()) {
            for (StateVariable stateVariable : getStateVariables()) {
                errors.addAll(stateVariable.validate());
            }
        }

        if (hasActions()) {
            for (Action action : getActions()) {

                // Instead of bailing out here, we try to continue if an action is invalid
                // errors.addAll(action.validate());

                List<ValidationError> actionErrors = action.validate();
            	if(actionErrors.size() > 0) {
                    actions.remove(action.getName()); // Remove it
                    log.warning("Discarding invalid action of service '" + getServiceId() + "': " + action.getName());
                    for (ValidationError actionError : actionErrors) {
                        log.warning("Invalid action '" + action.getName() + "': " + actionError);
                    }
            	}
            }
        }

        return errors;
    }

    public abstract Action getQueryStateVariableAction();

    @Override
    public String toString() {
        return "(" + getClass().getSimpleName() + ") ServiceId: " + getServiceId();
    }
}

clinglibrary/src/main/java/org/fourthline/cling/UpnpServiceConfiguration.java
public interface UpnpServiceConfiguration {

    /**
     * @return A new instance of the {@link org.fourthline.cling.transport.spi.NetworkAddressFactory} interface.
     */
    public NetworkAddressFactory createNetworkAddressFactory();

    /**
     * @return The shared implementation of {@link org.fourthline.cling.transport.spi.DatagramProcessor}.
     */
    public DatagramProcessor getDatagramProcessor();

    /**
     * @return The shared implementation of {@link org.fourthline.cling.transport.spi.SOAPActionProcessor}.
     */
    public SOAPActionProcessor getSoapActionProcessor();

    /**
     * @return The shared implementation of {@link org.fourthline.cling.transport.spi.GENAEventProcessor}.
     */
    public GENAEventProcessor getGenaEventProcessor();

    /**
     * @return A new instance of the {@link org.fourthline.cling.transport.spi.StreamClient} interface.
     */
    public StreamClient createStreamClient();

    /**
     * @param networkAddressFactory The configured {@link org.fourthline.cling.transport.spi.NetworkAddressFactory}.
     * @return A new instance of the {@link org.fourthline.cling.transport.spi.MulticastReceiver} interface.
     */
    public MulticastReceiver createMulticastReceiver(NetworkAddressFactory networkAddressFactory);

    /**
     * @param networkAddressFactory The configured {@link org.fourthline.cling.transport.spi.NetworkAddressFactory}.
     * @return A new instance of the {@link org.fourthline.cling.transport.spi.DatagramIO} interface.
     */
    public DatagramIO createDatagramIO(NetworkAddressFactory networkAddressFactory);

    /**
     * @param networkAddressFactory The configured {@link org.fourthline.cling.transport.spi.NetworkAddressFactory}.
     * @return A new instance of the {@link org.fourthline.cling.transport.spi.StreamServer} interface.
     */
    public StreamServer createStreamServer(NetworkAddressFactory networkAddressFactory);

    /**
     * @return The executor which runs the listening background threads for multicast datagrams.
     */
    public Executor getMulticastReceiverExecutor();

    /**
     * @return The executor which runs the listening background threads for unicast datagrams.
     */
    public Executor getDatagramIOExecutor();

    /**
     * @return The executor which runs the listening background threads for HTTP requests.
     */
    public ExecutorService getStreamServerExecutorService();

    /**
     * @return The shared implementation of {@link org.fourthline.cling.binding.xml.DeviceDescriptorBinder} for the UPnP 1.0 Device Architecture..
     */
    public DeviceDescriptorBinder getDeviceDescriptorBinderUDA10();

    /**
     * @return The shared implementation of {@link org.fourthline.cling.binding.xml.ServiceDescriptorBinder} for the UPnP 1.0 Device Architecture..
     */
    public ServiceDescriptorBinder getServiceDescriptorBinderUDA10();

    /**
     * Returns service types that can be handled by this UPnP stack, all others will be ignored.
     *

     * Return null to completely disable remote device and service discovery.
     * All incoming notifications and search responses will then be dropped immediately.
     * This is mostly useful in applications that only provide services with no (remote)
     * control point functionality.
     *

     *

     * Note that a discovered service type with version 2 or 3 will match an exclusive
     * service type with version 1. UPnP services are required to be backwards
     * compatible, version 2 is a superset of version 1, and version 3 is a superset
     * of version 2, etc.
     *

     *
     * @return An array of service types that are exclusively discovered, no other service will
     *         be discovered. A null return value will disable discovery!
     *         An empty array means all services will be discovered.
     */
    public ServiceType[] getExclusiveServiceTypes();

    /**
     * @return The time in milliseconds to wait between each registry maintenance operation.
     */
    public int getRegistryMaintenanceIntervalMillis();

    /**
     * Optional setting for flooding alive NOTIFY messages for local devices.
     *

     * Use this to advertise local devices at the specified interval, independent of its
     * {@link org.fourthline.cling.model.meta.DeviceIdentity#maxAgeSeconds} value. Note
     * that this will increase network traffic.
     *

     *

     * Some control points (XBMC and other Platinum UPnP SDK based devices, OPPO-93) seem
     * to not properly receive SSDP M-SEARCH replies sent by Cling, but will handle NOTIFY
     * alive messages just fine.
     *

     *
     * @return The time in milliseconds for ALIVE message intervals, set to 0 to disable
     */
    public int getAliveIntervalMillis();

    /**
     * Ignore the received event subscription timeout from remote control points.
     *

     * Some control points have trouble renewing subscriptions properly; enabling this option
     * in conjunction with a high value for
     * {@link org.fourthline.cling.model.UserConstants#DEFAULT_SUBSCRIPTION_DURATION_SECONDS}
     * ensures that your devices will not disappear on such control points.
     *

     *
     * @return true if the timeout in incoming event subscriptions should be ignored
     *         and the default value ({@link org.fourthline.cling.model.UserConstants#DEFAULT_SUBSCRIPTION_DURATION_SECONDS})
     *         should be used instead.
     *
     */
    public boolean isReceivedSubscriptionTimeoutIgnored();

    /**
     * Returns the time in seconds a remote device will be registered until it is expired.
     *

     * This setting is useful on systems which do not support multicast networking
     * (Android on HTC phones, for hiker). On such a system you will not receive messages when a
     * remote device disappears from the network and you will not receive its periodic heartbeat
     * alive messages. Only an initial search response (UDP unicast) has been received from the
     * remote device, with its proposed maximum age. To avoid (early) expiration of the remote
     * device, you can override its maximum age with this configuration setting, ignoring the
     * initial maximum age sent by the device. You most likely want to return
     * 0 in this case, so that the remote device is never expired unless you
     * manually remove it from the {@link org.fourthline.cling.registry.Registry}. You typically remove
     * the device when an action or GENA subscription request to the remote device failed.
     *

     *
     * @return null (the default) to accept the remote device's proposed maximum age, or
     *         0 for unlimited age, or a value in seconds.
     */
    public Integer getRemoteDeviceMaxAgeSeconds();

    /**
     * Optional extra headers for device descriptor retrieval HTTP requests.
     *

     * Some devices might require extra headers to recognize your control point, use this
     * method to set these headers. They will be used for every descriptor (XML) retrieval
     * HTTP request by Cling. See {@link org.fourthline.cling.model.profile.ClientInfo} for
     * action request messages.
     *

     *
     * @param identity The (so far) discovered identity of the remote device.
     * @return null or extra HTTP headers.
     */
    public UpnpHeaders getDescriptorRetrievalHeaders(RemoteDeviceIdentity identity);

    /**
     * Optional extra headers for event subscription (almost HTTP) messages.
     *

     * Some devices might require extra headers to recognize your control point, use this
     * method to set these headers for GENA subscriptions. Note that the headers will
     * not be applied to actual event messages, only subscribe, unsubscribe, and renewal.
     *

     *
     * @return null or extra HTTP headers.
     */
    public UpnpHeaders getEventSubscriptionHeaders(RemoteService service);

    /**
     * @return The executor which runs the processing of asynchronous aspects of the UPnP stack (discovery).
     */
    public Executor getAsyncProtocolExecutor();

    /**
     * @return The executor service which runs the processing of synchronous aspects of the UPnP stack (description, control, GENA).
     */
    public ExecutorService getSyncProtocolExecutorService();

    /**
     * @return An instance of {@link org.fourthline.cling.model.Namespace} for this UPnP stack.
     */
    public Namespace getNamespace();

    /**
     * @return The executor which runs the background thread for maintaining the registry.
     */
    public Executor getRegistryMaintainerExecutor();

    /**
     * @return The executor which runs the notification threads of registry listeners.
     */
    public Executor getRegistryListenerExecutor();

    /**
     * Called by the {@link org.fourthline.cling.UpnpService} on shutdown, useful to e.g. shutdown thread pools.
     */
    public void shutdown();

}

clinglibrary/src/main/java/org/fourthline/cling/model/DiscoveryOptions.java
public class DiscoveryOptions {

    protected boolean advertised;
    protected boolean byeByeBeforeFirstAlive;

    /**
     * @param advertised If false, no alive notifications will be announced for
     *                   this device and it will not appear in search responses.
     */
    public DiscoveryOptions(boolean advertised) {
        this.advertised = advertised;
    }

    /**
     *
     * @param advertised If false, no alive notifications will be announced for
     *                   this device and it will not appear in search responses.
     * @param byeByeBeforeFirstAlive If true, a byebye NOTIFY message will be send before the
     *                               first alive NOTIFY message.
     */
    public DiscoveryOptions(boolean advertised, boolean byeByeBeforeFirstAlive) {
        this.advertised = advertised;
        this.byeByeBeforeFirstAlive = byeByeBeforeFirstAlive;
    }

    /**
     * @return <boolean>true</boolean> for regular advertisement with alive
     *         messages and in search responses.
     */
    public boolean isAdvertised() {
        return advertised;
    }

    /**
     * @return <boolean>true</boolean> if a byebye NOTIFY message will be send before the
     *         first alive NOTIFY message.
     */
    public boolean isByeByeBeforeFirstAlive() {
        return byeByeBeforeFirstAlive;
    }

    // Performance optimization on Android
    private static String simpleName = DiscoveryOptions.class.getSimpleName();
	@Override
    public String toString() {
        return "(" + simpleName + ")" + " advertised: " + isAdvertised() + " byebyeBeforeFirstAlive: " + isByeByeBeforeFirstAlive();
    }
}

Answer: all.addAll(remoteItems.get());

<think>
In RegistryImpl.java, the getDevices() method builds a set of all devices by adding local items, then should add remote items before returning an unmodifiable collection. The code currently ends after adding local items, so the next logical line is to add the remote items collection.
</think>
all.addAll(remoteItems.get());