Merge "Integrate binder service to vr window manager for controller input."
diff --git a/services/vr/vr_window_manager/Android.mk_disable b/services/vr/vr_window_manager/Android.mk_disable
index 9a6f752..cc1db7b 100644
--- a/services/vr/vr_window_manager/Android.mk_disable
+++ b/services/vr/vr_window_manager/Android.mk_disable
@@ -14,6 +14,30 @@
 
 LOCAL_PATH := $(call my-dir)
 
+binder_src := \
+  vr_window_manager_binder.cpp \
+  aidl/android/service/vr/IVrWindowManager.aidl
+
+static_libs := \
+  libcutils
+
+shared_libs := \
+  libbase \
+  libbinder \
+  libutils
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(binder_src)
+LOCAL_STATIC_LIBRARIES := $(static_libs)
+LOCAL_SHARED_LIBRARIES := $(shared_libs)
+LOCAL_CPPFLAGS += -std=c++11
+LOCAL_CFLAGS += -DLOG_TAG=\"VrWindowManager\"
+LOCAL_LDLIBS := -llog
+LOCAL_MODULE := libvrwm_binder
+LOCAL_MODULE_TAGS := optional
+include $(BUILD_STATIC_LIBRARY)
+
+
 native_src := \
   application.cpp \
   controller_mesh.cpp \
@@ -94,7 +118,7 @@
 include $(CLEAR_VARS)
 LOCAL_SRC_FILES := $(native_src)
 LOCAL_C_INCLUDES := hardware/qcom/display/msm8996/libgralloc
-LOCAL_STATIC_LIBRARIES := $(static_libs)
+LOCAL_STATIC_LIBRARIES := $(static_libs) libvrwm_binder
 LOCAL_SHARED_LIBRARIES := $(shared_libs)
 LOCAL_SHARED_LIBRARIES += libgvr
 LOCAL_STATIC_LIBRARIES += libgvr_ext
@@ -126,3 +150,27 @@
 LOCAL_AAPT_FLAGS += --extra-packages com.google.vr.cardboard
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
 include $(BUILD_PACKAGE)
+
+
+cmd_src := \
+  vr_wm_ctl.cpp \
+  aidl/android/service/vr/IVrWindowManager.aidl
+
+static_libs := \
+  libcutils
+
+shared_libs := \
+  libbase \
+  libbinder \
+  libutils
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(cmd_src)
+LOCAL_STATIC_LIBRARIES := $(static_libs)
+LOCAL_SHARED_LIBRARIES := $(shared_libs)
+LOCAL_CPPFLAGS += -std=c++11
+LOCAL_CFLAGS += -DLOG_TAG=\"vrwmctl\"
+LOCAL_LDLIBS := -llog
+LOCAL_MODULE := vr_wm_ctl
+LOCAL_MODULE_TAGS := optional
+include $(BUILD_EXECUTABLE)
diff --git a/services/vr/vr_window_manager/AndroidManifest.xml b/services/vr/vr_window_manager/AndroidManifest.xml
index 5cc4b5c..d5008a3 100644
--- a/services/vr/vr_window_manager/AndroidManifest.xml
+++ b/services/vr/vr_window_manager/AndroidManifest.xml
@@ -27,7 +27,7 @@
     <service android:name=".VrWindowManagerService" />
     <receiver android:name="com.google.vr.windowmanager.BootCompletedReceiver">
       <intent-filter>
-        <action android:name="android.intent.action.BOOT_COMPLETED" />
+        <!-- action android:name="android.intent.action.BOOT_COMPLETED" / -->
       </intent-filter>
     </receiver>
   </application>
diff --git a/services/vr/vr_window_manager/aidl/android/service/vr/IVrWindowManager.aidl b/services/vr/vr_window_manager/aidl/android/service/vr/IVrWindowManager.aidl
new file mode 100644
index 0000000..b5dbb8b
--- /dev/null
+++ b/services/vr/vr_window_manager/aidl/android/service/vr/IVrWindowManager.aidl
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.vr;
+
+/** @hide */
+interface IVrWindowManager {
+    const String SERVICE_NAME = "vr_window_manager";
+    void connectController(in FileDescriptor fd) = 0;
+    void disconnectController() = 1;
+    void enterVrMode() = 2;
+    void exitVrMode() = 3;
+    void setDebugMode(int mode) = 4;
+}
+
diff --git a/services/vr/vr_window_manager/application.cpp b/services/vr/vr_window_manager/application.cpp
index 62db639..895f25f 100644
--- a/services/vr/vr_window_manager/application.cpp
+++ b/services/vr/vr_window_manager/application.cpp
@@ -226,10 +226,8 @@
     if (fade_value_ > 1.0f)
       fade_value_ = 1.0f;
 
-    quat controller_quat(controller_orientation_.qw, controller_orientation_.qx,
-        controller_orientation_.qy, controller_orientation_.qz);
-    controller_position_ = elbow_model_.Update(
-        delta, last_pose_.GetRotation(), controller_quat, false);
+    controller_position_ = elbow_model_.Update(delta, last_pose_.GetRotation(),
+                                               controller_orientation_, false);
 
     dvrBeginRenderFrameEds(graphics_context_, pose.orientation,
                            pose.translation);
@@ -257,6 +255,61 @@
 }
 
 void Application::ProcessControllerInput() {
+  if (controller_data_provider_) {
+    shmem_controller_active_ = false;
+    const void* data = controller_data_provider_->LockControllerData();
+    // TODO(kpschoedel): define wire format.
+    if (data) {
+      struct wire_format {
+        uint32_t version;
+        uint32_t timestamph;
+        uint32_t timestampl;
+        uint32_t quat_count;
+        float q[4];
+        uint32_t buttonsh;
+        uint32_t buttonsl;
+      } __attribute__((__aligned__(32)));
+      const wire_format* wire_data = static_cast<const wire_format*>(data);
+      static uint64_t last_timestamp = 0;
+      if (wire_data->version == 1) {
+        shmem_controller_active_ = true;
+        uint64_t timestamp =
+            (((uint64_t)wire_data->timestamph) << 32) | wire_data->timestampl;
+        if (timestamp == last_timestamp) {
+          static uint64_t last_logged_timestamp = 0;
+          if (last_logged_timestamp != last_timestamp) {
+            last_logged_timestamp = last_timestamp;
+            ALOGI("Controller shmem stale T=0x%" PRIX64, last_timestamp);
+          }
+        } else {
+          last_timestamp = timestamp;
+          controller_orientation_ = quat(wire_data->q[3], wire_data->q[0],
+                                         wire_data->q[1], wire_data->q[2]);
+          shmem_controller_buttons_ =
+              (((uint64_t)wire_data->buttonsh) << 32) | wire_data->buttonsl;
+        }
+      } else if (wire_data->version == 0xFEEDFACE) {
+        static bool logged_init = false;
+        if (!logged_init) {
+          logged_init = true;
+          ALOGI("Controller shmem waiting for data");
+        }
+      }
+    }
+    controller_data_provider_->UnlockControllerData();
+    if (shmem_controller_active_) {
+      // TODO(kpschoedel): change to ALOGV or remove.
+      ALOGI("Controller shmem orientation: %f %f %f %f",
+            controller_orientation_.x(), controller_orientation_.y(),
+            controller_orientation_.z(), controller_orientation_.w());
+      if (shmem_controller_buttons_) {
+        ALOGI("Controller shmem buttons: %017" PRIX64,
+            shmem_controller_buttons_);
+      }
+      return;
+    }
+  }
+
   if (!controller_)
     return;
 
@@ -289,8 +342,11 @@
     controller_connection_state_logged_ = false;
   }
 
-  if (new_api_status == gvr::kControllerApiOk)
-    controller_orientation_ = controller_state_->GetOrientation();
+  if (new_api_status == gvr::kControllerApiOk) {
+    gvr_quatf orientation = controller_state_->GetOrientation();
+    controller_orientation_ =
+        quat(orientation.qw, orientation.qx, orientation.qy, orientation.qz);
+  }
 
   controller_api_status_ = new_api_status;
   controller_connection_state_ = new_connection_state;
diff --git a/services/vr/vr_window_manager/application.h b/services/vr/vr_window_manager/application.h
index 47a0927..0c6385f 100644
--- a/services/vr/vr_window_manager/application.h
+++ b/services/vr/vr_window_manager/application.h
@@ -11,6 +11,7 @@
 #include <chrono>
 #include <mutex>
 
+#include "controller_data_provider.h"
 #include "elbow_model.h"
 
 struct DvrGraphicsContext;
@@ -32,6 +33,10 @@
 
   void DrawFrame();
 
+  void SetControllerDataProvider(ControllerDataProvider* provider) {
+    controller_data_provider_ = provider;
+  }
+
  protected:
   enum class MainThreadTask {
     EnteringVrMode,
@@ -69,9 +74,11 @@
   std::unique_ptr<gvr::ControllerState> controller_state_;
   gvr::ControllerApiStatus controller_api_status_;
   gvr::ControllerConnectionState controller_connection_state_;
-  gvr_quatf controller_orientation_;
+  quat controller_orientation_;
+  bool shmem_controller_active_ = false;
   bool controller_api_status_logged_;
   bool controller_connection_state_logged_;
+  uint64_t shmem_controller_buttons_;
 
   bool is_visible_ = false;
   std::chrono::time_point<std::chrono::system_clock> visibility_button_press_;
@@ -93,6 +100,9 @@
   jobject app_context_;
   jobject class_loader_;
 
+  // Controller data provider from shared memory buffer.
+  ControllerDataProvider* controller_data_provider_ = nullptr;
+
   Application(const Application&) = delete;
   void operator=(const Application&) = delete;
 };
diff --git a/services/vr/vr_window_manager/controller_data_provider.h b/services/vr/vr_window_manager/controller_data_provider.h
new file mode 100644
index 0000000..bc1450c
--- /dev/null
+++ b/services/vr/vr_window_manager/controller_data_provider.h
@@ -0,0 +1,19 @@
+#ifndef VR_WINDOW_MANAGER_CONTROLLER_DATA_PROVIDER_H_
+#define VR_WINDOW_MANAGER_CONTROLLER_DATA_PROVIDER_H_
+
+namespace android {
+namespace dvr {
+
+class ControllerDataProvider {
+ public:
+  virtual ~ControllerDataProvider() {}
+  // Returns data pointer or nullptr. If pointer is valid, call to
+  // UnlockControllerData is required.
+  virtual const void* LockControllerData() = 0;
+  virtual void UnlockControllerData() = 0;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // VR_WINDOW_MANAGER_CONTROLLER_DATA_PROVIDER_H_
\ No newline at end of file
diff --git a/services/vr/vr_window_manager/shell_view.cpp b/services/vr/vr_window_manager/shell_view.cpp
index 11680af..29ade64 100644
--- a/services/vr/vr_window_manager/shell_view.cpp
+++ b/services/vr/vr_window_manager/shell_view.cpp
@@ -190,14 +190,6 @@
   return layer_transform;
 }
 
-vec3 FromGvrVec3f(const gvr_vec3f& vec3f) {
-  return vec3(vec3f.x, vec3f.y, vec3f.z);
-}
-
-quat FromGvrQuatf(const gvr_quatf& quaternion) {
-  return quat(quaternion.qw, quaternion.qx, quaternion.qy, quaternion.qz);
-}
-
 // Determine if ths frame should be shown or hidden.
 ViewMode CalculateVisibilityFromLayerConfig(const HwcCallback::Frame& frame,
                                             uint32_t vr_app) {
@@ -302,15 +294,24 @@
 }
 
 void ShellView::EnableDebug(bool debug) {
+  ALOGI("EnableDebug(%d)", (int)debug); // XXX TODO delete
   QueueTask(debug ? MainThreadTask::EnableDebugMode
                   : MainThreadTask::DisableDebugMode);
 }
 
 void ShellView::VrMode(bool mode) {
+  ALOGI("VrMode(%d)", (int)mode); // XXX TODO delete
   QueueTask(mode ? MainThreadTask::EnteringVrMode
                  : MainThreadTask::ExitingVrMode);
 }
 
+void ShellView::dumpInternal(String8& result) {
+  result.append("[shell]\n");
+  result.appendFormat("initialized = %s\n", initialized_ ? "true" : "false");
+  result.appendFormat("is_visible = %s\n", is_visible_ ? "true" : "false");
+  result.appendFormat("debug_mode = %s\n\n", debug_mode_ ? "true" : "false");
+}
+
 void ShellView::AdvanceFrame() {
   if (!pending_frames_.empty()) {
     // Check if we should advance the frame.
@@ -398,7 +399,11 @@
 
   if (visibility == ViewMode::Hidden && debug_mode_)
     visibility = ViewMode::VR;
-  current_vr_app_ = frame->layers().front().appid;
+
+  if (frame->layers().empty())
+    current_vr_app_ = 0;
+  else
+    current_vr_app_ = frame->layers().front().appid;
 
   std::unique_lock<std::mutex> l(pending_frame_mutex_);
 
@@ -418,7 +423,8 @@
 
   // If we are showing ourselves the main thread is not processing anything,
   // so give it a kick.
-  if (visibility != ViewMode::Hidden && current_frame_.visibility == ViewMode::Hidden) {
+  if (visibility != ViewMode::Hidden &&
+      current_frame_.visibility == ViewMode::Hidden) {
     QueueTask(MainThreadTask::EnteringVrMode);
     QueueTask(MainThreadTask::Show);
   }
@@ -598,23 +604,50 @@
   vec3 pointer_location = last_pose_.GetPosition();
   quat view_quaternion = last_pose_.GetRotation();
 
-  if (controller_ && controller_api_status_ == gvr::kControllerApiOk) {
-    view_quaternion = FromGvrQuatf(controller_orientation_);
+  bool gvr_api_active =
+      controller_ && controller_api_status_ == gvr::kControllerApiOk;
+
+  if (gvr_api_active || shmem_controller_active_) {
+    view_quaternion = controller_orientation_;
     vec4 controller_location = controller_translate_ * vec4(0, 0, 0, 1);
     pointer_location = vec3(controller_location.x(), controller_location.y(),
                             controller_location.z());
 
-    if (controller_state_->GetButtonDown(gvr::kControllerButtonClick))
-      OnClick(true);
+    if (shmem_controller_active_) {
+      uint64_t buttons = shmem_controller_buttons_;
+      shmem_controller_buttons_ = 0;
+      while (buttons) {
+        switch (buttons & 0xF) {
+          case 0x1:
+            OnClick(false);
+            break;
+          case 0x3:
+            OnTouchpadButton(false, AMOTION_EVENT_BUTTON_BACK);
+            break;
+          case 0x9:
+            OnClick(true);
+            break;
+          case 0xB:
+            OnTouchpadButton(true, AMOTION_EVENT_BUTTON_BACK);
+            break;
+          default:
+            break;
+        }
+        buttons >>= 4;
+      }
+    } else if (controller_) {
+      if (controller_state_->GetButtonDown(gvr::kControllerButtonClick))
+        OnClick(true);
 
-    if (controller_state_->GetButtonUp(gvr::kControllerButtonClick))
-      OnClick(false);
+      if (controller_state_->GetButtonUp(gvr::kControllerButtonClick))
+        OnClick(false);
 
-    if (controller_state_->GetButtonDown(gvr::kControllerButtonApp))
-      OnTouchpadButton(true, AMOTION_EVENT_BUTTON_BACK);
+      if (controller_state_->GetButtonDown(gvr::kControllerButtonApp))
+        OnTouchpadButton(true, AMOTION_EVENT_BUTTON_BACK);
 
-    if (controller_state_->GetButtonUp(gvr::kControllerButtonApp))
-      OnTouchpadButton(false, AMOTION_EVENT_BUTTON_BACK);
+      if (controller_state_->GetButtonUp(gvr::kControllerButtonApp))
+        OnTouchpadButton(false, AMOTION_EVENT_BUTTON_BACK);
+    }
   }
 
   vec3 view_direction = vec3(view_quaternion * vec3(0, 0, -1));
@@ -643,7 +676,7 @@
 
 void ShellView::DrawController(const mat4& perspective, const mat4& eye_matrix,
                                const mat4& head_matrix) {
-  if (!controller_)
+  if (!controller_ && !shmem_controller_active_)
     return;
 
   controller_program_->Use();
@@ -653,7 +686,7 @@
       controller_program_->GetProgram(), "uViewProjection");
   glUniformMatrix4fv(view_projection_location, 1, 0, mvp.data());
 
-  quat view_quaternion = FromGvrQuatf(controller_orientation_);
+  quat view_quaternion = controller_orientation_;
   view_quaternion.toRotationMatrix();
 
   vec3 world_pos = last_pose_.GetPosition() + controller_position_;
diff --git a/services/vr/vr_window_manager/shell_view.h b/services/vr/vr_window_manager/shell_view.h
index ba46e6d..14ad0f3 100644
--- a/services/vr/vr_window_manager/shell_view.h
+++ b/services/vr/vr_window_manager/shell_view.h
@@ -9,6 +9,7 @@
 
 #include "application.h"
 #include "reticle.h"
+#include "shell_view_binder_interface.h"
 #include "surface_flinger_view.h"
 
 namespace android {
@@ -20,7 +21,9 @@
   App,
 };
 
-class ShellView : public Application, public HwcCallback::Client {
+class ShellView : public Application,
+                  public android::dvr::ShellViewBinderInterface,
+                  public HwcCallback::Client {
  public:
   ShellView();
   virtual ~ShellView();
@@ -31,8 +34,10 @@
   int AllocateResources() override;
   void DeallocateResources() override;
 
-  void EnableDebug(bool debug);
-  void VrMode(bool mode);
+  // ShellViewBinderInterface:
+  void EnableDebug(bool debug) override;
+  void VrMode(bool mode) override;
+  void dumpInternal(String8& result) override;
 
  protected:
   void DrawEye(EyeType eye, const mat4& perspective, const mat4& eye_matrix,
diff --git a/services/vr/vr_window_manager/shell_view_binder_interface.h b/services/vr/vr_window_manager/shell_view_binder_interface.h
new file mode 100644
index 0000000..b58e4bd
--- /dev/null
+++ b/services/vr/vr_window_manager/shell_view_binder_interface.h
@@ -0,0 +1,20 @@
+#ifndef VR_WINDOW_MANAGER_SHELL_VIEWBINDER_INTERFACE_H_
+#define VR_WINDOW_MANAGER_SHELL_VIEWBINDER_INTERFACE_H_
+
+namespace android {
+namespace dvr {
+
+class ShellViewBinderInterface {
+ public:
+  ShellViewBinderInterface() {};
+  virtual ~ShellViewBinderInterface() {};
+
+  virtual void EnableDebug(bool debug) = 0;
+  virtual void VrMode(bool mode) = 0;
+  virtual void dumpInternal(String8& result) = 0;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // VR_WINDOW_MANAGER_SHELL_VIEWBINDER_INTERFACE_H_
diff --git a/services/vr/vr_window_manager/vr_window_manager.cpp b/services/vr/vr_window_manager/vr_window_manager.cpp
index 8d9ad79..c51ddee 100644
--- a/services/vr/vr_window_manager/vr_window_manager.cpp
+++ b/services/vr/vr_window_manager/vr_window_manager.cpp
@@ -1,18 +1,33 @@
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
 #include <binder/ProcessState.h>
 
 #include "shell_view.h"
+#include "vr_window_manager_binder.h"
 
 int main(int /* argc */, char** /* argv */) {
+  android::dvr::ShellView app;
+  const int app_status = app.Initialize(nullptr, nullptr, nullptr);
+  LOG_ALWAYS_FATAL_IF(app_status != 0, "failed to initialize: %d", app_status);
+
+  android::service::vr::VrWindowManagerBinder service(app);
+  const int status = service.Initialize();
+  LOG_ALWAYS_FATAL_IF(status != 0, "initialization failed: %d", status);
+
   android::ProcessState::self()->startThreadPool();
 
-  android::dvr::ShellView app;
-  if (app.Initialize(nullptr, nullptr, nullptr)) {
-    ALOGE("Failed to initialize");
-    return 1;
-  }
+  android::sp<android::IServiceManager> sm(android::defaultServiceManager());
+  const android::status_t service_status = sm->addService(
+      android::service::vr::VrWindowManagerBinder::SERVICE_NAME(), &service,
+      false /*allowIsolated*/);
+  LOG_ALWAYS_FATAL_IF(service_status != android::OK, "service not added: %d",
+                      static_cast<int>(service_status));
+
+  app.SetControllerDataProvider(&service);
 
   while (true)
     app.DrawFrame();
 
+  android::IPCThreadState::self()->joinThreadPool();
   return 0;
 }
diff --git a/services/vr/vr_window_manager/vr_window_manager_binder.cpp b/services/vr/vr_window_manager/vr_window_manager_binder.cpp
new file mode 100644
index 0000000..c2138b7
--- /dev/null
+++ b/services/vr/vr_window_manager/vr_window_manager_binder.cpp
@@ -0,0 +1,156 @@
+#include "vr_window_manager_binder.h"
+
+#include <inttypes.h>
+#include <sys/mman.h>
+
+#include <binder/IPCThreadState.h>
+#include <binder/PermissionCache.h>
+#include <binder/Status.h>
+#include <cutils/log.h>
+#include <private/android_filesystem_config.h>
+#include <utils/Errors.h>
+
+namespace android {
+namespace service {
+namespace vr {
+
+namespace {
+const String16 kDumpPermission("android.permission.DUMP");
+const String16 kSendMeControllerInputPermission("TODO");  // TODO(kpschoedel)
+}  // anonymous namespace
+
+constexpr size_t AshmemControllerDataProvider::kRegionLength;
+
+status_t AshmemControllerDataProvider::Connect(const int in_fd) {
+  if (in_fd < 0) {
+    return BAD_VALUE;
+  }
+  if (fd_.get() >= 0) {
+    // The VrCore is dead. Long live the VrCore.
+    Disconnect();
+  }
+  void* const shared_region =
+      ::mmap(nullptr, kRegionLength, PROT_READ, MAP_SHARED, in_fd, 0);
+  if (shared_region == MAP_FAILED) {
+    shared_region_ = nullptr;
+    return NO_MEMORY;
+  }
+
+  errno = 0;
+  const int fd = ::fcntl(in_fd, F_DUPFD_CLOEXEC, 0);
+  if (fd < 0) {
+    ::munmap(shared_region, kRegionLength);
+    return -errno;
+  }
+  fd_.reset(fd);
+  ALOGI("controller connected %d -> %d @ %p", in_fd, fd, shared_region);
+
+  std::lock_guard<std::mutex> guard(mutex_);
+  shared_region_ = shared_region;
+  return OK;
+}
+
+status_t AshmemControllerDataProvider::Disconnect() {
+  if (shared_region_ == nullptr || fd_.get() < 0) {
+    return INVALID_OPERATION;
+  }
+  std::lock_guard<std::mutex> guard(mutex_);
+  ::munmap(shared_region_, kRegionLength);
+  shared_region_ = nullptr;
+  fd_.reset();
+  ALOGI("controller disconnected");
+  return OK;
+}
+
+const void* AshmemControllerDataProvider::LockControllerData() {
+  mutex_.lock();
+  if (!shared_region_) {
+    mutex_.unlock();
+    return nullptr;
+  }
+  return shared_region_;
+}
+
+void AshmemControllerDataProvider::UnlockControllerData() { mutex_.unlock(); }
+
+void AshmemControllerDataProvider::dumpInternal(String8& result) {
+  result.appendFormat("[controller]\nfd = %d\n", fd_.get());
+  if (shared_region_) {
+    int32_t* p = reinterpret_cast<int32_t*>(shared_region_);
+    result.appendFormat("header = ");
+    for (int i = 0; i < 8; ++i) {
+      result.appendFormat("%c 0x%08" PRIX32, i ? ',' : '[', p[i]);
+    }
+    result.appendFormat(" ]\n\n");
+  }
+}
+
+int VrWindowManagerBinder::Initialize() { return 0; }
+
+binder::Status VrWindowManagerBinder::connectController(
+    const ::android::base::unique_fd& in_fd) {
+  // TODO(kpschoedel): check permission
+#if 0
+  int32_t pid, uid;
+  if (!PermissionCache::checkCallingPermission(kSendMeControllerInputPermission,
+                                               &pid, &uid)) {
+    ALOGE("permission denied to pid=%" PRId32 " uid=%" PRId32, pid, uid);
+    return binder::Status::fromStatusT(PERMISSION_DENIED);
+  }
+#endif
+  return binder::Status::fromStatusT(Connect(in_fd.get()));
+}
+
+binder::Status VrWindowManagerBinder::disconnectController() {
+  // TODO(kpschoedel): check permission
+#if 0
+  int32_t pid, uid;
+  if (!PermissionCache::checkCallingPermission(kSendMeControllerInputPermission,
+                                               &pid, &uid)) {
+    ALOGE("permission denied to pid=%" PRId32 " uid=%" PRId32, pid, uid);
+    return binder::Status::fromStatusT(PERMISSION_DENIED);
+  }
+#endif
+  return binder::Status::fromStatusT(Disconnect());
+}
+
+binder::Status VrWindowManagerBinder::enterVrMode() {
+  // TODO(kpschoedel): check permission
+  app_.VrMode(true);
+  return binder::Status::ok();
+}
+
+binder::Status VrWindowManagerBinder::exitVrMode() {
+  // TODO(kpschoedel): check permission
+  app_.VrMode(false);
+  return binder::Status::ok();
+}
+
+binder::Status VrWindowManagerBinder::setDebugMode(int32_t mode) {
+  // TODO(kpschoedel): check permission
+  app_.EnableDebug(static_cast<bool>(mode));
+  return binder::Status::ok();
+}
+
+status_t VrWindowManagerBinder::dump(
+    int fd, const Vector<String16>& args [[gnu::unused]]) {
+  String8 result;
+  const android::IPCThreadState* ipc = android::IPCThreadState::self();
+  const int pid = ipc->getCallingPid();
+  const int uid = ipc->getCallingUid();
+  if ((uid != AID_SHELL) &&
+      !PermissionCache::checkPermission(kDumpPermission, pid, uid)) {
+    result.appendFormat("Permission denial: can't dump " LOG_TAG
+                        " from pid=%d, uid=%d\n",
+                        pid, uid);
+  } else {
+    app_.dumpInternal(result);
+    AshmemControllerDataProvider::dumpInternal(result);
+  }
+  write(fd, result.string(), result.size());
+  return OK;
+}
+
+}  // namespace vr
+}  // namespace service
+}  // namespace android
diff --git a/services/vr/vr_window_manager/vr_window_manager_binder.h b/services/vr/vr_window_manager/vr_window_manager_binder.h
new file mode 100644
index 0000000..99ca27a
--- /dev/null
+++ b/services/vr/vr_window_manager/vr_window_manager_binder.h
@@ -0,0 +1,77 @@
+#ifndef VR_WINDOW_MANAGER_VR_WINDOW_MANAGER_BINDER_H_
+#define VR_WINDOW_MANAGER_VR_WINDOW_MANAGER_BINDER_H_
+
+#include <android/service/vr/BnVrWindowManager.h>
+
+#include <mutex>
+
+#include "controller_data_provider.h"
+#include "shell_view_binder_interface.h"
+
+namespace android {
+namespace service {
+namespace vr {
+
+class AshmemControllerDataProvider : public dvr::ControllerDataProvider {
+ public:
+  AshmemControllerDataProvider() {}
+  virtual ~AshmemControllerDataProvider() {}
+
+  status_t Connect(int fd);
+  status_t Disconnect();
+
+  // ControllerDataProvider:
+  const void* LockControllerData() override;
+  void UnlockControllerData() override;
+
+ protected:
+  void dumpInternal(String8& result);
+
+ private:
+  static constexpr size_t kRegionLength = 8192;  // TODO(kpschoedel)
+  ::android::base::unique_fd fd_;
+
+  // Mutex for guarding shared_region_.
+  std::mutex mutex_;
+  void* shared_region_ = nullptr;
+
+  AshmemControllerDataProvider(const AshmemControllerDataProvider&) = delete;
+  void operator=(const AshmemControllerDataProvider&) = delete;
+};
+
+class VrWindowManagerBinder : public BnVrWindowManager,
+                              public AshmemControllerDataProvider {
+ public:
+  VrWindowManagerBinder(android::dvr::ShellViewBinderInterface& app)
+      : app_(app) {}
+  virtual ~VrWindowManagerBinder() {}
+
+  // Must be called before clients can connect.
+  // Returns 0 if initialization is successful.
+  int Initialize();
+  static char const* getServiceName() { return "vr_window_manager"; }
+
+ protected:
+  // Implements IVrWindowManagerBinder.
+  ::android::binder::Status connectController(
+      const ::android::base::unique_fd& fd) override;
+  ::android::binder::Status disconnectController() override;
+  ::android::binder::Status enterVrMode() override;
+  ::android::binder::Status exitVrMode() override;
+  ::android::binder::Status setDebugMode(int32_t mode) override;
+
+  // Implements BBinder::dump().
+  status_t dump(int fd, const Vector<String16>& args) override;
+
+ private:
+  android::dvr::ShellViewBinderInterface& app_;
+
+  VrWindowManagerBinder(const VrWindowManagerBinder&) = delete;
+  void operator=(const VrWindowManagerBinder&) = delete;
+};
+
+}  // namespace vr
+}  // namespace service
+}  // namespace android
+
+#endif  // VR_WINDOW_MANAGER_VR_WINDOW_MANAGER_BINDER_H_
diff --git a/services/vr/vr_window_manager/vr_window_manager_binder_test.cpp b/services/vr/vr_window_manager/vr_window_manager_binder_test.cpp
new file mode 100644
index 0000000..f43e803
--- /dev/null
+++ b/services/vr/vr_window_manager/vr_window_manager_binder_test.cpp
@@ -0,0 +1,29 @@
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <cutils/log.h>
+
+#include "vr_window_manager_binder.h"
+
+int main() {
+  ALOGI("Starting");
+  android::service::vr::VrWindowManagerBinder service;
+  const int status = service.Initialize();
+  LOG_ALWAYS_FATAL_IF(status != 0, "initialization failed: %d", status);
+
+  signal(SIGPIPE, SIG_IGN);
+  android::sp<android::ProcessState> ps(android::ProcessState::self());
+  ps->setThreadPoolMaxThreadCount(4);
+  ps->startThreadPool();
+  ps->giveThreadPoolName();
+
+  android::sp<android::IServiceManager> sm(android::defaultServiceManager());
+  const android::status_t service_status = sm->addService(
+      android::service::vr::VrWindowManagerBinder::SERVICE_NAME(), &service,
+      false /*allowIsolated*/);
+  LOG_ALWAYS_FATAL_IF(service_status != android::OK, "service not added: %d",
+                      static_cast<int>(service_status));
+
+  android::IPCThreadState::self()->joinThreadPool();
+  return 0;
+}
diff --git a/services/vr/vr_window_manager/vr_wm_ctl.cpp b/services/vr/vr_window_manager/vr_wm_ctl.cpp
new file mode 100644
index 0000000..c67b2eb
--- /dev/null
+++ b/services/vr/vr_window_manager/vr_wm_ctl.cpp
@@ -0,0 +1,48 @@
+#include <android/service/vr/BpVrWindowManager.h>
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <inttypes.h>
+
+void usage() { fprintf(stderr, "usage: vr_wm_ctl [enter|exit|debug N]\n"); }
+
+int report(const android::binder::Status& status) {
+  if (status.isOk()) {
+    fprintf(stderr, "ok\n");
+    return 0;
+  }
+  fprintf(stderr, "failed (%" PRId32 ") %s\n", status.exceptionCode(),
+          status.exceptionMessage().string());
+  return (int)status.exceptionCode();
+}
+
+int main(int argc, char* argv[]) {
+  android::sp<android::IServiceManager> sm(android::defaultServiceManager());
+  if (sm == nullptr) {
+    fprintf(stderr, "service manager not found\n");
+    exit(1);
+  }
+
+  android::sp<android::service::vr::IVrWindowManager> vrwm =
+      android::interface_cast<android::service::vr::IVrWindowManager>(
+          sm->getService(
+              android::service::vr::IVrWindowManager::SERVICE_NAME()));
+  if (vrwm == nullptr) {
+    fprintf(stderr, "service not found\n");
+    exit(1);
+  }
+
+  android::binder::Status status;
+  if ((argc == 2) && (strcmp(argv[1], "enter") == 0)) {
+    exit(report(vrwm->enterVrMode()));
+  } else if ((argc == 2) && (strcmp(argv[1], "exit") == 0)) {
+    exit(report(vrwm->exitVrMode()));
+  } else if ((argc == 3) && (strcmp(argv[1], "debug") == 0)) {
+    exit(report(vrwm->setDebugMode(atoi(argv[2]))));
+  } else {
+    usage();
+    exit(2);
+  }
+
+  return 0;
+}