Cherry-pick: Reland [Android] Mark posted UI thread tasks as asynchronous

Clean cherry-pick of chromium
crrev.com/d51117a99a4e023e791134185c6db12b28028a1d

BUG: 18283959

Original description:

Chromium shares a message loop with Android on the browser UI thread.
This can cause problems when the associated Looper has a sync barrier,
preventing posted Chromium tasks from being dispatched until the
barrier is removed. Make this sharing more fair by marking all Chromium
Message tasks as asynchronous, avoiding stalls when there is a sync
barrier.

Note: This change was originally landed in crrev.com/512333002, but was
reverted to gather more data about sync barrier impact and investigate
alternative solutions. The investigation determiend that this approach
is fine for the near future, though it will likely evolve in time.

Change-Id: I9879a9c1bdad0dcec12c2bbee62356a5b12d9596
diff --git a/base/android/java/src/org/chromium/base/SystemMessageHandler.java b/base/android/java/src/org/chromium/base/SystemMessageHandler.java
index e1fbb0f..fd3dc5a 100644
--- a/base/android/java/src/org/chromium/base/SystemMessageHandler.java
+++ b/base/android/java/src/org/chromium/base/SystemMessageHandler.java
@@ -6,9 +6,15 @@
 
 import android.os.Handler;
 import android.os.Message;
+import android.util.Log;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 
 class SystemMessageHandler extends Handler {
 
+    private static final String TAG = "SystemMessageHandler";
+
     private static final int SCHEDULED_WORK = 1;
     private static final int DELAYED_SCHEDULED_WORK = 2;
 
@@ -16,8 +22,27 @@
     private long mMessagePumpDelegateNative = 0;
     private long mDelayedScheduledTimeTicks = 0;
 
+    // Reflected API for marking a message as asynchronous. This is a workaround
+    // to provide fair Chromium task dispatch when served by the Android UI
+    // thread's Looper, avoiding stalls when the Looper has a sync barrier.
+    // Note: Use of this API is experimental and likely to evolve in the future.
+    private Method mMessageMethodSetAsynchronous;
+
     private SystemMessageHandler(long messagePumpDelegateNative) {
         mMessagePumpDelegateNative = messagePumpDelegateNative;
+
+        try {
+            Class<?> messageClass = Class.forName("android.os.Message");
+            mMessageMethodSetAsynchronous = messageClass.getMethod(
+                    "setAsynchronous", new Class[]{boolean.class});
+        } catch (ClassNotFoundException e) {
+            Log.e(TAG, "Failed to find android.os.Message class:" + e);
+        } catch (NoSuchMethodException e) {
+            Log.e(TAG, "Failed to load Message.setAsynchronous method:" + e);
+        } catch (RuntimeException e) {
+            Log.e(TAG, "Exception while loading Message.setAsynchronous method: " + e);
+        }
+
     }
 
     @Override
@@ -31,7 +56,7 @@
     @SuppressWarnings("unused")
     @CalledByNative
     private void scheduleWork() {
-        sendEmptyMessage(SCHEDULED_WORK);
+        sendMessage(obtainAsyncMessage(SCHEDULED_WORK));
     }
 
     @SuppressWarnings("unused")
@@ -41,7 +66,7 @@
             removeMessages(DELAYED_SCHEDULED_WORK);
         }
         mDelayedScheduledTimeTicks = delayedTimeTicks;
-        sendEmptyMessageDelayed(DELAYED_SCHEDULED_WORK, millis);
+        sendMessageDelayed(obtainAsyncMessage(DELAYED_SCHEDULED_WORK), millis);
     }
 
     @SuppressWarnings("unused")
@@ -51,6 +76,31 @@
         removeMessages(DELAYED_SCHEDULED_WORK);
     }
 
+    private Message obtainAsyncMessage(int what) {
+        Message msg = Message.obtain();
+        msg.what = what;
+        if (mMessageMethodSetAsynchronous != null) {
+            // If invocation fails, assume this is indicative of future
+            // failures, and avoid log spam by nulling the reflected method.
+            try {
+                mMessageMethodSetAsynchronous.invoke(msg, true);
+            } catch (IllegalAccessException e) {
+                Log.e(TAG, "Illegal access to asynchronous message creation, disabling.");
+                mMessageMethodSetAsynchronous = null;
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Illegal argument for asynchronous message creation, disabling.");
+                mMessageMethodSetAsynchronous = null;
+            } catch (InvocationTargetException e) {
+                Log.e(TAG, "Invocation exception during asynchronous message creation, disabling.");
+                mMessageMethodSetAsynchronous = null;
+            } catch (RuntimeException e) {
+                Log.e(TAG, "Runtime exception during asynchronous message creation, disabling.");
+                mMessageMethodSetAsynchronous = null;
+            }
+        }
+        return msg;
+    }
+
     @CalledByNative
     private static SystemMessageHandler create(long messagePumpDelegateNative) {
         return new SystemMessageHandler(messagePumpDelegateNative);