Merge "Make NsdService only start the native daemon when needed and automatically clean it up." am: 05081aa3b0

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

Change-Id: I6caa9933f307118414039b9dd2391939aeb15c0f
diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java
index 64f20b8..5a25cfc 100644
--- a/core/java/android/net/nsd/NsdManager.java
+++ b/core/java/android/net/nsd/NsdManager.java
@@ -200,6 +200,9 @@
     public static final int RESOLVE_SERVICE_SUCCEEDED               = BASE + 20;
 
     /** @hide */
+    public static final int DAEMON_CLEANUP                          = BASE + 21;
+
+    /** @hide */
     public static final int ENABLE                                  = BASE + 24;
     /** @hide */
     public static final int DISABLE                                 = BASE + 25;
diff --git a/services/core/java/com/android/server/NsdService.java b/services/core/java/com/android/server/NsdService.java
index d907505..38f7cf6 100644
--- a/services/core/java/com/android/server/NsdService.java
+++ b/services/core/java/com/android/server/NsdService.java
@@ -61,6 +61,7 @@
     private static final String MDNS_TAG = "mDnsConnector";
 
     private static final boolean DBG = true;
+    private static final long CLEANUP_DELAY_MS = 3000;
 
     private final Context mContext;
     private final NsdSettings mNsdSettings;
@@ -77,6 +78,7 @@
     private final SparseArray<ClientInfo> mIdToClientInfoMap= new SparseArray<>();
 
     private final AsyncChannel mReplyChannel = new AsyncChannel();
+    private final long mCleanupDelayMs;
 
     private static final int INVALID_ID = 0;
     private int mUniqueId = 1;
@@ -92,6 +94,22 @@
             return NsdManager.nameOf(what);
         }
 
+        void maybeStartDaemon() {
+            mDaemon.maybeStart();
+            maybeScheduleStop();
+        }
+
+        void maybeScheduleStop() {
+            if (!isAnyRequestActive()) {
+                cancelStop();
+                sendMessageDelayed(NsdManager.DAEMON_CLEANUP, mCleanupDelayMs);
+            }
+        }
+
+        void cancelStop() {
+            this.removeMessages(NsdManager.DAEMON_CLEANUP);
+        }
+
         /**
          * Observes the NSD on/off setting, and takes action when changed.
          */
@@ -151,10 +169,6 @@
                             cInfo.expungeAllRequests();
                             mClients.remove(msg.replyTo);
                         }
-                        //Last client
-                        if (mClients.size() == 0) {
-                            mDaemon.stop();
-                        }
                         break;
                     case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
                         AsyncChannel ac = new AsyncChannel();
@@ -180,6 +194,9 @@
                         replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED,
                                 NsdManager.FAILURE_INTERNAL_ERROR);
                         break;
+                    case NsdManager.DAEMON_CLEANUP:
+                        mDaemon.maybeStop();
+                        break;
                     case NsdManager.NATIVE_DAEMON_EVENT:
                     default:
                         Slog.e(TAG, "Unhandled " + msg);
@@ -212,16 +229,13 @@
             @Override
             public void enter() {
                 sendNsdStateChangeBroadcast(true);
-                if (mClients.size() > 0) {
-                    mDaemon.start();
-                }
             }
 
             @Override
             public void exit() {
-                if (mClients.size() > 0) {
-                    mDaemon.stop();
-                }
+                // TODO: it is incorrect to stop the daemon without expunging all requests
+                // and sending error callbacks to clients.
+                maybeScheduleStop();
             }
 
             private boolean requestLimitReached(ClientInfo clientInfo) {
@@ -236,12 +250,15 @@
                 clientInfo.mClientIds.put(clientId, globalId);
                 clientInfo.mClientRequests.put(clientId, what);
                 mIdToClientInfoMap.put(globalId, clientInfo);
+                // Remove the cleanup event because here comes a new request.
+                cancelStop();
             }
 
             private void removeRequestMap(int clientId, int globalId, ClientInfo clientInfo) {
                 clientInfo.mClientIds.delete(clientId);
                 clientInfo.mClientRequests.delete(clientId);
                 mIdToClientInfoMap.remove(globalId);
+                maybeScheduleStop();
             }
 
             @Override
@@ -251,14 +268,12 @@
                 int id;
                 switch (msg.what) {
                     case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
-                        //First client
-                        if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL &&
-                                mClients.size() == 0) {
-                            mDaemon.start();
-                        }
                         return NOT_HANDLED;
                     case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
                         return NOT_HANDLED;
+                }
+
+                switch (msg.what) {
                     case NsdManager.DISABLE:
                         //TODO: cleanup clients
                         transitionTo(mDisabledState);
@@ -274,6 +289,7 @@
                             break;
                         }
 
+                        maybeStartDaemon();
                         id = getUniqueId();
                         if (discoverServices(id, servInfo.getServiceType())) {
                             if (DBG) {
@@ -316,6 +332,7 @@
                             break;
                         }
 
+                        maybeStartDaemon();
                         id = getUniqueId();
                         if (registerService(id, (NsdServiceInfo) msg.obj)) {
                             if (DBG) Slog.d(TAG, "Register " + msg.arg2 + " " + id);
@@ -357,6 +374,7 @@
                             break;
                         }
 
+                        maybeStartDaemon();
                         id = getUniqueId();
                         if (resolveService(id, servInfo)) {
                             clientInfo.mResolvedService = new NsdServiceInfo();
@@ -513,6 +531,10 @@
        }
     }
 
+    private boolean isAnyRequestActive() {
+        return mIdToClientInfoMap.size() != 0;
+    }
+
     private String unescape(String s) {
         StringBuilder sb = new StringBuilder(s.length());
         for (int i = 0; i < s.length(); ++i) {
@@ -538,7 +560,9 @@
     }
 
     @VisibleForTesting
-    NsdService(Context ctx, NsdSettings settings, Handler handler, DaemonConnectionSupplier fn) {
+    NsdService(Context ctx, NsdSettings settings, Handler handler,
+            DaemonConnectionSupplier fn, long cleanupDelayMs) {
+        mCleanupDelayMs = cleanupDelayMs;
         mContext = ctx;
         mNsdSettings = settings;
         mNsdStateMachine = new NsdStateMachine(TAG, handler);
@@ -552,7 +576,8 @@
         HandlerThread thread = new HandlerThread(TAG);
         thread.start();
         Handler handler = new Handler(thread.getLooper());
-        NsdService service = new NsdService(context, settings, handler, DaemonConnection::new);
+        NsdService service = new NsdService(context, settings, handler,
+                DaemonConnection::new, CLEANUP_DELAY_MS);
         service.mDaemonCallback.awaitConnection();
         return service;
     }
@@ -681,12 +706,16 @@
     @VisibleForTesting
     public static class DaemonConnection {
         final NativeDaemonConnector mNativeConnector;
+        boolean mIsStarted = false;
 
         DaemonConnection(NativeCallbackReceiver callback) {
             mNativeConnector = new NativeDaemonConnector(callback, "mdns", 10, MDNS_TAG, 25, null);
             new Thread(mNativeConnector, MDNS_TAG).start();
         }
 
+        /**
+         * Executes the specified cmd on the daemon.
+         */
         public boolean execute(Object... args) {
             if (DBG) {
                 Slog.d(TAG, "mdnssd " + Arrays.toString(args));
@@ -700,12 +729,26 @@
             return true;
         }
 
-        public void start() {
+        /**
+         * Starts the daemon if it is not already started.
+         */
+        public void maybeStart() {
+            if (mIsStarted) {
+                return;
+            }
             execute("start-service");
+            mIsStarted = true;
         }
 
-        public void stop() {
+        /**
+         * Stops the daemon if it is started.
+         */
+        public void maybeStop() {
+            if (!mIsStarted) {
+                return;
+            }
             execute("stop-service");
+            mIsStarted = false;
         }
     }
 
@@ -864,6 +907,7 @@
             }
             mClientIds.clear();
             mClientRequests.clear();
+            mNsdStateMachine.maybeScheduleStop();
         }
 
         // mClientIds is a sparse array of listener id -> mDnsClient id.  For a given mDnsClient id,