WifiStack: Copy over runWithScissors method

Handler.runWithScissors is a simple @hide utility in Handler that
should never become part of the formal Android API surface. Copy over the
method to wifi.

Bug: 142809066
Test: atest com.android.server.wifi.util
Change-Id: I675121bb5b4e65e0a0a3db0b9ebee7277817d7c6
diff --git a/service/java/com/android/server/wifi/WifiThreadRunner.java b/service/java/com/android/server/wifi/WifiThreadRunner.java
index aa79fcf..3a0fd04 100644
--- a/service/java/com/android/server/wifi/WifiThreadRunner.java
+++ b/service/java/com/android/server/wifi/WifiThreadRunner.java
@@ -19,6 +19,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
 import android.util.Log;
 
 import com.android.server.wifi.util.GeneralUtil.Mutable;
@@ -66,7 +68,7 @@
     @Nullable
     public <T> T call(@NonNull Supplier<T> supplier, T valueToReturnOnTimeout) {
         Mutable<T> result = new Mutable<>();
-        boolean runWithScissorsSuccess = mHandler.runWithScissors(
+        boolean runWithScissorsSuccess = runWithScissors(mHandler,
                 () -> result.value = supplier.get(),
                 RUN_WITH_SCISSORS_TIMEOUT_MILLIS);
         if (runWithScissorsSuccess) {
@@ -87,7 +89,7 @@
      */
     public boolean run(@NonNull Runnable runnable) {
         boolean runWithScissorsSuccess =
-                mHandler.runWithScissors(runnable, RUN_WITH_SCISSORS_TIMEOUT_MILLIS);
+                runWithScissors(mHandler, runnable, RUN_WITH_SCISSORS_TIMEOUT_MILLIS);
         if (runWithScissorsSuccess) {
             return true;
         } else {
@@ -105,4 +107,114 @@
     public boolean post(@NonNull Runnable runnable) {
         return mHandler.post(runnable);
     }
+
+    // Note: @hide methods copied from android.os.Handler
+    /**
+     * Runs the specified task synchronously.
+     * <p>
+     * If the current thread is the same as the handler thread, then the runnable
+     * runs immediately without being enqueued.  Otherwise, posts the runnable
+     * to the handler and waits for it to complete before returning.
+     * </p><p>
+     * This method is dangerous!  Improper use can result in deadlocks.
+     * Never call this method while any locks are held or use it in a
+     * possibly re-entrant manner.
+     * </p><p>
+     * This method is occasionally useful in situations where a background thread
+     * must synchronously await completion of a task that must run on the
+     * handler's thread.  However, this problem is often a symptom of bad design.
+     * Consider improving the design (if possible) before resorting to this method.
+     * </p><p>
+     * One example of where you might want to use this method is when you just
+     * set up a Handler thread and need to perform some initialization steps on
+     * it before continuing execution.
+     * </p><p>
+     * If timeout occurs then this method returns <code>false</code> but the runnable
+     * will remain posted on the handler and may already be in progress or
+     * complete at a later time.
+     * </p><p>
+     * When using this method, be sure to use {@link Looper#quitSafely} when
+     * quitting the looper.  Otherwise {@link #runWithScissors} may hang indefinitely.
+     * (TODO: We should fix this by making MessageQueue aware of blocking runnables.)
+     * </p>
+     *
+     * @param r The Runnable that will be executed synchronously.
+     * @param timeout The timeout in milliseconds, or 0 to wait indefinitely.
+     *
+     * @return Returns true if the Runnable was successfully executed.
+     *         Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.
+     *
+     * @hide This method is prone to abuse and should probably not be in the API.
+     * If we ever do make it part of the API, we might want to rename it to something
+     * less funny like runUnsafe().
+     */
+    private static boolean runWithScissors(@NonNull Handler handler, @NonNull Runnable r,
+            long timeout) {
+        if (r == null) {
+            throw new IllegalArgumentException("runnable must not be null");
+        }
+        if (timeout < 0) {
+            throw new IllegalArgumentException("timeout must be non-negative");
+        }
+
+        if (Looper.myLooper() == handler.getLooper()) {
+            r.run();
+            return true;
+        }
+
+        BlockingRunnable br = new BlockingRunnable(r);
+        return br.postAndWait(handler, timeout);
+    }
+
+    private static final class BlockingRunnable implements Runnable {
+        private final Runnable mTask;
+        private boolean mDone;
+
+        BlockingRunnable(Runnable task) {
+            mTask = task;
+        }
+
+        @Override
+        public void run() {
+            try {
+                mTask.run();
+            } finally {
+                synchronized (this) {
+                    mDone = true;
+                    notifyAll();
+                }
+            }
+        }
+
+        public boolean postAndWait(Handler handler, long timeout) {
+            if (!handler.post(this)) {
+                return false;
+            }
+
+            synchronized (this) {
+                if (timeout > 0) {
+                    final long expirationTime = SystemClock.uptimeMillis() + timeout;
+                    while (!mDone) {
+                        long delay = expirationTime - SystemClock.uptimeMillis();
+                        if (delay <= 0) {
+                            return false; // timeout
+                        }
+                        try {
+                            wait(delay);
+                        } catch (InterruptedException ex) {
+                        }
+                    }
+                } else {
+                    while (!mDone) {
+                        try {
+                            wait();
+                        } catch (InterruptedException ex) {
+                        }
+                    }
+                }
+            }
+            return true;
+        }
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiThreadRunnerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiThreadRunnerTest.java
index 7a051e2..59f2ed8 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiThreadRunnerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiThreadRunnerTest.java
@@ -85,7 +85,7 @@
 
     @Test
     public void callFailure_returnValueOnTimeout() {
-        doReturn(false).when(mHandler).runWithScissors(any(), anyLong());
+        doReturn(false).when(mHandler).post(any());
 
         Integer result = mWifiThreadRunner.call(mSupplier, VALUE_ON_TIMEOUT);
 
@@ -110,7 +110,7 @@
 
     @Test
     public void runFailure() {
-        doReturn(false).when(mHandler).runWithScissors(any(), anyLong());
+        doReturn(false).when(mHandler).post(any());
 
         boolean runSuccess = mWifiThreadRunner.run(mRunnable);