Revise virtual touchpad interface.

- Explicit start and stop, outside of which the evdev devices
  don't exist.
- Permission test (not compiled by default pending build & SELinux
  support for temporarily retaining a second copy of the service
  for vr_wm).
- Enforce a single user of the touchpad.
- Support 'dumpsys'.

Bug: 36051900
Test: log inspection
Change-Id: I038ed2632d5adf50a3565a981031691d5dc5f7cd
diff --git a/services/vr/virtual_touchpad/Android.mk b/services/vr/virtual_touchpad/Android.mk
index b78eb99..88b2dd9 100644
--- a/services/vr/virtual_touchpad/Android.mk
+++ b/services/vr/virtual_touchpad/Android.mk
@@ -9,7 +9,8 @@
   VirtualTouchpadEvdev.cpp
 
 shared_libs := \
-  libbase
+  libbase \
+  libutils
 
 include $(CLEAR_VARS)
 LOCAL_SRC_FILES := $(src)
@@ -24,21 +25,23 @@
 
 # Touchpad unit tests.
 
-test_src_files := \
-  tests/VirtualTouchpad_test.cpp
-
-static_libs := \
+test_static_libs := \
   libbase \
   libcutils \
-  libutils \
   libvirtualtouchpad
 
+test_shared_libs := \
+  libutils
+
+test_src_files := \
+  tests/VirtualTouchpad_test.cpp
+
 $(foreach file,$(test_src_files), \
     $(eval include $(CLEAR_VARS)) \
     $(eval LOCAL_SRC_FILES := $(file)) \
     $(eval LOCAL_C_INCLUDES := $(LOCAL_PATH)/include) \
-    $(eval LOCAL_STATIC_LIBRARIES := $(static_libs)) \
-    $(eval LOCAL_SHARED_LIBRARIES := $(shared_libs)) \
+    $(eval LOCAL_STATIC_LIBRARIES := $(test_static_libs)) \
+    $(eval LOCAL_SHARED_LIBRARIES := $(test_shared_libs)) \
     $(eval LOCAL_CPPFLAGS += -std=c++11) \
     $(eval LOCAL_LDLIBS := -llog) \
     $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \
@@ -70,7 +73,7 @@
 LOCAL_STATIC_LIBRARIES := $(static_libs)
 LOCAL_SHARED_LIBRARIES := $(shared_libs)
 LOCAL_CPPFLAGS += -std=c++11
-LOCAL_CFLAGS += -DLOG_TAG=\"VrVirtualTouchpad\"
+LOCAL_CFLAGS += -DLOG_TAG=\"VrVirtualTouchpad\" -DSELINUX_ACCESS_CONTROL
 LOCAL_LDLIBS := -llog
 LOCAL_MODULE := virtual_touchpad
 LOCAL_MODULE_TAGS := optional
diff --git a/services/vr/virtual_touchpad/EvdevInjector.cpp b/services/vr/virtual_touchpad/EvdevInjector.cpp
index d8a1dfa..b4c0a52 100644
--- a/services/vr/virtual_touchpad/EvdevInjector.cpp
+++ b/services/vr/virtual_touchpad/EvdevInjector.cpp
@@ -307,5 +307,11 @@
   return 0;
 }
 
+void EvdevInjector::dumpInternal(String8& result) {
+  result.append("[injector]\n");
+  result.appendFormat("state = %d\n", static_cast<int>(state_));
+  result.appendFormat("error = %d\n\n", error_);
+}
+
 }  // namespace dvr
 }  // namespace android
diff --git a/services/vr/virtual_touchpad/EvdevInjector.h b/services/vr/virtual_touchpad/EvdevInjector.h
index 1b1c4da..c69dbef 100644
--- a/services/vr/virtual_touchpad/EvdevInjector.h
+++ b/services/vr/virtual_touchpad/EvdevInjector.h
@@ -3,6 +3,7 @@
 
 #include <android-base/unique_fd.h>
 #include <linux/uinput.h>
+#include <utils/String8.h>
 
 #include <cstdint>
 #include <memory>
@@ -99,6 +100,8 @@
   int SendMultiTouchXY(int32_t slot, int32_t id, int32_t x, int32_t y);
   int SendMultiTouchLift(int32_t slot);
 
+  void dumpInternal(String8& result);
+
  protected:
   // Must be called only between construction and ConfigureBegin().
   inline void SetUInputForTesting(UInput* uinput) { uinput_ = uinput; }
diff --git a/services/vr/virtual_touchpad/VirtualTouchpadClient.cpp b/services/vr/virtual_touchpad/VirtualTouchpadClient.cpp
index 175173f..782b19c 100644
--- a/services/vr/virtual_touchpad/VirtualTouchpadClient.cpp
+++ b/services/vr/virtual_touchpad/VirtualTouchpadClient.cpp
@@ -10,17 +10,49 @@
 
 class VirtualTouchpadClientImpl : public VirtualTouchpadClient {
  public:
-  VirtualTouchpadClientImpl(sp<IVirtualTouchpadService> service)
-      : service_(service) {}
-  ~VirtualTouchpadClientImpl() override {}
+  VirtualTouchpadClientImpl() {}
+  ~VirtualTouchpadClientImpl() override {
+    if (service_ != nullptr) {
+      Detach();
+    }
+  }
 
-  status_t Touch(int touchpad,
-                 float x, float y, float pressure) override {
+  status_t Attach() {
+    if (service_ != nullptr) {
+      return ALREADY_EXISTS;
+    }
+    sp<IServiceManager> sm = defaultServiceManager();
+    if (sm == nullptr) {
+      ALOGE("no service manager");
+      return NO_INIT;
+    }
+    sp<IVirtualTouchpadService> service =
+        interface_cast<IVirtualTouchpadService>(
+            sm->getService(IVirtualTouchpadService::SERVICE_NAME()));
+    if (service == nullptr) {
+      ALOGE("failed to get service");
+      return NAME_NOT_FOUND;
+    }
+    service_ = service;
+    return service_->attach().transactionError();
+  }
+
+  status_t Detach() {
+    if (service_ == nullptr) {
+      return NO_INIT;
+    }
+    status_t status = service_->detach().transactionError();
+    service_ = nullptr;
+    return status;
+  }
+
+  status_t Touch(int touchpad, float x, float y, float pressure) override {
     if (service_ == nullptr) {
       return NO_INIT;
     }
     return service_->touch(touchpad, x, y, pressure).transactionError();
   }
+
   status_t ButtonState(int touchpad, int buttons) override {
     if (service_ == nullptr) {
       return NO_INIT;
@@ -28,6 +60,12 @@
     return service_->buttonState(touchpad, buttons).transactionError();
   }
 
+  void dumpInternal(String8& result) override {
+    result.append("[virtual touchpad]\n");
+    result.appendFormat("connected = %s\n\n",
+                        service_ != nullptr ? "true" : "false");
+  }
+
  private:
   sp<IVirtualTouchpadService> service_;
 };
@@ -35,18 +73,7 @@
 }  // anonymous namespace
 
 sp<VirtualTouchpad> VirtualTouchpadClient::Create() {
-  sp<IServiceManager> sm = defaultServiceManager();
-  if (sm == nullptr) {
-    ALOGE("no service manager");
-    return sp<VirtualTouchpad>();
-  }
-  sp<IVirtualTouchpadService> service = interface_cast<IVirtualTouchpadService>(
-      sm->getService(IVirtualTouchpadService::SERVICE_NAME()));
-  if (service == nullptr) {
-    ALOGE("failed to get service");
-    return sp<VirtualTouchpad>();
-  }
-  return new VirtualTouchpadClientImpl(service);
+  return new VirtualTouchpadClientImpl();
 }
 
 }  // namespace dvr
diff --git a/services/vr/virtual_touchpad/VirtualTouchpadEvdev.cpp b/services/vr/virtual_touchpad/VirtualTouchpadEvdev.cpp
index f25a2ad..bc29408 100644
--- a/services/vr/virtual_touchpad/VirtualTouchpadEvdev.cpp
+++ b/services/vr/virtual_touchpad/VirtualTouchpadEvdev.cpp
@@ -32,15 +32,10 @@
 
 sp<VirtualTouchpad> VirtualTouchpadEvdev::Create() {
   VirtualTouchpadEvdev* const touchpad = new VirtualTouchpadEvdev();
-  const status_t status = touchpad->Initialize();
-  if (status) {
-    ALOGE("initialization failed: %d", status);
-    return sp<VirtualTouchpad>();
-  }
   return sp<VirtualTouchpad>(touchpad);
 }
 
-int VirtualTouchpadEvdev::Initialize() {
+status_t VirtualTouchpadEvdev::Attach() {
   if (!injector_) {
     owned_injector_.reset(new EvdevInjector());
     injector_ = owned_injector_.get();
@@ -56,9 +51,20 @@
   return injector_->GetError();
 }
 
+status_t VirtualTouchpadEvdev::Detach() {
+  injector_->Close();
+  injector_ = nullptr;
+  owned_injector_.reset();
+  last_device_x_ = INT32_MIN;
+  last_device_y_ = INT32_MIN;
+  touches_ = 0;
+  last_motion_event_buttons_ = 0;
+  return OK;
+}
+
 int VirtualTouchpadEvdev::Touch(int touchpad, float x, float y,
                                 float pressure) {
-  (void)touchpad; // TODO(b/35992608) Support multiple virtual touchpad devices.
+  (void)touchpad;  // TODO(b/35992608) Support multiple touchpad devices.
   if ((x < 0.0f) || (x >= 1.0f) || (y < 0.0f) || (y >= 1.0f)) {
     return EINVAL;
   }
@@ -104,7 +110,7 @@
 }
 
 int VirtualTouchpadEvdev::ButtonState(int touchpad, int buttons) {
-  (void)touchpad; // TODO(b/35992608) Support multiple virtual touchpad devices.
+  (void)touchpad;  // TODO(b/35992608) Support multiple touchpad devices.
   const int changes = last_motion_event_buttons_ ^ buttons;
   if (!changes) {
     return 0;
@@ -123,10 +129,26 @@
     injector_->SendKey(BTN_BACK, (buttons & AMOTION_EVENT_BUTTON_BACK)
                                      ? EvdevInjector::KEY_PRESS
                                      : EvdevInjector::KEY_RELEASE);
+    injector_->SendSynReport();
   }
   last_motion_event_buttons_ = buttons;
   return injector_->GetError();
 }
 
+void VirtualTouchpadEvdev::dumpInternal(String8& result) {
+  result.append("[virtual touchpad]\n");
+  if (!injector_) {
+    result.append("injector = none\n");
+    return;
+  }
+  result.appendFormat("injector = %s\n", owned_injector_ ? "normal" : "test");
+  result.appendFormat("touches = %d\n", touches_);
+  result.appendFormat("last_position = (%" PRId32 ", %" PRId32 ")\n",
+                      last_device_x_, last_device_y_);
+  result.appendFormat("last_buttons = 0x%" PRIX32 "\n\n",
+                      last_motion_event_buttons_);
+  injector_->dumpInternal(result);
+}
+
 }  // namespace dvr
 }  // namespace android
diff --git a/services/vr/virtual_touchpad/VirtualTouchpadEvdev.h b/services/vr/virtual_touchpad/VirtualTouchpadEvdev.h
index ec8006b..b158cec 100644
--- a/services/vr/virtual_touchpad/VirtualTouchpadEvdev.h
+++ b/services/vr/virtual_touchpad/VirtualTouchpadEvdev.h
@@ -18,15 +18,17 @@
   static sp<VirtualTouchpad> Create();
 
   // VirtualTouchpad implementation:
+  status_t Attach() override;
+  status_t Detach() override;
   status_t Touch(int touchpad, float x, float y, float pressure) override;
   status_t ButtonState(int touchpad, int buttons) override;
+  void dumpInternal(String8& result) override;
 
  protected:
   VirtualTouchpadEvdev() {}
   ~VirtualTouchpadEvdev() override {}
-  status_t Initialize();
 
-  // Must be called only between construction and Initialize().
+  // Must be called only between construction and Attach().
   inline void SetEvdevInjectorForTesting(EvdevInjector* injector) {
     injector_ = injector;
   }
diff --git a/services/vr/virtual_touchpad/VirtualTouchpadService.cpp b/services/vr/virtual_touchpad/VirtualTouchpadService.cpp
index a1f281c..2e2f622 100644
--- a/services/vr/virtual_touchpad/VirtualTouchpadService.cpp
+++ b/services/vr/virtual_touchpad/VirtualTouchpadService.cpp
@@ -1,22 +1,136 @@
 #include "VirtualTouchpadService.h"
 
+#include <inttypes.h>
+
+#include <binder/IPCThreadState.h>
+#include <binder/PermissionCache.h>
 #include <binder/Status.h>
+#include <cutils/log.h>
 #include <linux/input.h>
-#include <log/log.h>
+#include <private/android_filesystem_config.h>
 #include <utils/Errors.h>
 
 namespace android {
 namespace dvr {
 
-binder::Status VirtualTouchpadService::touch(int touchpad,
-                                             float x, float y, float pressure) {
-  const status_t error = touchpad_->Touch(touchpad, x, y, pressure);
-  return error ? binder::Status::fromStatusT(error) : binder::Status::ok();
+namespace {
+const String16 kDumpPermission("android.permission.DUMP");
+const String16 kTouchPermission("android.permission.RESTRICTED_VR_ACCESS");
+}  // anonymous namespace
+
+VirtualTouchpadService::~VirtualTouchpadService() {
+  if (client_pid_) {
+    client_pid_ = 0;
+    touchpad_->Detach();
+  }
+}
+
+binder::Status VirtualTouchpadService::attach() {
+  pid_t pid;
+  if (!CheckTouchPermission(&pid)) {
+    return binder::Status::fromStatusT(PERMISSION_DENIED);
+  }
+  if (client_pid_ == pid) {
+    // The same client has called attach() twice with no intervening detach().
+    // This indicates a problem with the client, so return an error.
+    // However, since the client is already attached, any touchpad actions
+    // it takes will still work.
+    ALOGE("pid=%ld attached twice", static_cast<long>(pid));
+    return binder::Status::fromStatusT(ALREADY_EXISTS);
+  }
+  if (client_pid_ != 0) {
+    // Attach while another client is attached. This can happen if the client
+    // dies without cleaning up after itself, so move ownership to the current
+    // caller. If two actual clients have connected, the problem will be
+    // reported when the previous client performs any touchpad action.
+    ALOGE("pid=%ld replaces %ld", static_cast<long>(pid),
+          static_cast<long>(client_pid_));
+  }
+  client_pid_ = pid;
+  if (const status_t error = touchpad_->Attach()) {
+    return binder::Status::fromStatusT(error);
+  }
+  return binder::Status::ok();
+}
+
+binder::Status VirtualTouchpadService::detach() {
+  if (!CheckPermissions()) {
+    return binder::Status::fromStatusT(PERMISSION_DENIED);
+  }
+  client_pid_ = 0;
+  if (const status_t error = touchpad_->Detach()) {
+    return binder::Status::fromStatusT(error);
+  }
+  return binder::Status::ok();
+}
+
+binder::Status VirtualTouchpadService::touch(int touchpad, float x, float y,
+                                             float pressure) {
+  if (!CheckPermissions()) {
+    return binder::Status::fromStatusT(PERMISSION_DENIED);
+  }
+  if (const status_t error = touchpad_->Touch(touchpad, x, y, pressure)) {
+    return binder::Status::fromStatusT(error);
+  }
+  return binder::Status::ok();
 }
 
 binder::Status VirtualTouchpadService::buttonState(int touchpad, int buttons) {
-  const status_t error = touchpad_->ButtonState(touchpad, buttons);
-  return error ? binder::Status::fromStatusT(error) : binder::Status::ok();
+  if (!CheckPermissions()) {
+    return binder::Status::fromStatusT(PERMISSION_DENIED);
+  }
+  if (const status_t error = touchpad_->ButtonState(touchpad, buttons)) {
+    return binder::Status::fromStatusT(error);
+  }
+  return binder::Status::ok();
+}
+
+status_t VirtualTouchpadService::dump(
+    int fd, const Vector<String16>& args[[gnu::unused]]) {
+  String8 result;
+  const android::IPCThreadState* ipc = android::IPCThreadState::self();
+  const pid_t pid = ipc->getCallingPid();
+  const uid_t uid = ipc->getCallingUid();
+  if ((uid != AID_SHELL) &&
+      !PermissionCache::checkPermission(kDumpPermission, pid, uid)) {
+    result.appendFormat("Permission denial: can't dump " LOG_TAG
+                        " from pid=%ld, uid=%ld\n",
+                        static_cast<long>(pid), static_cast<long>(uid));
+  } else {
+    result.appendFormat("[service]\nclient_pid = %ld\n\n",
+                        static_cast<long>(client_pid_));
+    touchpad_->dumpInternal(result);
+  }
+  write(fd, result.string(), result.size());
+  return OK;
+}
+
+bool VirtualTouchpadService::CheckPermissions() {
+  pid_t pid;
+  if (!CheckTouchPermission(&pid)) {
+    return false;
+  }
+  if (client_pid_ != pid) {
+    ALOGE("pid=%ld is not owner", static_cast<long>(pid));
+    return false;
+  }
+  return true;
+}
+
+bool VirtualTouchpadService::CheckTouchPermission(pid_t* out_pid) {
+  const android::IPCThreadState* ipc = android::IPCThreadState::self();
+  *out_pid = ipc->getCallingPid();
+#ifdef SELINUX_ACCESS_CONTROL
+  return true;
+#else
+  const uid_t uid = ipc->getCallingUid();
+  const bool permission = PermissionCache::checkPermission(kTouchPermission, *out_pid, uid);
+  if (!permission) {
+    ALOGE("permission denied to pid=%ld uid=%ld", static_cast<long>(*out_pid),
+          static_cast<long>(uid));
+  }
+  return permission;
+#endif
 }
 
 }  // namespace dvr
diff --git a/services/vr/virtual_touchpad/VirtualTouchpadService.h b/services/vr/virtual_touchpad/VirtualTouchpadService.h
index 9b880b2..194d787 100644
--- a/services/vr/virtual_touchpad/VirtualTouchpadService.h
+++ b/services/vr/virtual_touchpad/VirtualTouchpadService.h
@@ -14,17 +14,28 @@
 class VirtualTouchpadService : public BnVirtualTouchpadService {
  public:
   VirtualTouchpadService(sp<VirtualTouchpad> touchpad)
-      : touchpad_(touchpad) {}
-  ~VirtualTouchpadService() override {}
+      : touchpad_(touchpad), client_pid_(0) {}
+  ~VirtualTouchpadService() override;
 
  protected:
   // Implements IVirtualTouchpadService.
+  binder::Status attach() override;
+  binder::Status detach() override;
   binder::Status touch(int touchpad, float x, float y, float pressure) override;
   binder::Status buttonState(int touchpad, int buttons) override;
 
+  // Implements BBinder::dump().
+  status_t dump(int fd, const Vector<String16>& args) override;
+
  private:
+  bool CheckPermissions();
+  bool CheckTouchPermission(pid_t* out_pid);
+
   sp<VirtualTouchpad> touchpad_;
 
+  // Only one client at a time can use the virtual touchpad.
+  pid_t client_pid_;
+
   VirtualTouchpadService(const VirtualTouchpadService&) = delete;
   void operator=(const VirtualTouchpadService&) = delete;
 };
diff --git a/services/vr/virtual_touchpad/aidl/android/dvr/VirtualTouchpadService.aidl b/services/vr/virtual_touchpad/aidl/android/dvr/VirtualTouchpadService.aidl
index 496c5e2..9cfb186 100644
--- a/services/vr/virtual_touchpad/aidl/android/dvr/VirtualTouchpadService.aidl
+++ b/services/vr/virtual_touchpad/aidl/android/dvr/VirtualTouchpadService.aidl
@@ -6,6 +6,16 @@
   const String SERVICE_NAME = "virtual_touchpad";
 
   /**
+   * Initialize the virtual touchpad.
+   */
+  void attach() = 0;
+
+  /**
+   * Shut down the virtual touchpad.
+   */
+  void detach() = 1;
+
+  /**
    * Generate a simulated touch event.
    *
    * @param touchpad Selects touchpad.
@@ -15,7 +25,7 @@
    *
    * Position values in the range [0.0, 1.0) map to the screen.
    */
-  void touch(int touchpad, float x, float y, float pressure);
+  void touch(int touchpad, float x, float y, float pressure) = 2;
 
   /**
    * Generate a simulated touchpad button state event.
@@ -23,5 +33,5 @@
    * @param touchpad Selects touchpad.
    * @param buttons A union of MotionEvent BUTTON_* values.
    */
-  void buttonState(int touchpad, int buttons);
+  void buttonState(int touchpad, int buttons) = 3;
 }
diff --git a/services/vr/virtual_touchpad/include/VirtualTouchpad.h b/services/vr/virtual_touchpad/include/VirtualTouchpad.h
index d24d121..b1ee700 100644
--- a/services/vr/virtual_touchpad/include/VirtualTouchpad.h
+++ b/services/vr/virtual_touchpad/include/VirtualTouchpad.h
@@ -3,6 +3,7 @@
 
 #include <utils/Errors.h>
 #include <utils/RefBase.h>
+#include <utils/String8.h>
 #include <utils/StrongPointer.h>
 
 namespace android {
@@ -21,10 +22,18 @@
   // Implementations should provide this, and hide their constructors.
   // For the user, switching implementations should be as simple as changing
   // the class whose |Create()| is called.
+  // Implementations should be minimial; major resource allocation should
+  // be performed in Attach().
   static sp<VirtualTouchpad> Create() {
     return sp<VirtualTouchpad>();
   }
 
+  // Initialize a virtual touchpad.
+  virtual status_t Attach() = 0;
+
+  // Shut down a virtual touchpad.
+  virtual status_t Detach() = 0;
+
   // Generate a simulated touch event.
   //
   // @param touchpad Touchpad selector index.
@@ -49,6 +58,9 @@
   //
   virtual status_t ButtonState(int touchpad, int buttons) = 0;
 
+  // Report state for 'dumpsys'.
+  virtual void dumpInternal(String8& result) = 0;
+
  protected:
   VirtualTouchpad() {}
   ~VirtualTouchpad() override {}
diff --git a/services/vr/virtual_touchpad/include/VirtualTouchpadClient.h b/services/vr/virtual_touchpad/include/VirtualTouchpadClient.h
index dd9c265..471d9e0 100644
--- a/services/vr/virtual_touchpad/include/VirtualTouchpadClient.h
+++ b/services/vr/virtual_touchpad/include/VirtualTouchpadClient.h
@@ -13,8 +13,11 @@
  public:
   // VirtualTouchpad implementation:
   static sp<VirtualTouchpad> Create();
+  status_t Attach() override;
+  status_t Detach() override;
   status_t Touch(int touchpad, float x, float y, float pressure) override;
   status_t ButtonState(int touchpad, int buttons) override;
+  void dumpInternal(String8& result) override;
 
  protected:
   VirtualTouchpadClient() {}
diff --git a/services/vr/virtual_touchpad/tests/VirtualTouchpad_test.cpp b/services/vr/virtual_touchpad/tests/VirtualTouchpad_test.cpp
index 469a2d0..b11a983 100644
--- a/services/vr/virtual_touchpad/tests/VirtualTouchpad_test.cpp
+++ b/services/vr/virtual_touchpad/tests/VirtualTouchpad_test.cpp
@@ -97,7 +97,6 @@
   static sp<VirtualTouchpad> Create(EvdevInjectorForTesting& injector) {
     VirtualTouchpadForTesting* const touchpad = new VirtualTouchpadForTesting();
     touchpad->SetEvdevInjectorForTesting(&injector);
-    touchpad->Initialize();
     return sp<VirtualTouchpad>(touchpad);
   }
 };
@@ -123,6 +122,9 @@
   EvdevInjectorForTesting injector(record);
   sp<VirtualTouchpad> touchpad(VirtualTouchpadForTesting::Create(injector));
 
+  status_t touch_status = touchpad->Attach();
+  EXPECT_EQ(0, touch_status);
+
   // Check some aspects of uinput_user_dev.
   const uinput_user_dev* uidev = injector.GetUiDev();
   for (int i = 0; i < ABS_CNT; ++i) {
@@ -151,6 +153,7 @@
   // From ConfigureKey(BTN_TOUCH):
   expect.IoctlSetInt(UI_SET_EVBIT, EV_KEY);
   expect.IoctlSetInt(UI_SET_KEYBIT, BTN_TOUCH);
+  expect.IoctlSetInt(UI_SET_KEYBIT, BTN_BACK);
   // From ConfigureEnd():
   expect.Write(uidev, sizeof(uinput_user_dev));
   expect.IoctlVoid(UI_DEV_CREATE);
@@ -158,7 +161,7 @@
 
   expect.Reset();
   record.Reset();
-  int touch_status = touchpad->Touch(VirtualTouchpad::PRIMARY, 0, 0, 0);
+  touch_status = touchpad->Touch(VirtualTouchpad::PRIMARY, 0, 0, 0);
   EXPECT_EQ(0, touch_status);
   expect.WriteInputEvent(EV_ABS, ABS_MT_SLOT, 0);
   expect.WriteInputEvent(EV_ABS, ABS_MT_TRACKING_ID, 0);
@@ -180,16 +183,22 @@
 
   expect.Reset();
   record.Reset();
-  touch_status = touchpad->Touch(VirtualTouchpad::PRIMARY, 1.0f, 1.0f, 1.0f);
+  touch_status = touchpad->Touch(VirtualTouchpad::PRIMARY, 0.99f, 0.99f, 0.99f);
   EXPECT_EQ(0, touch_status);
   expect.WriteInputEvent(EV_ABS, ABS_MT_TRACKING_ID, 0);
-  expect.WriteInputEvent(EV_ABS, ABS_MT_POSITION_X, width);
-  expect.WriteInputEvent(EV_ABS, ABS_MT_POSITION_Y, height);
+  expect.WriteInputEvent(EV_ABS, ABS_MT_POSITION_X, 0.99f * width);
+  expect.WriteInputEvent(EV_ABS, ABS_MT_POSITION_Y, 0.99f * height);
   expect.WriteInputEvent(EV_SYN, SYN_REPORT, 0);
   EXPECT_EQ(expect.GetString(), record.GetString());
 
   expect.Reset();
   record.Reset();
+  touch_status = touchpad->Touch(VirtualTouchpad::PRIMARY, 1.0f, 1.0f, 1.0f);
+  EXPECT_EQ(EINVAL, touch_status);
+  EXPECT_EQ(expect.GetString(), record.GetString());
+
+  expect.Reset();
+  record.Reset();
   touch_status =
       touchpad->Touch(VirtualTouchpad::PRIMARY, 0.25f, 0.75f, -0.01f);
   EXPECT_EQ(0, touch_status);
@@ -224,6 +233,10 @@
 
   expect.Reset();
   record.Reset();
+  touch_status = touchpad->Detach();
+  EXPECT_EQ(0, touch_status);
+  expect.Close();
+  EXPECT_EQ(expect.GetString(), record.GetString());
 }
 
 TEST_F(VirtualTouchpadTest, Badness) {
@@ -232,11 +245,14 @@
   EvdevInjectorForTesting injector(record);
   sp<VirtualTouchpad> touchpad(VirtualTouchpadForTesting::Create(injector));
 
+  status_t touch_status = touchpad->Attach();
+  EXPECT_EQ(0, touch_status);
+
   // Touch off-screen should return an error,
   // and should not result in any system calls.
   expect.Reset();
   record.Reset();
-  status_t touch_status =
+  touch_status =
       touchpad->Touch(VirtualTouchpad::PRIMARY, -0.25f, 0.75f, 1.0f);
   EXPECT_NE(OK, touch_status);
   touch_status =
@@ -256,6 +272,10 @@
                                        AMOTION_EVENT_BUTTON_FORWARD);
   EXPECT_NE(OK, touch_status);
   EXPECT_EQ(expect.GetString(), record.GetString());
+
+  // Repeated attach is an error.
+  touch_status = touchpad->Attach();
+  EXPECT_NE(0, touch_status);
 }
 
 }  // namespace dvr
diff --git a/services/vr/vr_window_manager/shell_view.cpp b/services/vr/vr_window_manager/shell_view.cpp
index fae1dd9..10588b7 100644
--- a/services/vr/vr_window_manager/shell_view.cpp
+++ b/services/vr/vr_window_manager/shell_view.cpp
@@ -250,8 +250,12 @@
 
   translate_ = Eigen::Translation3f(0, 0, -2.5f);
 
-  if (!InitializeTouch())
-    ALOGE("Failed to initialize virtual touchpad");
+  virtual_touchpad_ = VirtualTouchpadClient::Create();
+  const status_t touchpad_status = virtual_touchpad_->Attach();
+  if (touchpad_status != OK) {
+    ALOGE("Failed to connect to virtual touchpad");
+    return touchpad_status;
+  }
 
   surface_flinger_view_.reset(new SurfaceFlingerView);
   if (!surface_flinger_view_->Initialize(this))
@@ -702,22 +706,10 @@
   controller_mesh_->Draw();
 }
 
-bool ShellView::InitializeTouch() {
-  virtual_touchpad_ = VirtualTouchpadClient::Create();
-  if (!virtual_touchpad_.get()) {
-    ALOGE("Failed to connect to virtual touchpad");
-    return false;
-  }
-  return true;
-}
-
 void ShellView::Touch() {
   if (!virtual_touchpad_.get()) {
     ALOGE("missing virtual touchpad");
-    // Try to reconnect; useful in development.
-    if (!InitializeTouch()) {
-      return;
-    }
+    return;
   }
 
   const android::status_t status = virtual_touchpad_->Touch(
diff --git a/services/vr/vr_window_manager/shell_view.h b/services/vr/vr_window_manager/shell_view.h
index c477669..49456c6 100644
--- a/services/vr/vr_window_manager/shell_view.h
+++ b/services/vr/vr_window_manager/shell_view.h
@@ -58,7 +58,6 @@
              bool test_ime);
   bool IsImeHit(const vec3& view_location, const vec3& view_direction,
                 vec3 *hit_location);
-  bool InitializeTouch();
   void Touch();
   bool OnTouchpadButton(bool down, int button);
 
diff --git a/services/vr/vr_window_manager/vr_window_manager_binder.cpp b/services/vr/vr_window_manager/vr_window_manager_binder.cpp
index c2138b7..bd3f3ee 100644
--- a/services/vr/vr_window_manager/vr_window_manager_binder.cpp
+++ b/services/vr/vr_window_manager/vr_window_manager_binder.cpp
@@ -16,7 +16,8 @@
 
 namespace {
 const String16 kDumpPermission("android.permission.DUMP");
-const String16 kSendMeControllerInputPermission("TODO");  // TODO(kpschoedel)
+const String16 kSendMeControllerInputPermission(
+    "android.permission.RESTRICTED_VR_ACCESS");
 }  // anonymous namespace
 
 constexpr size_t AshmemControllerDataProvider::kRegionLength;
@@ -136,8 +137,8 @@
     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();
+  const pid_t pid = ipc->getCallingPid();
+  const uid_t uid = ipc->getCallingUid();
   if ((uid != AID_SHELL) &&
       !PermissionCache::checkPermission(kDumpPermission, pid, uid)) {
     result.appendFormat("Permission denial: can't dump " LOG_TAG