Merge "Test TetheringManager could be GC after getting connector"
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
index 0052267..b7f5872 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
@@ -28,6 +28,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
@@ -46,6 +47,7 @@
 import android.net.TetheringRequestParcel;
 import android.net.ip.IpServer;
 import android.os.Bundle;
+import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.ResultReceiver;
@@ -63,7 +65,6 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Matchers;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -503,38 +504,81 @@
         });
     }
 
+    private class ConnectorSupplier<T> implements Supplier<T> {
+        private T mResult = null;
+
+        public void set(T result) {
+            mResult = result;
+        }
+
+        @Override
+        public T get() {
+            return mResult;
+        }
+    }
+
+    private void forceGc() {
+        System.gc();
+        System.runFinalization();
+        System.gc();
+    }
+
     @Test
     public void testTetheringManagerLeak() throws Exception {
         runAsAccessNetworkState((none) -> {
             final ArrayList<ITetheringEventCallback> callbacks = new ArrayList<>();
+            final ConditionVariable registeredCv = new ConditionVariable(false);
             doAnswer((invocation) -> {
                 final Object[] args = invocation.getArguments();
                 callbacks.add((ITetheringEventCallback) args[0]);
+                registeredCv.open();
                 return null;
-            }).when(mTethering).registerTetheringEventCallback(Matchers.anyObject());
+            }).when(mTethering).registerTetheringEventCallback(any());
 
-            final Supplier<IBinder> supplier = () -> mMockConnector.getIBinder();
+            doAnswer((invocation) -> {
+                final Object[] args = invocation.getArguments();
+                callbacks.remove((ITetheringEventCallback) args[0]);
+                return null;
+            }).when(mTethering).unregisterTetheringEventCallback(any());
+
+            final ConnectorSupplier<IBinder> supplier = new ConnectorSupplier<>();
 
             TetheringManager tm = new TetheringManager(mMockConnector.getService(), supplier);
             assertNotNull(tm);
-            assertEquals("Internal callback is not registered", 1, callbacks.size());
+            assertEquals("Internal callback should not be registered", 0, callbacks.size());
 
-            final WeakReference weakTm = new WeakReference(tm);
+            final WeakReference<TetheringManager> weakTm = new WeakReference(tm);
             assertNotNull(weakTm.get());
 
+            // TetheringManager couldn't be GCed because pollingConnector thread implicitly
+            // reference TetheringManager object.
             tm = null;
+            forceGc();
+            assertNotNull(weakTm.get());
+
+            // After getting connector, pollingConnector thread stops and internal callback is
+            // registered.
+            supplier.set(mMockConnector.getIBinder());
+            final long timeout = 500L;
+            if (!registeredCv.block(timeout)) {
+                fail("TetheringManager poll connector fail after " + timeout + " ms");
+            }
+            assertEquals("Internal callback is not registered", 1, callbacks.size());
+            assertNotNull(weakTm.get());
+
             final int attempts = 100;
             final long waitIntervalMs = 50;
             for (int i = 0; i < attempts; i++) {
-                System.gc();
-                System.runFinalization();
-                System.gc();
+                forceGc();
                 if (weakTm.get() == null) break;
 
                 Thread.sleep(waitIntervalMs);
             }
             assertNull("TetheringManager weak reference still not null after " + attempts
                     + " attempts", weakTm.get());
+
+            // BUG: internal callback do not be unregistered after TetheringManager is GCed.
+            assertEquals(1, callbacks.size());
         });
     }
 }