Merge "New helpers: AndroidMockitoHelper.[run|call]On[Main|Ui]Thread()" into sc-dev
diff --git a/car-test-lib/src/android/car/test/mocks/AndroidMockitoHelper.java b/car-test-lib/src/android/car/test/mocks/AndroidMockitoHelper.java
index 150ab73..deb3946 100644
--- a/car-test-lib/src/android/car/test/mocks/AndroidMockitoHelper.java
+++ b/car-test-lib/src/android/car/test/mocks/AndroidMockitoHelper.java
@@ -17,12 +17,15 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.Activity;
 import android.app.ActivityManager;
 import android.car.test.util.UserTestingHelper;
 import android.car.test.util.UserTestingHelper.UserInfoBuilder;
@@ -32,8 +35,10 @@
 import android.content.pm.UserInfo;
 import android.content.pm.UserInfo.UserInfoFlag;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.IInterface;
+import android.os.Looper;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -42,6 +47,9 @@
 
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * Provides common Mockito calls for core Android classes.
@@ -247,6 +255,81 @@
         }
     }
 
+    // TODO(b/192307581): add unit tests
+    /**
+     * Returns the result of the giving {@code callable} in the main thread, preparing the
+     * {@link Looper} if needed and using a default timeout.
+     */
+    public static <T> T syncCallOnMainThread(Callable<T> c) throws Exception {
+        return syncCallOnMainThread(JavaMockitoHelper.ASYNC_TIMEOUT_MS, c);
+    }
+
+    // TODO(b/192307581): add unit tests
+    /**
+     * Returns the result of the giving {@code callable} in the main thread, preparing the
+     * {@link Looper} if needed.
+     */
+    public static <T> T syncCallOnMainThread(long timeoutMs, Callable<T> callable)
+            throws Exception {
+        boolean quitLooper = false;
+        Looper looper = Looper.getMainLooper();
+        if (looper == null) {
+            Log.i(TAG, "preparing main looper");
+            Looper.prepareMainLooper();
+            looper = Looper.getMainLooper();
+            assertWithMessage("Looper.getMainLooper()").that(looper).isNotNull();
+            quitLooper = true;
+        }
+        Log.i(TAG, "looper: " + looper);
+        AtomicReference<Exception> exception = new AtomicReference<>();
+        AtomicReference<T> ref = new AtomicReference<>();
+        try {
+            Handler handler = new Handler(looper);
+            CountDownLatch latch = new CountDownLatch(1);
+            handler.post(() -> {
+                T result = null;
+                try {
+                    result = callable.call();
+                } catch (Exception e) {
+                    exception.set(e);
+                }
+                ref.set(result);
+                latch.countDown();
+            });
+            JavaMockitoHelper.await(latch, timeoutMs);
+            Exception e = exception.get();
+            if (e != null) throw e;
+            return ref.get();
+        } finally {
+            if (quitLooper) {
+                Log.i(TAG, "quitting looper: " + looper);
+                looper.quitSafely();
+            }
+        }
+    }
+
+    // TODO(b/192307581): add unit tests
+    /**
+     * Runs the giving {@code runnable} in the activity's UI thread, using a default timeout.
+     */
+    public static void syncRunOnUiThread(Activity activity, Runnable runnable) throws Exception {
+        syncRunOnUiThread(JavaMockitoHelper.ASYNC_TIMEOUT_MS, activity, runnable);
+    }
+
+    // TODO(b/192307581): add unit tests
+    /**
+     * Runs the giving {@code runnable} in the activity's UI thread.
+     */
+    public static void syncRunOnUiThread(long timeoutMs, Activity activity, Runnable runnable)
+            throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        activity.runOnUiThread(() -> {
+            runnable.run();
+            latch.countDown();
+        });
+        JavaMockitoHelper.await(latch, timeoutMs);
+    }
+
     private AndroidMockitoHelper() {
         throw new UnsupportedOperationException("contains only static methods");
     }
diff --git a/car-test-lib/src/android/car/test/mocks/JavaMockitoHelper.java b/car-test-lib/src/android/car/test/mocks/JavaMockitoHelper.java
index 9430723..57d39a5 100644
--- a/car-test-lib/src/android/car/test/mocks/JavaMockitoHelper.java
+++ b/car-test-lib/src/android/car/test/mocks/JavaMockitoHelper.java
@@ -30,7 +30,7 @@
  */
 public final class JavaMockitoHelper {
 
-    private static final long ASYNC_TIMEOUT_MS = 500;
+    static final long ASYNC_TIMEOUT_MS = 500;
 
     private static final String TAG = JavaMockitoHelper.class.getSimpleName();
 
diff --git a/tests/carservice_unit_test/src/com/android/car/admin/NewUserDisclaimerActivityTest.java b/tests/carservice_unit_test/src/com/android/car/admin/NewUserDisclaimerActivityTest.java
index b1e8b72..626b5c0 100644
--- a/tests/carservice_unit_test/src/com/android/car/admin/NewUserDisclaimerActivityTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/admin/NewUserDisclaimerActivityTest.java
@@ -36,6 +36,7 @@
 import android.app.PendingIntent;
 import android.app.UiAutomation;
 import android.car.test.mocks.AbstractExtendedMockitoTestCase;
+import android.car.test.mocks.AndroidMockitoHelper;
 import android.car.test.mocks.JavaMockitoHelper;
 import android.content.Context;
 import android.content.Intent;
@@ -111,15 +112,12 @@
 
     @Test
     public void testAccept() throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-        mActivity.runOnUiThread(() -> {
+        AndroidMockitoHelper.syncRunOnUiThread(mActivity, () -> {
             mActivity.onCreate(/* savedInstanceState= */ null);
             Button button = mActivity.getAcceptButton();
             Log.d(TAG, "Clicking accept button: " + button);
             button.performClick();
-            latch.countDown();
         });
-        JavaMockitoHelper.await(latch, TIMEOUT_MS);
 
         verify(mService).setAcknowledged();
         assertWithMessage("activity is finishing").that(mActivity.isFinishing()).isTrue();