Re-adding GamepadTestCase

- Enable the GamepadTestCase
- Now listening for device added notification before
issuing new commands to the hid binary.
- Using stdin to communicate with hid binary instead of a file
- Switched to JUnit4 framework

Bug: 34052337
Test: Local build, cts test on sailfish
Change-Id: I86b5e55fbb64c0f04abbb1ed3fecd57332858efa
diff --git a/tests/tests/hardware/res/raw/gamepad_button_a_down.json b/tests/tests/hardware/res/raw/gamepad_button_a_down.json
new file mode 100644
index 0000000..21f5186
--- /dev/null
+++ b/tests/tests/hardware/res/raw/gamepad_button_a_down.json
@@ -0,0 +1,5 @@
+{
+  "id": 1,
+  "command": "report",
+  "report": [0x01, 0x01, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x00, 0x00]
+}
diff --git a/tests/tests/hardware/res/raw/gamepad_button_a_up.json b/tests/tests/hardware/res/raw/gamepad_button_a_up.json
new file mode 100644
index 0000000..ab1eb0e
--- /dev/null
+++ b/tests/tests/hardware/res/raw/gamepad_button_a_up.json
@@ -0,0 +1,5 @@
+{
+  "id": 1,
+  "command": "report",
+  "report": [0x01, 0x00, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x00, 0x00]
+}
\ No newline at end of file
diff --git a/tests/tests/hardware/res/raw/gamepad_delay.json b/tests/tests/hardware/res/raw/gamepad_delay.json
new file mode 100644
index 0000000..a25c3dd
--- /dev/null
+++ b/tests/tests/hardware/res/raw/gamepad_delay.json
@@ -0,0 +1,5 @@
+{
+  "id": 1,
+  "command": "delay",
+  "duration": 10
+}
diff --git a/tests/tests/hardware/res/raw/gamepad_press_a.json b/tests/tests/hardware/res/raw/gamepad_press_a.json
deleted file mode 100644
index ff3ca4f..0000000
--- a/tests/tests/hardware/res/raw/gamepad_press_a.json
+++ /dev/null
@@ -1,39 +0,0 @@
-{
-    "id": 1,
-    "command": "register",
-    "name": "Odie (Test)",
-    "vid": 0x18d1,
-    "pid": 0x2c40,
-    "descriptor": [0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x01, 0x05, 0x09, 0x0a, 0x01, 0x00,
-        0x0a, 0x02, 0x00, 0x0a, 0x04, 0x00, 0x0a, 0x05, 0x00, 0x0a, 0x07, 0x00, 0x0a, 0x08, 0x00,
-        0x0a, 0x0e, 0x00, 0x0a, 0x0f, 0x00, 0x0a, 0x0d, 0x00, 0x05, 0x0c, 0x0a, 0x24, 0x02, 0x0a,
-        0x23, 0x02, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x0b, 0x81, 0x02, 0x75, 0x01, 0x95,
-        0x01, 0x81, 0x03, 0x05, 0x01, 0x75, 0x04, 0x95, 0x01, 0x25, 0x07, 0x46, 0x3b, 0x01, 0x66,
-        0x14, 0x00, 0x09, 0x39, 0x81, 0x42, 0x66, 0x00, 0x00, 0x09, 0x01, 0xa1, 0x00, 0x09, 0x30,
-        0x09, 0x31, 0x09, 0x32, 0x09, 0x35, 0x05, 0x02, 0x09, 0xc5, 0x09, 0xc4, 0x15, 0x00, 0x26,
-        0xff, 0x00, 0x35, 0x00, 0x46, 0xff, 0x00, 0x75, 0x08, 0x95, 0x06, 0x81, 0x02, 0xc0, 0x85,
-        0x02, 0x05, 0x08, 0x0a, 0x01, 0x00, 0x0a, 0x02, 0x00, 0x0a, 0x03, 0x00, 0x0a, 0x04, 0x00,
-        0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x04, 0x91, 0x02, 0x75, 0x04, 0x95, 0x01, 0x91,
-        0x03, 0xc0, 0x05, 0x0c, 0x09, 0x01, 0xa1, 0x01, 0x85, 0x03, 0x05, 0x01, 0x09, 0x06, 0xa1,
-        0x02, 0x05, 0x06, 0x09, 0x20, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x01, 0x81,
-        0x02, 0x06, 0xbc, 0xff, 0x0a, 0xad, 0xbd, 0x75, 0x08, 0x95, 0x06, 0x81, 0x02, 0xc0, 0xc0],
-    "report": [0x01, 0x00, 0x80, 0x90, 0x80, 0x7f, 0x73, 0x00, 0x00]
-}
-
-{
-    "id": 1,
-    "command": "report",
-    "report": [0x01, 0x01, 0x80, 0x90, 0x80, 0x7f, 0x73, 0x00, 0x00]
-}
-
-{
-    "id": 1,
-    "command": "delay",
-    "duration": 10
-}
-
-{
-    "id": 1,
-    "command": "report",
-    "report": [0x01, 0x00, 0x80, 0x90, 0x80, 0x7f, 0x73, 0x00, 0x00]
-}
diff --git a/tests/tests/hardware/res/raw/gamepad_register_device.json b/tests/tests/hardware/res/raw/gamepad_register_device.json
new file mode 100644
index 0000000..cfd71eb
--- /dev/null
+++ b/tests/tests/hardware/res/raw/gamepad_register_device.json
@@ -0,0 +1,20 @@
+{
+  "id": 1,
+  "command": "register",
+  "name": "Odie (Test)",
+  "vid": 0x18d1,
+  "pid": 0x2c40,
+  "descriptor": [0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x01, 0x05, 0x09, 0x0a, 0x01, 0x00,
+    0x0a, 0x02, 0x00, 0x0a, 0x04, 0x00, 0x0a, 0x05, 0x00, 0x0a, 0x07, 0x00, 0x0a, 0x08, 0x00,
+    0x0a, 0x0e, 0x00, 0x0a, 0x0f, 0x00, 0x0a, 0x0d, 0x00, 0x05, 0x0c, 0x0a, 0x24, 0x02, 0x0a,
+    0x23, 0x02, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x0b, 0x81, 0x02, 0x75, 0x01, 0x95,
+    0x01, 0x81, 0x03, 0x05, 0x01, 0x75, 0x04, 0x95, 0x01, 0x25, 0x07, 0x46, 0x3b, 0x01, 0x66,
+    0x14, 0x00, 0x09, 0x39, 0x81, 0x42, 0x66, 0x00, 0x00, 0x09, 0x01, 0xa1, 0x00, 0x09, 0x30,
+    0x09, 0x31, 0x09, 0x32, 0x09, 0x35, 0x05, 0x02, 0x09, 0xc5, 0x09, 0xc4, 0x15, 0x00, 0x26,
+    0xff, 0x00, 0x35, 0x00, 0x46, 0xff, 0x00, 0x75, 0x08, 0x95, 0x06, 0x81, 0x02, 0xc0, 0x85,
+    0x02, 0x05, 0x08, 0x0a, 0x01, 0x00, 0x0a, 0x02, 0x00, 0x0a, 0x03, 0x00, 0x0a, 0x04, 0x00,
+    0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x04, 0x91, 0x02, 0x75, 0x04, 0x95, 0x01, 0x91,
+    0x03, 0xc0, 0x05, 0x0c, 0x09, 0x01, 0xa1, 0x01, 0x85, 0x03, 0x05, 0x01, 0x09, 0x06, 0xa1,
+    0x02, 0x05, 0x06, 0x09, 0x20, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x01, 0x81,
+    0x02, 0x06, 0xbc, 0xff, 0x0a, 0xad, 0xbd, 0x75, 0x08, 0x95, 0x06, 0x81, 0x02, 0xc0, 0xc0]
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/InputCallback.java b/tests/tests/hardware/src/android/hardware/input/cts/InputCallback.java
index accdcaf..b4bda4e 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/InputCallback.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/InputCallback.java
@@ -22,4 +22,7 @@
 public interface InputCallback {
     public void onKeyEvent(KeyEvent ev);
     public void onMotionEvent(MotionEvent ev);
+    public void onInputDeviceAdded(int deviceId);
+    public void onInputDeviceRemoved(int deviceId);
+    public void onInputDeviceChanged(int deviceId);
 }
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/InputCtsActivity.java b/tests/tests/hardware/src/android/hardware/input/cts/InputCtsActivity.java
index b16cadb..72aa056 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/InputCtsActivity.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/InputCtsActivity.java
@@ -17,15 +17,28 @@
 package android.hardware.input.cts;
 
 import android.app.Activity;
+import android.content.Context;
+import android.hardware.input.InputManager;
+import android.hardware.input.InputManager.InputDeviceListener;
+import android.os.Bundle;
+import android.util.Log;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 
-import java.util.ArrayList;
-import java.util.List;
+public class InputCtsActivity extends Activity implements InputDeviceListener {
+    private static final String TAG = "InputCtsActivity";
 
-public class InputCtsActivity extends Activity {
     private InputCallback mInputCallback;
 
+    private InputManager mInputManager;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mInputManager = getApplicationContext().getSystemService(InputManager.class);
+        mInputManager.registerInputDeviceListener(this, null);
+    }
+
     @Override
     public boolean dispatchGenericMotionEvent(MotionEvent ev) {
         if (mInputCallback != null) {
@@ -61,4 +74,20 @@
     public void setInputCallback(InputCallback callback) {
         mInputCallback = callback;
     }
+
+    @Override
+    public void onInputDeviceAdded(int deviceId) {
+        mInputCallback.onInputDeviceAdded(deviceId);
+    }
+
+    @Override
+    public void onInputDeviceRemoved(int deviceId) {
+        mInputCallback.onInputDeviceRemoved(deviceId);
+    }
+
+    @Override
+    public void onInputDeviceChanged(int deviceId) {
+        mInputManager.getInputDevice(deviceId); // if this isn't called, won't get new notifications
+        mInputCallback.onInputDeviceChanged(deviceId);
+    }
 }
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/GamepadTestCase.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/GamepadTestCase.java
new file mode 100644
index 0000000..42d2d77
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/GamepadTestCase.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2015 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.hardware.input.cts.tests;
+
+import android.util.Log;
+import android.view.KeyEvent;
+
+import java.io.Writer;
+import java.util.List;
+
+import org.junit.Test;
+
+import android.hardware.cts.R;
+
+public class GamepadTestCase extends InputTestCase {
+    private static final String TAG = "GamepadTests";
+
+    @Test
+    public void testButtonA() throws Exception {
+        registerInputDevice(R.raw.gamepad_register_device);
+
+        sendHidCommands(R.raw.gamepad_button_a_down);
+        sendHidCommands(R.raw.gamepad_delay);
+        assertReceivedKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BUTTON_A);
+
+        sendHidCommands(R.raw.gamepad_button_a_up);
+        assertReceivedKeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BUTTON_A);
+
+        assertNoMoreEvents();
+    }
+}
+
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
index fba5f51..06811f8de 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
@@ -16,68 +16,93 @@
 
 package android.hardware.input.cts.tests;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.app.Instrumentation;
 import android.app.UiAutomation;
-import android.hardware.input.cts.InputCtsActivity;
 import android.hardware.input.cts.InputCallback;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.test.ActivityInstrumentationTestCase2;
-import android.util.Log;
+import android.hardware.input.cts.InputCtsActivity;
+import android.os.ParcelFileDescriptor;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 
 import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.InputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.OutputStream;
-import java.io.Writer;
-import java.util.ArrayList;
+
 import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
-import java.util.List;
-import java.util.UUID;
 
-public class InputTestCase extends ActivityInstrumentationTestCase2<InputCtsActivity> {
-    private static final String TAG = "InputTestCase";
-    private static final String HID_EXECUTABLE = "hid";
-    private static final int SHELL_UID = 2000;
+import libcore.io.IoUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InputTestCase {
+    // hid executable expects "-" argument to read from stdin instead of a file
+    private static final String HID_COMMAND = "hid -";
     private static final String[] KEY_ACTIONS = {"DOWN", "UP", "MULTIPLE"};
 
-    private File mFifo;
-    private Writer mWriter;
+    private OutputStream mOutputStream;
 
-    private BlockingQueue<KeyEvent> mKeys;
-    private BlockingQueue<MotionEvent> mMotions;
+    private final BlockingQueue<KeyEvent> mKeys;
+    private final BlockingQueue<MotionEvent> mMotions;
     private InputListener mInputListener;
 
+    private Instrumentation mInstrumentation;
+
+    private volatile CountDownLatch mDeviceAddedSignal; // to wait for onInputDeviceAdded signal
+
     public InputTestCase() {
-        super(InputCtsActivity.class);
         mKeys = new LinkedBlockingQueue<KeyEvent>();
         mMotions = new LinkedBlockingQueue<MotionEvent>();
         mInputListener = new InputListener();
     }
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mFifo = setupFifo();
+    @Rule
+    public ActivityTestRule<InputCtsActivity> mActivityRule =
+        new ActivityTestRule<>(InputCtsActivity.class);
+
+    @Before
+    public void setUp() throws Exception {
         clearKeys();
         clearMotions();
-        getActivity().setInputCallback(mInputListener);
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mActivityRule.getActivity().setInputCallback(mInputListener);
+        setupPipes();
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        if (mFifo != null) {
-            mFifo.delete();
-            mFifo = null;
+    @After
+    public void tearDown() throws Exception {
+        IoUtils.closeQuietly(mOutputStream);
+    }
+
+    /**
+     * Register an input device. May cause a failure if the device added notification
+     * is not received within the timeout period
+     *
+     * @param resourceId The resource id from which to send the register command.
+     */
+    public void registerInputDevice(int resourceId) {
+        mDeviceAddedSignal = new CountDownLatch(1);
+        sendHidCommands(resourceId);
+        try {
+            mDeviceAddedSignal.await(2L, TimeUnit.SECONDS);
+        } catch (InterruptedException ex) {
+            fail("Device added notification was not received within the allotted time.");
         }
-        closeQuietly(mWriter);
-        mWriter = null;
-        super.tearDown();
     }
 
     /**
@@ -89,9 +114,8 @@
      */
     public void sendHidCommands(int id) {
         try {
-            Writer w = getWriter();
-            w.write(getEvents(id));
-            w.flush();
+            mOutputStream.write(getEvents(id).getBytes());
+            mOutputStream.flush();
         } catch (IOException e) {
             throw new RuntimeException(e);
         }
@@ -153,40 +177,20 @@
         mMotions.clear();
     }
 
-    private File setupFifo() throws ErrnoException {
-        File dir = getActivity().getCacheDir();
-        String filename = dir.getAbsolutePath() + File.separator +  UUID.randomUUID().toString();
-        Os.mkfifo(filename, 0666);
-        File f = new File(filename);
-        return f;
-    }
+    private void setupPipes() throws IOException {
+        UiAutomation ui = mInstrumentation.getUiAutomation();
+        ParcelFileDescriptor[] pipes = ui.executeShellCommandRw(HID_COMMAND);
 
-    private Writer getWriter() throws IOException {
-        if (mWriter == null) {
-            UiAutomation ui = getInstrumentation().getUiAutomation();
-            ui.executeShellCommand("hid " + mFifo.getAbsolutePath());
-            mWriter = new FileWriter(mFifo);
-        }
-        return mWriter;
+        mOutputStream = new ParcelFileDescriptor.AutoCloseOutputStream(pipes[1]);
+        IoUtils.closeQuietly(pipes[0]); // hid command is write-only
     }
 
     private String getEvents(int id) throws IOException {
         InputStream is =
-            getInstrumentation().getTargetContext().getResources().openRawResource(id);
+            mInstrumentation.getTargetContext().getResources().openRawResource(id);
         return readFully(is);
     }
 
-
-    private static void closeQuietly(AutoCloseable closeable) {
-        if (closeable != null) {
-            try {
-                closeable.close();
-            } catch (RuntimeException rethrown) {
-                throw rethrown;
-            } catch (Exception ignored) { }
-        }
-    }
-
     private static String readFully(InputStream is) throws IOException {
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         int read = 0;
@@ -198,6 +202,7 @@
     }
 
     private class InputListener implements InputCallback {
+        @Override
         public void onKeyEvent(KeyEvent ev) {
             boolean done = false;
             do {
@@ -208,6 +213,7 @@
             } while (!done);
         }
 
+        @Override
         public void onMotionEvent(MotionEvent ev) {
             boolean done = false;
             do {
@@ -217,5 +223,20 @@
                 } catch (InterruptedException ignore) { }
             } while (!done);
         }
+
+        @Override
+        public void onInputDeviceAdded(int deviceId) {
+            mDeviceAddedSignal.countDown();
+        }
+
+        @Override
+        public void onInputDeviceRemoved(int deviceId) {
+        }
+
+        @Override
+        public void onInputDeviceChanged(int deviceId) {
+        }
     }
+
+
 }