[automerger skipped] Revert^2 "Change Ethernet API to use OutcomeReceiver" am: 39ef30ce64 -s ours

am skip reason: Merged-In I7c46545a47034be409071c2ec007d9e1480c6ed0 with SHA-1 77a3a8408b is already in history

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/2031104

Change-Id: Ic29b6207e02a9b51cc6e7a15bc2a7637936fc988
diff --git a/framework-t/Sources.bp b/framework-t/Sources.bp
index bc27852..217a1f6 100644
--- a/framework-t/Sources.bp
+++ b/framework-t/Sources.bp
@@ -131,8 +131,8 @@
         "src/android/net/EthernetNetworkUpdateRequest.java",
         "src/android/net/EthernetNetworkUpdateRequest.aidl",
         "src/android/net/IEthernetManager.aidl",
+        "src/android/net/IEthernetNetworkManagementListener.aidl",
         "src/android/net/IEthernetServiceListener.aidl",
-        "src/android/net/INetworkInterfaceOutcomeReceiver.aidl",
         "src/android/net/ITetheredInterfaceCallback.aidl",
     ],
     path: "src",
diff --git a/framework-t/src/android/net/EthernetManager.java b/framework-t/src/android/net/EthernetManager.java
index 4f61dbf..793f28d 100644
--- a/framework-t/src/android/net/EthernetManager.java
+++ b/framework-t/src/android/net/EthernetManager.java
@@ -30,7 +30,6 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.Build;
-import android.os.OutcomeReceiver;
 import android.os.RemoteException;
 
 import com.android.internal.annotations.GuardedBy;
@@ -41,6 +40,7 @@
 import java.util.ArrayList;
 import java.util.Objects;
 import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
 
 /**
  * A class that manages and configures Ethernet interfaces.
@@ -443,45 +443,41 @@
         return new TetheredInterfaceRequest(mService, cbInternal);
     }
 
-    private static final class NetworkInterfaceOutcomeReceiver
-            extends INetworkInterfaceOutcomeReceiver.Stub {
+    private static final class InternalNetworkManagementListener
+            extends IEthernetNetworkManagementListener.Stub {
         @NonNull
         private final Executor mExecutor;
         @NonNull
-        private final OutcomeReceiver<String, EthernetNetworkManagementException> mCallback;
+        private final BiConsumer<Network, EthernetNetworkManagementException> mListener;
 
-        NetworkInterfaceOutcomeReceiver(
+        InternalNetworkManagementListener(
                 @NonNull final Executor executor,
-                @NonNull final OutcomeReceiver<String, EthernetNetworkManagementException>
-                        callback) {
+                @NonNull final BiConsumer<Network, EthernetNetworkManagementException> listener) {
             Objects.requireNonNull(executor, "Pass a non-null executor");
-            Objects.requireNonNull(callback, "Pass a non-null callback");
+            Objects.requireNonNull(listener, "Pass a non-null listener");
             mExecutor = executor;
-            mCallback = callback;
+            mListener = listener;
         }
 
         @Override
-        public void onResult(@NonNull String iface) {
-            mExecutor.execute(() -> mCallback.onResult(iface));
-        }
-
-        @Override
-        public void onError(@NonNull EthernetNetworkManagementException e) {
-            mExecutor.execute(() -> mCallback.onError(e));
+        public void onComplete(
+                @Nullable final Network network,
+                @Nullable final EthernetNetworkManagementException e) {
+            mExecutor.execute(() -> mListener.accept(network, e));
         }
     }
 
-    private NetworkInterfaceOutcomeReceiver makeNetworkInterfaceOutcomeReceiver(
+    private InternalNetworkManagementListener getInternalNetworkManagementListener(
             @Nullable final Executor executor,
-            @Nullable final OutcomeReceiver<String, EthernetNetworkManagementException> callback) {
-        if (null != callback) {
-            Objects.requireNonNull(executor, "Pass a non-null executor, or a null callback");
+            @Nullable final BiConsumer<Network, EthernetNetworkManagementException> listener) {
+        if (null != listener) {
+            Objects.requireNonNull(executor, "Pass a non-null executor, or a null listener");
         }
-        final NetworkInterfaceOutcomeReceiver proxy;
-        if (null == callback) {
+        final InternalNetworkManagementListener proxy;
+        if (null == listener) {
             proxy = null;
         } else {
-            proxy = new NetworkInterfaceOutcomeReceiver(executor, callback);
+            proxy = new InternalNetworkManagementListener(executor, listener);
         }
         return proxy;
     }
@@ -496,17 +492,14 @@
      * Similarly, use {@link NetworkCapabilities.Builder} to build a {@code NetworkCapabilities}
      * object for this network to put inside the {@code request}.
      *
-     * This function accepts an {@link OutcomeReceiver} that is called once the operation has
-     * finished execution.
+     * If non-null, the listener will be called exactly once after this is called, unless
+     * a synchronous exception was thrown.
      *
      * @param iface the name of the interface to act upon.
      * @param request the {@link EthernetNetworkUpdateRequest} used to set an ethernet network's
      *                {@link StaticIpConfiguration} and {@link NetworkCapabilities} values.
-     * @param executor an {@link Executor} to execute the callback on. Optional if callback is null.
-     * @param callback an optional {@link OutcomeReceiver} to listen for completion of the
-     *                 operation. On success, {@link OutcomeReceiver#onResult} is called with the
-     *                 interface name. On error, {@link OutcomeReceiver#onError} is called with more
-     *                 information about the error.
+     * @param executor an {@link Executor} to execute the listener on. Optional if listener is null.
+     * @param listener an optional {@link BiConsumer} to listen for completion of the operation.
      * @throws SecurityException if the process doesn't hold
      *                          {@link android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}.
      * @throws UnsupportedOperationException if called on a non-automotive device or on an
@@ -522,11 +515,11 @@
             @NonNull String iface,
             @NonNull EthernetNetworkUpdateRequest request,
             @Nullable @CallbackExecutor Executor executor,
-            @Nullable OutcomeReceiver<String, EthernetNetworkManagementException> callback) {
+            @Nullable BiConsumer<Network, EthernetNetworkManagementException> listener) {
         Objects.requireNonNull(iface, "iface must be non-null");
         Objects.requireNonNull(request, "request must be non-null");
-        final NetworkInterfaceOutcomeReceiver proxy = makeNetworkInterfaceOutcomeReceiver(
-                executor, callback);
+        final InternalNetworkManagementListener proxy = getInternalNetworkManagementListener(
+                executor, listener);
         try {
             mService.updateConfiguration(iface, request, proxy);
         } catch (RemoteException e) {
@@ -537,17 +530,15 @@
     /**
      * Set an ethernet network's link state up.
      *
-     * When the link is successfully turned up, the callback will be called with the network
-     * interface was torn down, if any. If any error or unexpected condition happens while the
-     * system tries to turn the interface down, the callback will be called with an appropriate
-     * exception. The callback is guaranteed to be called exactly once for each call to this method.
+     * When the link is successfully turned up, the listener will be called with the resulting
+     * network. If any error or unexpected condition happens while the system tries to turn the
+     * interface up, the listener will be called with an appropriate exception.
+     * The listener is guaranteed to be called exactly once for each call to this method, but this
+     * may take an unbounded amount of time depending on the actual network conditions.
      *
      * @param iface the name of the interface to act upon.
-     * @param executor an {@link Executor} to execute the callback on. Optional if callback is null.
-     * @param callback an optional {@link OutcomeReceiver} to listen for completion of the
-     *                 operation. On success, {@link OutcomeReceiver#onResult} is called with the
-     *                 interface name. On error, {@link OutcomeReceiver#onError} is called with more
-     *                 information about the error.
+     * @param executor an {@link Executor} to execute the listener on. Optional if listener is null.
+     * @param listener an optional {@link BiConsumer} to listen for completion of the operation.
      * @throws SecurityException if the process doesn't hold
      *                          {@link android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}.
      * @throws UnsupportedOperationException if called on a non-automotive device.
@@ -562,10 +553,10 @@
     public void connectNetwork(
             @NonNull String iface,
             @Nullable @CallbackExecutor Executor executor,
-            @Nullable OutcomeReceiver<String, EthernetNetworkManagementException> callback) {
+            @Nullable BiConsumer<Network, EthernetNetworkManagementException> listener) {
         Objects.requireNonNull(iface, "iface must be non-null");
-        final NetworkInterfaceOutcomeReceiver proxy = makeNetworkInterfaceOutcomeReceiver(
-                executor, callback);
+        final InternalNetworkManagementListener proxy = getInternalNetworkManagementListener(
+                executor, listener);
         try {
             mService.connectNetwork(iface, proxy);
         } catch (RemoteException e) {
@@ -576,17 +567,14 @@
     /**
      * Set an ethernet network's link state down.
      *
-     * When the link is successfully turned down, the callback will be called with the network
-     * interface was torn down, if any. If any error or unexpected condition happens while the
-     * system tries to turn the interface down, the callback will be called with an appropriate
-     * exception. The callback is guaranteed to be called exactly once for each call to this method.
+     * When the link is successfully turned down, the listener will be called with the network that
+     * was torn down, if any. If any error or unexpected condition happens while the system tries to
+     * turn the interface down, the listener will be called with an appropriate exception.
+     * The listener is guaranteed to be called exactly once for each call to this method.
      *
      * @param iface the name of the interface to act upon.
-     * @param executor an {@link Executor} to execute the callback on. Optional if callback is null.
-     * @param callback an optional {@link OutcomeReceiver} to listen for completion of the
-     *                 operation. On success, {@link OutcomeReceiver#onResult} is called with the
-     *                 interface name. On error, {@link OutcomeReceiver#onError} is called with more
-     *                 information about the error.
+     * @param executor an {@link Executor} to execute the listener on. Optional if listener is null.
+     * @param listener an optional {@link BiConsumer} to listen for completion of the operation.
      * @throws SecurityException if the process doesn't hold
      *                          {@link android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}.
      * @throws UnsupportedOperationException if called on a non-automotive device.
@@ -601,10 +589,10 @@
     public void disconnectNetwork(
             @NonNull String iface,
             @Nullable @CallbackExecutor Executor executor,
-            @Nullable OutcomeReceiver<String, EthernetNetworkManagementException> callback) {
+            @Nullable BiConsumer<Network, EthernetNetworkManagementException> listener) {
         Objects.requireNonNull(iface, "iface must be non-null");
-        final NetworkInterfaceOutcomeReceiver proxy = makeNetworkInterfaceOutcomeReceiver(
-                executor, callback);
+        final InternalNetworkManagementListener proxy = getInternalNetworkManagementListener(
+                executor, listener);
         try {
             mService.disconnectNetwork(iface, proxy);
         } catch (RemoteException e) {
diff --git a/framework-t/src/android/net/IEthernetManager.aidl b/framework-t/src/android/net/IEthernetManager.aidl
index 95ae907..544d02b 100644
--- a/framework-t/src/android/net/IEthernetManager.aidl
+++ b/framework-t/src/android/net/IEthernetManager.aidl
@@ -18,9 +18,8 @@
 
 import android.net.IpConfiguration;
 import android.net.IEthernetServiceListener;
-import android.net.EthernetNetworkManagementException;
+import android.net.IEthernetNetworkManagementListener;
 import android.net.EthernetNetworkUpdateRequest;
-import android.net.INetworkInterfaceOutcomeReceiver;
 import android.net.ITetheredInterfaceCallback;
 
 /**
@@ -40,7 +39,7 @@
     void requestTetheredInterface(in ITetheredInterfaceCallback callback);
     void releaseTetheredInterface(in ITetheredInterfaceCallback callback);
     void updateConfiguration(String iface, in EthernetNetworkUpdateRequest request,
-        in INetworkInterfaceOutcomeReceiver listener);
-    void connectNetwork(String iface, in INetworkInterfaceOutcomeReceiver listener);
-    void disconnectNetwork(String iface, in INetworkInterfaceOutcomeReceiver listener);
+        in IEthernetNetworkManagementListener listener);
+    void connectNetwork(String iface, in IEthernetNetworkManagementListener listener);
+    void disconnectNetwork(String iface, in IEthernetNetworkManagementListener listener);
 }
diff --git a/framework-t/src/android/net/INetworkInterfaceOutcomeReceiver.aidl b/framework-t/src/android/net/IEthernetNetworkManagementListener.aidl
similarity index 80%
rename from framework-t/src/android/net/INetworkInterfaceOutcomeReceiver.aidl
rename to framework-t/src/android/net/IEthernetNetworkManagementListener.aidl
index 85795ea..93edccf 100644
--- a/framework-t/src/android/net/INetworkInterfaceOutcomeReceiver.aidl
+++ b/framework-t/src/android/net/IEthernetNetworkManagementListener.aidl
@@ -17,9 +17,9 @@
 package android.net;
 
 import android.net.EthernetNetworkManagementException;
+import android.net.Network;
 
 /** @hide */
-oneway interface INetworkInterfaceOutcomeReceiver {
-    void onResult(in String iface);
-    void onError(in EthernetNetworkManagementException e);
+oneway interface IEthernetNetworkManagementListener {
+    void onComplete(in Network network, in EthernetNetworkManagementException exception);
 }
\ No newline at end of file
diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java
index 512fbce..209f372 100644
--- a/framework-t/src/android/net/nsd/NsdManager.java
+++ b/framework-t/src/android/net/nsd/NsdManager.java
@@ -45,6 +45,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.Objects;
+import java.util.concurrent.Executor;
 
 /**
  * The Network Service Discovery Manager class provides the API to discover services
@@ -285,8 +286,12 @@
     private final Context mContext;
 
     private int mListenerKey = FIRST_LISTENER_KEY;
+    @GuardedBy("mMapLock")
     private final SparseArray mListenerMap = new SparseArray();
+    @GuardedBy("mMapLock")
     private final SparseArray<NsdServiceInfo> mServiceMap = new SparseArray<>();
+    @GuardedBy("mMapLock")
+    private final SparseArray<Executor> mExecutorMap = new SparseArray<>();
     private final Object mMapLock = new Object();
     // Map of listener key sent by client -> per-network discovery tracker
     @GuardedBy("mPerNetworkDiscoveryMap")
@@ -299,6 +304,7 @@
         final String mServiceType;
         final int mProtocolType;
         final DiscoveryListener mBaseListener;
+        final Executor mBaseExecutor;
         final ArrayMap<Network, DelegatingDiscoveryListener> mPerNetworkListeners =
                 new ArrayMap<>();
 
@@ -308,7 +314,8 @@
                 final DelegatingDiscoveryListener wrappedListener = new DelegatingDiscoveryListener(
                         network, mBaseListener);
                 mPerNetworkListeners.put(network, wrappedListener);
-                discoverServices(mServiceType, mProtocolType, network, wrappedListener);
+                discoverServices(mServiceType, mProtocolType, network, mBaseExecutor,
+                        wrappedListener);
             }
 
             @Override
@@ -355,9 +362,10 @@
         }
 
         private PerNetworkDiscoveryTracker(String serviceType, int protocolType,
-                DiscoveryListener baseListener) {
+                Executor baseExecutor, DiscoveryListener baseListener) {
             mServiceType = serviceType;
             mProtocolType = protocolType;
+            mBaseExecutor = baseExecutor;
             mBaseListener = baseListener;
         }
 
@@ -644,9 +652,11 @@
             final int key = message.arg2;
             final Object listener;
             final NsdServiceInfo ns;
+            final Executor executor;
             synchronized (mMapLock) {
                 listener = mListenerMap.get(key);
                 ns = mServiceMap.get(key);
+                executor = mExecutorMap.get(key);
             }
             if (listener == null) {
                 Log.d(TAG, "Stale key " + message.arg2);
@@ -657,56 +667,64 @@
             }
             switch (what) {
                 case DISCOVER_SERVICES_STARTED:
-                    String s = getNsdServiceInfoType((NsdServiceInfo) message.obj);
-                    ((DiscoveryListener) listener).onDiscoveryStarted(s);
+                    final String s = getNsdServiceInfoType((NsdServiceInfo) message.obj);
+                    executor.execute(() -> ((DiscoveryListener) listener).onDiscoveryStarted(s));
                     break;
                 case DISCOVER_SERVICES_FAILED:
                     removeListener(key);
-                    ((DiscoveryListener) listener).onStartDiscoveryFailed(getNsdServiceInfoType(ns),
-                            message.arg1);
+                    executor.execute(() -> ((DiscoveryListener) listener).onStartDiscoveryFailed(
+                            getNsdServiceInfoType(ns), message.arg1));
                     break;
                 case SERVICE_FOUND:
-                    ((DiscoveryListener) listener).onServiceFound((NsdServiceInfo) message.obj);
+                    executor.execute(() -> ((DiscoveryListener) listener).onServiceFound(
+                            (NsdServiceInfo) message.obj));
                     break;
                 case SERVICE_LOST:
-                    ((DiscoveryListener) listener).onServiceLost((NsdServiceInfo) message.obj);
+                    executor.execute(() -> ((DiscoveryListener) listener).onServiceLost(
+                            (NsdServiceInfo) message.obj));
                     break;
                 case STOP_DISCOVERY_FAILED:
                     // TODO: failure to stop discovery should be internal and retried internally, as
                     // the effect for the client is indistinguishable from STOP_DISCOVERY_SUCCEEDED
                     removeListener(key);
-                    ((DiscoveryListener) listener).onStopDiscoveryFailed(getNsdServiceInfoType(ns),
-                            message.arg1);
+                    executor.execute(() -> ((DiscoveryListener) listener).onStopDiscoveryFailed(
+                            getNsdServiceInfoType(ns), message.arg1));
                     break;
                 case STOP_DISCOVERY_SUCCEEDED:
                     removeListener(key);
-                    ((DiscoveryListener) listener).onDiscoveryStopped(getNsdServiceInfoType(ns));
+                    executor.execute(() -> ((DiscoveryListener) listener).onDiscoveryStopped(
+                            getNsdServiceInfoType(ns)));
                     break;
                 case REGISTER_SERVICE_FAILED:
                     removeListener(key);
-                    ((RegistrationListener) listener).onRegistrationFailed(ns, message.arg1);
+                    executor.execute(() -> ((RegistrationListener) listener).onRegistrationFailed(
+                            ns, message.arg1));
                     break;
                 case REGISTER_SERVICE_SUCCEEDED:
-                    ((RegistrationListener) listener).onServiceRegistered(
-                            (NsdServiceInfo) message.obj);
+                    executor.execute(() -> ((RegistrationListener) listener).onServiceRegistered(
+                            (NsdServiceInfo) message.obj));
                     break;
                 case UNREGISTER_SERVICE_FAILED:
                     removeListener(key);
-                    ((RegistrationListener) listener).onUnregistrationFailed(ns, message.arg1);
+                    executor.execute(() -> ((RegistrationListener) listener).onUnregistrationFailed(
+                            ns, message.arg1));
                     break;
                 case UNREGISTER_SERVICE_SUCCEEDED:
                     // TODO: do not unregister listener until service is unregistered, or provide
                     // alternative way for unregistering ?
                     removeListener(message.arg2);
-                    ((RegistrationListener) listener).onServiceUnregistered(ns);
+                    executor.execute(() -> ((RegistrationListener) listener).onServiceUnregistered(
+                            ns));
                     break;
                 case RESOLVE_SERVICE_FAILED:
                     removeListener(key);
-                    ((ResolveListener) listener).onResolveFailed(ns, message.arg1);
+                    executor.execute(() -> ((ResolveListener) listener).onResolveFailed(
+                            ns, message.arg1));
                     break;
                 case RESOLVE_SERVICE_SUCCEEDED:
                     removeListener(key);
-                    ((ResolveListener) listener).onServiceResolved((NsdServiceInfo) message.obj);
+                    executor.execute(() -> ((ResolveListener) listener).onServiceResolved(
+                            (NsdServiceInfo) message.obj));
                     break;
                 default:
                     Log.d(TAG, "Ignored " + message);
@@ -722,7 +740,7 @@
     }
 
     // Assert that the listener is not in the map, then add it and returns its key
-    private int putListener(Object listener, NsdServiceInfo s) {
+    private int putListener(Object listener, Executor e, NsdServiceInfo s) {
         checkListener(listener);
         final int key;
         synchronized (mMapLock) {
@@ -733,6 +751,7 @@
             key = nextListenerKey();
             mListenerMap.put(key, listener);
             mServiceMap.put(key, s);
+            mExecutorMap.put(key, e);
         }
         return key;
     }
@@ -741,6 +760,7 @@
         synchronized (mMapLock) {
             mListenerMap.remove(key);
             mServiceMap.remove(key);
+            mExecutorMap.remove(key);
         }
     }
 
@@ -779,12 +799,33 @@
      */
     public void registerService(NsdServiceInfo serviceInfo, int protocolType,
             RegistrationListener listener) {
+        registerService(serviceInfo, protocolType, Runnable::run, listener);
+    }
+
+    /**
+     * Register a service to be discovered by other services.
+     *
+     * <p> The function call immediately returns after sending a request to register service
+     * to the framework. The application is notified of a successful registration
+     * through the callback {@link RegistrationListener#onServiceRegistered} or a failure
+     * through {@link RegistrationListener#onRegistrationFailed}.
+     *
+     * <p> The application should call {@link #unregisterService} when the service
+     * registration is no longer required, and/or whenever the application is stopped.
+     * @param serviceInfo The service being registered
+     * @param protocolType The service discovery protocol
+     * @param executor Executor to run listener callbacks with
+     * @param listener The listener notifies of a successful registration and is used to
+     * unregister this service through a call on {@link #unregisterService}. Cannot be null.
+     */
+    public void registerService(@NonNull NsdServiceInfo serviceInfo, int protocolType,
+            @NonNull Executor executor, @NonNull RegistrationListener listener) {
         if (serviceInfo.getPort() <= 0) {
             throw new IllegalArgumentException("Invalid port number");
         }
         checkServiceInfo(serviceInfo);
         checkProtocol(protocolType);
-        int key = putListener(listener, serviceInfo);
+        int key = putListener(listener, executor, serviceInfo);
         try {
             mService.registerService(key, serviceInfo);
         } catch (RemoteException e) {
@@ -815,14 +856,6 @@
     }
 
     /**
-     * Same as {@link #discoverServices(String, int, Network, DiscoveryListener)} with a null
-     * {@link Network}.
-     */
-    public void discoverServices(String serviceType, int protocolType, DiscoveryListener listener) {
-        discoverServices(serviceType, protocolType, (Network) null, listener);
-    }
-
-    /**
      * Initiate service discovery to browse for instances of a service type. Service discovery
      * consumes network bandwidth and will continue until the application calls
      * {@link #stopServiceDiscovery}.
@@ -846,13 +879,45 @@
      * @param serviceType The service type being discovered. Examples include "_http._tcp" for
      * http services or "_ipp._tcp" for printers
      * @param protocolType The service discovery protocol
-     * @param network Network to discover services on, or null to discover on all available networks
      * @param listener  The listener notifies of a successful discovery and is used
      * to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}.
      * Cannot be null. Cannot be in use for an active service discovery.
      */
+    public void discoverServices(String serviceType, int protocolType, DiscoveryListener listener) {
+        discoverServices(serviceType, protocolType, (Network) null, Runnable::run, listener);
+    }
+
+    /**
+     * Initiate service discovery to browse for instances of a service type. Service discovery
+     * consumes network bandwidth and will continue until the application calls
+     * {@link #stopServiceDiscovery}.
+     *
+     * <p> The function call immediately returns after sending a request to start service
+     * discovery to the framework. The application is notified of a success to initiate
+     * discovery through the callback {@link DiscoveryListener#onDiscoveryStarted} or a failure
+     * through {@link DiscoveryListener#onStartDiscoveryFailed}.
+     *
+     * <p> Upon successful start, application is notified when a service is found with
+     * {@link DiscoveryListener#onServiceFound} or when a service is lost with
+     * {@link DiscoveryListener#onServiceLost}.
+     *
+     * <p> Upon failure to start, service discovery is not active and application does
+     * not need to invoke {@link #stopServiceDiscovery}
+     *
+     * <p> The application should call {@link #stopServiceDiscovery} when discovery of this
+     * service type is no longer required, and/or whenever the application is paused or
+     * stopped.
+     * @param serviceType The service type being discovered. Examples include "_http._tcp" for
+     * http services or "_ipp._tcp" for printers
+     * @param protocolType The service discovery protocol
+     * @param network Network to discover services on, or null to discover on all available networks
+     * @param executor Executor to run listener callbacks with
+     * @param listener  The listener notifies of a successful discovery and is used
+     * to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}.
+     */
     public void discoverServices(@NonNull String serviceType, int protocolType,
-            @Nullable Network network, @NonNull DiscoveryListener listener) {
+            @Nullable Network network, @NonNull Executor executor,
+            @NonNull DiscoveryListener listener) {
         if (TextUtils.isEmpty(serviceType)) {
             throw new IllegalArgumentException("Service type cannot be empty");
         }
@@ -862,7 +927,7 @@
         s.setServiceType(serviceType);
         s.setNetwork(network);
 
-        int key = putListener(listener, s);
+        int key = putListener(listener, executor, s);
         try {
             mService.discoverServices(key, s);
         } catch (RemoteException e) {
@@ -899,18 +964,18 @@
      * themselves are encouraged to use this method instead of other overloads of
      * {@code discoverServices}, as they will receive proper notifications when a service becomes
      * available or unavailable due to network changes.
-     *
      * @param serviceType The service type being discovered. Examples include "_http._tcp" for
      * http services or "_ipp._tcp" for printers
      * @param protocolType The service discovery protocol
      * @param networkRequest Request specifying networks that should be considered when discovering
+     * @param executor Executor to run listener callbacks with
      * @param listener  The listener notifies of a successful discovery and is used
      * to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}.
-     * Cannot be null. Cannot be in use for an active service discovery.
      */
     @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public void discoverServices(@NonNull String serviceType, int protocolType,
-            @NonNull NetworkRequest networkRequest, @NonNull DiscoveryListener listener) {
+            @NonNull NetworkRequest networkRequest, @NonNull Executor executor,
+            @NonNull DiscoveryListener listener) {
         if (TextUtils.isEmpty(serviceType)) {
             throw new IllegalArgumentException("Service type cannot be empty");
         }
@@ -920,10 +985,10 @@
         NsdServiceInfo s = new NsdServiceInfo();
         s.setServiceType(serviceType);
 
-        final int baseListenerKey = putListener(listener, s);
+        final int baseListenerKey = putListener(listener, executor, s);
 
         final PerNetworkDiscoveryTracker discoveryInfo = new PerNetworkDiscoveryTracker(
-                serviceType, protocolType, listener);
+                serviceType, protocolType, executor, listener);
 
         synchronized (mPerNetworkDiscoveryMap) {
             mPerNetworkDiscoveryMap.put(baseListenerKey, discoveryInfo);
@@ -974,8 +1039,21 @@
      * Cannot be in use for an active service resolution.
      */
     public void resolveService(NsdServiceInfo serviceInfo, ResolveListener listener) {
+        resolveService(serviceInfo, Runnable::run, listener);
+    }
+
+    /**
+     * Resolve a discovered service. An application can resolve a service right before
+     * establishing a connection to fetch the IP and port details on which to setup
+     * the connection.
+     * @param serviceInfo service to be resolved
+     * @param executor Executor to run listener callbacks with
+     * @param listener to receive callback upon success or failure.
+     */
+    public void resolveService(@NonNull NsdServiceInfo serviceInfo,
+            @NonNull Executor executor, @NonNull ResolveListener listener) {
         checkServiceInfo(serviceInfo);
-        int key = putListener(listener, serviceInfo);
+        int key = putListener(listener, executor, serviceInfo);
         try {
             mService.resolveService(key, serviceInfo);
         } catch (RemoteException e) {