The isLegacy field of Nsd metrics should indicate whether the data was collected from the old backend or not. However, it is currently only dependent on the ENABLE_PLATFORM_MDNS_BACKEND compat change value, which is incorrect. This is because the NsdService always uses the new backend since Android U, regardless of the compat change value. Therefore, the isLegacy data should be obtained from each transaction. Bug: 287546772 Bug: 299880473 Test: atest FrameworksNetTestCases NsdManagerTest Change-Id: I156abd656b90578d710696a69ccf7dfca97a2c9c
2819 lines
131 KiB
Java
2819 lines
131 KiB
Java
/*
|
|
* Copyright (C) 2021 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.server;
|
|
|
|
import static android.Manifest.permission.NETWORK_SETTINGS;
|
|
import static android.Manifest.permission.NETWORK_STACK;
|
|
import static android.net.ConnectivityManager.NETID_UNSET;
|
|
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
|
|
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
|
|
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
|
|
import static android.net.nsd.NsdManager.MDNS_DISCOVERY_MANAGER_EVENT;
|
|
import static android.net.nsd.NsdManager.MDNS_SERVICE_EVENT;
|
|
import static android.net.nsd.NsdManager.RESOLVE_SERVICE_SUCCEEDED;
|
|
import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
|
|
import static com.android.modules.utils.build.SdkLevel.isAtLeastU;
|
|
import static com.android.networkstack.apishim.ConstantsShim.REGISTER_NSD_OFFLOAD_ENGINE;
|
|
import static com.android.server.connectivity.mdns.MdnsAdvertiser.AdvertiserMetrics;
|
|
import static com.android.server.connectivity.mdns.MdnsConstants.NO_PACKET;
|
|
import static com.android.server.connectivity.mdns.MdnsRecord.MAX_LABEL_LENGTH;
|
|
import static com.android.server.connectivity.mdns.util.MdnsUtils.Clock;
|
|
|
|
import android.annotation.NonNull;
|
|
import android.annotation.Nullable;
|
|
import android.app.ActivityManager;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.net.ConnectivityManager;
|
|
import android.net.INetd;
|
|
import android.net.InetAddresses;
|
|
import android.net.LinkProperties;
|
|
import android.net.Network;
|
|
import android.net.mdns.aidl.DiscoveryInfo;
|
|
import android.net.mdns.aidl.GetAddressInfo;
|
|
import android.net.mdns.aidl.IMDnsEventListener;
|
|
import android.net.mdns.aidl.RegistrationInfo;
|
|
import android.net.mdns.aidl.ResolutionInfo;
|
|
import android.net.nsd.INsdManager;
|
|
import android.net.nsd.INsdManagerCallback;
|
|
import android.net.nsd.INsdServiceConnector;
|
|
import android.net.nsd.IOffloadEngine;
|
|
import android.net.nsd.MDnsManager;
|
|
import android.net.nsd.NsdManager;
|
|
import android.net.nsd.NsdServiceInfo;
|
|
import android.net.nsd.OffloadEngine;
|
|
import android.net.nsd.OffloadServiceInfo;
|
|
import android.net.wifi.WifiManager;
|
|
import android.os.Binder;
|
|
import android.os.Handler;
|
|
import android.os.HandlerThread;
|
|
import android.os.IBinder;
|
|
import android.os.Looper;
|
|
import android.os.Message;
|
|
import android.os.RemoteCallbackList;
|
|
import android.os.RemoteException;
|
|
import android.os.UserHandle;
|
|
import android.provider.DeviceConfig;
|
|
import android.text.TextUtils;
|
|
import android.util.ArraySet;
|
|
import android.util.Log;
|
|
import android.util.Pair;
|
|
import android.util.SparseArray;
|
|
|
|
import com.android.internal.annotations.VisibleForTesting;
|
|
import com.android.internal.util.IndentingPrintWriter;
|
|
import com.android.internal.util.State;
|
|
import com.android.internal.util.StateMachine;
|
|
import com.android.metrics.NetworkNsdReportedMetrics;
|
|
import com.android.modules.utils.build.SdkLevel;
|
|
import com.android.net.module.util.CollectionUtils;
|
|
import com.android.net.module.util.DeviceConfigUtils;
|
|
import com.android.net.module.util.InetAddressUtils;
|
|
import com.android.net.module.util.PermissionUtils;
|
|
import com.android.net.module.util.SharedLog;
|
|
import com.android.server.connectivity.mdns.ExecutorProvider;
|
|
import com.android.server.connectivity.mdns.MdnsAdvertiser;
|
|
import com.android.server.connectivity.mdns.MdnsDiscoveryManager;
|
|
import com.android.server.connectivity.mdns.MdnsFeatureFlags;
|
|
import com.android.server.connectivity.mdns.MdnsInterfaceSocket;
|
|
import com.android.server.connectivity.mdns.MdnsMultinetworkSocketClient;
|
|
import com.android.server.connectivity.mdns.MdnsSearchOptions;
|
|
import com.android.server.connectivity.mdns.MdnsServiceBrowserListener;
|
|
import com.android.server.connectivity.mdns.MdnsServiceInfo;
|
|
import com.android.server.connectivity.mdns.MdnsSocketProvider;
|
|
import com.android.server.connectivity.mdns.util.MdnsUtils;
|
|
|
|
import java.io.FileDescriptor;
|
|
import java.io.PrintWriter;
|
|
import java.net.Inet6Address;
|
|
import java.net.InetAddress;
|
|
import java.net.NetworkInterface;
|
|
import java.net.SocketException;
|
|
import java.net.UnknownHostException;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
import java.util.Set;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
/**
|
|
* Network Service Discovery Service handles remote service discovery operation requests by
|
|
* implementing the INsdManager interface.
|
|
*
|
|
* @hide
|
|
*/
|
|
public class NsdService extends INsdManager.Stub {
|
|
private static final String TAG = "NsdService";
|
|
private static final String MDNS_TAG = "mDnsConnector";
|
|
/**
|
|
* Enable discovery using the Java DiscoveryManager, instead of the legacy mdnsresponder
|
|
* implementation.
|
|
*/
|
|
private static final String MDNS_DISCOVERY_MANAGER_VERSION = "mdns_discovery_manager_version";
|
|
private static final String LOCAL_DOMAIN_NAME = "local";
|
|
|
|
/**
|
|
* Enable advertising using the Java MdnsAdvertiser, instead of the legacy mdnsresponder
|
|
* implementation.
|
|
*/
|
|
private static final String MDNS_ADVERTISER_VERSION = "mdns_advertiser_version";
|
|
|
|
/**
|
|
* Comma-separated list of type:flag mappings indicating the flags to use to allowlist
|
|
* discovery/advertising using MdnsDiscoveryManager / MdnsAdvertiser for a given type.
|
|
*
|
|
* For example _mytype._tcp.local and _othertype._tcp.local would be configured with:
|
|
* _mytype._tcp:mytype,_othertype._tcp.local:othertype
|
|
*
|
|
* In which case the flags:
|
|
* "mdns_discovery_manager_allowlist_mytype_version",
|
|
* "mdns_advertiser_allowlist_mytype_version",
|
|
* "mdns_discovery_manager_allowlist_othertype_version",
|
|
* "mdns_advertiser_allowlist_othertype_version"
|
|
* would be used to toggle MdnsDiscoveryManager / MdnsAdvertiser for each type. The flags will
|
|
* be read with
|
|
* {@link DeviceConfigUtils#isTetheringFeatureEnabled}
|
|
*
|
|
* @see #MDNS_DISCOVERY_MANAGER_ALLOWLIST_FLAG_PREFIX
|
|
* @see #MDNS_ADVERTISER_ALLOWLIST_FLAG_PREFIX
|
|
* @see #MDNS_ALLOWLIST_FLAG_SUFFIX
|
|
*/
|
|
private static final String MDNS_TYPE_ALLOWLIST_FLAGS = "mdns_type_allowlist_flags";
|
|
|
|
private static final String MDNS_DISCOVERY_MANAGER_ALLOWLIST_FLAG_PREFIX =
|
|
"mdns_discovery_manager_allowlist_";
|
|
private static final String MDNS_ADVERTISER_ALLOWLIST_FLAG_PREFIX =
|
|
"mdns_advertiser_allowlist_";
|
|
private static final String MDNS_ALLOWLIST_FLAG_SUFFIX = "_version";
|
|
|
|
@VisibleForTesting
|
|
static final String MDNS_CONFIG_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF =
|
|
"mdns_config_running_app_active_importance_cutoff";
|
|
@VisibleForTesting
|
|
static final int DEFAULT_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF =
|
|
ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
|
|
private final int mRunningAppActiveImportanceCutoff;
|
|
|
|
public static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
|
|
private static final long CLEANUP_DELAY_MS = 10000;
|
|
private static final int IFACE_IDX_ANY = 0;
|
|
private static final int MAX_SERVICES_COUNT_METRIC_PER_CLIENT = 100;
|
|
@VisibleForTesting
|
|
static final int NO_TRANSACTION = -1;
|
|
private static final int NO_SENT_QUERY_COUNT = 0;
|
|
private static final int DISCOVERY_QUERY_SENT_CALLBACK = 1000;
|
|
private static final SharedLog LOGGER = new SharedLog("serviceDiscovery");
|
|
|
|
private final Context mContext;
|
|
private final NsdStateMachine mNsdStateMachine;
|
|
private final MDnsManager mMDnsManager;
|
|
private final MDnsEventCallback mMDnsEventCallback;
|
|
@NonNull
|
|
private final Dependencies mDeps;
|
|
@NonNull
|
|
private final MdnsMultinetworkSocketClient mMdnsSocketClient;
|
|
@NonNull
|
|
private final MdnsDiscoveryManager mMdnsDiscoveryManager;
|
|
@NonNull
|
|
private final MdnsSocketProvider mMdnsSocketProvider;
|
|
@NonNull
|
|
private final MdnsAdvertiser mAdvertiser;
|
|
@NonNull
|
|
private final Clock mClock;
|
|
private final SharedLog mServiceLogs = LOGGER.forSubComponent(TAG);
|
|
// WARNING : Accessing these values in any thread is not safe, it must only be changed in the
|
|
// state machine thread. If change this outside state machine, it will need to introduce
|
|
// synchronization.
|
|
private boolean mIsDaemonStarted = false;
|
|
private boolean mIsMonitoringSocketsStarted = false;
|
|
|
|
/**
|
|
* Clients receiving asynchronous messages
|
|
*/
|
|
private final HashMap<NsdServiceConnector, ClientInfo> mClients = new HashMap<>();
|
|
|
|
/* A map from transaction(unique) id to client info */
|
|
private final SparseArray<ClientInfo> mTransactionIdToClientInfoMap = new SparseArray<>();
|
|
|
|
// Note this is not final to avoid depending on the Wi-Fi service starting before NsdService
|
|
@Nullable
|
|
private WifiManager.MulticastLock mHeldMulticastLock;
|
|
// Fulfilled network requests that require the Wi-Fi lock: key is the obtained Network
|
|
// (non-null), value is the requested Network (nullable)
|
|
@NonNull
|
|
private final ArraySet<Network> mWifiLockRequiredNetworks = new ArraySet<>();
|
|
@NonNull
|
|
private final ArraySet<Integer> mRunningAppActiveUids = new ArraySet<>();
|
|
|
|
private final long mCleanupDelayMs;
|
|
|
|
private static final int INVALID_ID = 0;
|
|
private int mUniqueId = 1;
|
|
// The count of the connected legacy clients.
|
|
private int mLegacyClientCount = 0;
|
|
// The number of client that ever connected.
|
|
private int mClientNumberId = 1;
|
|
|
|
private final RemoteCallbackList<IOffloadEngine> mOffloadEngines =
|
|
new RemoteCallbackList<>();
|
|
|
|
private static class OffloadEngineInfo {
|
|
@NonNull final String mInterfaceName;
|
|
final long mOffloadCapabilities;
|
|
final long mOffloadType;
|
|
@NonNull final IOffloadEngine mOffloadEngine;
|
|
|
|
OffloadEngineInfo(@NonNull IOffloadEngine offloadEngine,
|
|
@NonNull String interfaceName, long capabilities, long offloadType) {
|
|
this.mOffloadEngine = offloadEngine;
|
|
this.mInterfaceName = interfaceName;
|
|
this.mOffloadCapabilities = capabilities;
|
|
this.mOffloadType = offloadType;
|
|
}
|
|
}
|
|
|
|
@VisibleForTesting
|
|
static class MdnsListener implements MdnsServiceBrowserListener {
|
|
protected final int mClientRequestId;
|
|
protected final int mTransactionId;
|
|
@NonNull
|
|
protected final NsdServiceInfo mReqServiceInfo;
|
|
@NonNull
|
|
protected final String mListenedServiceType;
|
|
|
|
MdnsListener(int clientRequestId, int transactionId, @NonNull NsdServiceInfo reqServiceInfo,
|
|
@NonNull String listenedServiceType) {
|
|
mClientRequestId = clientRequestId;
|
|
mTransactionId = transactionId;
|
|
mReqServiceInfo = reqServiceInfo;
|
|
mListenedServiceType = listenedServiceType;
|
|
}
|
|
|
|
@NonNull
|
|
public String getListenedServiceType() {
|
|
return mListenedServiceType;
|
|
}
|
|
|
|
@Override
|
|
public void onServiceFound(@NonNull MdnsServiceInfo serviceInfo,
|
|
boolean isServiceFromCache) { }
|
|
|
|
@Override
|
|
public void onServiceUpdated(@NonNull MdnsServiceInfo serviceInfo) { }
|
|
|
|
@Override
|
|
public void onServiceRemoved(@NonNull MdnsServiceInfo serviceInfo) { }
|
|
|
|
@Override
|
|
public void onServiceNameDiscovered(@NonNull MdnsServiceInfo serviceInfo,
|
|
boolean isServiceFromCache) { }
|
|
|
|
@Override
|
|
public void onServiceNameRemoved(@NonNull MdnsServiceInfo serviceInfo) { }
|
|
|
|
@Override
|
|
public void onSearchStoppedWithError(int error) { }
|
|
|
|
@Override
|
|
public void onSearchFailedToStart() { }
|
|
|
|
@Override
|
|
public void onDiscoveryQuerySent(@NonNull List<String> subtypes,
|
|
int sentQueryTransactionId) { }
|
|
|
|
@Override
|
|
public void onFailedToParseMdnsResponse(int receivedPacketNumber, int errorCode) { }
|
|
}
|
|
|
|
private class DiscoveryListener extends MdnsListener {
|
|
|
|
DiscoveryListener(int clientRequestId, int transactionId,
|
|
@NonNull NsdServiceInfo reqServiceInfo, @NonNull String listenServiceType) {
|
|
super(clientRequestId, transactionId, reqServiceInfo, listenServiceType);
|
|
}
|
|
|
|
@Override
|
|
public void onServiceNameDiscovered(@NonNull MdnsServiceInfo serviceInfo,
|
|
boolean isServiceFromCache) {
|
|
mNsdStateMachine.sendMessage(MDNS_DISCOVERY_MANAGER_EVENT, mTransactionId,
|
|
NsdManager.SERVICE_FOUND,
|
|
new MdnsEvent(mClientRequestId, serviceInfo, isServiceFromCache));
|
|
}
|
|
|
|
@Override
|
|
public void onServiceNameRemoved(@NonNull MdnsServiceInfo serviceInfo) {
|
|
mNsdStateMachine.sendMessage(MDNS_DISCOVERY_MANAGER_EVENT, mTransactionId,
|
|
NsdManager.SERVICE_LOST,
|
|
new MdnsEvent(mClientRequestId, serviceInfo));
|
|
}
|
|
|
|
@Override
|
|
public void onDiscoveryQuerySent(@NonNull List<String> subtypes,
|
|
int sentQueryTransactionId) {
|
|
mNsdStateMachine.sendMessage(MDNS_DISCOVERY_MANAGER_EVENT, mTransactionId,
|
|
DISCOVERY_QUERY_SENT_CALLBACK, new MdnsEvent(mClientRequestId));
|
|
}
|
|
}
|
|
|
|
private class ResolutionListener extends MdnsListener {
|
|
|
|
ResolutionListener(int clientRequestId, int transactionId,
|
|
@NonNull NsdServiceInfo reqServiceInfo, @NonNull String listenServiceType) {
|
|
super(clientRequestId, transactionId, reqServiceInfo, listenServiceType);
|
|
}
|
|
|
|
@Override
|
|
public void onServiceFound(MdnsServiceInfo serviceInfo, boolean isServiceFromCache) {
|
|
mNsdStateMachine.sendMessage(MDNS_DISCOVERY_MANAGER_EVENT, mTransactionId,
|
|
NsdManager.RESOLVE_SERVICE_SUCCEEDED,
|
|
new MdnsEvent(mClientRequestId, serviceInfo, isServiceFromCache));
|
|
}
|
|
|
|
@Override
|
|
public void onDiscoveryQuerySent(@NonNull List<String> subtypes,
|
|
int sentQueryTransactionId) {
|
|
mNsdStateMachine.sendMessage(MDNS_DISCOVERY_MANAGER_EVENT, mTransactionId,
|
|
DISCOVERY_QUERY_SENT_CALLBACK, new MdnsEvent(mClientRequestId));
|
|
}
|
|
}
|
|
|
|
private class ServiceInfoListener extends MdnsListener {
|
|
|
|
ServiceInfoListener(int clientRequestId, int transactionId,
|
|
@NonNull NsdServiceInfo reqServiceInfo, @NonNull String listenServiceType) {
|
|
super(clientRequestId, transactionId, reqServiceInfo, listenServiceType);
|
|
}
|
|
|
|
@Override
|
|
public void onServiceFound(@NonNull MdnsServiceInfo serviceInfo,
|
|
boolean isServiceFromCache) {
|
|
mNsdStateMachine.sendMessage(MDNS_DISCOVERY_MANAGER_EVENT, mTransactionId,
|
|
NsdManager.SERVICE_UPDATED,
|
|
new MdnsEvent(mClientRequestId, serviceInfo, isServiceFromCache));
|
|
}
|
|
|
|
@Override
|
|
public void onServiceUpdated(@NonNull MdnsServiceInfo serviceInfo) {
|
|
mNsdStateMachine.sendMessage(MDNS_DISCOVERY_MANAGER_EVENT, mTransactionId,
|
|
NsdManager.SERVICE_UPDATED,
|
|
new MdnsEvent(mClientRequestId, serviceInfo));
|
|
}
|
|
|
|
@Override
|
|
public void onServiceRemoved(@NonNull MdnsServiceInfo serviceInfo) {
|
|
mNsdStateMachine.sendMessage(MDNS_DISCOVERY_MANAGER_EVENT, mTransactionId,
|
|
NsdManager.SERVICE_UPDATED_LOST,
|
|
new MdnsEvent(mClientRequestId, serviceInfo));
|
|
}
|
|
|
|
@Override
|
|
public void onDiscoveryQuerySent(@NonNull List<String> subtypes,
|
|
int sentQueryTransactionId) {
|
|
mNsdStateMachine.sendMessage(MDNS_DISCOVERY_MANAGER_EVENT, mTransactionId,
|
|
DISCOVERY_QUERY_SENT_CALLBACK, new MdnsEvent(mClientRequestId));
|
|
}
|
|
}
|
|
|
|
private class SocketRequestMonitor implements MdnsSocketProvider.SocketRequestMonitor {
|
|
@Override
|
|
public void onSocketRequestFulfilled(@Nullable Network socketNetwork,
|
|
@NonNull MdnsInterfaceSocket socket, @NonNull int[] transports) {
|
|
// The network may be null for Wi-Fi SoftAp interfaces (tethering), but there is no APF
|
|
// filtering on such interfaces, so taking the multicast lock is not necessary to
|
|
// disable APF filtering of multicast.
|
|
if (socketNetwork == null
|
|
|| !CollectionUtils.contains(transports, TRANSPORT_WIFI)
|
|
|| CollectionUtils.contains(transports, TRANSPORT_VPN)) {
|
|
return;
|
|
}
|
|
|
|
if (mWifiLockRequiredNetworks.add(socketNetwork)) {
|
|
updateMulticastLock();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onSocketDestroyed(@Nullable Network socketNetwork,
|
|
@NonNull MdnsInterfaceSocket socket) {
|
|
if (mWifiLockRequiredNetworks.remove(socketNetwork)) {
|
|
updateMulticastLock();
|
|
}
|
|
}
|
|
}
|
|
|
|
private class UidImportanceListener implements ActivityManager.OnUidImportanceListener {
|
|
private final Handler mHandler;
|
|
|
|
private UidImportanceListener(Handler handler) {
|
|
mHandler = handler;
|
|
}
|
|
|
|
@Override
|
|
public void onUidImportance(int uid, int importance) {
|
|
mHandler.post(() -> handleUidImportanceChanged(uid, importance));
|
|
}
|
|
}
|
|
|
|
private void handleUidImportanceChanged(int uid, int importance) {
|
|
// Lower importance values are more "important"
|
|
final boolean modified = importance <= mRunningAppActiveImportanceCutoff
|
|
? mRunningAppActiveUids.add(uid)
|
|
: mRunningAppActiveUids.remove(uid);
|
|
if (modified) {
|
|
updateMulticastLock();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Take or release the lock based on updated internal state.
|
|
*
|
|
* This determines whether the lock needs to be held based on
|
|
* {@link #mWifiLockRequiredNetworks}, {@link #mRunningAppActiveUids} and
|
|
* {@link ClientInfo#mClientRequests}, so it must be called after any of the these have been
|
|
* updated.
|
|
*/
|
|
private void updateMulticastLock() {
|
|
final int needsLockUid = getMulticastLockNeededUid();
|
|
if (needsLockUid >= 0 && mHeldMulticastLock == null) {
|
|
final WifiManager wm = mContext.getSystemService(WifiManager.class);
|
|
if (wm == null) {
|
|
Log.wtf(TAG, "Got a TRANSPORT_WIFI network without WifiManager");
|
|
return;
|
|
}
|
|
mHeldMulticastLock = wm.createMulticastLock(TAG);
|
|
mHeldMulticastLock.acquire();
|
|
mServiceLogs.log("Taking multicast lock for uid " + needsLockUid);
|
|
} else if (needsLockUid < 0 && mHeldMulticastLock != null) {
|
|
mHeldMulticastLock.release();
|
|
mHeldMulticastLock = null;
|
|
mServiceLogs.log("Released multicast lock");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return The UID of an app requiring the multicast lock, or -1 if none.
|
|
*/
|
|
private int getMulticastLockNeededUid() {
|
|
if (mWifiLockRequiredNetworks.size() == 0) {
|
|
// Return early if NSD is not active, or not on any relevant network
|
|
return -1;
|
|
}
|
|
for (int i = 0; i < mTransactionIdToClientInfoMap.size(); i++) {
|
|
final ClientInfo clientInfo = mTransactionIdToClientInfoMap.valueAt(i);
|
|
if (!mRunningAppActiveUids.contains(clientInfo.mUid)) {
|
|
// Ignore non-active UIDs
|
|
continue;
|
|
}
|
|
|
|
if (clientInfo.hasAnyJavaBackendRequestForNetworks(mWifiLockRequiredNetworks)) {
|
|
return clientInfo.mUid;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Data class of mdns service callback information.
|
|
*/
|
|
private static class MdnsEvent {
|
|
final int mClientRequestId;
|
|
@Nullable
|
|
final MdnsServiceInfo mMdnsServiceInfo;
|
|
final boolean mIsServiceFromCache;
|
|
|
|
MdnsEvent(int clientRequestId) {
|
|
this(clientRequestId, null /* mdnsServiceInfo */, false /* isServiceFromCache */);
|
|
}
|
|
|
|
MdnsEvent(int clientRequestId, @Nullable MdnsServiceInfo mdnsServiceInfo) {
|
|
this(clientRequestId, mdnsServiceInfo, false /* isServiceFromCache */);
|
|
}
|
|
|
|
MdnsEvent(int clientRequestId, @Nullable MdnsServiceInfo mdnsServiceInfo,
|
|
boolean isServiceFromCache) {
|
|
mClientRequestId = clientRequestId;
|
|
mMdnsServiceInfo = mdnsServiceInfo;
|
|
mIsServiceFromCache = isServiceFromCache;
|
|
}
|
|
}
|
|
|
|
private class NsdStateMachine extends StateMachine {
|
|
|
|
private final DefaultState mDefaultState = new DefaultState();
|
|
private final EnabledState mEnabledState = new EnabledState();
|
|
|
|
@Override
|
|
protected String getWhatToString(int what) {
|
|
return NsdManager.nameOf(what);
|
|
}
|
|
|
|
private void maybeStartDaemon() {
|
|
if (mIsDaemonStarted) {
|
|
if (DBG) Log.d(TAG, "Daemon is already started.");
|
|
return;
|
|
}
|
|
mMDnsManager.registerEventListener(mMDnsEventCallback);
|
|
mMDnsManager.startDaemon();
|
|
mIsDaemonStarted = true;
|
|
maybeScheduleStop();
|
|
mServiceLogs.log("Start mdns_responder daemon");
|
|
}
|
|
|
|
private void maybeStopDaemon() {
|
|
if (!mIsDaemonStarted) {
|
|
if (DBG) Log.d(TAG, "Daemon has not been started.");
|
|
return;
|
|
}
|
|
mMDnsManager.unregisterEventListener(mMDnsEventCallback);
|
|
mMDnsManager.stopDaemon();
|
|
mIsDaemonStarted = false;
|
|
mServiceLogs.log("Stop mdns_responder daemon");
|
|
}
|
|
|
|
private boolean isAnyRequestActive() {
|
|
return mTransactionIdToClientInfoMap.size() != 0;
|
|
}
|
|
|
|
private void scheduleStop() {
|
|
sendMessageDelayed(NsdManager.DAEMON_CLEANUP, mCleanupDelayMs);
|
|
}
|
|
private void maybeScheduleStop() {
|
|
// The native daemon should stay alive and can't be cleanup
|
|
// if any legacy client connected.
|
|
if (!isAnyRequestActive() && mLegacyClientCount == 0) {
|
|
scheduleStop();
|
|
}
|
|
}
|
|
|
|
private void cancelStop() {
|
|
this.removeMessages(NsdManager.DAEMON_CLEANUP);
|
|
}
|
|
|
|
private void maybeStartMonitoringSockets() {
|
|
if (mIsMonitoringSocketsStarted) {
|
|
if (DBG) Log.d(TAG, "Socket monitoring is already started.");
|
|
return;
|
|
}
|
|
|
|
mMdnsSocketProvider.startMonitoringSockets();
|
|
mIsMonitoringSocketsStarted = true;
|
|
}
|
|
|
|
private void maybeStopMonitoringSocketsIfNoActiveRequest() {
|
|
if (!mIsMonitoringSocketsStarted) return;
|
|
if (isAnyRequestActive()) return;
|
|
|
|
mMdnsSocketProvider.requestStopWhenInactive();
|
|
mIsMonitoringSocketsStarted = false;
|
|
}
|
|
|
|
NsdStateMachine(String name, Handler handler) {
|
|
super(name, handler);
|
|
addState(mDefaultState);
|
|
addState(mEnabledState, mDefaultState);
|
|
State initialState = mEnabledState;
|
|
setInitialState(initialState);
|
|
setLogRecSize(25);
|
|
}
|
|
|
|
class DefaultState extends State {
|
|
@Override
|
|
public boolean processMessage(Message msg) {
|
|
final ClientInfo cInfo;
|
|
final int clientRequestId = msg.arg2;
|
|
switch (msg.what) {
|
|
case NsdManager.REGISTER_CLIENT:
|
|
final ConnectorArgs arg = (ConnectorArgs) msg.obj;
|
|
final INsdManagerCallback cb = arg.callback;
|
|
try {
|
|
cb.asBinder().linkToDeath(arg.connector, 0);
|
|
final String tag = "Client" + arg.uid + "-" + mClientNumberId++;
|
|
final NetworkNsdReportedMetrics metrics =
|
|
mDeps.makeNetworkNsdReportedMetrics(
|
|
(int) mClock.elapsedRealtime());
|
|
cInfo = new ClientInfo(cb, arg.uid, arg.useJavaBackend,
|
|
mServiceLogs.forSubComponent(tag), metrics);
|
|
mClients.put(arg.connector, cInfo);
|
|
} catch (RemoteException e) {
|
|
Log.w(TAG, "Client request id " + clientRequestId
|
|
+ " has already died");
|
|
}
|
|
break;
|
|
case NsdManager.UNREGISTER_CLIENT:
|
|
final NsdServiceConnector connector = (NsdServiceConnector) msg.obj;
|
|
cInfo = mClients.remove(connector);
|
|
if (cInfo != null) {
|
|
cInfo.expungeAllRequests();
|
|
if (cInfo.isPreSClient()) {
|
|
mLegacyClientCount -= 1;
|
|
}
|
|
}
|
|
maybeStopMonitoringSocketsIfNoActiveRequest();
|
|
maybeScheduleStop();
|
|
break;
|
|
case NsdManager.DISCOVER_SERVICES:
|
|
cInfo = getClientInfoForReply(msg);
|
|
if (cInfo != null) {
|
|
cInfo.onDiscoverServicesFailedImmediately(clientRequestId,
|
|
NsdManager.FAILURE_INTERNAL_ERROR, true /* isLegacy */);
|
|
}
|
|
break;
|
|
case NsdManager.STOP_DISCOVERY:
|
|
cInfo = getClientInfoForReply(msg);
|
|
if (cInfo != null) {
|
|
cInfo.onStopDiscoveryFailed(
|
|
clientRequestId, NsdManager.FAILURE_INTERNAL_ERROR);
|
|
}
|
|
break;
|
|
case NsdManager.REGISTER_SERVICE:
|
|
cInfo = getClientInfoForReply(msg);
|
|
if (cInfo != null) {
|
|
cInfo.onRegisterServiceFailedImmediately(clientRequestId,
|
|
NsdManager.FAILURE_INTERNAL_ERROR, true /* isLegacy */);
|
|
}
|
|
break;
|
|
case NsdManager.UNREGISTER_SERVICE:
|
|
cInfo = getClientInfoForReply(msg);
|
|
if (cInfo != null) {
|
|
cInfo.onUnregisterServiceFailed(
|
|
clientRequestId, NsdManager.FAILURE_INTERNAL_ERROR);
|
|
}
|
|
break;
|
|
case NsdManager.RESOLVE_SERVICE:
|
|
cInfo = getClientInfoForReply(msg);
|
|
if (cInfo != null) {
|
|
cInfo.onResolveServiceFailedImmediately(clientRequestId,
|
|
NsdManager.FAILURE_INTERNAL_ERROR, true /* isLegacy */);
|
|
}
|
|
break;
|
|
case NsdManager.STOP_RESOLUTION:
|
|
cInfo = getClientInfoForReply(msg);
|
|
if (cInfo != null) {
|
|
cInfo.onStopResolutionFailed(
|
|
clientRequestId, NsdManager.FAILURE_OPERATION_NOT_RUNNING);
|
|
}
|
|
break;
|
|
case NsdManager.REGISTER_SERVICE_CALLBACK:
|
|
cInfo = getClientInfoForReply(msg);
|
|
if (cInfo != null) {
|
|
cInfo.onServiceInfoCallbackRegistrationFailed(
|
|
clientRequestId, NsdManager.FAILURE_BAD_PARAMETERS);
|
|
}
|
|
break;
|
|
case NsdManager.DAEMON_CLEANUP:
|
|
maybeStopDaemon();
|
|
break;
|
|
// This event should be only sent by the legacy (target SDK < S) clients.
|
|
// Mark the sending client as legacy.
|
|
case NsdManager.DAEMON_STARTUP:
|
|
cInfo = getClientInfoForReply(msg);
|
|
if (cInfo != null) {
|
|
cancelStop();
|
|
cInfo.setPreSClient();
|
|
mLegacyClientCount += 1;
|
|
maybeStartDaemon();
|
|
}
|
|
break;
|
|
default:
|
|
Log.e(TAG, "Unhandled " + msg);
|
|
return NOT_HANDLED;
|
|
}
|
|
return HANDLED;
|
|
}
|
|
|
|
private ClientInfo getClientInfoForReply(Message msg) {
|
|
final ListenerArgs args = (ListenerArgs) msg.obj;
|
|
return mClients.get(args.connector);
|
|
}
|
|
}
|
|
|
|
class EnabledState extends State {
|
|
@Override
|
|
public void enter() {
|
|
sendNsdStateChangeBroadcast(true);
|
|
}
|
|
|
|
@Override
|
|
public void exit() {
|
|
// TODO: it is incorrect to stop the daemon without expunging all requests
|
|
// and sending error callbacks to clients.
|
|
scheduleStop();
|
|
}
|
|
|
|
private boolean requestLimitReached(ClientInfo clientInfo) {
|
|
if (clientInfo.mClientRequests.size() >= ClientInfo.MAX_LIMIT) {
|
|
if (DBG) Log.d(TAG, "Exceeded max outstanding requests " + clientInfo);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private ClientRequest storeLegacyRequestMap(int clientRequestId, int transactionId,
|
|
ClientInfo clientInfo, int what, long startTimeMs) {
|
|
final LegacyClientRequest request =
|
|
new LegacyClientRequest(transactionId, what, startTimeMs);
|
|
clientInfo.mClientRequests.put(clientRequestId, request);
|
|
mTransactionIdToClientInfoMap.put(transactionId, clientInfo);
|
|
// Remove the cleanup event because here comes a new request.
|
|
cancelStop();
|
|
return request;
|
|
}
|
|
|
|
private void storeAdvertiserRequestMap(int clientRequestId, int transactionId,
|
|
ClientInfo clientInfo, @Nullable Network requestedNetwork) {
|
|
clientInfo.mClientRequests.put(clientRequestId, new AdvertiserClientRequest(
|
|
transactionId, requestedNetwork, mClock.elapsedRealtime()));
|
|
mTransactionIdToClientInfoMap.put(transactionId, clientInfo);
|
|
updateMulticastLock();
|
|
}
|
|
|
|
private void removeRequestMap(
|
|
int clientRequestId, int transactionId, ClientInfo clientInfo) {
|
|
final ClientRequest existing = clientInfo.mClientRequests.get(clientRequestId);
|
|
if (existing == null) return;
|
|
clientInfo.mClientRequests.remove(clientRequestId);
|
|
mTransactionIdToClientInfoMap.remove(transactionId);
|
|
|
|
if (existing instanceof LegacyClientRequest) {
|
|
maybeScheduleStop();
|
|
} else {
|
|
maybeStopMonitoringSocketsIfNoActiveRequest();
|
|
updateMulticastLock();
|
|
}
|
|
}
|
|
|
|
private ClientRequest storeDiscoveryManagerRequestMap(int clientRequestId,
|
|
int transactionId, MdnsListener listener, ClientInfo clientInfo,
|
|
@Nullable Network requestedNetwork) {
|
|
final DiscoveryManagerRequest request = new DiscoveryManagerRequest(transactionId,
|
|
listener, requestedNetwork, mClock.elapsedRealtime());
|
|
clientInfo.mClientRequests.put(clientRequestId, request);
|
|
mTransactionIdToClientInfoMap.put(transactionId, clientInfo);
|
|
updateMulticastLock();
|
|
return request;
|
|
}
|
|
|
|
/**
|
|
* Truncate a service name to up to 63 UTF-8 bytes.
|
|
*
|
|
* See RFC6763 4.1.1: service instance names are UTF-8 and up to 63 bytes. Truncating
|
|
* names used in registerService follows historical behavior (see mdnsresponder
|
|
* handle_regservice_request).
|
|
*/
|
|
@NonNull
|
|
private String truncateServiceName(@NonNull String originalName) {
|
|
return MdnsUtils.truncateServiceName(originalName, MAX_LABEL_LENGTH);
|
|
}
|
|
|
|
private void stopDiscoveryManagerRequest(ClientRequest request, int clientRequestId,
|
|
int transactionId, ClientInfo clientInfo) {
|
|
clientInfo.unregisterMdnsListenerFromRequest(request);
|
|
removeRequestMap(clientRequestId, transactionId, clientInfo);
|
|
}
|
|
|
|
@Override
|
|
public boolean processMessage(Message msg) {
|
|
final ClientInfo clientInfo;
|
|
final int transactionId;
|
|
final int clientRequestId = msg.arg2;
|
|
final ListenerArgs args;
|
|
final OffloadEngineInfo offloadEngineInfo;
|
|
switch (msg.what) {
|
|
case NsdManager.DISCOVER_SERVICES: {
|
|
if (DBG) Log.d(TAG, "Discover services");
|
|
args = (ListenerArgs) msg.obj;
|
|
clientInfo = mClients.get(args.connector);
|
|
// If the binder death notification for a INsdManagerCallback was received
|
|
// before any calls are received by NsdService, the clientInfo would be
|
|
// cleared and cause NPE. Add a null check here to prevent this corner case.
|
|
if (clientInfo == null) {
|
|
Log.e(TAG, "Unknown connector in discovery");
|
|
break;
|
|
}
|
|
|
|
if (requestLimitReached(clientInfo)) {
|
|
clientInfo.onDiscoverServicesFailedImmediately(clientRequestId,
|
|
NsdManager.FAILURE_MAX_LIMIT, true /* isLegacy */);
|
|
break;
|
|
}
|
|
|
|
final NsdServiceInfo info = args.serviceInfo;
|
|
transactionId = getUniqueId();
|
|
final Pair<String, String> typeAndSubtype =
|
|
parseTypeAndSubtype(info.getServiceType());
|
|
final String serviceType = typeAndSubtype == null
|
|
? null : typeAndSubtype.first;
|
|
if (clientInfo.mUseJavaBackend
|
|
|| mDeps.isMdnsDiscoveryManagerEnabled(mContext)
|
|
|| useDiscoveryManagerForType(serviceType)) {
|
|
if (serviceType == null) {
|
|
clientInfo.onDiscoverServicesFailedImmediately(clientRequestId,
|
|
NsdManager.FAILURE_INTERNAL_ERROR, false /* isLegacy */);
|
|
break;
|
|
}
|
|
|
|
final String listenServiceType = serviceType + ".local";
|
|
maybeStartMonitoringSockets();
|
|
final MdnsListener listener = new DiscoveryListener(clientRequestId,
|
|
transactionId, info, listenServiceType);
|
|
final MdnsSearchOptions.Builder optionsBuilder =
|
|
MdnsSearchOptions.newBuilder()
|
|
.setNetwork(info.getNetwork())
|
|
.setRemoveExpiredService(true)
|
|
.setIsPassiveMode(true);
|
|
if (typeAndSubtype.second != null) {
|
|
// The parsing ensures subtype starts with an underscore.
|
|
// MdnsSearchOptions expects the underscore to not be present.
|
|
optionsBuilder.addSubtype(typeAndSubtype.second.substring(1));
|
|
}
|
|
mMdnsDiscoveryManager.registerListener(
|
|
listenServiceType, listener, optionsBuilder.build());
|
|
final ClientRequest request = storeDiscoveryManagerRequestMap(
|
|
clientRequestId, transactionId, listener, clientInfo,
|
|
info.getNetwork());
|
|
clientInfo.onDiscoverServicesStarted(clientRequestId, info, request);
|
|
clientInfo.log("Register a DiscoveryListener " + transactionId
|
|
+ " for service type:" + listenServiceType);
|
|
} else {
|
|
maybeStartDaemon();
|
|
if (discoverServices(transactionId, info)) {
|
|
if (DBG) {
|
|
Log.d(TAG, "Discover " + msg.arg2 + " " + transactionId
|
|
+ info.getServiceType());
|
|
}
|
|
final ClientRequest request = storeLegacyRequestMap(clientRequestId,
|
|
transactionId, clientInfo, msg.what,
|
|
mClock.elapsedRealtime());
|
|
clientInfo.onDiscoverServicesStarted(
|
|
clientRequestId, info, request);
|
|
} else {
|
|
stopServiceDiscovery(transactionId);
|
|
clientInfo.onDiscoverServicesFailedImmediately(clientRequestId,
|
|
NsdManager.FAILURE_INTERNAL_ERROR, true /* isLegacy */);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case NsdManager.STOP_DISCOVERY: {
|
|
if (DBG) Log.d(TAG, "Stop service discovery");
|
|
args = (ListenerArgs) msg.obj;
|
|
clientInfo = mClients.get(args.connector);
|
|
// If the binder death notification for a INsdManagerCallback was received
|
|
// before any calls are received by NsdService, the clientInfo would be
|
|
// cleared and cause NPE. Add a null check here to prevent this corner case.
|
|
if (clientInfo == null) {
|
|
Log.e(TAG, "Unknown connector in stop discovery");
|
|
break;
|
|
}
|
|
|
|
final ClientRequest request =
|
|
clientInfo.mClientRequests.get(clientRequestId);
|
|
if (request == null) {
|
|
Log.e(TAG, "Unknown client request in STOP_DISCOVERY");
|
|
break;
|
|
}
|
|
transactionId = request.mTransactionId;
|
|
// Note isMdnsDiscoveryManagerEnabled may have changed to false at this
|
|
// point, so this needs to check the type of the original request to
|
|
// unregister instead of looking at the flag value.
|
|
if (request instanceof DiscoveryManagerRequest) {
|
|
stopDiscoveryManagerRequest(
|
|
request, clientRequestId, transactionId, clientInfo);
|
|
clientInfo.onStopDiscoverySucceeded(clientRequestId, request);
|
|
clientInfo.log("Unregister the DiscoveryListener " + transactionId);
|
|
} else {
|
|
removeRequestMap(clientRequestId, transactionId, clientInfo);
|
|
if (stopServiceDiscovery(transactionId)) {
|
|
clientInfo.onStopDiscoverySucceeded(clientRequestId, request);
|
|
} else {
|
|
clientInfo.onStopDiscoveryFailed(
|
|
clientRequestId, NsdManager.FAILURE_INTERNAL_ERROR);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case NsdManager.REGISTER_SERVICE: {
|
|
if (DBG) Log.d(TAG, "Register service");
|
|
args = (ListenerArgs) msg.obj;
|
|
clientInfo = mClients.get(args.connector);
|
|
// If the binder death notification for a INsdManagerCallback was received
|
|
// before any calls are received by NsdService, the clientInfo would be
|
|
// cleared and cause NPE. Add a null check here to prevent this corner case.
|
|
if (clientInfo == null) {
|
|
Log.e(TAG, "Unknown connector in registration");
|
|
break;
|
|
}
|
|
|
|
if (requestLimitReached(clientInfo)) {
|
|
clientInfo.onRegisterServiceFailedImmediately(clientRequestId,
|
|
NsdManager.FAILURE_MAX_LIMIT, true /* isLegacy */);
|
|
break;
|
|
}
|
|
|
|
transactionId = getUniqueId();
|
|
final NsdServiceInfo serviceInfo = args.serviceInfo;
|
|
final String serviceType = serviceInfo.getServiceType();
|
|
final Pair<String, String> typeSubtype = parseTypeAndSubtype(serviceType);
|
|
final String registerServiceType = typeSubtype == null
|
|
? null : typeSubtype.first;
|
|
if (clientInfo.mUseJavaBackend
|
|
|| mDeps.isMdnsAdvertiserEnabled(mContext)
|
|
|| useAdvertiserForType(registerServiceType)) {
|
|
if (registerServiceType == null) {
|
|
Log.e(TAG, "Invalid service type: " + serviceType);
|
|
clientInfo.onRegisterServiceFailedImmediately(clientRequestId,
|
|
NsdManager.FAILURE_INTERNAL_ERROR, false /* isLegacy */);
|
|
break;
|
|
}
|
|
serviceInfo.setServiceType(registerServiceType);
|
|
serviceInfo.setServiceName(truncateServiceName(
|
|
serviceInfo.getServiceName()));
|
|
|
|
maybeStartMonitoringSockets();
|
|
// TODO: pass in the subtype as well. Including the subtype in the
|
|
// service type would generate service instance names like
|
|
// Name._subtype._sub._type._tcp, which is incorrect
|
|
// (it should be Name._type._tcp).
|
|
mAdvertiser.addService(transactionId, serviceInfo, typeSubtype.second);
|
|
storeAdvertiserRequestMap(clientRequestId, transactionId, clientInfo,
|
|
serviceInfo.getNetwork());
|
|
} else {
|
|
maybeStartDaemon();
|
|
if (registerService(transactionId, serviceInfo)) {
|
|
if (DBG) {
|
|
Log.d(TAG, "Register " + clientRequestId
|
|
+ " " + transactionId);
|
|
}
|
|
storeLegacyRequestMap(clientRequestId, transactionId, clientInfo,
|
|
msg.what, mClock.elapsedRealtime());
|
|
// Return success after mDns reports success
|
|
} else {
|
|
unregisterService(transactionId);
|
|
clientInfo.onRegisterServiceFailedImmediately(clientRequestId,
|
|
NsdManager.FAILURE_INTERNAL_ERROR, true /* isLegacy */);
|
|
}
|
|
|
|
}
|
|
break;
|
|
}
|
|
case NsdManager.UNREGISTER_SERVICE: {
|
|
if (DBG) Log.d(TAG, "unregister service");
|
|
args = (ListenerArgs) msg.obj;
|
|
clientInfo = mClients.get(args.connector);
|
|
// If the binder death notification for a INsdManagerCallback was received
|
|
// before any calls are received by NsdService, the clientInfo would be
|
|
// cleared and cause NPE. Add a null check here to prevent this corner case.
|
|
if (clientInfo == null) {
|
|
Log.e(TAG, "Unknown connector in unregistration");
|
|
break;
|
|
}
|
|
final ClientRequest request =
|
|
clientInfo.mClientRequests.get(clientRequestId);
|
|
if (request == null) {
|
|
Log.e(TAG, "Unknown client request in UNREGISTER_SERVICE");
|
|
break;
|
|
}
|
|
transactionId = request.mTransactionId;
|
|
removeRequestMap(clientRequestId, transactionId, clientInfo);
|
|
|
|
// Note isMdnsAdvertiserEnabled may have changed to false at this point,
|
|
// so this needs to check the type of the original request to unregister
|
|
// instead of looking at the flag value.
|
|
if (request instanceof AdvertiserClientRequest) {
|
|
final AdvertiserMetrics metrics =
|
|
mAdvertiser.getAdvertiserMetrics(transactionId);
|
|
mAdvertiser.removeService(transactionId);
|
|
clientInfo.onUnregisterServiceSucceeded(
|
|
clientRequestId, request, metrics);
|
|
} else {
|
|
if (unregisterService(transactionId)) {
|
|
clientInfo.onUnregisterServiceSucceeded(clientRequestId, request,
|
|
new AdvertiserMetrics(NO_PACKET /* repliedRequestsCount */,
|
|
NO_PACKET /* sentPacketCount */,
|
|
0 /* conflictDuringProbingCount */,
|
|
0 /* conflictAfterProbingCount */));
|
|
} else {
|
|
clientInfo.onUnregisterServiceFailed(
|
|
clientRequestId, NsdManager.FAILURE_INTERNAL_ERROR);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case NsdManager.RESOLVE_SERVICE: {
|
|
if (DBG) Log.d(TAG, "Resolve service");
|
|
args = (ListenerArgs) msg.obj;
|
|
clientInfo = mClients.get(args.connector);
|
|
// If the binder death notification for a INsdManagerCallback was received
|
|
// before any calls are received by NsdService, the clientInfo would be
|
|
// cleared and cause NPE. Add a null check here to prevent this corner case.
|
|
if (clientInfo == null) {
|
|
Log.e(TAG, "Unknown connector in resolution");
|
|
break;
|
|
}
|
|
|
|
final NsdServiceInfo info = args.serviceInfo;
|
|
transactionId = getUniqueId();
|
|
final Pair<String, String> typeSubtype =
|
|
parseTypeAndSubtype(info.getServiceType());
|
|
final String serviceType = typeSubtype == null
|
|
? null : typeSubtype.first;
|
|
if (clientInfo.mUseJavaBackend
|
|
|| mDeps.isMdnsDiscoveryManagerEnabled(mContext)
|
|
|| useDiscoveryManagerForType(serviceType)) {
|
|
if (serviceType == null) {
|
|
clientInfo.onResolveServiceFailedImmediately(clientRequestId,
|
|
NsdManager.FAILURE_INTERNAL_ERROR, false /* isLegacy */);
|
|
break;
|
|
}
|
|
final String resolveServiceType = serviceType + ".local";
|
|
|
|
maybeStartMonitoringSockets();
|
|
final MdnsListener listener = new ResolutionListener(clientRequestId,
|
|
transactionId, info, resolveServiceType);
|
|
final MdnsSearchOptions options = MdnsSearchOptions.newBuilder()
|
|
.setNetwork(info.getNetwork())
|
|
.setIsPassiveMode(true)
|
|
.setResolveInstanceName(info.getServiceName())
|
|
.setRemoveExpiredService(true)
|
|
.build();
|
|
mMdnsDiscoveryManager.registerListener(
|
|
resolveServiceType, listener, options);
|
|
storeDiscoveryManagerRequestMap(clientRequestId, transactionId,
|
|
listener, clientInfo, info.getNetwork());
|
|
clientInfo.log("Register a ResolutionListener " + transactionId
|
|
+ " for service type:" + resolveServiceType);
|
|
} else {
|
|
if (clientInfo.mResolvedService != null) {
|
|
clientInfo.onResolveServiceFailedImmediately(clientRequestId,
|
|
NsdManager.FAILURE_ALREADY_ACTIVE, true /* isLegacy */);
|
|
break;
|
|
}
|
|
|
|
maybeStartDaemon();
|
|
if (resolveService(transactionId, info)) {
|
|
clientInfo.mResolvedService = new NsdServiceInfo();
|
|
storeLegacyRequestMap(clientRequestId, transactionId, clientInfo,
|
|
msg.what, mClock.elapsedRealtime());
|
|
} else {
|
|
clientInfo.onResolveServiceFailedImmediately(clientRequestId,
|
|
NsdManager.FAILURE_INTERNAL_ERROR, true /* isLegacy */);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case NsdManager.STOP_RESOLUTION: {
|
|
if (DBG) Log.d(TAG, "Stop service resolution");
|
|
args = (ListenerArgs) msg.obj;
|
|
clientInfo = mClients.get(args.connector);
|
|
// If the binder death notification for a INsdManagerCallback was received
|
|
// before any calls are received by NsdService, the clientInfo would be
|
|
// cleared and cause NPE. Add a null check here to prevent this corner case.
|
|
if (clientInfo == null) {
|
|
Log.e(TAG, "Unknown connector in stop resolution");
|
|
break;
|
|
}
|
|
|
|
final ClientRequest request =
|
|
clientInfo.mClientRequests.get(clientRequestId);
|
|
if (request == null) {
|
|
Log.e(TAG, "Unknown client request in STOP_RESOLUTION");
|
|
break;
|
|
}
|
|
transactionId = request.mTransactionId;
|
|
// Note isMdnsDiscoveryManagerEnabled may have changed to false at this
|
|
// point, so this needs to check the type of the original request to
|
|
// unregister instead of looking at the flag value.
|
|
if (request instanceof DiscoveryManagerRequest) {
|
|
stopDiscoveryManagerRequest(
|
|
request, clientRequestId, transactionId, clientInfo);
|
|
clientInfo.onStopResolutionSucceeded(clientRequestId, request);
|
|
clientInfo.log("Unregister the ResolutionListener " + transactionId);
|
|
} else {
|
|
removeRequestMap(clientRequestId, transactionId, clientInfo);
|
|
if (stopResolveService(transactionId)) {
|
|
clientInfo.onStopResolutionSucceeded(clientRequestId, request);
|
|
} else {
|
|
clientInfo.onStopResolutionFailed(
|
|
clientRequestId, NsdManager.FAILURE_OPERATION_NOT_RUNNING);
|
|
}
|
|
clientInfo.mResolvedService = null;
|
|
}
|
|
break;
|
|
}
|
|
case NsdManager.REGISTER_SERVICE_CALLBACK: {
|
|
if (DBG) Log.d(TAG, "Register a service callback");
|
|
args = (ListenerArgs) msg.obj;
|
|
clientInfo = mClients.get(args.connector);
|
|
// If the binder death notification for a INsdManagerCallback was received
|
|
// before any calls are received by NsdService, the clientInfo would be
|
|
// cleared and cause NPE. Add a null check here to prevent this corner case.
|
|
if (clientInfo == null) {
|
|
Log.e(TAG, "Unknown connector in callback registration");
|
|
break;
|
|
}
|
|
|
|
final NsdServiceInfo info = args.serviceInfo;
|
|
transactionId = getUniqueId();
|
|
final Pair<String, String> typeAndSubtype =
|
|
parseTypeAndSubtype(info.getServiceType());
|
|
final String serviceType = typeAndSubtype == null
|
|
? null : typeAndSubtype.first;
|
|
if (serviceType == null) {
|
|
clientInfo.onServiceInfoCallbackRegistrationFailed(clientRequestId,
|
|
NsdManager.FAILURE_BAD_PARAMETERS);
|
|
break;
|
|
}
|
|
final String resolveServiceType = serviceType + ".local";
|
|
|
|
maybeStartMonitoringSockets();
|
|
final MdnsListener listener = new ServiceInfoListener(clientRequestId,
|
|
transactionId, info, resolveServiceType);
|
|
final MdnsSearchOptions options = MdnsSearchOptions.newBuilder()
|
|
.setNetwork(info.getNetwork())
|
|
.setIsPassiveMode(true)
|
|
.setResolveInstanceName(info.getServiceName())
|
|
.setRemoveExpiredService(true)
|
|
.build();
|
|
mMdnsDiscoveryManager.registerListener(
|
|
resolveServiceType, listener, options);
|
|
storeDiscoveryManagerRequestMap(clientRequestId, transactionId, listener,
|
|
clientInfo, info.getNetwork());
|
|
clientInfo.onServiceInfoCallbackRegistered(transactionId);
|
|
clientInfo.log("Register a ServiceInfoListener " + transactionId
|
|
+ " for service type:" + resolveServiceType);
|
|
break;
|
|
}
|
|
case NsdManager.UNREGISTER_SERVICE_CALLBACK: {
|
|
if (DBG) Log.d(TAG, "Unregister a service callback");
|
|
args = (ListenerArgs) msg.obj;
|
|
clientInfo = mClients.get(args.connector);
|
|
// If the binder death notification for a INsdManagerCallback was received
|
|
// before any calls are received by NsdService, the clientInfo would be
|
|
// cleared and cause NPE. Add a null check here to prevent this corner case.
|
|
if (clientInfo == null) {
|
|
Log.e(TAG, "Unknown connector in callback unregistration");
|
|
break;
|
|
}
|
|
|
|
final ClientRequest request =
|
|
clientInfo.mClientRequests.get(clientRequestId);
|
|
if (request == null) {
|
|
Log.e(TAG, "Unknown client request in UNREGISTER_SERVICE_CALLBACK");
|
|
break;
|
|
}
|
|
transactionId = request.mTransactionId;
|
|
if (request instanceof DiscoveryManagerRequest) {
|
|
stopDiscoveryManagerRequest(
|
|
request, clientRequestId, transactionId, clientInfo);
|
|
clientInfo.onServiceInfoCallbackUnregistered(clientRequestId, request);
|
|
clientInfo.log("Unregister the ServiceInfoListener " + transactionId);
|
|
} else {
|
|
loge("Unregister failed with non-DiscoveryManagerRequest.");
|
|
}
|
|
break;
|
|
}
|
|
case MDNS_SERVICE_EVENT:
|
|
if (!handleMDnsServiceEvent(msg.arg1, msg.arg2, msg.obj)) {
|
|
return NOT_HANDLED;
|
|
}
|
|
break;
|
|
case MDNS_DISCOVERY_MANAGER_EVENT:
|
|
if (!handleMdnsDiscoveryManagerEvent(msg.arg1, msg.arg2, msg.obj)) {
|
|
return NOT_HANDLED;
|
|
}
|
|
break;
|
|
case NsdManager.REGISTER_OFFLOAD_ENGINE:
|
|
offloadEngineInfo = (OffloadEngineInfo) msg.obj;
|
|
// TODO: Limits the number of registrations created by a given class.
|
|
mOffloadEngines.register(offloadEngineInfo.mOffloadEngine,
|
|
offloadEngineInfo);
|
|
sendAllOffloadServiceInfos(offloadEngineInfo);
|
|
break;
|
|
case NsdManager.UNREGISTER_OFFLOAD_ENGINE:
|
|
mOffloadEngines.unregister((IOffloadEngine) msg.obj);
|
|
break;
|
|
default:
|
|
return NOT_HANDLED;
|
|
}
|
|
return HANDLED;
|
|
}
|
|
|
|
private boolean handleMDnsServiceEvent(int code, int transactionId, Object obj) {
|
|
NsdServiceInfo servInfo;
|
|
ClientInfo clientInfo = mTransactionIdToClientInfoMap.get(transactionId);
|
|
if (clientInfo == null) {
|
|
Log.e(TAG, String.format(
|
|
"transactionId %d for %d has no client mapping", transactionId, code));
|
|
return false;
|
|
}
|
|
|
|
/* This goes in response as msg.arg2 */
|
|
int clientRequestId = clientInfo.getClientRequestId(transactionId);
|
|
if (clientRequestId < 0) {
|
|
// This can happen because of race conditions. For example,
|
|
// SERVICE_FOUND may race with STOP_SERVICE_DISCOVERY,
|
|
// and we may get in this situation.
|
|
Log.d(TAG, String.format("%d for transactionId %d that is no longer active",
|
|
code, transactionId));
|
|
return false;
|
|
}
|
|
final ClientRequest request = clientInfo.mClientRequests.get(clientRequestId);
|
|
if (request == null) {
|
|
Log.e(TAG, "Unknown client request. clientRequestId=" + clientRequestId);
|
|
return false;
|
|
}
|
|
if (DBG) {
|
|
Log.d(TAG, String.format(
|
|
"MDns service event code:%d transactionId=%d", code, transactionId));
|
|
}
|
|
switch (code) {
|
|
case IMDnsEventListener.SERVICE_FOUND: {
|
|
final DiscoveryInfo info = (DiscoveryInfo) obj;
|
|
final String name = info.serviceName;
|
|
final String type = info.registrationType;
|
|
servInfo = new NsdServiceInfo(name, type);
|
|
final int foundNetId = info.netId;
|
|
if (foundNetId == 0L) {
|
|
// Ignore services that do not have a Network: they are not usable
|
|
// by apps, as they would need privileged permissions to use
|
|
// interfaces that do not have an associated Network.
|
|
break;
|
|
}
|
|
if (foundNetId == INetd.DUMMY_NET_ID) {
|
|
// Ignore services on the dummy0 interface: they are only seen when
|
|
// discovering locally advertised services, and are not reachable
|
|
// through that interface.
|
|
break;
|
|
}
|
|
setServiceNetworkForCallback(servInfo, info.netId, info.interfaceIdx);
|
|
|
|
clientInfo.onServiceFound(clientRequestId, servInfo, request);
|
|
break;
|
|
}
|
|
case IMDnsEventListener.SERVICE_LOST: {
|
|
final DiscoveryInfo info = (DiscoveryInfo) obj;
|
|
final String name = info.serviceName;
|
|
final String type = info.registrationType;
|
|
final int lostNetId = info.netId;
|
|
servInfo = new NsdServiceInfo(name, type);
|
|
// The network could be set to null (netId 0) if it was torn down when the
|
|
// service is lost
|
|
// TODO: avoid returning null in that case, possibly by remembering
|
|
// found services on the same interface index and their network at the time
|
|
setServiceNetworkForCallback(servInfo, lostNetId, info.interfaceIdx);
|
|
clientInfo.onServiceLost(clientRequestId, servInfo, request);
|
|
break;
|
|
}
|
|
case IMDnsEventListener.SERVICE_DISCOVERY_FAILED:
|
|
clientInfo.onDiscoverServicesFailed(clientRequestId,
|
|
NsdManager.FAILURE_INTERNAL_ERROR, true /* isLegacy */,
|
|
transactionId,
|
|
request.calculateRequestDurationMs(mClock.elapsedRealtime()));
|
|
break;
|
|
case IMDnsEventListener.SERVICE_REGISTERED: {
|
|
final RegistrationInfo info = (RegistrationInfo) obj;
|
|
final String name = info.serviceName;
|
|
servInfo = new NsdServiceInfo(name, null /* serviceType */);
|
|
clientInfo.onRegisterServiceSucceeded(clientRequestId, servInfo, request);
|
|
break;
|
|
}
|
|
case IMDnsEventListener.SERVICE_REGISTRATION_FAILED:
|
|
clientInfo.onRegisterServiceFailed(clientRequestId,
|
|
NsdManager.FAILURE_INTERNAL_ERROR, true /* isLegacy */,
|
|
transactionId,
|
|
request.calculateRequestDurationMs(mClock.elapsedRealtime()));
|
|
break;
|
|
case IMDnsEventListener.SERVICE_RESOLVED: {
|
|
final ResolutionInfo info = (ResolutionInfo) obj;
|
|
int index = 0;
|
|
final String fullName = info.serviceFullName;
|
|
while (index < fullName.length() && fullName.charAt(index) != '.') {
|
|
if (fullName.charAt(index) == '\\') {
|
|
++index;
|
|
}
|
|
++index;
|
|
}
|
|
if (index >= fullName.length()) {
|
|
Log.e(TAG, "Invalid service found " + fullName);
|
|
break;
|
|
}
|
|
|
|
String name = unescape(fullName.substring(0, index));
|
|
String rest = fullName.substring(index);
|
|
String type = rest.replace(".local.", "");
|
|
|
|
final NsdServiceInfo serviceInfo = clientInfo.mResolvedService;
|
|
serviceInfo.setServiceName(name);
|
|
serviceInfo.setServiceType(type);
|
|
serviceInfo.setPort(info.port);
|
|
serviceInfo.setTxtRecords(info.txtRecord);
|
|
// Network will be added after SERVICE_GET_ADDR_SUCCESS
|
|
|
|
stopResolveService(transactionId);
|
|
removeRequestMap(clientRequestId, transactionId, clientInfo);
|
|
|
|
final int transactionId2 = getUniqueId();
|
|
if (getAddrInfo(transactionId2, info.hostname, info.interfaceIdx)) {
|
|
storeLegacyRequestMap(clientRequestId, transactionId2, clientInfo,
|
|
NsdManager.RESOLVE_SERVICE, request.mStartTimeMs);
|
|
} else {
|
|
clientInfo.onResolveServiceFailed(clientRequestId,
|
|
NsdManager.FAILURE_INTERNAL_ERROR, true /* isLegacy */,
|
|
transactionId,
|
|
request.calculateRequestDurationMs(mClock.elapsedRealtime()));
|
|
clientInfo.mResolvedService = null;
|
|
}
|
|
break;
|
|
}
|
|
case IMDnsEventListener.SERVICE_RESOLUTION_FAILED:
|
|
/* NNN resolveId errorCode */
|
|
stopResolveService(transactionId);
|
|
removeRequestMap(clientRequestId, transactionId, clientInfo);
|
|
clientInfo.onResolveServiceFailed(clientRequestId,
|
|
NsdManager.FAILURE_INTERNAL_ERROR, true /* isLegacy */,
|
|
transactionId,
|
|
request.calculateRequestDurationMs(mClock.elapsedRealtime()));
|
|
clientInfo.mResolvedService = null;
|
|
break;
|
|
case IMDnsEventListener.SERVICE_GET_ADDR_FAILED:
|
|
/* NNN resolveId errorCode */
|
|
stopGetAddrInfo(transactionId);
|
|
removeRequestMap(clientRequestId, transactionId, clientInfo);
|
|
clientInfo.onResolveServiceFailed(clientRequestId,
|
|
NsdManager.FAILURE_INTERNAL_ERROR, true /* isLegacy */,
|
|
transactionId,
|
|
request.calculateRequestDurationMs(mClock.elapsedRealtime()));
|
|
clientInfo.mResolvedService = null;
|
|
break;
|
|
case IMDnsEventListener.SERVICE_GET_ADDR_SUCCESS: {
|
|
/* NNN resolveId hostname ttl addr interfaceIdx netId */
|
|
final GetAddressInfo info = (GetAddressInfo) obj;
|
|
final String address = info.address;
|
|
final int netId = info.netId;
|
|
InetAddress serviceHost = null;
|
|
try {
|
|
serviceHost = InetAddress.getByName(address);
|
|
} catch (UnknownHostException e) {
|
|
Log.wtf(TAG, "Invalid host in GET_ADDR_SUCCESS", e);
|
|
}
|
|
|
|
// If the resolved service is on an interface without a network, consider it
|
|
// as a failure: it would not be usable by apps as they would need
|
|
// privileged permissions.
|
|
if (netId != NETID_UNSET && serviceHost != null) {
|
|
clientInfo.mResolvedService.setHost(serviceHost);
|
|
setServiceNetworkForCallback(clientInfo.mResolvedService,
|
|
netId, info.interfaceIdx);
|
|
clientInfo.onResolveServiceSucceeded(
|
|
clientRequestId, clientInfo.mResolvedService, request);
|
|
} else {
|
|
clientInfo.onResolveServiceFailed(clientRequestId,
|
|
NsdManager.FAILURE_INTERNAL_ERROR, true /* isLegacy */,
|
|
transactionId,
|
|
request.calculateRequestDurationMs(mClock.elapsedRealtime()));
|
|
}
|
|
stopGetAddrInfo(transactionId);
|
|
removeRequestMap(clientRequestId, transactionId, clientInfo);
|
|
clientInfo.mResolvedService = null;
|
|
break;
|
|
}
|
|
default:
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@Nullable
|
|
private NsdServiceInfo buildNsdServiceInfoFromMdnsEvent(
|
|
final MdnsEvent event, int code) {
|
|
final MdnsServiceInfo serviceInfo = event.mMdnsServiceInfo;
|
|
final String[] typeArray = serviceInfo.getServiceType();
|
|
final String joinedType;
|
|
if (typeArray.length == 0
|
|
|| !typeArray[typeArray.length - 1].equals(LOCAL_DOMAIN_NAME)) {
|
|
Log.wtf(TAG, "MdnsServiceInfo type does not end in .local: "
|
|
+ Arrays.toString(typeArray));
|
|
return null;
|
|
} else {
|
|
joinedType = TextUtils.join(".",
|
|
Arrays.copyOfRange(typeArray, 0, typeArray.length - 1));
|
|
}
|
|
final String serviceType;
|
|
switch (code) {
|
|
case NsdManager.SERVICE_FOUND:
|
|
case NsdManager.SERVICE_LOST:
|
|
// For consistency with historical behavior, discovered service types have
|
|
// a dot at the end.
|
|
serviceType = joinedType + ".";
|
|
break;
|
|
case RESOLVE_SERVICE_SUCCEEDED:
|
|
// For consistency with historical behavior, resolved service types have
|
|
// a dot at the beginning.
|
|
serviceType = "." + joinedType;
|
|
break;
|
|
default:
|
|
serviceType = joinedType;
|
|
break;
|
|
}
|
|
final String serviceName = serviceInfo.getServiceInstanceName();
|
|
final NsdServiceInfo servInfo = new NsdServiceInfo(serviceName, serviceType);
|
|
final Network network = serviceInfo.getNetwork();
|
|
// In MdnsDiscoveryManagerEvent, the Network can be null which means it is a
|
|
// network for Tethering interface. In other words, the network == null means the
|
|
// network has netId = INetd.LOCAL_NET_ID.
|
|
setServiceNetworkForCallback(
|
|
servInfo,
|
|
network == null ? INetd.LOCAL_NET_ID : network.netId,
|
|
serviceInfo.getInterfaceIndex());
|
|
return servInfo;
|
|
}
|
|
|
|
private boolean handleMdnsDiscoveryManagerEvent(
|
|
int transactionId, int code, Object obj) {
|
|
final ClientInfo clientInfo = mTransactionIdToClientInfoMap.get(transactionId);
|
|
if (clientInfo == null) {
|
|
Log.e(TAG, String.format(
|
|
"id %d for %d has no client mapping", transactionId, code));
|
|
return false;
|
|
}
|
|
|
|
final MdnsEvent event = (MdnsEvent) obj;
|
|
final int clientRequestId = event.mClientRequestId;
|
|
final ClientRequest request = clientInfo.mClientRequests.get(clientRequestId);
|
|
if (request == null) {
|
|
Log.e(TAG, "Unknown client request. clientRequestId=" + clientRequestId);
|
|
return false;
|
|
}
|
|
|
|
// Deal with the discovery sent callback
|
|
if (code == DISCOVERY_QUERY_SENT_CALLBACK) {
|
|
request.onQuerySent();
|
|
return true;
|
|
}
|
|
|
|
// Deal with other callbacks.
|
|
final NsdServiceInfo info = buildNsdServiceInfoFromMdnsEvent(event, code);
|
|
// Errors are already logged if null
|
|
if (info == null) return false;
|
|
mServiceLogs.log(String.format(
|
|
"MdnsDiscoveryManager event code=%s transactionId=%d",
|
|
NsdManager.nameOf(code), transactionId));
|
|
switch (code) {
|
|
case NsdManager.SERVICE_FOUND:
|
|
clientInfo.onServiceFound(clientRequestId, info, request);
|
|
break;
|
|
case NsdManager.SERVICE_LOST:
|
|
clientInfo.onServiceLost(clientRequestId, info, request);
|
|
break;
|
|
case NsdManager.RESOLVE_SERVICE_SUCCEEDED: {
|
|
final MdnsServiceInfo serviceInfo = event.mMdnsServiceInfo;
|
|
info.setPort(serviceInfo.getPort());
|
|
|
|
Map<String, String> attrs = serviceInfo.getAttributes();
|
|
for (Map.Entry<String, String> kv : attrs.entrySet()) {
|
|
final String key = kv.getKey();
|
|
try {
|
|
info.setAttribute(key, serviceInfo.getAttributeAsBytes(key));
|
|
} catch (IllegalArgumentException e) {
|
|
Log.e(TAG, "Invalid attribute", e);
|
|
}
|
|
}
|
|
final List<InetAddress> addresses = getInetAddresses(serviceInfo);
|
|
if (addresses.size() != 0) {
|
|
info.setHostAddresses(addresses);
|
|
request.setServiceFromCache(event.mIsServiceFromCache);
|
|
clientInfo.onResolveServiceSucceeded(clientRequestId, info, request);
|
|
} else {
|
|
// No address. Notify resolution failure.
|
|
clientInfo.onResolveServiceFailed(clientRequestId,
|
|
NsdManager.FAILURE_INTERNAL_ERROR, false /* isLegacy */,
|
|
transactionId,
|
|
request.calculateRequestDurationMs(mClock.elapsedRealtime()));
|
|
}
|
|
|
|
// Unregister the listener immediately like IMDnsEventListener design
|
|
if (!(request instanceof DiscoveryManagerRequest)) {
|
|
Log.wtf(TAG, "non-DiscoveryManager request in DiscoveryManager event");
|
|
break;
|
|
}
|
|
stopDiscoveryManagerRequest(
|
|
request, clientRequestId, transactionId, clientInfo);
|
|
break;
|
|
}
|
|
case NsdManager.SERVICE_UPDATED: {
|
|
final MdnsServiceInfo serviceInfo = event.mMdnsServiceInfo;
|
|
info.setPort(serviceInfo.getPort());
|
|
|
|
Map<String, String> attrs = serviceInfo.getAttributes();
|
|
for (Map.Entry<String, String> kv : attrs.entrySet()) {
|
|
final String key = kv.getKey();
|
|
try {
|
|
info.setAttribute(key, serviceInfo.getAttributeAsBytes(key));
|
|
} catch (IllegalArgumentException e) {
|
|
Log.e(TAG, "Invalid attribute", e);
|
|
}
|
|
}
|
|
|
|
final List<InetAddress> addresses = getInetAddresses(serviceInfo);
|
|
info.setHostAddresses(addresses);
|
|
clientInfo.onServiceUpdated(clientRequestId, info, request);
|
|
// Set the ServiceFromCache flag only if the service is actually being
|
|
// retrieved from the cache. This flag should not be overridden by later
|
|
// service updates, which may not be cached.
|
|
if (event.mIsServiceFromCache) {
|
|
request.setServiceFromCache(true);
|
|
}
|
|
break;
|
|
}
|
|
case NsdManager.SERVICE_UPDATED_LOST:
|
|
clientInfo.onServiceUpdatedLost(clientRequestId, request);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
@NonNull
|
|
private static List<InetAddress> getInetAddresses(@NonNull MdnsServiceInfo serviceInfo) {
|
|
final List<String> v4Addrs = serviceInfo.getIpv4Addresses();
|
|
final List<String> v6Addrs = serviceInfo.getIpv6Addresses();
|
|
final List<InetAddress> addresses = new ArrayList<>(v4Addrs.size() + v6Addrs.size());
|
|
for (String ipv4Address : v4Addrs) {
|
|
try {
|
|
addresses.add(InetAddresses.parseNumericAddress(ipv4Address));
|
|
} catch (IllegalArgumentException e) {
|
|
Log.wtf(TAG, "Invalid ipv4 address", e);
|
|
}
|
|
}
|
|
for (String ipv6Address : v6Addrs) {
|
|
try {
|
|
final Inet6Address addr = (Inet6Address) InetAddresses.parseNumericAddress(
|
|
ipv6Address);
|
|
addresses.add(InetAddressUtils.withScopeId(addr, serviceInfo.getInterfaceIndex()));
|
|
} catch (IllegalArgumentException e) {
|
|
Log.wtf(TAG, "Invalid ipv6 address", e);
|
|
}
|
|
}
|
|
return addresses;
|
|
}
|
|
|
|
private static void setServiceNetworkForCallback(NsdServiceInfo info, int netId, int ifaceIdx) {
|
|
switch (netId) {
|
|
case NETID_UNSET:
|
|
info.setNetwork(null);
|
|
break;
|
|
case INetd.LOCAL_NET_ID:
|
|
// Special case for LOCAL_NET_ID: Networks on netId 99 are not generally
|
|
// visible / usable for apps, so do not return it. Store the interface
|
|
// index instead, so at least if the client tries to resolve the service
|
|
// with that NsdServiceInfo, it will be done on the same interface.
|
|
// If they recreate the NsdServiceInfo themselves, resolution would be
|
|
// done on all interfaces as before T, which should also work.
|
|
info.setNetwork(null);
|
|
info.setInterfaceIndex(ifaceIdx);
|
|
break;
|
|
default:
|
|
info.setNetwork(new Network(netId));
|
|
}
|
|
}
|
|
|
|
// The full service name is escaped from standard DNS rules on mdnsresponder, making it suitable
|
|
// for passing to standard system DNS APIs such as res_query() . Thus, make the service name
|
|
// unescape for getting right service address. See "Notes on DNS Name Escaping" on
|
|
// external/mdnsresponder/mDNSShared/dns_sd.h for more details.
|
|
private String unescape(String s) {
|
|
StringBuilder sb = new StringBuilder(s.length());
|
|
for (int i = 0; i < s.length(); ++i) {
|
|
char c = s.charAt(i);
|
|
if (c == '\\') {
|
|
if (++i >= s.length()) {
|
|
Log.e(TAG, "Unexpected end of escape sequence in: " + s);
|
|
break;
|
|
}
|
|
c = s.charAt(i);
|
|
if (c != '.' && c != '\\') {
|
|
if (i + 2 >= s.length()) {
|
|
Log.e(TAG, "Unexpected end of escape sequence in: " + s);
|
|
break;
|
|
}
|
|
c = (char) ((c - '0') * 100 + (s.charAt(i + 1) - '0') * 10
|
|
+ (s.charAt(i + 2) - '0'));
|
|
i += 2;
|
|
}
|
|
}
|
|
sb.append(c);
|
|
}
|
|
return sb.toString();
|
|
}
|
|
|
|
/**
|
|
* Check the given service type is valid and construct it to a service type
|
|
* which can use for discovery / resolution service.
|
|
*
|
|
* <p>The valid service type should be 2 labels, or 3 labels if the query is for a
|
|
* subtype (see RFC6763 7.1). Each label is up to 63 characters and must start with an
|
|
* underscore; they are alphanumerical characters or dashes or underscore, except the
|
|
* last one that is just alphanumerical. The last label must be _tcp or _udp.
|
|
*
|
|
* <p>The subtype may also be specified with a comma after the service type, for example
|
|
* _type._tcp,_subtype.
|
|
*
|
|
* @param serviceType the request service type for discovery / resolution service
|
|
* @return constructed service type or null if the given service type is invalid.
|
|
*/
|
|
@Nullable
|
|
public static Pair<String, String> parseTypeAndSubtype(String serviceType) {
|
|
if (TextUtils.isEmpty(serviceType)) return null;
|
|
|
|
final String typeOrSubtypePattern = "_[a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]";
|
|
final Pattern serviceTypePattern = Pattern.compile(
|
|
// Optional leading subtype (_subtype._type._tcp)
|
|
// (?: xxx) is a non-capturing parenthesis, don't capture the dot
|
|
"^(?:(" + typeOrSubtypePattern + ")\\.)?"
|
|
// Actual type (_type._tcp.local)
|
|
+ "(" + typeOrSubtypePattern + "\\._(?:tcp|udp))"
|
|
// Drop '.' at the end of service type that is compatible with old backend.
|
|
// e.g. allow "_type._tcp.local."
|
|
+ "\\.?"
|
|
// Optional subtype after comma, for "_type._tcp,_subtype" format
|
|
+ "(?:,(" + typeOrSubtypePattern + "))?"
|
|
+ "$");
|
|
final Matcher matcher = serviceTypePattern.matcher(serviceType);
|
|
if (!matcher.matches()) return null;
|
|
// Use the subtype either at the beginning or after the comma
|
|
final String subtype = matcher.group(1) != null ? matcher.group(1) : matcher.group(3);
|
|
return new Pair<>(matcher.group(2), subtype);
|
|
}
|
|
|
|
@VisibleForTesting
|
|
NsdService(Context ctx, Handler handler, long cleanupDelayMs) {
|
|
this(ctx, handler, cleanupDelayMs, new Dependencies());
|
|
}
|
|
|
|
@VisibleForTesting
|
|
NsdService(Context ctx, Handler handler, long cleanupDelayMs, Dependencies deps) {
|
|
mCleanupDelayMs = cleanupDelayMs;
|
|
mContext = ctx;
|
|
mNsdStateMachine = new NsdStateMachine(TAG, handler);
|
|
mNsdStateMachine.start();
|
|
mMDnsManager = ctx.getSystemService(MDnsManager.class);
|
|
mMDnsEventCallback = new MDnsEventCallback(mNsdStateMachine);
|
|
mDeps = deps;
|
|
|
|
mMdnsSocketProvider = deps.makeMdnsSocketProvider(ctx, handler.getLooper(),
|
|
LOGGER.forSubComponent("MdnsSocketProvider"), new SocketRequestMonitor());
|
|
// Netlink monitor starts on boot, and intentionally never stopped, to ensure that all
|
|
// address events are received.
|
|
handler.post(mMdnsSocketProvider::startNetLinkMonitor);
|
|
|
|
// NsdService is started after ActivityManager (startOtherServices in SystemServer, vs.
|
|
// startBootstrapServices).
|
|
mRunningAppActiveImportanceCutoff = mDeps.getDeviceConfigInt(
|
|
MDNS_CONFIG_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF,
|
|
DEFAULT_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF);
|
|
final ActivityManager am = ctx.getSystemService(ActivityManager.class);
|
|
am.addOnUidImportanceListener(new UidImportanceListener(handler),
|
|
mRunningAppActiveImportanceCutoff);
|
|
|
|
mMdnsSocketClient =
|
|
new MdnsMultinetworkSocketClient(handler.getLooper(), mMdnsSocketProvider,
|
|
LOGGER.forSubComponent("MdnsMultinetworkSocketClient"));
|
|
mMdnsDiscoveryManager = deps.makeMdnsDiscoveryManager(new ExecutorProvider(),
|
|
mMdnsSocketClient, LOGGER.forSubComponent("MdnsDiscoveryManager"));
|
|
handler.post(() -> mMdnsSocketClient.setCallback(mMdnsDiscoveryManager));
|
|
MdnsFeatureFlags flags = new MdnsFeatureFlags.Builder().setIsMdnsOffloadFeatureEnabled(
|
|
mDeps.isTetheringFeatureNotChickenedOut(
|
|
MdnsFeatureFlags.NSD_FORCE_DISABLE_MDNS_OFFLOAD)).build();
|
|
mAdvertiser = deps.makeMdnsAdvertiser(handler.getLooper(), mMdnsSocketProvider,
|
|
new AdvertiserCallback(), LOGGER.forSubComponent("MdnsAdvertiser"), flags);
|
|
mClock = deps.makeClock();
|
|
}
|
|
|
|
/**
|
|
* Dependencies of NsdService, for injection in tests.
|
|
*/
|
|
@VisibleForTesting
|
|
public static class Dependencies {
|
|
/**
|
|
* Check whether the MdnsDiscoveryManager feature is enabled.
|
|
*
|
|
* @param context The global context information about an app environment.
|
|
* @return true if the MdnsDiscoveryManager feature is enabled.
|
|
*/
|
|
public boolean isMdnsDiscoveryManagerEnabled(Context context) {
|
|
return isAtLeastU() || DeviceConfigUtils.isTetheringFeatureEnabled(context,
|
|
MDNS_DISCOVERY_MANAGER_VERSION);
|
|
}
|
|
|
|
/**
|
|
* Check whether the MdnsAdvertiser feature is enabled.
|
|
*
|
|
* @param context The global context information about an app environment.
|
|
* @return true if the MdnsAdvertiser feature is enabled.
|
|
*/
|
|
public boolean isMdnsAdvertiserEnabled(Context context) {
|
|
return isAtLeastU() || DeviceConfigUtils.isTetheringFeatureEnabled(context,
|
|
MDNS_ADVERTISER_VERSION);
|
|
}
|
|
|
|
/**
|
|
* Get the type allowlist flag value.
|
|
* @see #MDNS_TYPE_ALLOWLIST_FLAGS
|
|
*/
|
|
@Nullable
|
|
public String getTypeAllowlistFlags() {
|
|
return DeviceConfigUtils.getDeviceConfigProperty(NAMESPACE_TETHERING,
|
|
MDNS_TYPE_ALLOWLIST_FLAGS, null);
|
|
}
|
|
|
|
/**
|
|
* @see DeviceConfigUtils#isTetheringFeatureEnabled
|
|
*/
|
|
public boolean isFeatureEnabled(Context context, String feature) {
|
|
return DeviceConfigUtils.isTetheringFeatureEnabled(context, feature);
|
|
}
|
|
|
|
/**
|
|
* @see DeviceConfigUtils#isTetheringFeatureNotChickenedOut
|
|
*/
|
|
public boolean isTetheringFeatureNotChickenedOut(String feature) {
|
|
return DeviceConfigUtils.isTetheringFeatureNotChickenedOut(feature);
|
|
}
|
|
|
|
/**
|
|
* @see MdnsDiscoveryManager
|
|
*/
|
|
public MdnsDiscoveryManager makeMdnsDiscoveryManager(
|
|
@NonNull ExecutorProvider executorProvider,
|
|
@NonNull MdnsMultinetworkSocketClient socketClient, @NonNull SharedLog sharedLog) {
|
|
return new MdnsDiscoveryManager(executorProvider, socketClient, sharedLog);
|
|
}
|
|
|
|
/**
|
|
* @see MdnsAdvertiser
|
|
*/
|
|
public MdnsAdvertiser makeMdnsAdvertiser(
|
|
@NonNull Looper looper, @NonNull MdnsSocketProvider socketProvider,
|
|
@NonNull MdnsAdvertiser.AdvertiserCallback cb, @NonNull SharedLog sharedLog,
|
|
MdnsFeatureFlags featureFlags) {
|
|
return new MdnsAdvertiser(looper, socketProvider, cb, sharedLog, featureFlags);
|
|
}
|
|
|
|
/**
|
|
* @see MdnsSocketProvider
|
|
*/
|
|
public MdnsSocketProvider makeMdnsSocketProvider(@NonNull Context context,
|
|
@NonNull Looper looper, @NonNull SharedLog sharedLog,
|
|
@NonNull MdnsSocketProvider.SocketRequestMonitor socketCreationCallback) {
|
|
return new MdnsSocketProvider(context, looper, sharedLog, socketCreationCallback);
|
|
}
|
|
|
|
/**
|
|
* @see DeviceConfig#getInt(String, String, int)
|
|
*/
|
|
public int getDeviceConfigInt(@NonNull String config, int defaultValue) {
|
|
return DeviceConfig.getInt(NAMESPACE_TETHERING, config, defaultValue);
|
|
}
|
|
|
|
/**
|
|
* @see Binder#getCallingUid()
|
|
*/
|
|
public int getCallingUid() {
|
|
return Binder.getCallingUid();
|
|
}
|
|
|
|
/**
|
|
* @see NetworkNsdReportedMetrics
|
|
*/
|
|
public NetworkNsdReportedMetrics makeNetworkNsdReportedMetrics(int clientId) {
|
|
return new NetworkNsdReportedMetrics(clientId);
|
|
}
|
|
|
|
/**
|
|
* @see MdnsUtils.Clock
|
|
*/
|
|
public Clock makeClock() {
|
|
return new Clock();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return whether a type is allowlisted to use the Java backend.
|
|
* @param type The service type
|
|
* @param flagPrefix One of {@link #MDNS_ADVERTISER_ALLOWLIST_FLAG_PREFIX} or
|
|
* {@link #MDNS_DISCOVERY_MANAGER_ALLOWLIST_FLAG_PREFIX}.
|
|
*/
|
|
private boolean isTypeAllowlistedForJavaBackend(@Nullable String type,
|
|
@NonNull String flagPrefix) {
|
|
if (type == null) return false;
|
|
final String typesConfig = mDeps.getTypeAllowlistFlags();
|
|
if (TextUtils.isEmpty(typesConfig)) return false;
|
|
|
|
final String mappingPrefix = type + ":";
|
|
String mappedFlag = null;
|
|
for (String mapping : TextUtils.split(typesConfig, ",")) {
|
|
if (mapping.startsWith(mappingPrefix)) {
|
|
mappedFlag = mapping.substring(mappingPrefix.length());
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (mappedFlag == null) return false;
|
|
|
|
return mDeps.isFeatureEnabled(mContext,
|
|
flagPrefix + mappedFlag + MDNS_ALLOWLIST_FLAG_SUFFIX);
|
|
}
|
|
|
|
private boolean useDiscoveryManagerForType(@Nullable String type) {
|
|
return isTypeAllowlistedForJavaBackend(type, MDNS_DISCOVERY_MANAGER_ALLOWLIST_FLAG_PREFIX);
|
|
}
|
|
|
|
private boolean useAdvertiserForType(@Nullable String type) {
|
|
return isTypeAllowlistedForJavaBackend(type, MDNS_ADVERTISER_ALLOWLIST_FLAG_PREFIX);
|
|
}
|
|
|
|
public static NsdService create(Context context) {
|
|
HandlerThread thread = new HandlerThread(TAG);
|
|
thread.start();
|
|
Handler handler = new Handler(thread.getLooper());
|
|
NsdService service = new NsdService(context, handler, CLEANUP_DELAY_MS);
|
|
return service;
|
|
}
|
|
|
|
private static class MDnsEventCallback extends IMDnsEventListener.Stub {
|
|
private final StateMachine mStateMachine;
|
|
|
|
MDnsEventCallback(StateMachine sm) {
|
|
mStateMachine = sm;
|
|
}
|
|
|
|
@Override
|
|
public void onServiceRegistrationStatus(final RegistrationInfo status) {
|
|
mStateMachine.sendMessage(
|
|
MDNS_SERVICE_EVENT, status.result, status.id, status);
|
|
}
|
|
|
|
@Override
|
|
public void onServiceDiscoveryStatus(final DiscoveryInfo status) {
|
|
mStateMachine.sendMessage(
|
|
MDNS_SERVICE_EVENT, status.result, status.id, status);
|
|
}
|
|
|
|
@Override
|
|
public void onServiceResolutionStatus(final ResolutionInfo status) {
|
|
mStateMachine.sendMessage(
|
|
MDNS_SERVICE_EVENT, status.result, status.id, status);
|
|
}
|
|
|
|
@Override
|
|
public void onGettingServiceAddressStatus(final GetAddressInfo status) {
|
|
mStateMachine.sendMessage(
|
|
MDNS_SERVICE_EVENT, status.result, status.id, status);
|
|
}
|
|
|
|
@Override
|
|
public int getInterfaceVersion() throws RemoteException {
|
|
return this.VERSION;
|
|
}
|
|
|
|
@Override
|
|
public String getInterfaceHash() throws RemoteException {
|
|
return this.HASH;
|
|
}
|
|
}
|
|
|
|
private void sendAllOffloadServiceInfos(@NonNull OffloadEngineInfo offloadEngineInfo) {
|
|
final String targetInterface = offloadEngineInfo.mInterfaceName;
|
|
final IOffloadEngine offloadEngine = offloadEngineInfo.mOffloadEngine;
|
|
final List<MdnsAdvertiser.OffloadServiceInfoWrapper> offloadWrappers =
|
|
mAdvertiser.getAllInterfaceOffloadServiceInfos(targetInterface);
|
|
for (MdnsAdvertiser.OffloadServiceInfoWrapper wrapper : offloadWrappers) {
|
|
try {
|
|
offloadEngine.onOffloadServiceUpdated(wrapper.mOffloadServiceInfo);
|
|
} catch (RemoteException e) {
|
|
// Can happen in regular cases, do not log a stacktrace
|
|
Log.i(TAG, "Failed to send offload callback, remote died: " + e.getMessage());
|
|
}
|
|
}
|
|
}
|
|
|
|
private void sendOffloadServiceInfosUpdate(@NonNull String targetInterfaceName,
|
|
@NonNull OffloadServiceInfo offloadServiceInfo, boolean isRemove) {
|
|
final int count = mOffloadEngines.beginBroadcast();
|
|
try {
|
|
for (int i = 0; i < count; i++) {
|
|
final OffloadEngineInfo offloadEngineInfo =
|
|
(OffloadEngineInfo) mOffloadEngines.getBroadcastCookie(i);
|
|
final String interfaceName = offloadEngineInfo.mInterfaceName;
|
|
if (!targetInterfaceName.equals(interfaceName)
|
|
|| ((offloadEngineInfo.mOffloadType
|
|
& offloadServiceInfo.getOffloadType()) == 0)) {
|
|
continue;
|
|
}
|
|
try {
|
|
if (isRemove) {
|
|
mOffloadEngines.getBroadcastItem(i).onOffloadServiceRemoved(
|
|
offloadServiceInfo);
|
|
} else {
|
|
mOffloadEngines.getBroadcastItem(i).onOffloadServiceUpdated(
|
|
offloadServiceInfo);
|
|
}
|
|
} catch (RemoteException e) {
|
|
// Can happen in regular cases, do not log a stacktrace
|
|
Log.i(TAG, "Failed to send offload callback, remote died: " + e.getMessage());
|
|
}
|
|
}
|
|
} finally {
|
|
mOffloadEngines.finishBroadcast();
|
|
}
|
|
}
|
|
|
|
private class AdvertiserCallback implements MdnsAdvertiser.AdvertiserCallback {
|
|
// TODO: add a callback to notify when a service is being added on each interface (as soon
|
|
// as probing starts), and call mOffloadCallbacks. This callback is for
|
|
// OFFLOAD_CAPABILITY_FILTER_REPLIES offload type.
|
|
|
|
@Override
|
|
public void onRegisterServiceSucceeded(int transactionId, NsdServiceInfo registeredInfo) {
|
|
mServiceLogs.log("onRegisterServiceSucceeded: transactionId " + transactionId);
|
|
final ClientInfo clientInfo = getClientInfoOrLog(transactionId);
|
|
if (clientInfo == null) return;
|
|
|
|
final int clientRequestId = getClientRequestIdOrLog(clientInfo, transactionId);
|
|
if (clientRequestId < 0) return;
|
|
|
|
// onRegisterServiceSucceeded only has the service name in its info. This aligns with
|
|
// historical behavior.
|
|
final NsdServiceInfo cbInfo = new NsdServiceInfo(registeredInfo.getServiceName(), null);
|
|
final ClientRequest request = clientInfo.mClientRequests.get(clientRequestId);
|
|
clientInfo.onRegisterServiceSucceeded(clientRequestId, cbInfo, request);
|
|
}
|
|
|
|
@Override
|
|
public void onRegisterServiceFailed(int transactionId, int errorCode) {
|
|
final ClientInfo clientInfo = getClientInfoOrLog(transactionId);
|
|
if (clientInfo == null) return;
|
|
|
|
final int clientRequestId = getClientRequestIdOrLog(clientInfo, transactionId);
|
|
if (clientRequestId < 0) return;
|
|
final ClientRequest request = clientInfo.mClientRequests.get(clientRequestId);
|
|
clientInfo.onRegisterServiceFailed(clientRequestId, errorCode, false /* isLegacy */,
|
|
transactionId, request.calculateRequestDurationMs(mClock.elapsedRealtime()));
|
|
}
|
|
|
|
@Override
|
|
public void onOffloadStartOrUpdate(@NonNull String interfaceName,
|
|
@NonNull OffloadServiceInfo offloadServiceInfo) {
|
|
sendOffloadServiceInfosUpdate(interfaceName, offloadServiceInfo, false /* isRemove */);
|
|
}
|
|
|
|
@Override
|
|
public void onOffloadStop(@NonNull String interfaceName,
|
|
@NonNull OffloadServiceInfo offloadServiceInfo) {
|
|
sendOffloadServiceInfosUpdate(interfaceName, offloadServiceInfo, true /* isRemove */);
|
|
}
|
|
|
|
private ClientInfo getClientInfoOrLog(int transactionId) {
|
|
final ClientInfo clientInfo = mTransactionIdToClientInfoMap.get(transactionId);
|
|
if (clientInfo == null) {
|
|
Log.e(TAG, String.format("Callback for service %d has no client", transactionId));
|
|
}
|
|
return clientInfo;
|
|
}
|
|
|
|
private int getClientRequestIdOrLog(@NonNull ClientInfo info, int transactionId) {
|
|
final int clientRequestId = info.getClientRequestId(transactionId);
|
|
if (clientRequestId < 0) {
|
|
Log.e(TAG, String.format(
|
|
"Client request ID not found for service %d", transactionId));
|
|
}
|
|
return clientRequestId;
|
|
}
|
|
}
|
|
|
|
private static class ConnectorArgs {
|
|
@NonNull public final NsdServiceConnector connector;
|
|
@NonNull public final INsdManagerCallback callback;
|
|
public final boolean useJavaBackend;
|
|
public final int uid;
|
|
|
|
ConnectorArgs(@NonNull NsdServiceConnector connector, @NonNull INsdManagerCallback callback,
|
|
boolean useJavaBackend, int uid) {
|
|
this.connector = connector;
|
|
this.callback = callback;
|
|
this.useJavaBackend = useJavaBackend;
|
|
this.uid = uid;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public INsdServiceConnector connect(INsdManagerCallback cb, boolean useJavaBackend) {
|
|
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET, "NsdService");
|
|
final int uid = mDeps.getCallingUid();
|
|
if (cb == null) {
|
|
throw new IllegalArgumentException("Unknown client callback from uid=" + uid);
|
|
}
|
|
if (DBG) Log.d(TAG, "New client connect. useJavaBackend=" + useJavaBackend);
|
|
final INsdServiceConnector connector = new NsdServiceConnector();
|
|
mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(NsdManager.REGISTER_CLIENT,
|
|
new ConnectorArgs((NsdServiceConnector) connector, cb, useJavaBackend, uid)));
|
|
return connector;
|
|
}
|
|
|
|
private static class ListenerArgs {
|
|
public final NsdServiceConnector connector;
|
|
public final NsdServiceInfo serviceInfo;
|
|
ListenerArgs(NsdServiceConnector connector, NsdServiceInfo serviceInfo) {
|
|
this.connector = connector;
|
|
this.serviceInfo = serviceInfo;
|
|
}
|
|
}
|
|
|
|
private class NsdServiceConnector extends INsdServiceConnector.Stub
|
|
implements IBinder.DeathRecipient {
|
|
@Override
|
|
public void registerService(int listenerKey, NsdServiceInfo serviceInfo) {
|
|
mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
|
|
NsdManager.REGISTER_SERVICE, 0, listenerKey,
|
|
new ListenerArgs(this, serviceInfo)));
|
|
}
|
|
|
|
@Override
|
|
public void unregisterService(int listenerKey) {
|
|
mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
|
|
NsdManager.UNREGISTER_SERVICE, 0, listenerKey,
|
|
new ListenerArgs(this, null)));
|
|
}
|
|
|
|
@Override
|
|
public void discoverServices(int listenerKey, NsdServiceInfo serviceInfo) {
|
|
mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
|
|
NsdManager.DISCOVER_SERVICES, 0, listenerKey,
|
|
new ListenerArgs(this, serviceInfo)));
|
|
}
|
|
|
|
@Override
|
|
public void stopDiscovery(int listenerKey) {
|
|
mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
|
|
NsdManager.STOP_DISCOVERY, 0, listenerKey, new ListenerArgs(this, null)));
|
|
}
|
|
|
|
@Override
|
|
public void resolveService(int listenerKey, NsdServiceInfo serviceInfo) {
|
|
mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
|
|
NsdManager.RESOLVE_SERVICE, 0, listenerKey,
|
|
new ListenerArgs(this, serviceInfo)));
|
|
}
|
|
|
|
@Override
|
|
public void stopResolution(int listenerKey) {
|
|
mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
|
|
NsdManager.STOP_RESOLUTION, 0, listenerKey, new ListenerArgs(this, null)));
|
|
}
|
|
|
|
@Override
|
|
public void registerServiceInfoCallback(int listenerKey, NsdServiceInfo serviceInfo) {
|
|
mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
|
|
NsdManager.REGISTER_SERVICE_CALLBACK, 0, listenerKey,
|
|
new ListenerArgs(this, serviceInfo)));
|
|
}
|
|
|
|
@Override
|
|
public void unregisterServiceInfoCallback(int listenerKey) {
|
|
mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
|
|
NsdManager.UNREGISTER_SERVICE_CALLBACK, 0, listenerKey,
|
|
new ListenerArgs(this, null)));
|
|
}
|
|
|
|
@Override
|
|
public void startDaemon() {
|
|
mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
|
|
NsdManager.DAEMON_STARTUP, new ListenerArgs(this, null)));
|
|
}
|
|
|
|
@Override
|
|
public void binderDied() {
|
|
mNsdStateMachine.sendMessage(
|
|
mNsdStateMachine.obtainMessage(NsdManager.UNREGISTER_CLIENT, this));
|
|
|
|
}
|
|
|
|
@Override
|
|
public void registerOffloadEngine(String ifaceName, IOffloadEngine cb,
|
|
@OffloadEngine.OffloadCapability long offloadCapabilities,
|
|
@OffloadEngine.OffloadType long offloadTypes) {
|
|
checkOffloadEnginePermission(mContext);
|
|
Objects.requireNonNull(ifaceName);
|
|
Objects.requireNonNull(cb);
|
|
mNsdStateMachine.sendMessage(
|
|
mNsdStateMachine.obtainMessage(NsdManager.REGISTER_OFFLOAD_ENGINE,
|
|
new OffloadEngineInfo(cb, ifaceName, offloadCapabilities,
|
|
offloadTypes)));
|
|
}
|
|
|
|
@Override
|
|
public void unregisterOffloadEngine(IOffloadEngine cb) {
|
|
checkOffloadEnginePermission(mContext);
|
|
Objects.requireNonNull(cb);
|
|
mNsdStateMachine.sendMessage(
|
|
mNsdStateMachine.obtainMessage(NsdManager.UNREGISTER_OFFLOAD_ENGINE, cb));
|
|
}
|
|
|
|
private static void checkOffloadEnginePermission(Context context) {
|
|
if (!SdkLevel.isAtLeastT()) {
|
|
throw new SecurityException("API is not available in before API level 33");
|
|
}
|
|
// REGISTER_NSD_OFFLOAD_ENGINE was only added to the SDK in V, but may
|
|
// be back ported to older builds: accept it as long as it's signature-protected
|
|
if (PermissionUtils.checkAnyPermissionOf(context, REGISTER_NSD_OFFLOAD_ENGINE)
|
|
&& (SdkLevel.isAtLeastV() || PermissionUtils.isSystemSignaturePermission(
|
|
context, REGISTER_NSD_OFFLOAD_ENGINE))) {
|
|
return;
|
|
}
|
|
if (PermissionUtils.checkAnyPermissionOf(context, NETWORK_STACK,
|
|
PERMISSION_MAINLINE_NETWORK_STACK, NETWORK_SETTINGS)) {
|
|
return;
|
|
}
|
|
throw new SecurityException("Requires one of the following permissions: "
|
|
+ String.join(", ", List.of(REGISTER_NSD_OFFLOAD_ENGINE, NETWORK_STACK,
|
|
PERMISSION_MAINLINE_NETWORK_STACK, NETWORK_SETTINGS)) + ".");
|
|
}
|
|
}
|
|
|
|
private void sendNsdStateChangeBroadcast(boolean isEnabled) {
|
|
final Intent intent = new Intent(NsdManager.ACTION_NSD_STATE_CHANGED);
|
|
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
|
|
int nsdState = isEnabled ? NsdManager.NSD_STATE_ENABLED : NsdManager.NSD_STATE_DISABLED;
|
|
intent.putExtra(NsdManager.EXTRA_NSD_STATE, nsdState);
|
|
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
|
|
}
|
|
|
|
private int getUniqueId() {
|
|
if (++mUniqueId == INVALID_ID) return ++mUniqueId;
|
|
return mUniqueId;
|
|
}
|
|
|
|
private boolean registerService(int transactionId, NsdServiceInfo service) {
|
|
if (DBG) {
|
|
Log.d(TAG, "registerService: " + transactionId + " " + service);
|
|
}
|
|
String name = service.getServiceName();
|
|
String type = service.getServiceType();
|
|
int port = service.getPort();
|
|
byte[] textRecord = service.getTxtRecord();
|
|
final int registerInterface = getNetworkInterfaceIndex(service);
|
|
if (service.getNetwork() != null && registerInterface == IFACE_IDX_ANY) {
|
|
Log.e(TAG, "Interface to register service on not found");
|
|
return false;
|
|
}
|
|
return mMDnsManager.registerService(
|
|
transactionId, name, type, port, textRecord, registerInterface);
|
|
}
|
|
|
|
private boolean unregisterService(int transactionId) {
|
|
return mMDnsManager.stopOperation(transactionId);
|
|
}
|
|
|
|
private boolean discoverServices(int transactionId, NsdServiceInfo serviceInfo) {
|
|
final String type = serviceInfo.getServiceType();
|
|
final int discoverInterface = getNetworkInterfaceIndex(serviceInfo);
|
|
if (serviceInfo.getNetwork() != null && discoverInterface == IFACE_IDX_ANY) {
|
|
Log.e(TAG, "Interface to discover service on not found");
|
|
return false;
|
|
}
|
|
return mMDnsManager.discover(transactionId, type, discoverInterface);
|
|
}
|
|
|
|
private boolean stopServiceDiscovery(int transactionId) {
|
|
return mMDnsManager.stopOperation(transactionId);
|
|
}
|
|
|
|
private boolean resolveService(int transactionId, NsdServiceInfo service) {
|
|
final String name = service.getServiceName();
|
|
final String type = service.getServiceType();
|
|
final int resolveInterface = getNetworkInterfaceIndex(service);
|
|
if (service.getNetwork() != null && resolveInterface == IFACE_IDX_ANY) {
|
|
Log.e(TAG, "Interface to resolve service on not found");
|
|
return false;
|
|
}
|
|
return mMDnsManager.resolve(transactionId, name, type, "local.", resolveInterface);
|
|
}
|
|
|
|
/**
|
|
* Guess the interface to use to resolve or discover a service on a specific network.
|
|
*
|
|
* This is an imperfect guess, as for example the network may be gone or not yet fully
|
|
* registered. This is fine as failing is correct if the network is gone, and a client
|
|
* attempting to resolve/discover on a network not yet setup would have a bad time anyway; also
|
|
* this is to support the legacy mdnsresponder implementation, which historically resolved
|
|
* services on an unspecified network.
|
|
*/
|
|
private int getNetworkInterfaceIndex(NsdServiceInfo serviceInfo) {
|
|
final Network network = serviceInfo.getNetwork();
|
|
if (network == null) {
|
|
// Fallback to getInterfaceIndex if present (typically if the NsdServiceInfo was
|
|
// provided by NsdService from discovery results, and the service was found on an
|
|
// interface that has no app-usable Network).
|
|
if (serviceInfo.getInterfaceIndex() != 0) {
|
|
return serviceInfo.getInterfaceIndex();
|
|
}
|
|
return IFACE_IDX_ANY;
|
|
}
|
|
|
|
String interfaceName = getNetworkInterfaceName(network);
|
|
if (interfaceName == null) {
|
|
return IFACE_IDX_ANY;
|
|
}
|
|
return getNetworkInterfaceIndexByName(interfaceName);
|
|
}
|
|
|
|
private String getNetworkInterfaceName(@Nullable Network network) {
|
|
if (network == null) {
|
|
return null;
|
|
}
|
|
final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
|
|
if (cm == null) {
|
|
Log.wtf(TAG, "No ConnectivityManager");
|
|
return null;
|
|
}
|
|
final LinkProperties lp = cm.getLinkProperties(network);
|
|
if (lp == null) {
|
|
return null;
|
|
}
|
|
// Only resolve on non-stacked interfaces
|
|
return lp.getInterfaceName();
|
|
}
|
|
|
|
private int getNetworkInterfaceIndexByName(final String ifaceName) {
|
|
final NetworkInterface iface;
|
|
try {
|
|
iface = NetworkInterface.getByName(ifaceName);
|
|
} catch (SocketException e) {
|
|
Log.e(TAG, "Error querying interface", e);
|
|
return IFACE_IDX_ANY;
|
|
}
|
|
|
|
if (iface == null) {
|
|
Log.e(TAG, "Interface not found: " + ifaceName);
|
|
return IFACE_IDX_ANY;
|
|
}
|
|
|
|
return iface.getIndex();
|
|
}
|
|
|
|
private boolean stopResolveService(int transactionId) {
|
|
return mMDnsManager.stopOperation(transactionId);
|
|
}
|
|
|
|
private boolean getAddrInfo(int transactionId, String hostname, int interfaceIdx) {
|
|
return mMDnsManager.getServiceAddress(transactionId, hostname, interfaceIdx);
|
|
}
|
|
|
|
private boolean stopGetAddrInfo(int transactionId) {
|
|
return mMDnsManager.stopOperation(transactionId);
|
|
}
|
|
|
|
@Override
|
|
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
|
|
if (!PermissionUtils.checkDumpPermission(mContext, TAG, writer)) return;
|
|
|
|
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
|
|
// Dump state machine logs
|
|
mNsdStateMachine.dump(fd, pw, args);
|
|
|
|
// Dump service and clients logs
|
|
pw.println();
|
|
pw.println("Logs:");
|
|
pw.increaseIndent();
|
|
mServiceLogs.reverseDump(pw);
|
|
pw.decreaseIndent();
|
|
}
|
|
|
|
private abstract static class ClientRequest {
|
|
private final int mTransactionId;
|
|
private final long mStartTimeMs;
|
|
private int mFoundServiceCount = 0;
|
|
private int mLostServiceCount = 0;
|
|
private final Set<String> mServices = new ArraySet<>();
|
|
private boolean mIsServiceFromCache = false;
|
|
private int mSentQueryCount = NO_SENT_QUERY_COUNT;
|
|
|
|
private ClientRequest(int transactionId, long startTimeMs) {
|
|
mTransactionId = transactionId;
|
|
mStartTimeMs = startTimeMs;
|
|
}
|
|
|
|
public long calculateRequestDurationMs(long stopTimeMs) {
|
|
return stopTimeMs - mStartTimeMs;
|
|
}
|
|
|
|
public void onServiceFound(String serviceName) {
|
|
mFoundServiceCount++;
|
|
if (mServices.size() <= MAX_SERVICES_COUNT_METRIC_PER_CLIENT) {
|
|
mServices.add(serviceName);
|
|
}
|
|
}
|
|
|
|
public void onServiceLost() {
|
|
mLostServiceCount++;
|
|
}
|
|
|
|
public int getFoundServiceCount() {
|
|
return mFoundServiceCount;
|
|
}
|
|
|
|
public int getLostServiceCount() {
|
|
return mLostServiceCount;
|
|
}
|
|
|
|
public int getServicesCount() {
|
|
return mServices.size();
|
|
}
|
|
|
|
public void setServiceFromCache(boolean isServiceFromCache) {
|
|
mIsServiceFromCache = isServiceFromCache;
|
|
}
|
|
|
|
public boolean isServiceFromCache() {
|
|
return mIsServiceFromCache;
|
|
}
|
|
|
|
public void onQuerySent() {
|
|
mSentQueryCount++;
|
|
}
|
|
|
|
public int getSentQueryCount() {
|
|
return mSentQueryCount;
|
|
}
|
|
}
|
|
|
|
private static class LegacyClientRequest extends ClientRequest {
|
|
private final int mRequestCode;
|
|
|
|
private LegacyClientRequest(int transactionId, int requestCode, long startTimeMs) {
|
|
super(transactionId, startTimeMs);
|
|
mRequestCode = requestCode;
|
|
}
|
|
}
|
|
|
|
private abstract static class JavaBackendClientRequest extends ClientRequest {
|
|
@Nullable
|
|
private final Network mRequestedNetwork;
|
|
|
|
private JavaBackendClientRequest(int transactionId, @Nullable Network requestedNetwork,
|
|
long startTimeMs) {
|
|
super(transactionId, startTimeMs);
|
|
mRequestedNetwork = requestedNetwork;
|
|
}
|
|
|
|
@Nullable
|
|
public Network getRequestedNetwork() {
|
|
return mRequestedNetwork;
|
|
}
|
|
}
|
|
|
|
private static class AdvertiserClientRequest extends JavaBackendClientRequest {
|
|
private AdvertiserClientRequest(int transactionId, @Nullable Network requestedNetwork,
|
|
long startTimeMs) {
|
|
super(transactionId, requestedNetwork, startTimeMs);
|
|
}
|
|
}
|
|
|
|
private static class DiscoveryManagerRequest extends JavaBackendClientRequest {
|
|
@NonNull
|
|
private final MdnsListener mListener;
|
|
|
|
private DiscoveryManagerRequest(int transactionId, @NonNull MdnsListener listener,
|
|
@Nullable Network requestedNetwork, long startTimeMs) {
|
|
super(transactionId, requestedNetwork, startTimeMs);
|
|
mListener = listener;
|
|
}
|
|
}
|
|
|
|
/* Information tracked per client */
|
|
private class ClientInfo {
|
|
|
|
private static final int MAX_LIMIT = 10;
|
|
private final INsdManagerCallback mCb;
|
|
/* Remembers a resolved service until getaddrinfo completes */
|
|
private NsdServiceInfo mResolvedService;
|
|
|
|
/* A map from client request ID (listenerKey) to the request */
|
|
private final SparseArray<ClientRequest> mClientRequests = new SparseArray<>();
|
|
|
|
// The target SDK of this client < Build.VERSION_CODES.S
|
|
private boolean mIsPreSClient = false;
|
|
private final int mUid;
|
|
// The flag of using java backend if the client's target SDK >= U
|
|
private final boolean mUseJavaBackend;
|
|
// Store client logs
|
|
private final SharedLog mClientLogs;
|
|
// Report the nsd metrics data
|
|
private final NetworkNsdReportedMetrics mMetrics;
|
|
|
|
private ClientInfo(INsdManagerCallback cb, int uid, boolean useJavaBackend,
|
|
SharedLog sharedLog, NetworkNsdReportedMetrics metrics) {
|
|
mCb = cb;
|
|
mUid = uid;
|
|
mUseJavaBackend = useJavaBackend;
|
|
mClientLogs = sharedLog;
|
|
mClientLogs.log("New client. useJavaBackend=" + useJavaBackend);
|
|
mMetrics = metrics;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
StringBuilder sb = new StringBuilder();
|
|
sb.append("mResolvedService ").append(mResolvedService).append("\n");
|
|
sb.append("mIsLegacy ").append(mIsPreSClient).append("\n");
|
|
sb.append("mUseJavaBackend ").append(mUseJavaBackend).append("\n");
|
|
sb.append("mUid ").append(mUid).append("\n");
|
|
for (int i = 0; i < mClientRequests.size(); i++) {
|
|
int clientRequestId = mClientRequests.keyAt(i);
|
|
sb.append("clientRequestId ")
|
|
.append(clientRequestId)
|
|
.append(" transactionId ").append(mClientRequests.valueAt(i).mTransactionId)
|
|
.append(" type ").append(
|
|
mClientRequests.valueAt(i).getClass().getSimpleName())
|
|
.append("\n");
|
|
}
|
|
return sb.toString();
|
|
}
|
|
|
|
private boolean isPreSClient() {
|
|
return mIsPreSClient;
|
|
}
|
|
|
|
private void setPreSClient() {
|
|
mIsPreSClient = true;
|
|
}
|
|
|
|
private MdnsListener unregisterMdnsListenerFromRequest(ClientRequest request) {
|
|
final MdnsListener listener =
|
|
((DiscoveryManagerRequest) request).mListener;
|
|
mMdnsDiscoveryManager.unregisterListener(
|
|
listener.getListenedServiceType(), listener);
|
|
return listener;
|
|
}
|
|
|
|
// Remove any pending requests from the global map when we get rid of a client,
|
|
// and send cancellations to the daemon.
|
|
private void expungeAllRequests() {
|
|
mClientLogs.log("Client unregistered. expungeAllRequests!");
|
|
// TODO: to keep handler responsive, do not clean all requests for that client at once.
|
|
for (int i = 0; i < mClientRequests.size(); i++) {
|
|
final int clientRequestId = mClientRequests.keyAt(i);
|
|
final ClientRequest request = mClientRequests.valueAt(i);
|
|
final int transactionId = request.mTransactionId;
|
|
mTransactionIdToClientInfoMap.remove(transactionId);
|
|
if (DBG) {
|
|
Log.d(TAG, "Terminating clientRequestId " + clientRequestId
|
|
+ " transactionId " + transactionId
|
|
+ " type " + mClientRequests.get(clientRequestId));
|
|
}
|
|
|
|
if (request instanceof DiscoveryManagerRequest) {
|
|
final MdnsListener listener = unregisterMdnsListenerFromRequest(request);
|
|
if (listener instanceof DiscoveryListener) {
|
|
mMetrics.reportServiceDiscoveryStop(false /* isLegacy */, transactionId,
|
|
request.calculateRequestDurationMs(mClock.elapsedRealtime()),
|
|
request.getFoundServiceCount(),
|
|
request.getLostServiceCount(),
|
|
request.getServicesCount(),
|
|
request.getSentQueryCount());
|
|
} else if (listener instanceof ResolutionListener) {
|
|
mMetrics.reportServiceResolutionStop(false /* isLegacy */, transactionId,
|
|
request.calculateRequestDurationMs(mClock.elapsedRealtime()));
|
|
} else if (listener instanceof ServiceInfoListener) {
|
|
mMetrics.reportServiceInfoCallbackUnregistered(transactionId,
|
|
request.calculateRequestDurationMs(mClock.elapsedRealtime()),
|
|
request.getFoundServiceCount(),
|
|
request.getLostServiceCount(),
|
|
request.isServiceFromCache(),
|
|
request.getSentQueryCount());
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (request instanceof AdvertiserClientRequest) {
|
|
final AdvertiserMetrics metrics =
|
|
mAdvertiser.getAdvertiserMetrics(transactionId);
|
|
mAdvertiser.removeService(transactionId);
|
|
mMetrics.reportServiceUnregistration(false /* isLegacy */, transactionId,
|
|
request.calculateRequestDurationMs(mClock.elapsedRealtime()),
|
|
metrics.mRepliedRequestsCount, metrics.mSentPacketCount,
|
|
metrics.mConflictDuringProbingCount,
|
|
metrics.mConflictAfterProbingCount);
|
|
continue;
|
|
}
|
|
|
|
if (!(request instanceof LegacyClientRequest)) {
|
|
throw new IllegalStateException("Unknown request type: " + request.getClass());
|
|
}
|
|
|
|
switch (((LegacyClientRequest) request).mRequestCode) {
|
|
case NsdManager.DISCOVER_SERVICES:
|
|
stopServiceDiscovery(transactionId);
|
|
mMetrics.reportServiceDiscoveryStop(true /* isLegacy */, transactionId,
|
|
request.calculateRequestDurationMs(mClock.elapsedRealtime()),
|
|
request.getFoundServiceCount(),
|
|
request.getLostServiceCount(),
|
|
request.getServicesCount(),
|
|
NO_SENT_QUERY_COUNT);
|
|
break;
|
|
case NsdManager.RESOLVE_SERVICE:
|
|
stopResolveService(transactionId);
|
|
mMetrics.reportServiceResolutionStop(true /* isLegacy */, transactionId,
|
|
request.calculateRequestDurationMs(mClock.elapsedRealtime()));
|
|
break;
|
|
case NsdManager.REGISTER_SERVICE:
|
|
unregisterService(transactionId);
|
|
mMetrics.reportServiceUnregistration(true /* isLegacy */, transactionId,
|
|
request.calculateRequestDurationMs(mClock.elapsedRealtime()),
|
|
NO_PACKET /* repliedRequestsCount */,
|
|
NO_PACKET /* sentPacketCount */,
|
|
0 /* conflictDuringProbingCount */,
|
|
0 /* conflictAfterProbingCount */);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
mClientRequests.clear();
|
|
updateMulticastLock();
|
|
}
|
|
|
|
/**
|
|
* Returns true if this client has any Java backend request that requests one of the given
|
|
* networks.
|
|
*/
|
|
boolean hasAnyJavaBackendRequestForNetworks(@NonNull ArraySet<Network> networks) {
|
|
for (int i = 0; i < mClientRequests.size(); i++) {
|
|
final ClientRequest req = mClientRequests.valueAt(i);
|
|
if (!(req instanceof JavaBackendClientRequest)) {
|
|
continue;
|
|
}
|
|
final Network reqNetwork = ((JavaBackendClientRequest) mClientRequests.valueAt(i))
|
|
.getRequestedNetwork();
|
|
if (MdnsUtils.isAnyNetworkMatched(reqNetwork, networks)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// mClientRequests is a sparse array of client request id -> ClientRequest. For a given
|
|
// transaction id, return the corresponding client request id.
|
|
private int getClientRequestId(final int transactionId) {
|
|
for (int i = 0; i < mClientRequests.size(); i++) {
|
|
if (mClientRequests.valueAt(i).mTransactionId == transactionId) {
|
|
return mClientRequests.keyAt(i);
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
private void log(String message) {
|
|
mClientLogs.log(message);
|
|
}
|
|
|
|
private static boolean isLegacyClientRequest(@NonNull ClientRequest request) {
|
|
return !(request instanceof DiscoveryManagerRequest)
|
|
&& !(request instanceof AdvertiserClientRequest);
|
|
}
|
|
|
|
void onDiscoverServicesStarted(int listenerKey, NsdServiceInfo info,
|
|
ClientRequest request) {
|
|
mMetrics.reportServiceDiscoveryStarted(
|
|
isLegacyClientRequest(request), request.mTransactionId);
|
|
try {
|
|
mCb.onDiscoverServicesStarted(listenerKey, info);
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "Error calling onDiscoverServicesStarted", e);
|
|
}
|
|
}
|
|
void onDiscoverServicesFailedImmediately(int listenerKey, int error, boolean isLegacy) {
|
|
onDiscoverServicesFailed(listenerKey, error, isLegacy, NO_TRANSACTION,
|
|
0L /* durationMs */);
|
|
}
|
|
|
|
void onDiscoverServicesFailed(int listenerKey, int error, boolean isLegacy,
|
|
int transactionId, long durationMs) {
|
|
mMetrics.reportServiceDiscoveryFailed(isLegacy, transactionId, durationMs);
|
|
try {
|
|
mCb.onDiscoverServicesFailed(listenerKey, error);
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "Error calling onDiscoverServicesFailed", e);
|
|
}
|
|
}
|
|
|
|
void onServiceFound(int listenerKey, NsdServiceInfo info, ClientRequest request) {
|
|
request.onServiceFound(info.getServiceName());
|
|
try {
|
|
mCb.onServiceFound(listenerKey, info);
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "Error calling onServiceFound(", e);
|
|
}
|
|
}
|
|
|
|
void onServiceLost(int listenerKey, NsdServiceInfo info, ClientRequest request) {
|
|
request.onServiceLost();
|
|
try {
|
|
mCb.onServiceLost(listenerKey, info);
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "Error calling onServiceLost(", e);
|
|
}
|
|
}
|
|
|
|
void onStopDiscoveryFailed(int listenerKey, int error) {
|
|
try {
|
|
mCb.onStopDiscoveryFailed(listenerKey, error);
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "Error calling onStopDiscoveryFailed", e);
|
|
}
|
|
}
|
|
|
|
void onStopDiscoverySucceeded(int listenerKey, ClientRequest request) {
|
|
mMetrics.reportServiceDiscoveryStop(
|
|
isLegacyClientRequest(request),
|
|
request.mTransactionId,
|
|
request.calculateRequestDurationMs(mClock.elapsedRealtime()),
|
|
request.getFoundServiceCount(),
|
|
request.getLostServiceCount(),
|
|
request.getServicesCount(),
|
|
request.getSentQueryCount());
|
|
try {
|
|
mCb.onStopDiscoverySucceeded(listenerKey);
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "Error calling onStopDiscoverySucceeded", e);
|
|
}
|
|
}
|
|
|
|
void onRegisterServiceFailedImmediately(int listenerKey, int error, boolean isLegacy) {
|
|
onRegisterServiceFailed(listenerKey, error, isLegacy, NO_TRANSACTION,
|
|
0L /* durationMs */);
|
|
}
|
|
|
|
void onRegisterServiceFailed(int listenerKey, int error, boolean isLegacy,
|
|
int transactionId, long durationMs) {
|
|
mMetrics.reportServiceRegistrationFailed(isLegacy, transactionId, durationMs);
|
|
try {
|
|
mCb.onRegisterServiceFailed(listenerKey, error);
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "Error calling onRegisterServiceFailed", e);
|
|
}
|
|
}
|
|
|
|
void onRegisterServiceSucceeded(int listenerKey, NsdServiceInfo info,
|
|
ClientRequest request) {
|
|
mMetrics.reportServiceRegistrationSucceeded(isLegacyClientRequest(request),
|
|
request.mTransactionId,
|
|
request.calculateRequestDurationMs(mClock.elapsedRealtime()));
|
|
try {
|
|
mCb.onRegisterServiceSucceeded(listenerKey, info);
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "Error calling onRegisterServiceSucceeded", e);
|
|
}
|
|
}
|
|
|
|
void onUnregisterServiceFailed(int listenerKey, int error) {
|
|
try {
|
|
mCb.onUnregisterServiceFailed(listenerKey, error);
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "Error calling onUnregisterServiceFailed", e);
|
|
}
|
|
}
|
|
|
|
void onUnregisterServiceSucceeded(int listenerKey, ClientRequest request,
|
|
AdvertiserMetrics metrics) {
|
|
mMetrics.reportServiceUnregistration(isLegacyClientRequest(request),
|
|
request.mTransactionId,
|
|
request.calculateRequestDurationMs(mClock.elapsedRealtime()),
|
|
metrics.mRepliedRequestsCount, metrics.mSentPacketCount,
|
|
metrics.mConflictDuringProbingCount, metrics.mConflictAfterProbingCount);
|
|
try {
|
|
mCb.onUnregisterServiceSucceeded(listenerKey);
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "Error calling onUnregisterServiceSucceeded", e);
|
|
}
|
|
}
|
|
|
|
void onResolveServiceFailedImmediately(int listenerKey, int error, boolean isLegacy) {
|
|
onResolveServiceFailed(listenerKey, error, isLegacy, NO_TRANSACTION,
|
|
0L /* durationMs */);
|
|
}
|
|
|
|
void onResolveServiceFailed(int listenerKey, int error, boolean isLegacy,
|
|
int transactionId, long durationMs) {
|
|
mMetrics.reportServiceResolutionFailed(isLegacy, transactionId, durationMs);
|
|
try {
|
|
mCb.onResolveServiceFailed(listenerKey, error);
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "Error calling onResolveServiceFailed", e);
|
|
}
|
|
}
|
|
|
|
void onResolveServiceSucceeded(int listenerKey, NsdServiceInfo info,
|
|
ClientRequest request) {
|
|
mMetrics.reportServiceResolved(
|
|
isLegacyClientRequest(request),
|
|
request.mTransactionId,
|
|
request.calculateRequestDurationMs(mClock.elapsedRealtime()),
|
|
request.isServiceFromCache(),
|
|
request.getSentQueryCount());
|
|
try {
|
|
mCb.onResolveServiceSucceeded(listenerKey, info);
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "Error calling onResolveServiceSucceeded", e);
|
|
}
|
|
}
|
|
|
|
void onStopResolutionFailed(int listenerKey, int error) {
|
|
try {
|
|
mCb.onStopResolutionFailed(listenerKey, error);
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "Error calling onStopResolutionFailed", e);
|
|
}
|
|
}
|
|
|
|
void onStopResolutionSucceeded(int listenerKey, ClientRequest request) {
|
|
mMetrics.reportServiceResolutionStop(
|
|
isLegacyClientRequest(request),
|
|
request.mTransactionId,
|
|
request.calculateRequestDurationMs(mClock.elapsedRealtime()));
|
|
try {
|
|
mCb.onStopResolutionSucceeded(listenerKey);
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "Error calling onStopResolutionSucceeded", e);
|
|
}
|
|
}
|
|
|
|
void onServiceInfoCallbackRegistrationFailed(int listenerKey, int error) {
|
|
mMetrics.reportServiceInfoCallbackRegistrationFailed(NO_TRANSACTION);
|
|
try {
|
|
mCb.onServiceInfoCallbackRegistrationFailed(listenerKey, error);
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "Error calling onServiceInfoCallbackRegistrationFailed", e);
|
|
}
|
|
}
|
|
|
|
void onServiceInfoCallbackRegistered(int transactionId) {
|
|
mMetrics.reportServiceInfoCallbackRegistered(transactionId);
|
|
}
|
|
|
|
void onServiceUpdated(int listenerKey, NsdServiceInfo info, ClientRequest request) {
|
|
request.onServiceFound(info.getServiceName());
|
|
try {
|
|
mCb.onServiceUpdated(listenerKey, info);
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "Error calling onServiceUpdated", e);
|
|
}
|
|
}
|
|
|
|
void onServiceUpdatedLost(int listenerKey, ClientRequest request) {
|
|
request.onServiceLost();
|
|
try {
|
|
mCb.onServiceUpdatedLost(listenerKey);
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "Error calling onServiceUpdatedLost", e);
|
|
}
|
|
}
|
|
|
|
void onServiceInfoCallbackUnregistered(int listenerKey, ClientRequest request) {
|
|
mMetrics.reportServiceInfoCallbackUnregistered(
|
|
request.mTransactionId,
|
|
request.calculateRequestDurationMs(mClock.elapsedRealtime()),
|
|
request.getFoundServiceCount(),
|
|
request.getLostServiceCount(),
|
|
request.isServiceFromCache(),
|
|
request.getSentQueryCount());
|
|
try {
|
|
mCb.onServiceInfoCallbackUnregistered(listenerKey);
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "Error calling onServiceInfoCallbackUnregistered", e);
|
|
}
|
|
}
|
|
}
|
|
}
|