Merge "Support discovering services with a NetworkRequest"
This commit is contained in:
@@ -18,6 +18,7 @@ package android.net.nsd;
|
|||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
|
import android.annotation.RequiresPermission;
|
||||||
import android.annotation.SdkConstant;
|
import android.annotation.SdkConstant;
|
||||||
import android.annotation.SdkConstant.SdkConstantType;
|
import android.annotation.SdkConstant.SdkConstantType;
|
||||||
import android.annotation.SystemService;
|
import android.annotation.SystemService;
|
||||||
@@ -25,16 +26,22 @@ import android.app.compat.CompatChanges;
|
|||||||
import android.compat.annotation.ChangeId;
|
import android.compat.annotation.ChangeId;
|
||||||
import android.compat.annotation.EnabledSince;
|
import android.compat.annotation.EnabledSince;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.ConnectivityManager.NetworkCallback;
|
||||||
import android.net.Network;
|
import android.net.Network;
|
||||||
|
import android.net.NetworkRequest;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.util.ArrayMap;
|
||||||
|
import android.util.ArraySet;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.GuardedBy;
|
||||||
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@@ -281,9 +288,180 @@ public final class NsdManager {
|
|||||||
private final SparseArray mListenerMap = new SparseArray();
|
private final SparseArray mListenerMap = new SparseArray();
|
||||||
private final SparseArray<NsdServiceInfo> mServiceMap = new SparseArray<>();
|
private final SparseArray<NsdServiceInfo> mServiceMap = new SparseArray<>();
|
||||||
private final Object mMapLock = new Object();
|
private final Object mMapLock = new Object();
|
||||||
|
// Map of listener key sent by client -> per-network discovery tracker
|
||||||
|
@GuardedBy("mPerNetworkDiscoveryMap")
|
||||||
|
private final ArrayMap<Integer, PerNetworkDiscoveryTracker>
|
||||||
|
mPerNetworkDiscoveryMap = new ArrayMap<>();
|
||||||
|
|
||||||
private final ServiceHandler mHandler;
|
private final ServiceHandler mHandler;
|
||||||
|
|
||||||
|
private class PerNetworkDiscoveryTracker {
|
||||||
|
final String mServiceType;
|
||||||
|
final int mProtocolType;
|
||||||
|
final DiscoveryListener mBaseListener;
|
||||||
|
final ArrayMap<Network, DelegatingDiscoveryListener> mPerNetworkListeners =
|
||||||
|
new ArrayMap<>();
|
||||||
|
|
||||||
|
final NetworkCallback mNetworkCb = new NetworkCallback() {
|
||||||
|
@Override
|
||||||
|
public void onAvailable(@NonNull Network network) {
|
||||||
|
final DelegatingDiscoveryListener wrappedListener = new DelegatingDiscoveryListener(
|
||||||
|
network, mBaseListener);
|
||||||
|
mPerNetworkListeners.put(network, wrappedListener);
|
||||||
|
discoverServices(mServiceType, mProtocolType, network, wrappedListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLost(@NonNull Network network) {
|
||||||
|
final DelegatingDiscoveryListener listener = mPerNetworkListeners.get(network);
|
||||||
|
if (listener == null) return;
|
||||||
|
listener.notifyAllServicesLost();
|
||||||
|
// Listener will be removed from map in discovery stopped callback
|
||||||
|
stopServiceDiscovery(listener);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Accessed from mHandler
|
||||||
|
private boolean mStopRequested;
|
||||||
|
|
||||||
|
public void start(@NonNull NetworkRequest request) {
|
||||||
|
final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
|
||||||
|
cm.registerNetworkCallback(request, mNetworkCb, mHandler);
|
||||||
|
mHandler.post(() -> mBaseListener.onDiscoveryStarted(mServiceType));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop discovery on all networks tracked by this class.
|
||||||
|
*
|
||||||
|
* This will request all underlying listeners to stop, and the last one to stop will call
|
||||||
|
* onDiscoveryStopped or onStopDiscoveryFailed.
|
||||||
|
*
|
||||||
|
* Must be called on the handler thread.
|
||||||
|
*/
|
||||||
|
public void requestStop() {
|
||||||
|
mHandler.post(() -> {
|
||||||
|
mStopRequested = true;
|
||||||
|
final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
|
||||||
|
cm.unregisterNetworkCallback(mNetworkCb);
|
||||||
|
if (mPerNetworkListeners.size() == 0) {
|
||||||
|
mBaseListener.onDiscoveryStopped(mServiceType);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < mPerNetworkListeners.size(); i++) {
|
||||||
|
final DelegatingDiscoveryListener listener = mPerNetworkListeners.valueAt(i);
|
||||||
|
stopServiceDiscovery(listener);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private PerNetworkDiscoveryTracker(String serviceType, int protocolType,
|
||||||
|
DiscoveryListener baseListener) {
|
||||||
|
mServiceType = serviceType;
|
||||||
|
mProtocolType = protocolType;
|
||||||
|
mBaseListener = baseListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subset of NsdServiceInfo that is tracked to generate service lost notifications when a
|
||||||
|
* network is lost.
|
||||||
|
*
|
||||||
|
* Service lost notifications only contain service name, type and network, so only track
|
||||||
|
* that information (Network is known from the listener). This also implements
|
||||||
|
* equals/hashCode for usage in maps.
|
||||||
|
*/
|
||||||
|
private class TrackedNsdInfo {
|
||||||
|
private final String mServiceName;
|
||||||
|
private final String mServiceType;
|
||||||
|
TrackedNsdInfo(NsdServiceInfo info) {
|
||||||
|
mServiceName = info.getServiceName();
|
||||||
|
mServiceType = info.getServiceType();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(mServiceName, mServiceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (!(obj instanceof TrackedNsdInfo)) return false;
|
||||||
|
final TrackedNsdInfo other = (TrackedNsdInfo) obj;
|
||||||
|
return Objects.equals(mServiceName, other.mServiceName)
|
||||||
|
&& Objects.equals(mServiceType, other.mServiceType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DelegatingDiscoveryListener implements DiscoveryListener {
|
||||||
|
private final Network mNetwork;
|
||||||
|
private final DiscoveryListener mWrapped;
|
||||||
|
private final ArraySet<TrackedNsdInfo> mFoundInfo = new ArraySet<>();
|
||||||
|
|
||||||
|
private DelegatingDiscoveryListener(Network network, DiscoveryListener listener) {
|
||||||
|
mNetwork = network;
|
||||||
|
mWrapped = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
void notifyAllServicesLost() {
|
||||||
|
for (int i = 0; i < mFoundInfo.size(); i++) {
|
||||||
|
final TrackedNsdInfo trackedInfo = mFoundInfo.valueAt(i);
|
||||||
|
final NsdServiceInfo serviceInfo = new NsdServiceInfo(
|
||||||
|
trackedInfo.mServiceName, trackedInfo.mServiceType);
|
||||||
|
serviceInfo.setNetwork(mNetwork);
|
||||||
|
mWrapped.onServiceLost(serviceInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartDiscoveryFailed(String serviceType, int errorCode) {
|
||||||
|
// The delegated listener is used when NsdManager takes care of starting/stopping
|
||||||
|
// discovery on multiple networks. Failure to start on one network is not a global
|
||||||
|
// failure to be reported up, as other networks may succeed: just log.
|
||||||
|
Log.e(TAG, "Failed to start discovery for " + serviceType + " on " + mNetwork
|
||||||
|
+ " with code " + errorCode);
|
||||||
|
mPerNetworkListeners.remove(mNetwork);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDiscoveryStarted(String serviceType) {
|
||||||
|
// Wrapped listener was called upon registration, it is not called for discovery
|
||||||
|
// on each network
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStopDiscoveryFailed(String serviceType, int errorCode) {
|
||||||
|
Log.e(TAG, "Failed to stop discovery for " + serviceType + " on " + mNetwork
|
||||||
|
+ " with code " + errorCode);
|
||||||
|
mPerNetworkListeners.remove(mNetwork);
|
||||||
|
if (mStopRequested && mPerNetworkListeners.size() == 0) {
|
||||||
|
// Do not report onStopDiscoveryFailed when some underlying listeners failed:
|
||||||
|
// this does not mean that all listeners did, and onStopDiscoveryFailed is not
|
||||||
|
// actionable anyway. Just report that discovery stopped.
|
||||||
|
mWrapped.onDiscoveryStopped(serviceType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDiscoveryStopped(String serviceType) {
|
||||||
|
mPerNetworkListeners.remove(mNetwork);
|
||||||
|
if (mStopRequested && mPerNetworkListeners.size() == 0) {
|
||||||
|
mWrapped.onDiscoveryStopped(serviceType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceFound(NsdServiceInfo serviceInfo) {
|
||||||
|
mFoundInfo.add(new TrackedNsdInfo(serviceInfo));
|
||||||
|
mWrapped.onServiceFound(serviceInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceLost(NsdServiceInfo serviceInfo) {
|
||||||
|
mFoundInfo.remove(new TrackedNsdInfo(serviceInfo));
|
||||||
|
mWrapped.onServiceLost(serviceInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new Nsd instance. Applications use
|
* Create a new Nsd instance. Applications use
|
||||||
* {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
|
* {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
|
||||||
@@ -641,7 +819,7 @@ public final class NsdManager {
|
|||||||
* {@link Network}.
|
* {@link Network}.
|
||||||
*/
|
*/
|
||||||
public void discoverServices(String serviceType, int protocolType, DiscoveryListener listener) {
|
public void discoverServices(String serviceType, int protocolType, DiscoveryListener listener) {
|
||||||
discoverServices(serviceType, protocolType, null, listener);
|
discoverServices(serviceType, protocolType, (Network) null, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -692,6 +870,67 @@ public final class NsdManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiate service discovery to browse for instances of a service type. Service discovery
|
||||||
|
* consumes network bandwidth and will continue until the application calls
|
||||||
|
* {@link #stopServiceDiscovery}.
|
||||||
|
*
|
||||||
|
* <p> The function call immediately returns after sending a request to start service
|
||||||
|
* discovery to the framework. The application is notified of a success to initiate
|
||||||
|
* discovery through the callback {@link DiscoveryListener#onDiscoveryStarted} or a failure
|
||||||
|
* through {@link DiscoveryListener#onStartDiscoveryFailed}.
|
||||||
|
*
|
||||||
|
* <p> Upon successful start, application is notified when a service is found with
|
||||||
|
* {@link DiscoveryListener#onServiceFound} or when a service is lost with
|
||||||
|
* {@link DiscoveryListener#onServiceLost}.
|
||||||
|
*
|
||||||
|
* <p> Upon failure to start, service discovery is not active and application does
|
||||||
|
* not need to invoke {@link #stopServiceDiscovery}
|
||||||
|
*
|
||||||
|
* <p> The application should call {@link #stopServiceDiscovery} when discovery of this
|
||||||
|
* service type is no longer required, and/or whenever the application is paused or
|
||||||
|
* stopped.
|
||||||
|
*
|
||||||
|
* <p> During discovery, new networks may connect or existing networks may disconnect - for
|
||||||
|
* example if wifi is reconnected. When a service was found on a network that disconnects,
|
||||||
|
* {@link DiscoveryListener#onServiceLost} will be called. If a new network connects that
|
||||||
|
* matches the {@link NetworkRequest}, {@link DiscoveryListener#onServiceFound} will be called
|
||||||
|
* for services found on that network. Applications that do not want to track networks
|
||||||
|
* themselves are encouraged to use this method instead of other overloads of
|
||||||
|
* {@code discoverServices}, as they will receive proper notifications when a service becomes
|
||||||
|
* available or unavailable due to network changes.
|
||||||
|
*
|
||||||
|
* @param serviceType The service type being discovered. Examples include "_http._tcp" for
|
||||||
|
* http services or "_ipp._tcp" for printers
|
||||||
|
* @param protocolType The service discovery protocol
|
||||||
|
* @param networkRequest Request specifying networks that should be considered when discovering
|
||||||
|
* @param listener The listener notifies of a successful discovery and is used
|
||||||
|
* to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}.
|
||||||
|
* Cannot be null. Cannot be in use for an active service discovery.
|
||||||
|
*/
|
||||||
|
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
|
||||||
|
public void discoverServices(@NonNull String serviceType, int protocolType,
|
||||||
|
@NonNull NetworkRequest networkRequest, @NonNull DiscoveryListener listener) {
|
||||||
|
if (TextUtils.isEmpty(serviceType)) {
|
||||||
|
throw new IllegalArgumentException("Service type cannot be empty");
|
||||||
|
}
|
||||||
|
Objects.requireNonNull(networkRequest, "NetworkRequest cannot be null");
|
||||||
|
checkProtocol(protocolType);
|
||||||
|
|
||||||
|
NsdServiceInfo s = new NsdServiceInfo();
|
||||||
|
s.setServiceType(serviceType);
|
||||||
|
|
||||||
|
final int baseListenerKey = putListener(listener, s);
|
||||||
|
|
||||||
|
final PerNetworkDiscoveryTracker discoveryInfo = new PerNetworkDiscoveryTracker(
|
||||||
|
serviceType, protocolType, listener);
|
||||||
|
|
||||||
|
synchronized (mPerNetworkDiscoveryMap) {
|
||||||
|
mPerNetworkDiscoveryMap.put(baseListenerKey, discoveryInfo);
|
||||||
|
discoveryInfo.start(networkRequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop service discovery initiated with {@link #discoverServices}. An active service
|
* Stop service discovery initiated with {@link #discoverServices}. An active service
|
||||||
* discovery is notified to the application with {@link DiscoveryListener#onDiscoveryStarted}
|
* discovery is notified to the application with {@link DiscoveryListener#onDiscoveryStarted}
|
||||||
@@ -710,6 +949,14 @@ public final class NsdManager {
|
|||||||
*/
|
*/
|
||||||
public void stopServiceDiscovery(DiscoveryListener listener) {
|
public void stopServiceDiscovery(DiscoveryListener listener) {
|
||||||
int id = getListenerKey(listener);
|
int id = getListenerKey(listener);
|
||||||
|
// If this is a PerNetworkDiscovery request, handle it as such
|
||||||
|
synchronized (mPerNetworkDiscoveryMap) {
|
||||||
|
final PerNetworkDiscoveryTracker info = mPerNetworkDiscoveryMap.get(id);
|
||||||
|
if (info != null) {
|
||||||
|
info.requestStop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
mService.stopDiscovery(id);
|
mService.stopDiscovery(id);
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
|
|||||||
Reference in New Issue
Block a user