Clear inline mocks at the end of individual Bluetooth tests

When using inline mock maker, clean up inline mocks to prevent
OutOfMemory errors. See https://github.com/mockito/mockito/issues/1614.
See also b/259280359.

Ideally, inline mocks are cleared after each individual test instead of
the test class, but Bluetooth unit tests crashed when attempting to
clear inline mocks in the @After method, so ag/20652385 resorted to
clearing them in an @AfterClass method.

This CL uses Rules instead to clear inline mocks after each individual
test. It takes inspiration from
https://github.com/mockito/mockito/issues/1902, in particular:

"JUnit runs @Before after each rule, and @After before each rule. In
this particular case clearInlineMocks() removed internal handlers of all
inline mocks, but MockitoJUnit rule still needs to use them... The
solution is to wrap clearInlineMocks() in a TestRule as well, and..."
and enforce the ordering.

Test: atest CarServiceUnitTest -- --exclude-filter \
'"CarServiceUnitTest com.android.car.bluetooth.FastPairProviderTest"'
Bug: 261644033
Bug: 261724684
Bug: 261727445

Change-Id: I6f8257fb9aac3c802c0b08b563165860e54f1a4a
diff --git a/tests/carservice_unit_test/src/com/android/car/bluetooth/AbstractExtendedMockitoBluetoothTestCase.java b/tests/carservice_unit_test/src/com/android/car/bluetooth/AbstractExtendedMockitoBluetoothTestCase.java
index a985d27..d647351 100644
--- a/tests/carservice_unit_test/src/com/android/car/bluetooth/AbstractExtendedMockitoBluetoothTestCase.java
+++ b/tests/carservice_unit_test/src/com/android/car/bluetooth/AbstractExtendedMockitoBluetoothTestCase.java
@@ -43,8 +43,11 @@
 import com.android.internal.util.test.FakeSettingsProvider;
 
 import org.junit.After;
-import org.junit.AfterClass;
 import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoSession;
@@ -90,6 +93,21 @@
     private final List<Class<?>> mStaticSpiedClasses = new ArrayList<>();
     private MockitoSession mSession;
 
+    @Rule
+    public final TestRule mClearInlineMocksRule = new TestRule() {
+        @Override
+        public Statement apply(Statement base, Description description) {
+            return new Statement() {
+                @Override
+                public void evaluate() throws Throwable {
+                    // When using inline mock maker, clean up inline mocks to prevent OutOfMemory
+                    // errors. See https://github.com/mockito/mockito/issues/1614 and b/259280359.
+                    Mockito.framework().clearInlineMocks();
+                }
+            };
+        }
+    };
+
     @Before
     public final void startSession() {
         mSession = newSessionBuilder().startMocking();
@@ -97,20 +115,20 @@
 
     @After
     public final void finishSession() {
-        if (mSession != null) {
-            mSession.finishMocking();
-        } else {
+        // mSession.finishMocking() must ALWAYS be called (hence the over-protective try/finally
+        // statements), otherwise it would cause failures on future tests as mockito
+        // cannot start a session when a previous one is not finished
+        if (mSession == null) {
             Log.w(TAG, getClass().getSimpleName() + ".finishSession(): no session");
+            return;
         }
-    }
-
-    @AfterClass
-    public static void finishOnce() {
-        // TODO(b/261644033): Move the below line to {@link tearDown} method once the test flakiness
-        //  is fixed. The inline mocks must be cleared between tests in the @After annotated method.
-        // When using inline mock maker, clean up inline mocks to prevent OutOfMemory errors.
-        // See https://github.com/mockito/mockito/issues/1614 and b/259280359.
-        Mockito.framework().clearInlineMocks();
+        try {
+            mSession.finishMocking();
+        } finally {
+            // Shouldn't need to set mSession to null as JUnit always instantiate a new object,
+            // but it doesn't hurt....
+            mSession = null;
+        }
     }
 
     /**
diff --git a/tests/carservice_unit_test/src/com/android/car/bluetooth/BluetoothConnectionRetryManagerTest.java b/tests/carservice_unit_test/src/com/android/car/bluetooth/BluetoothConnectionRetryManagerTest.java
index 2a49b63..4ee0800 100644
--- a/tests/carservice_unit_test/src/com/android/car/bluetooth/BluetoothConnectionRetryManagerTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/bluetooth/BluetoothConnectionRetryManagerTest.java
@@ -37,7 +37,6 @@
 import android.os.SystemClock;
 
 import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.RequiresDevice;
 
 import org.junit.After;
@@ -56,9 +55,6 @@
  * Run:
  * atest BluetoothConnectionRetryManagerTest
  */
-// TODO(b/261724684): Remove the {@link FlakyTest} annotation once the test is fixed to safely
-//  remove the looper messages before exiting the test.
-@FlakyTest
 @RequiresDevice
 @RunWith(MockitoJUnitRunner.class)
 public class BluetoothConnectionRetryManagerTest
diff --git a/tests/carservice_unit_test/src/com/android/car/bluetooth/FastPairAdvertiserTest.java b/tests/carservice_unit_test/src/com/android/car/bluetooth/FastPairAdvertiserTest.java
index 254242f..5e1fb65 100644
--- a/tests/carservice_unit_test/src/com/android/car/bluetooth/FastPairAdvertiserTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/bluetooth/FastPairAdvertiserTest.java
@@ -51,10 +51,13 @@
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
 
 import org.junit.After;
-import org.junit.AfterClass;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
 import org.junit.runner.RunWith;
+import org.junit.runners.model.Statement;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
@@ -132,6 +135,21 @@
         }
     };
 
+    @Rule
+    public final TestRule mClearInlineMocksRule = new TestRule() {
+        @Override
+        public Statement apply(Statement base, Description description) {
+            return new Statement() {
+                @Override
+                public void evaluate() throws Throwable {
+                    // When using inline mock maker, clean up inline mocks to prevent OutOfMemory
+                    // errors. See https://github.com/mockito/mockito/issues/1614 and b/259280359.
+                    Mockito.framework().clearInlineMocks();
+                }
+            };
+        }
+    };
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -172,15 +190,6 @@
         mMockitoSession.finishMocking();
     }
 
-    @AfterClass
-    public static void finishOnce() {
-        // TODO(b/261727445): Move the below line to {@link tearDown} method once the test flakiness
-        //  is fixed. The inline mocks must be cleared between tests in the @After annotated method.
-        // When using inline mock maker, clean up inline mocks to prevent OutOfMemory errors.
-        // See https://github.com/mockito/mockito/issues/1614 and b/259280359.
-        Mockito.framework().clearInlineMocks();
-    }
-
     private void waitForAdvertisingHandlerToSettle() {
         // TODO (243518804): Remove the need for this by adding a way to wait on state transitions
         try {