Add mojo service to get notified of vsync.

R=ppi@chromium.org
BUG=Fixes https://github.com/domokit/mojo/issues/535

Review URL: https://codereview.chromium.org/1458453003 .
diff --git a/mojo/services/vsync/interfaces/BUILD.gn b/mojo/services/vsync/interfaces/BUILD.gn
new file mode 100644
index 0000000..087262b
--- /dev/null
+++ b/mojo/services/vsync/interfaces/BUILD.gn
@@ -0,0 +1,12 @@
+# Copyright 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/module_args/mojo.gni")
+import("$mojo_sdk_root/mojo/public/tools/bindings/mojom.gni")
+
+mojom("interfaces") {
+  sources = [
+    "vsync.mojom",
+  ]
+}
diff --git a/mojo/services/vsync/interfaces/vsync.mojom b/mojo/services/vsync/interfaces/vsync.mojom
new file mode 100644
index 0000000..5cc66a1
--- /dev/null
+++ b/mojo/services/vsync/interfaces/vsync.mojom
@@ -0,0 +1,13 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[DartPackage="mojo_services"]
+module vsync;
+
+interface VSyncProvider {
+  // Waits for the next vsync and returns its timestamp once it happens. The
+  // timestamps can only be compared with other calls of this method.
+  // Only one callback can be parked at a given time.
+  AwaitVSync() => (int64 time_stamp);
+};
diff --git a/services/vsync/BUILD.gn b/services/vsync/BUILD.gn
new file mode 100644
index 0000000..fa46639
--- /dev/null
+++ b/services/vsync/BUILD.gn
@@ -0,0 +1,16 @@
+# Copyright 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/android/config.gni")
+import("//build/config/android/rules.gni")
+
+android_library("vsync") {
+  java_files = [ "src/org/chromium/mojo/vsync/VSyncProviderImpl.java" ]
+
+  deps = [
+    "//mojo/public/java:bindings",
+    "//mojo/public/java:system",
+    "//mojo/services/vsync/interfaces:interfaces_java",
+  ]
+}
diff --git a/services/vsync/src/org/chromium/mojo/vsync/VSyncProviderImpl.java b/services/vsync/src/org/chromium/mojo/vsync/VSyncProviderImpl.java
new file mode 100644
index 0000000..adca2bc
--- /dev/null
+++ b/services/vsync/src/org/chromium/mojo/vsync/VSyncProviderImpl.java
@@ -0,0 +1,81 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.mojo.vsync;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.view.Choreographer;
+
+import org.chromium.mojo.system.Core;
+import org.chromium.mojo.system.MojoException;
+import org.chromium.mojo.system.RunLoop;
+import org.chromium.mojom.vsync.VSyncProvider;
+
+/**
+ * Android implementation of VSyncProvider.
+ */
+public class VSyncProviderImpl implements VSyncProvider, Choreographer.FrameCallback {
+    private final RunLoop mRunLoop;
+    private Choreographer mChoreographer;
+    private AwaitVSyncResponse mCallback;
+    private Binding mBinding = null;
+
+    public VSyncProviderImpl(Core core, Looper looper) {
+        mRunLoop = core.getCurrentRunLoop();
+        // The choreographer must be initialized on a thread with a looper.
+        new Handler(looper).post(new Runnable() {
+            @Override
+            public void run() {
+                final Choreographer choreographer = Choreographer.getInstance();
+                mRunLoop.postDelayedTask(new Runnable() {
+                    @Override
+                    public void run() {
+                        mChoreographer = choreographer;
+                        if (mCallback != null) {
+                            mChoreographer.postFrameCallback(VSyncProviderImpl.this);
+                        }
+                    }
+                }, 0);
+            }
+        });
+    }
+
+    public void setBinding(Binding binding) {
+        if (mBinding != null) {
+            mBinding.unbind().close();
+        }
+        mBinding = binding;
+    }
+
+    @Override
+    public void close() {}
+
+    @Override
+    public void onConnectionError(MojoException e) {}
+
+    @Override
+    public void awaitVSync(final AwaitVSyncResponse callback) {
+        if (mCallback != null) {
+            setBinding(null);
+            return;
+        }
+        mCallback = callback;
+        if (mChoreographer != null) {
+            // Posting from another thread is allowed on a choreographer.
+            mChoreographer.postFrameCallback(this);
+        }
+    }
+
+    @Override
+    public void doFrame(final long frameTimeNanos) {
+        mRunLoop.postDelayedTask(new Runnable() {
+            @Override
+            public void run() {
+                mCallback.call(frameTimeNanos / 1000);
+                mCallback = null;
+            }
+        }, 0);
+    }
+}
diff --git a/shell/BUILD.gn b/shell/BUILD.gn
index a09eb67..981df80 100644
--- a/shell/BUILD.gn
+++ b/shell/BUILD.gn
@@ -351,6 +351,7 @@
       "android/apk/src/org/chromium/mojo/shell/SharingApplicationDelegate.java",
       "android/apk/src/org/chromium/mojo/shell/ShellService.java",
       "android/apk/src/org/chromium/mojo/shell/ViewportActivity.java",
+      "android/apk/src/org/chromium/mojo/shell/VsyncFactory.java",
     ]
 
     deps = [
@@ -364,6 +365,7 @@
       "//mojo/services/input/interfaces:interfaces_java",
       "//mojo/services/keyboard/interfaces:interfaces_java",
       "//mojo/services/nfc/interfaces:interfaces_java",
+      "//mojo/services/vsync/interfaces:interfaces_java",
       "//services/authentication",
       "//services/input",
       "//services/intent_receiver:bindings_java",
@@ -373,6 +375,7 @@
       "//services/native_viewport:native_viewport_java",
       "//services/nfc_message_sink:bindings_java",
       "//services/sharing_sink:bindings_java",
+      "//services/vsync",
       "//third_party/android_tools:android_support_v13_java",
     ]
   }
diff --git a/shell/android/apk/src/org/chromium/mojo/shell/JavaApplicationRegistry.java b/shell/android/apk/src/org/chromium/mojo/shell/JavaApplicationRegistry.java
index b0e81db..67241c1 100644
--- a/shell/android/apk/src/org/chromium/mojo/shell/JavaApplicationRegistry.java
+++ b/shell/android/apk/src/org/chromium/mojo/shell/JavaApplicationRegistry.java
@@ -4,6 +4,8 @@
 
 package org.chromium.mojo.shell;
 
+import android.os.HandlerThread;
+
 import org.chromium.base.ApplicationStatus;
 import org.chromium.base.CalledByNative;
 import org.chromium.base.JNINamespace;
@@ -30,6 +32,8 @@
 @JNINamespace("shell")
 public class JavaApplicationRegistry {
     private final Map<String, ApplicationDelegate> mApplicationDelegateMap = new HashMap<>();
+    // Thread with a Looper required to get callbacks from the android framework..
+    private final HandlerThread mHandlerThread = new HandlerThread("FrameworkThread");
 
     private static final class ApplicationRunnable implements Runnable {
         private final ApplicationDelegate mApplicationDelegate;
@@ -51,7 +55,9 @@
         }
     }
 
-    private JavaApplicationRegistry() {}
+    private JavaApplicationRegistry() {
+        mHandlerThread.start();
+    }
 
     private void registerApplicationDelegate(String url, ApplicationDelegate applicationDelegate) {
         mApplicationDelegateMap.put(url, applicationDelegate);
@@ -97,6 +103,9 @@
         registry.registerApplicationDelegate("mojo:sharing", new SharingApplicationDelegate());
         registry.registerApplicationDelegate(
                 "mojo:native_viewport_support", new NativeViewportSupportApplicationDelegate());
+        registry.registerApplicationDelegate(
+                "mojo:vsync", new ServiceProviderFactoryApplicationDelegate(
+                                      new VsyncFactory(registry.mHandlerThread.getLooper())));
         return registry;
     }
 
diff --git a/shell/android/apk/src/org/chromium/mojo/shell/VsyncFactory.java b/shell/android/apk/src/org/chromium/mojo/shell/VsyncFactory.java
new file mode 100644
index 0000000..b159ce2
--- /dev/null
+++ b/shell/android/apk/src/org/chromium/mojo/shell/VsyncFactory.java
@@ -0,0 +1,35 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.mojo.shell;
+
+import android.os.Looper;
+
+import org.chromium.mojo.application.ServiceFactoryBinder;
+import org.chromium.mojo.bindings.InterfaceRequest;
+import org.chromium.mojo.system.impl.CoreImpl;
+import org.chromium.mojo.vsync.VSyncProviderImpl;
+import org.chromium.mojom.vsync.VSyncProvider;
+
+/**
+ * A ServiceFactoryBinder for the vsync service.
+ */
+final class VsyncFactory implements ServiceFactoryBinder<VSyncProvider> {
+    private final Looper mLooper;
+
+    public VsyncFactory(Looper looper) {
+        mLooper = looper;
+    }
+
+    @Override
+    public void bind(InterfaceRequest<VSyncProvider> request) {
+        VSyncProviderImpl implementation = new VSyncProviderImpl(CoreImpl.getInstance(), mLooper);
+        implementation.setBinding(VSyncProvider.MANAGER.bind(implementation, request));
+    }
+
+    @Override
+    public String getInterfaceName() {
+        return VSyncProvider.MANAGER.getName();
+    }
+}