Merge "Support discovering services with a NetworkRequest"
This commit is contained in:
commit
c9a39b3066
@ -18,6 +18,7 @@ package android.net.nsd;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.annotation.SdkConstant;
|
||||
import android.annotation.SdkConstant.SdkConstantType;
|
||||
import android.annotation.SystemService;
|
||||
@ -25,16 +26,22 @@ import android.app.compat.CompatChanges;
|
||||
import android.compat.annotation.ChangeId;
|
||||
import android.compat.annotation.EnabledSince;
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.ConnectivityManager.NetworkCallback;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkRequest;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.util.Objects;
|
||||
@ -281,9 +288,180 @@ public final class NsdManager {
|
||||
private final SparseArray mListenerMap = new SparseArray();
|
||||
private final SparseArray<NsdServiceInfo> mServiceMap = new SparseArray<>();
|
||||
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 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
|
||||
* {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
|
||||
@ -641,7 +819,7 @@ public final class NsdManager {
|
||||
* {@link Network}.
|
||||
*/
|
||||
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
|
||||
* discovery is notified to the application with {@link DiscoveryListener#onDiscoveryStarted}
|
||||
@ -710,6 +949,14 @@ public final class NsdManager {
|
||||
*/
|
||||
public void stopServiceDiscovery(DiscoveryListener 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 {
|
||||
mService.stopDiscovery(id);
|
||||
} catch (RemoteException e) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user