Service and KitchenSink logic to read the kernel's input event queues

This patch provides a native service that can manually be built, installed to a device, and launched, e.g.

sh# /system/bin/com.android.car.keventreader /dev/input/event*

The service will monitor the files provided as input (expecting them to be kernel input queues in the format
described at, e.g. https://github.com/torvalds/linux/blob/master/include/uapi/linux/input.h), and generate
events upon reading EV_KEY events from each such file.

The patch also includes a KitchenSink hook to talk to the native service and receive key events from it.

Bug: 78258802
Test: manual on Mojave
Change-Id: If7f2f1f72e4dc6a26cd9d32b31e90e494d55650b
Merged-In: If7f2f1f72e4dc6a26cd9d32b31e90e494d55650b
diff --git a/tests/EmbeddedKitchenSinkApp/Android.mk b/tests/EmbeddedKitchenSinkApp/Android.mk
index 3c04fd5..7a8c145 100644
--- a/tests/EmbeddedKitchenSinkApp/Android.mk
+++ b/tests/EmbeddedKitchenSinkApp/Android.mk
@@ -47,7 +47,8 @@
 LOCAL_STATIC_JAVA_LIBRARIES += \
     android.hidl.base-V1.0-java \
     android.hardware.automotive.vehicle-V2.0-java \
-    vehicle-hal-support-lib
+    vehicle-hal-support-lib \
+    com.android.car.keventreader-client
 
 include packages/services/Car/car-support-lib/car-support.mk
 
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/input_test.xml b/tests/EmbeddedKitchenSinkApp/res/layout/input_test.xml
index 1d9f317..03a90e0 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/input_test.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/input_test.xml
@@ -24,4 +24,21 @@
             android:id="@+id/input_buttons">
         <!-- Filled at runtime. -->
     </LinearLayout>
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:layout_marginLeft="15dp"
+        android:layout_marginRight="15dp"
+        android:layout_marginTop="20dp"
+        android:fillViewport="true">
+        <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="bottom"
+                android:scrollbars="vertical"
+                android:textSize="24px"
+                android:typeface="monospace"
+                android:id="@+id/events_list"/>
+    </ScrollView>
 </LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/input/InputTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/input/InputTestFragment.java
index ff6ef21..4b9ec1d 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/input/InputTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/input/InputTestFragment.java
@@ -30,6 +30,7 @@
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.support.v4.app.Fragment;
+import android.text.method.ScrollingMovementMethod;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
@@ -40,6 +41,12 @@
 import android.widget.TextView;
 import android.widget.Toast;
 
+import android.support.v4.app.Fragment;
+
+import com.android.car.keventreader.EventReaderService;
+import com.android.car.keventreader.IEventCallback;
+import com.android.car.keventreader.KeypressEvent;
+
 import com.google.android.car.kitchensink.R;
 import com.google.android.collect.Lists;
 
@@ -67,6 +74,25 @@
 
     private IVehicle mVehicle;
 
+    private EventReaderService mEventReaderService;
+
+    private final IEventCallback.Stub mKeypressEventHandler = new IEventCallback.Stub() {
+        private String prettyPrint(KeypressEvent event) {
+            return String.format("Event{source = %s, keycode = %s, key%s}\n",
+                event.source,
+                event.keycodeToString(),
+                event.isKeydown ? "down" : "up");
+        }
+
+        @Override
+        public void onEvent(KeypressEvent keypressEvent) throws RemoteException {
+            Log.d(TAG, "received event " + keypressEvent);
+            mInputEventsList.append(prettyPrint(keypressEvent));
+        }
+    };
+
+    private TextView mInputEventsList;
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -79,6 +105,12 @@
             throw new RuntimeException("Failed to connect to IVehicle");
         }
         Log.d(TAG, "Connected to IVehicle service: " + mVehicle);
+
+        mEventReaderService = EventReaderService.tryGet();
+        Log.d(TAG, "Key Event Reader service: " + mEventReaderService);
+        if (mEventReaderService != null) {
+            mEventReaderService.registerCallback(mKeypressEventHandler);
+        }
     }
 
     @Nullable
@@ -87,6 +119,9 @@
             @Nullable Bundle savedInstanceState) {
         View view = inflater.inflate(R.layout.input_test, container, false);
 
+        mInputEventsList = view.findViewById(R.id.events_list);
+        mInputEventsList.setMovementMethod(new ScrollingMovementMethod());
+
         TextView steeringWheelLabel = new TextView(getActivity() /*context*/);
         steeringWheelLabel.setText(R.string.steering_wheel);
         steeringWheelLabel.setTextSize(getResources().getDimension(R.dimen.car_title2_size));
@@ -161,6 +196,9 @@
     public void onDestroyView() {
         super.onDestroyView();
         mButtons.clear();
+        if (mEventReaderService != null) {
+            mEventReaderService.unregisterCallback(mKeypressEventHandler);
+        }
     }
 
     private void addButtonsToPanel(LinearLayout root, List<View> buttons) {
diff --git a/tools/keventreader/Android.mk b/tools/keventreader/Android.mk
new file mode 100644
index 0000000..1220a1a
--- /dev/null
+++ b/tools/keventreader/Android.mk
@@ -0,0 +1,20 @@
+# Copyright (C) 2018 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.
+#
+#
+
+LOCAL_PATH := $(call my-dir)
+
+# Include the sub-makefiles
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/keventreader/client/Android.mk b/tools/keventreader/client/Android.mk
new file mode 100644
index 0000000..529feea
--- /dev/null
+++ b/tools/keventreader/client/Android.mk
@@ -0,0 +1,31 @@
+# Copyright (C) 2018 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.
+#
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := com.android.car.keventreader-client
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SRC_FILES += ../common/com/android/car/keventreader/IEventCallback.aidl
+LOCAL_SRC_FILES += ../common/com/android/car/keventreader/IEventProvider.aidl
+
+LOCAL_AIDL_INCLUDES += $(LOCAL_PATH)/../common
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tools/keventreader/client/src/com/android/car/keventreader/EventReaderService.java b/tools/keventreader/client/src/com/android/car/keventreader/EventReaderService.java
new file mode 100644
index 0000000..b50bbb1
--- /dev/null
+++ b/tools/keventreader/client/src/com/android/car/keventreader/EventReaderService.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2018 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 com.android.car.keventreader;
+
+import android.annotation.Nullable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+import java.util.Objects;
+
+public final class EventReaderService {
+  private static final String TAG = "car.keventreader";
+  private static final String SERVICE_NAME = "com.android.car.keventreader";
+  private final IEventProvider mService;
+
+  private EventReaderService(IEventProvider service) {
+      mService = Objects.requireNonNull(service);
+  }
+
+  @Nullable
+  private static IEventProvider getService() {
+      return IEventProvider.Stub.asInterface(ServiceManager.getService(SERVICE_NAME));
+  }
+
+  @Nullable
+  public static EventReaderService tryGet() {
+      IEventProvider provider = getService();
+      if (provider == null) return null;
+      return new EventReaderService(provider);
+  }
+
+  public boolean registerCallback(IEventCallback callback) {
+      try {
+          mService.registerCallback(callback);
+          return true;
+      } catch (RemoteException e) {
+          Log.e(TAG, "unable to register new callback", e);
+          return false;
+      }
+  }
+
+  public boolean unregisterCallback(IEventCallback callback) {
+      try {
+          mService.unregisterCallback(callback);
+          return true;
+      } catch (RemoteException e) {
+          Log.e(TAG, "unable to remove callback registration", e);
+          return false;
+      }
+  }
+}
diff --git a/tools/keventreader/client/src/com/android/car/keventreader/KeypressEvent.java b/tools/keventreader/client/src/com/android/car/keventreader/KeypressEvent.java
new file mode 100644
index 0000000..34aa992
--- /dev/null
+++ b/tools/keventreader/client/src/com/android/car/keventreader/KeypressEvent.java
@@ -0,0 +1,633 @@
+/*
+ * Copyright (C) 2018 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 com.android.car.keventreader;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import java.util.HashMap;
+import java.util.Map;
+
+public final class KeypressEvent implements Parcelable {
+    private static final Map<Integer, String> KEYCODE_NAME_MAP = new HashMap<Integer, String>() {{
+        put(0,"RESERVED");
+        put(1,"ESC");
+        put(2,"1");
+        put(3,"2");
+        put(4,"3");
+        put(5,"4");
+        put(6,"5");
+        put(7,"6");
+        put(8,"7");
+        put(9,"8");
+        put(10,"9");
+        put(11,"0");
+        put(12,"MINUS");
+        put(13,"EQUAL");
+        put(14,"BACKSPACE");
+        put(15,"TAB");
+        put(16,"Q");
+        put(17,"W");
+        put(18,"E");
+        put(19,"R");
+        put(20,"T");
+        put(21,"Y");
+        put(22,"U");
+        put(23,"I");
+        put(24,"O");
+        put(25,"P");
+        put(26,"LEFTBRACE");
+        put(27,"RIGHTBRACE");
+        put(28,"ENTER");
+        put(29,"LEFTCTRL");
+        put(30,"A");
+        put(31,"S");
+        put(32,"D");
+        put(33,"F");
+        put(34,"G");
+        put(35,"H");
+        put(36,"J");
+        put(37,"K");
+        put(38,"L");
+        put(39,"SEMICOLON");
+        put(40,"APOSTROPHE");
+        put(41,"GRAVE");
+        put(42,"LEFTSHIFT");
+        put(43,"BACKSLASH");
+        put(44,"Z");
+        put(45,"X");
+        put(46,"C");
+        put(47,"V");
+        put(48,"B");
+        put(49,"N");
+        put(50,"M");
+        put(51,"COMMA");
+        put(52,"DOT");
+        put(53,"SLASH");
+        put(54,"RIGHTSHIFT");
+        put(55,"KPASTERISK");
+        put(56,"LEFTALT");
+        put(57,"SPACE");
+        put(58,"CAPSLOCK");
+        put(59,"F1");
+        put(60,"F2");
+        put(61,"F3");
+        put(62,"F4");
+        put(63,"F5");
+        put(64,"F6");
+        put(65,"F7");
+        put(66,"F8");
+        put(67,"F9");
+        put(68,"F10");
+        put(69,"NUMLOCK");
+        put(70,"SCROLLLOCK");
+        put(71,"KP7");
+        put(72,"KP8");
+        put(73,"KP9");
+        put(74,"KPMINUS");
+        put(75,"KP4");
+        put(76,"KP5");
+        put(77,"KP6");
+        put(78,"KPPLUS");
+        put(79,"KP1");
+        put(80,"KP2");
+        put(81,"KP3");
+        put(82,"KP0");
+        put(83,"KPDOT");
+        put(85,"ZENKAKUHANKAKU");
+        put(86,"102ND");
+        put(87,"F11");
+        put(88,"F12");
+        put(89,"RO");
+        put(90,"KATAKANA");
+        put(91,"HIRAGANA");
+        put(92,"HENKAN");
+        put(93,"KATAKANAHIRAGANA");
+        put(94,"MUHENKAN");
+        put(95,"KPJPCOMMA");
+        put(96,"KPENTER");
+        put(97,"RIGHTCTRL");
+        put(98,"KPSLASH");
+        put(99,"SYSRQ");
+        put(100,"RIGHTALT");
+        put(101,"LINEFEED");
+        put(102,"HOME");
+        put(103,"UP");
+        put(104,"PAGEUP");
+        put(105,"LEFT");
+        put(106,"RIGHT");
+        put(107,"END");
+        put(108,"DOWN");
+        put(109,"PAGEDOWN");
+        put(110,"INSERT");
+        put(111,"DELETE");
+        put(112,"MACRO");
+        put(113,"MUTE");
+        put(114,"VOLUMEDOWN");
+        put(115,"VOLUMEUP");
+        put(116,"POWER");
+        put(117,"KPEQUAL");
+        put(118,"KPPLUSMINUS");
+        put(119,"PAUSE");
+        put(120,"SCALE");
+        put(121,"KPCOMMA");
+        put(122,"HANGEUL");
+        put(123,"HANJA");
+        put(124,"YEN");
+        put(125,"LEFTMETA");
+        put(126,"RIGHTMETA");
+        put(127,"COMPOSE");
+        put(128,"STOP");
+        put(129,"AGAIN");
+        put(130,"PROPS");
+        put(131,"UNDO");
+        put(132,"FRONT");
+        put(133,"COPY");
+        put(134,"OPEN");
+        put(135,"PASTE");
+        put(136,"FIND");
+        put(137,"CUT");
+        put(138,"HELP");
+        put(139,"MENU");
+        put(140,"CALC");
+        put(141,"SETUP");
+        put(142,"SLEEP");
+        put(143,"WAKEUP");
+        put(144,"FILE");
+        put(145,"SENDFILE");
+        put(146,"DELETEFILE");
+        put(147,"XFER");
+        put(148,"PROG1");
+        put(149,"PROG2");
+        put(150,"WWW");
+        put(151,"MSDOS");
+        put(152,"SCREENLOCK");
+        put(153,"ROTATE_DISPLAY");
+        put(154,"CYCLEWINDOWS");
+        put(155,"MAIL");
+        put(156,"BOOKMARKS");
+        put(157,"COMPUTER");
+        put(158,"BACK");
+        put(159,"FORWARD");
+        put(160,"CLOSECD");
+        put(161,"EJECTCD");
+        put(162,"EJECTCLOSECD");
+        put(163,"NEXTSONG");
+        put(164,"PLAYPAUSE");
+        put(165,"PREVIOUSSONG");
+        put(166,"STOPCD");
+        put(167,"RECORD");
+        put(168,"REWIND");
+        put(169,"PHONE");
+        put(170,"ISO");
+        put(171,"CONFIG");
+        put(172,"HOMEPAGE");
+        put(173,"REFRESH");
+        put(174,"EXIT");
+        put(175,"MOVE");
+        put(176,"EDIT");
+        put(177,"SCROLLUP");
+        put(178,"SCROLLDOWN");
+        put(179,"KPLEFTPAREN");
+        put(180,"KPRIGHTPAREN");
+        put(181,"NEW");
+        put(182,"REDO");
+        put(183,"F13");
+        put(184,"F14");
+        put(185,"F15");
+        put(186,"F16");
+        put(187,"F17");
+        put(188,"F18");
+        put(189,"F19");
+        put(190,"F20");
+        put(191,"F21");
+        put(192,"F22");
+        put(193,"F23");
+        put(194,"F24");
+        put(200,"PLAYCD");
+        put(201,"PAUSECD");
+        put(202,"PROG3");
+        put(203,"PROG4");
+        put(204,"DASHBOARD");
+        put(205,"SUSPEND");
+        put(206,"CLOSE");
+        put(207,"PLAY");
+        put(208,"FASTFORWARD");
+        put(209,"BASSBOOST");
+        put(210,"PRINT");
+        put(211,"HP");
+        put(212,"CAMERA");
+        put(213,"SOUND");
+        put(214,"QUESTION");
+        put(215,"EMAIL");
+        put(216,"CHAT");
+        put(217,"SEARCH");
+        put(218,"CONNECT");
+        put(219,"FINANCE");
+        put(220,"SPORT");
+        put(221,"SHOP");
+        put(222,"ALTERASE");
+        put(223,"CANCEL");
+        put(224,"BRIGHTNESSDOWN");
+        put(225,"BRIGHTNESSUP");
+        put(226,"MEDIA");
+        put(227,"SWITCHVIDEOMODE");
+        put(228,"KBDILLUMTOGGLE");
+        put(229,"KBDILLUMDOWN");
+        put(230,"KBDILLUMUP");
+        put(231,"SEND");
+        put(232,"REPLY");
+        put(233,"FORWARDMAIL");
+        put(234,"SAVE");
+        put(235,"DOCUMENTS");
+        put(236,"BATTERY");
+        put(237,"BLUETOOTH");
+        put(238,"WLAN");
+        put(239,"UWB");
+        put(240,"UNKNOWN");
+        put(241,"VIDEO_NEXT");
+        put(242,"VIDEO_PREV");
+        put(243,"BRIGHTNESS_CYCLE");
+        put(244,"BRIGHTNESS_AUTO");
+        put(245,"DISPLAY_OFF");
+        put(246,"WWAN");
+        put(247,"RFKILL");
+        put(248,"MICMUTE");
+        put(0x160,"OK");
+        put(0x161,"SELECT");
+        put(0x162,"GOTO");
+        put(0x163,"CLEAR");
+        put(0x164,"POWER2");
+        put(0x165,"OPTION");
+        put(0x166,"INFO");
+        put(0x167,"TIME");
+        put(0x168,"VENDOR");
+        put(0x169,"ARCHIVE");
+        put(0x16a,"PROGRAM");
+        put(0x16b,"CHANNEL");
+        put(0x16c,"FAVORITES");
+        put(0x16d,"EPG");
+        put(0x16e,"PVR");
+        put(0x16f,"MHP");
+        put(0x170,"LANGUAGE");
+        put(0x171,"TITLE");
+        put(0x172,"SUBTITLE");
+        put(0x173,"ANGLE");
+        put(0x174,"ZOOM");
+        put(0x175,"MODE");
+        put(0x176,"KEYBOARD");
+        put(0x177,"SCREEN");
+        put(0x178,"PC");
+        put(0x179,"TV");
+        put(0x17a,"TV2");
+        put(0x17b,"VCR");
+        put(0x17c,"VCR2");
+        put(0x17d,"SAT");
+        put(0x17e,"SAT2");
+        put(0x17f,"CD");
+        put(0x180,"TAPE");
+        put(0x181,"RADIO");
+        put(0x182,"TUNER");
+        put(0x183,"PLAYER");
+        put(0x184,"TEXT");
+        put(0x185,"DVD");
+        put(0x186,"AUX");
+        put(0x187,"MP3");
+        put(0x188,"AUDIO");
+        put(0x189,"VIDEO");
+        put(0x18a,"DIRECTORY");
+        put(0x18b,"LIST");
+        put(0x18c,"MEMO");
+        put(0x18d,"CALENDAR");
+        put(0x18e,"RED");
+        put(0x18f,"GREEN");
+        put(0x190,"YELLOW");
+        put(0x191,"BLUE");
+        put(0x192,"CHANNELUP");
+        put(0x193,"CHANNELDOWN");
+        put(0x194,"FIRST");
+        put(0x195,"LAST");
+        put(0x196,"AB");
+        put(0x197,"NEXT");
+        put(0x198,"RESTART");
+        put(0x199,"SLOW");
+        put(0x19a,"SHUFFLE");
+        put(0x19b,"BREAK");
+        put(0x19c,"PREVIOUS");
+        put(0x19d,"DIGITS");
+        put(0x19e,"TEEN");
+        put(0x19f,"TWEN");
+        put(0x1a0,"VIDEOPHONE");
+        put(0x1a1,"GAMES");
+        put(0x1a2,"ZOOMIN");
+        put(0x1a3,"ZOOMOUT");
+        put(0x1a4,"ZOOMRESET");
+        put(0x1a5,"WORDPROCESSOR");
+        put(0x1a6,"EDITOR");
+        put(0x1a7,"SPREADSHEET");
+        put(0x1a8,"GRAPHICSEDITOR");
+        put(0x1a9,"PRESENTATION");
+        put(0x1aa,"DATABASE");
+        put(0x1ab,"NEWS");
+        put(0x1ac,"VOICEMAIL");
+        put(0x1ad,"ADDRESSBOOK");
+        put(0x1ae,"MESSENGER");
+        put(0x1af,"DISPLAYTOGGLE");
+        put(0x1b0,"SPELLCHECK");
+        put(0x1b1,"LOGOFF");
+        put(0x1b2,"DOLLAR");
+        put(0x1b3,"EURO");
+        put(0x1b4,"FRAMEBACK");
+        put(0x1b5,"FRAMEFORWARD");
+        put(0x1b6,"CONTEXT_MENU");
+        put(0x1b7,"MEDIA_REPEAT");
+        put(0x1b8,"10CHANNELSUP");
+        put(0x1b9,"10CHANNELSDOWN");
+        put(0x1ba,"IMAGES");
+        put(0x1c0,"DEL_EOL");
+        put(0x1c1,"DEL_EOS");
+        put(0x1c2,"INS_LINE");
+        put(0x1c3,"DEL_LINE");
+        put(0x1d0,"FN");
+        put(0x1d1,"FN_ESC");
+        put(0x1d2,"FN_F1");
+        put(0x1d3,"FN_F2");
+        put(0x1d4,"FN_F3");
+        put(0x1d5,"FN_F4");
+        put(0x1d6,"FN_F5");
+        put(0x1d7,"FN_F6");
+        put(0x1d8,"FN_F7");
+        put(0x1d9,"FN_F8");
+        put(0x1da,"FN_F9");
+        put(0x1db,"FN_F10");
+        put(0x1dc,"FN_F11");
+        put(0x1dd,"FN_F12");
+        put(0x1de,"FN_1");
+        put(0x1df,"FN_2");
+        put(0x1e0,"FN_D");
+        put(0x1e1,"FN_E");
+        put(0x1e2,"FN_F");
+        put(0x1e3,"FN_S");
+        put(0x1e4,"FN_B");
+        put(0x1f1,"BRL_DOT1");
+        put(0x1f2,"BRL_DOT2");
+        put(0x1f3,"BRL_DOT3");
+        put(0x1f4,"BRL_DOT4");
+        put(0x1f5,"BRL_DOT5");
+        put(0x1f6,"BRL_DOT6");
+        put(0x1f7,"BRL_DOT7");
+        put(0x1f8,"BRL_DOT8");
+        put(0x1f9,"BRL_DOT9");
+        put(0x1fa,"BRL_DOT10");
+        put(0x200,"NUMERIC_0");
+        put(0x201,"NUMERIC_1");
+        put(0x202,"NUMERIC_2");
+        put(0x203,"NUMERIC_3");
+        put(0x204,"NUMERIC_4");
+        put(0x205,"NUMERIC_5");
+        put(0x206,"NUMERIC_6");
+        put(0x207,"NUMERIC_7");
+        put(0x208,"NUMERIC_8");
+        put(0x209,"NUMERIC_9");
+        put(0x20a,"NUMERIC_STAR");
+        put(0x20b,"NUMERIC_POUND");
+        put(0x20c,"NUMERIC_A");
+        put(0x20d,"NUMERIC_B");
+        put(0x20e,"NUMERIC_C");
+        put(0x20f,"NUMERIC_D");
+        put(0x210,"CAMERA_FOCUS");
+        put(0x211,"WPS_BUTTON");
+        put(0x212,"TOUCHPAD_TOGGLE");
+        put(0x213,"TOUCHPAD_ON");
+        put(0x214,"TOUCHPAD_OFF");
+        put(0x215,"CAMERA_ZOOMIN");
+        put(0x216,"CAMERA_ZOOMOUT");
+        put(0x217,"CAMERA_UP");
+        put(0x218,"CAMERA_DOWN");
+        put(0x219,"CAMERA_LEFT");
+        put(0x21a,"CAMERA_RIGHT");
+        put(0x21b,"ATTENDANT_ON");
+        put(0x21c,"ATTENDANT_OFF");
+        put(0x21d,"ATTENDANT_TOGGLE");
+        put(0x21e,"LIGHTS_TOGGLE");
+        put(0x230,"ALS_TOGGLE");
+        put(0x240,"BUTTONCONFIG");
+        put(0x241,"TASKMANAGER");
+        put(0x242,"JOURNAL");
+        put(0x243,"CONTROLPANEL");
+        put(0x244,"APPSELECT");
+        put(0x245,"SCREENSAVER");
+        put(0x246,"VOICECOMMAND");
+        put(0x247,"ASSISTANT");
+        put(0x250,"BRIGHTNESS_MIN");
+        put(0x251,"BRIGHTNESS_MAX");
+        put(0x260,"KBDINPUTASSIST_PREV");
+        put(0x261,"KBDINPUTASSIST_NEXT");
+        put(0x262,"KBDINPUTASSIST_PREVGROUP");
+        put(0x263,"KBDINPUTASSIST_NEXTGROUP");
+        put(0x264,"KBDINPUTASSIST_ACCEPT");
+        put(0x265,"KBDINPUTASSIST_CANCEL");
+        put(0x266,"RIGHT_UP");
+        put(0x267,"RIGHT_DOWN");
+        put(0x268,"LEFT_UP");
+        put(0x269,"LEFT_DOWN");
+        put(0x26a,"ROOT_MENU");
+        put(0x26b,"MEDIA_TOP_MENU");
+        put(0x26c,"NUMERIC_11");
+        put(0x26d,"NUMERIC_12");
+        put(0x26e,"AUDIO_DESC");
+        put(0x26f,"3D_MODE");
+        put(0x270,"NEXT_FAVORITE");
+        put(0x271,"STOP_RECORD");
+        put(0x272,"PAUSE_RECORD");
+        put(0x273,"VOD");
+        put(0x274,"UNMUTE");
+        put(0x275,"FASTREVERSE");
+        put(0x276,"SLOWREVERSE");
+        put(0x277,"DATA");
+        put(0x278,"ONSCREEN_KEYBOARD");
+        put(113,"MIN_INTERESTING");
+        put(0x2ff,"MAX");
+        put(0x100,"MISC");
+        put(0x100,"0");
+        put(0x101,"1");
+        put(0x102,"2");
+        put(0x103,"3");
+        put(0x104,"4");
+        put(0x105,"5");
+        put(0x106,"6");
+        put(0x107,"7");
+        put(0x108,"8");
+        put(0x109,"9");
+        put(0x110,"MOUSE");
+        put(0x110,"LEFT");
+        put(0x111,"RIGHT");
+        put(0x112,"MIDDLE");
+        put(0x113,"SIDE");
+        put(0x114,"EXTRA");
+        put(0x115,"FORWARD");
+        put(0x116,"BACK");
+        put(0x117,"TASK");
+        put(0x120,"JOYSTICK");
+        put(0x120,"TRIGGER");
+        put(0x121,"THUMB");
+        put(0x122,"THUMB2");
+        put(0x123,"TOP");
+        put(0x124,"TOP2");
+        put(0x125,"PINKIE");
+        put(0x126,"BASE");
+        put(0x127,"BASE2");
+        put(0x128,"BASE3");
+        put(0x129,"BASE4");
+        put(0x12a,"BASE5");
+        put(0x12b,"BASE6");
+        put(0x12f,"DEAD");
+        put(0x130,"GAMEPAD");
+        put(0x130,"SOUTH");
+        put(0x131,"EAST");
+        put(0x132,"C");
+        put(0x133,"NORTH");
+        put(0x134,"WEST");
+        put(0x135,"Z");
+        put(0x136,"TL");
+        put(0x137,"TR");
+        put(0x138,"TL2");
+        put(0x139,"TR2");
+        put(0x13a,"SELECT");
+        put(0x13b,"START");
+        put(0x13c,"MODE");
+        put(0x13d,"THUMBL");
+        put(0x13e,"THUMBR");
+        put(0x140,"DIGI");
+        put(0x140,"TOOL_PEN");
+        put(0x141,"TOOL_RUBBER");
+        put(0x142,"TOOL_BRUSH");
+        put(0x143,"TOOL_PENCIL");
+        put(0x144,"TOOL_AIRBRUSH");
+        put(0x145,"TOOL_FINGER");
+        put(0x146,"TOOL_MOUSE");
+        put(0x147,"TOOL_LENS");
+        put(0x148,"TOOL_QUINTTAP");
+        put(0x149,"STYLUS3");
+        put(0x14a,"TOUCH");
+        put(0x14b,"STYLUS");
+        put(0x14c,"STYLUS2");
+        put(0x14d,"TOOL_DOUBLETAP");
+        put(0x14e,"TOOL_TRIPLETAP");
+        put(0x14f,"TOOL_QUADTAP");
+        put(0x150,"WHEEL");
+        put(0x150,"GEAR_DOWN");
+        put(0x151,"GEAR_UP");
+        put(0x220,"DPAD_UP");
+        put(0x221,"DPAD_DOWN");
+        put(0x222,"DPAD_LEFT");
+        put(0x223,"DPAD_RIGHT");
+        put(0x2c0,"TRIGGER_HAPPY");
+        put(0x2c0,"TRIGGER_HAPPY1");
+        put(0x2c1,"TRIGGER_HAPPY2");
+        put(0x2c2,"TRIGGER_HAPPY3");
+        put(0x2c3,"TRIGGER_HAPPY4");
+        put(0x2c4,"TRIGGER_HAPPY5");
+        put(0x2c5,"TRIGGER_HAPPY6");
+        put(0x2c6,"TRIGGER_HAPPY7");
+        put(0x2c7,"TRIGGER_HAPPY8");
+        put(0x2c8,"TRIGGER_HAPPY9");
+        put(0x2c9,"TRIGGER_HAPPY10");
+        put(0x2ca,"TRIGGER_HAPPY11");
+        put(0x2cb,"TRIGGER_HAPPY12");
+        put(0x2cc,"TRIGGER_HAPPY13");
+        put(0x2cd,"TRIGGER_HAPPY14");
+        put(0x2ce,"TRIGGER_HAPPY15");
+        put(0x2cf,"TRIGGER_HAPPY16");
+        put(0x2d0,"TRIGGER_HAPPY17");
+        put(0x2d1,"TRIGGER_HAPPY18");
+        put(0x2d2,"TRIGGER_HAPPY19");
+        put(0x2d3,"TRIGGER_HAPPY20");
+        put(0x2d4,"TRIGGER_HAPPY21");
+        put(0x2d5,"TRIGGER_HAPPY22");
+        put(0x2d6,"TRIGGER_HAPPY23");
+        put(0x2d7,"TRIGGER_HAPPY24");
+        put(0x2d8,"TRIGGER_HAPPY25");
+        put(0x2d9,"TRIGGER_HAPPY26");
+        put(0x2da,"TRIGGER_HAPPY27");
+        put(0x2db,"TRIGGER_HAPPY28");
+        put(0x2dc,"TRIGGER_HAPPY29");
+        put(0x2dd,"TRIGGER_HAPPY30");
+        put(0x2de,"TRIGGER_HAPPY31");
+        put(0x2df,"TRIGGER_HAPPY32");
+        put(0x2e0,"TRIGGER_HAPPY33");
+        put(0x2e1,"TRIGGER_HAPPY34");
+        put(0x2e2,"TRIGGER_HAPPY35");
+        put(0x2e3,"TRIGGER_HAPPY36");
+        put(0x2e4,"TRIGGER_HAPPY37");
+        put(0x2e5,"TRIGGER_HAPPY38");
+        put(0x2e6,"TRIGGER_HAPPY39");
+        put(0x2e7,"TRIGGER_HAPPY40");
+    }};
+
+    public final String source;
+    public final int keycode;
+    public final boolean isKeydown;
+
+    public static final Parcelable.Creator<KeypressEvent> CREATOR =
+        new Parcelable.Creator<KeypressEvent>() {
+            public KeypressEvent createFromParcel(Parcel in) {
+                return new KeypressEvent(in);
+            }
+
+            public KeypressEvent[] newArray(int size) {
+                return new KeypressEvent[size];
+            }
+        };
+
+    public KeypressEvent(Parcel in) {
+        source = in.readString();
+        keycode = in.readInt();
+        isKeydown = (in.readInt() != 0);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(source);
+        dest.writeInt(keycode);
+        dest.writeInt(isKeydown ? 1 : 0);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof KeypressEvent) {
+            KeypressEvent other = (KeypressEvent)o;
+            return other.source.equals(source) &&
+                    other.keycode == keycode &&
+                    other.isKeydown == isKeydown;
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return"Event{source = " + source + ", keycode = " + keycode +
+                ", isKeydown = " + isKeydown + "}";
+    }
+
+    public String keycodeToString() {
+        return KEYCODE_NAME_MAP.getOrDefault(keycode, Integer.toHexString(keycode));
+    }
+}
diff --git a/tools/keventreader/common/com/android/car/keventreader/IEventCallback.aidl b/tools/keventreader/common/com/android/car/keventreader/IEventCallback.aidl
new file mode 100644
index 0000000..5df8354
--- /dev/null
+++ b/tools/keventreader/common/com/android/car/keventreader/IEventCallback.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2018 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 com.android.car.keventreader;
+
+import com.android.car.keventreader.KeypressEvent;
+
+oneway interface IEventCallback {
+    void onEvent(in KeypressEvent event);
+}
diff --git a/tools/keventreader/common/com/android/car/keventreader/IEventProvider.aidl b/tools/keventreader/common/com/android/car/keventreader/IEventProvider.aidl
new file mode 100644
index 0000000..744d0b2
--- /dev/null
+++ b/tools/keventreader/common/com/android/car/keventreader/IEventProvider.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2018 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 com.android.car.keventreader;
+
+import com.android.car.keventreader.IEventCallback;
+
+interface IEventProvider {
+    void registerCallback(in IEventCallback callback);
+    void unregisterCallback(in IEventCallback callback);
+}
diff --git a/tools/keventreader/common/com/android/car/keventreader/KeypressEvent.aidl b/tools/keventreader/common/com/android/car/keventreader/KeypressEvent.aidl
new file mode 100644
index 0000000..197be3a
--- /dev/null
+++ b/tools/keventreader/common/com/android/car/keventreader/KeypressEvent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2018 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 com.android.car.keventreader;
+
+parcelable KeypressEvent cpp_header "event.h";
diff --git a/tools/keventreader/server/Android.mk b/tools/keventreader/server/Android.mk
new file mode 100644
index 0000000..726e2fc
--- /dev/null
+++ b/tools/keventreader/server/Android.mk
@@ -0,0 +1,47 @@
+# Copyright (C) 2018 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.
+#
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+    main.cpp \
+    eventgatherer.cpp \
+    inputsource.cpp \
+    keymap.cpp \
+    event.cpp \
+    eventprovider.cpp \
+    ../common/com/android/car/keventreader/IEventCallback.aidl \
+    ../common/com/android/car/keventreader/IEventProvider.aidl \
+
+LOCAL_C_INCLUDES += \
+    frameworks/base/include
+
+LOCAL_SHARED_LIBRARIES := \
+    libbinder \
+    liblog \
+    libutils
+
+LOCAL_AIDL_INCLUDES = $(LOCAL_PATH)/../common
+
+LOCAL_MODULE := com.android.car.keventreader
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_CFLAGS += -Wall -Werror
+LOCAL_CPPFLAGS += -std=c++17
+
+include $(BUILD_EXECUTABLE)
diff --git a/tools/keventreader/server/defines.h b/tools/keventreader/server/defines.h
new file mode 100644
index 0000000..59ff2c7
--- /dev/null
+++ b/tools/keventreader/server/defines.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+#define LOG_TAG "com.android.car.keventreader"
+#define SERVICE_NAME "com.android.car.keventreader"
diff --git a/tools/keventreader/server/event.cpp b/tools/keventreader/server/event.cpp
new file mode 100644
index 0000000..68ef512
--- /dev/null
+++ b/tools/keventreader/server/event.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+#include "event.h"
+
+#include <binder/Parcel.h>
+#include <utils/String8.h>
+#include <utils/String16.h>
+
+using namespace com::android::car::keventreader;
+
+KeypressEvent::KeypressEvent(const std::string source, uint32_t keycode, bool keydown) {
+    this->source = source;
+    this->keycode = keycode;
+    this->keydown = keydown;
+}
+
+status_t KeypressEvent::writeToParcel(Parcel* parcel) const {
+    String16 s16 = String16(source.c_str());
+    parcel->writeString16(s16);
+    parcel->writeUint32(keycode);
+    parcel->writeBool(keydown);
+
+    return OK;
+}
+
+status_t KeypressEvent::readFromParcel(const Parcel* parcel) {
+    String16 s16 = parcel->readString16();
+    source = std::string(String8(s16).c_str());
+    keycode = parcel->readUint32();
+    keydown = parcel->readBool();
+
+    return OK;
+}
diff --git a/tools/keventreader/server/event.h b/tools/keventreader/server/event.h
new file mode 100644
index 0000000..d4941f1
--- /dev/null
+++ b/tools/keventreader/server/event.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+#ifndef CAR_KEVENTREADER_EVENT
+#define CAR_KEVENTREADER_EVENT
+
+#include <binder/Parcelable.h>
+#include <string>
+#include <string_view>
+
+using namespace android;
+
+namespace com::android::car::keventreader {
+    struct KeypressEvent : public Parcelable {
+        // required to be callable as KeypressEvent() because Parcelable
+        KeypressEvent(const std::string source = "", uint32_t keycode = 0, bool keydown = false);
+
+        std::string source;
+        uint32_t keycode;
+        bool keydown;
+
+        virtual status_t writeToParcel(Parcel* parcel) const override;
+        virtual status_t readFromParcel(const Parcel* parcel) override;
+    };
+}
+
+#endif //CAR_KEVENTREADER_EVENT
diff --git a/tools/keventreader/server/eventgatherer.cpp b/tools/keventreader/server/eventgatherer.cpp
new file mode 100644
index 0000000..a157d0d
--- /dev/null
+++ b/tools/keventreader/server/eventgatherer.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+#include "eventgatherer.h"
+#include "defines.h"
+#include <utils/Log.h>
+
+using namespace com::android::car::keventreader;
+
+EventGatherer::EventGatherer(int argc, const char** argv) {
+    for (auto i = 1; i < argc; ++i) {
+        auto dev = std::make_unique<InputSource>(argv[i]);
+        if (dev && *dev) {
+            ALOGD("opened input source file %s", argv[i]);
+            mFds.push_back({dev->descriptor(), POLLIN, 0});
+            mDevices.emplace(dev->descriptor(), std::move(dev));
+        } else {
+            ALOGW("failed to open input source file %s", argv[i]);
+        }
+    }
+}
+
+size_t EventGatherer::size() const {
+    return mDevices.size();
+}
+
+std::vector<com::android::car::keventreader::KeypressEvent> EventGatherer::read() {
+    constexpr int FOREVER = -1;
+    std::vector<com::android::car::keventreader::KeypressEvent> result;
+
+    int count = poll(&mFds[0], mFds.size(), FOREVER);
+    if (count < 0) {
+        ALOGE("poll failed: errno = %d", errno);
+        return result;
+    }
+
+    for (auto& fd : mFds) {
+        if (fd.revents != 0) {
+            if (fd.revents == POLLIN) {
+                if (auto dev = mDevices[fd.fd].get()) {
+                    while(auto evt = dev->read()) {
+                        result.push_back(*evt);
+                    }
+                    --count; // found one source of events we care abour
+                }
+            }
+            fd.revents = 0;
+            if (count == 0) break; // if we read all events, no point in continuing the scan
+        }
+    }
+
+    return result;
+}
diff --git a/tools/keventreader/server/eventgatherer.h b/tools/keventreader/server/eventgatherer.h
new file mode 100644
index 0000000..71b5f9a
--- /dev/null
+++ b/tools/keventreader/server/eventgatherer.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+#ifndef CAR_KEVENTREADER_EVENTGATHERER
+#define CAR_KEVENTREADER_EVENTGATHERER
+
+#include "inputsource.h"
+#include "event.h"
+
+#include <map>
+#include <memory>
+#include <poll.h>
+#include <vector>
+
+namespace com::android::car::keventreader {
+    class EventGatherer {
+    public:
+        EventGatherer(int argc, const char** argv);
+
+        size_t size() const;
+
+        std::vector<com::android::car::keventreader::KeypressEvent> read();
+    private:
+        std::map<int, std::unique_ptr<InputSource>> mDevices;
+        std::vector<pollfd> mFds;
+    };
+}
+
+#endif //CAR_KEVENTREADER_EVENTGATHERER
diff --git a/tools/keventreader/server/eventprovider.cpp b/tools/keventreader/server/eventprovider.cpp
new file mode 100644
index 0000000..44ea266
--- /dev/null
+++ b/tools/keventreader/server/eventprovider.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+#include "defines.h"
+#include "eventprovider.h"
+#include <algorithm>
+#include <utils/Log.h>
+
+using namespace com::android::car::keventreader;
+
+EventProviderImpl::EventProviderImpl(EventGatherer&& g) : mGatherer(std::move(g)) {}
+
+std::thread EventProviderImpl::startLoop() {
+    auto t = std::thread( [this] () -> void {
+        while(true) {
+            auto events = mGatherer.read();
+            {
+                std::scoped_lock lock(mMutex);
+                for (auto&& cb : mCallbacks) {
+                    for (const auto& event : events) {
+                        cb->onEvent(event);
+                    }
+                }
+            }
+        }
+    });
+    return t;
+}
+
+Status EventProviderImpl::registerCallback(const sp<IEventCallback>& cb) {
+    std::scoped_lock lock(mMutex);
+    mCallbacks.push_back(cb);
+    return Status::ok();
+}
+
+Status EventProviderImpl::unregisterCallback(const sp<IEventCallback>& cb) {
+    std::scoped_lock lock(mMutex);
+    std::remove(mCallbacks.begin(), mCallbacks.end(), cb);
+    return Status::ok();
+}
diff --git a/tools/keventreader/server/eventprovider.h b/tools/keventreader/server/eventprovider.h
new file mode 100644
index 0000000..17a98b6
--- /dev/null
+++ b/tools/keventreader/server/eventprovider.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+#ifndef CAR_KEVENTREADER_EVENTPROVIDER
+#define CAR_KEVENTREADER_EVENTPROVIDER
+
+#include "com/android/car/keventreader/IEventCallback.h"
+#include "com/android/car/keventreader/BnEventProvider.h"
+#include <binder/Binder.h>
+#include "eventgatherer.h"
+#include <mutex>
+#include <thread>
+#include <vector>
+
+using namespace android;
+using namespace android::binder;
+
+namespace com::android::car::keventreader {
+    class EventProviderImpl : public BnEventProvider {
+    public:
+        EventProviderImpl(EventGatherer&&);
+        std::thread startLoop();
+
+        virtual Status registerCallback(const sp<IEventCallback>& callback) override;
+        virtual Status unregisterCallback(const sp<IEventCallback>& callback) override;
+    private:
+        EventGatherer mGatherer;
+        std::mutex mMutex;
+        std::vector<sp<IEventCallback>> mCallbacks;
+    };
+}
+
+#endif
diff --git a/tools/keventreader/server/inputsource.cpp b/tools/keventreader/server/inputsource.cpp
new file mode 100644
index 0000000..07b3c90
--- /dev/null
+++ b/tools/keventreader/server/inputsource.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+#include "inputsource.h"
+#include "defines.h"
+#include <utils/Log.h>
+
+#include <fcntl.h>
+#include <sstream>
+#include <string.h>
+#include <unistd.h>
+
+using namespace com::android::car::keventreader;
+
+InputSource::kevent::kevent() {
+    bzero(this, sizeof(*this));
+}
+
+bool InputSource::kevent::isKeypress() const {
+    return type == EV_KEY;
+}
+
+bool InputSource::kevent::isKeydown() const {
+    return isKeypress() && (value == 1);
+}
+
+bool InputSource::kevent::isKeyup() const {
+    return isKeypress() && (value == 0);
+}
+
+InputSource::InputSource(const char* file) : mFilePath(file), mDescriptor(open(file, O_RDONLY)) {}
+
+InputSource::operator bool() const {
+    return descriptor() >= 0;
+}
+
+int InputSource::descriptor() const {
+    return mDescriptor;
+}
+
+std::optional<com::android::car::keventreader::KeypressEvent> InputSource::read() const {
+    kevent evt;
+
+    auto cnt = ::read(mDescriptor, &evt, sizeof(evt));
+
+    // the kernel guarantees that we will always be able to read a whole number of events
+    if (cnt < static_cast<decltype(cnt)>(sizeof(evt))) {
+        return std::nullopt;
+    }
+    if (!evt.isKeypress()) {
+        return std::nullopt;
+    }
+
+    ALOGD("input source %s generated code %u (down = %s)",
+      mFilePath.c_str(), evt.code, evt.isKeydown() ? "true" : "false");
+    return com::android::car::keventreader::KeypressEvent(mFilePath, evt.code, evt.isKeydown());
+}
+
+InputSource::~InputSource() {
+    if (mDescriptor) ::close(mDescriptor);
+}
diff --git a/tools/keventreader/server/inputsource.h b/tools/keventreader/server/inputsource.h
new file mode 100644
index 0000000..282628e
--- /dev/null
+++ b/tools/keventreader/server/inputsource.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+#ifndef CAR_KEVENTREADER_INPUTSOURCE
+#define CAR_KEVENTREADER_INPUTSOURCE
+
+#include <linux/input.h>
+#include <optional>
+#include <string>
+#include "event.h"
+
+namespace com::android::car::keventreader {
+    class InputSource {
+    public:
+        explicit InputSource(const char* file);
+
+        explicit operator bool() const;
+
+        int descriptor() const;
+
+        std::optional<com::android::car::keventreader::KeypressEvent> read() const;
+
+        virtual ~InputSource();
+    private:
+        struct kevent : public ::input_event {
+            kevent();
+            bool isKeypress() const;
+            bool isKeydown() const;
+            bool isKeyup() const;
+        };
+        static_assert(sizeof(kevent) == sizeof(::input_event), "do not add data to input_event");
+
+        std::string mFilePath;
+        int mDescriptor;
+    };
+}
+
+#endif //CAR_KEVENTREADER_INPUTSOURCE
diff --git a/tools/keventreader/server/keymap.cpp b/tools/keventreader/server/keymap.cpp
new file mode 100644
index 0000000..2e7c06d
--- /dev/null
+++ b/tools/keventreader/server/keymap.cpp
@@ -0,0 +1,595 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+#include "keymap.h"
+#include <sstream>
+
+using namespace com::android::car::keventreader;
+
+Keymap& Keymap::get() {
+    static Keymap gKeymap;
+
+    return gKeymap;
+}
+
+Keymap::Keymap() {
+    fillMap();
+}
+
+std::string_view Keymap::getDisplayName(int keycode) {
+    auto iter = mKeyMap.find(keycode), end = mKeyMap.end();
+    if (iter == end) {
+        std::stringstream ss;
+        ss << "unknown " << keycode;
+        return ss.str();
+    }
+    return iter->second;
+}
+
+void Keymap::fillMap() {
+    mKeyMap.emplace(0,"RESERVED");
+    mKeyMap.emplace(1,"ESC");
+    mKeyMap.emplace(2,"1");
+    mKeyMap.emplace(3,"2");
+    mKeyMap.emplace(4,"3");
+    mKeyMap.emplace(5,"4");
+    mKeyMap.emplace(6,"5");
+    mKeyMap.emplace(7,"6");
+    mKeyMap.emplace(8,"7");
+    mKeyMap.emplace(9,"8");
+    mKeyMap.emplace(10,"9");
+    mKeyMap.emplace(11,"0");
+    mKeyMap.emplace(12,"MINUS");
+    mKeyMap.emplace(13,"EQUAL");
+    mKeyMap.emplace(14,"BACKSPACE");
+    mKeyMap.emplace(15,"TAB");
+    mKeyMap.emplace(16,"Q");
+    mKeyMap.emplace(17,"W");
+    mKeyMap.emplace(18,"E");
+    mKeyMap.emplace(19,"R");
+    mKeyMap.emplace(20,"T");
+    mKeyMap.emplace(21,"Y");
+    mKeyMap.emplace(22,"U");
+    mKeyMap.emplace(23,"I");
+    mKeyMap.emplace(24,"O");
+    mKeyMap.emplace(25,"P");
+    mKeyMap.emplace(26,"LEFTBRACE");
+    mKeyMap.emplace(27,"RIGHTBRACE");
+    mKeyMap.emplace(28,"ENTER");
+    mKeyMap.emplace(29,"LEFTCTRL");
+    mKeyMap.emplace(30,"A");
+    mKeyMap.emplace(31,"S");
+    mKeyMap.emplace(32,"D");
+    mKeyMap.emplace(33,"F");
+    mKeyMap.emplace(34,"G");
+    mKeyMap.emplace(35,"H");
+    mKeyMap.emplace(36,"J");
+    mKeyMap.emplace(37,"K");
+    mKeyMap.emplace(38,"L");
+    mKeyMap.emplace(39,"SEMICOLON");
+    mKeyMap.emplace(40,"APOSTROPHE");
+    mKeyMap.emplace(41,"GRAVE");
+    mKeyMap.emplace(42,"LEFTSHIFT");
+    mKeyMap.emplace(43,"BACKSLASH");
+    mKeyMap.emplace(44,"Z");
+    mKeyMap.emplace(45,"X");
+    mKeyMap.emplace(46,"C");
+    mKeyMap.emplace(47,"V");
+    mKeyMap.emplace(48,"B");
+    mKeyMap.emplace(49,"N");
+    mKeyMap.emplace(50,"M");
+    mKeyMap.emplace(51,"COMMA");
+    mKeyMap.emplace(52,"DOT");
+    mKeyMap.emplace(53,"SLASH");
+    mKeyMap.emplace(54,"RIGHTSHIFT");
+    mKeyMap.emplace(55,"KPASTERISK");
+    mKeyMap.emplace(56,"LEFTALT");
+    mKeyMap.emplace(57,"SPACE");
+    mKeyMap.emplace(58,"CAPSLOCK");
+    mKeyMap.emplace(59,"F1");
+    mKeyMap.emplace(60,"F2");
+    mKeyMap.emplace(61,"F3");
+    mKeyMap.emplace(62,"F4");
+    mKeyMap.emplace(63,"F5");
+    mKeyMap.emplace(64,"F6");
+    mKeyMap.emplace(65,"F7");
+    mKeyMap.emplace(66,"F8");
+    mKeyMap.emplace(67,"F9");
+    mKeyMap.emplace(68,"F10");
+    mKeyMap.emplace(69,"NUMLOCK");
+    mKeyMap.emplace(70,"SCROLLLOCK");
+    mKeyMap.emplace(71,"KP7");
+    mKeyMap.emplace(72,"KP8");
+    mKeyMap.emplace(73,"KP9");
+    mKeyMap.emplace(74,"KPMINUS");
+    mKeyMap.emplace(75,"KP4");
+    mKeyMap.emplace(76,"KP5");
+    mKeyMap.emplace(77,"KP6");
+    mKeyMap.emplace(78,"KPPLUS");
+    mKeyMap.emplace(79,"KP1");
+    mKeyMap.emplace(80,"KP2");
+    mKeyMap.emplace(81,"KP3");
+    mKeyMap.emplace(82,"KP0");
+    mKeyMap.emplace(83,"KPDOT");
+    mKeyMap.emplace(85,"ZENKAKUHANKAKU");
+    mKeyMap.emplace(86,"102ND");
+    mKeyMap.emplace(87,"F11");
+    mKeyMap.emplace(88,"F12");
+    mKeyMap.emplace(89,"RO");
+    mKeyMap.emplace(90,"KATAKANA");
+    mKeyMap.emplace(91,"HIRAGANA");
+    mKeyMap.emplace(92,"HENKAN");
+    mKeyMap.emplace(93,"KATAKANAHIRAGANA");
+    mKeyMap.emplace(94,"MUHENKAN");
+    mKeyMap.emplace(95,"KPJPCOMMA");
+    mKeyMap.emplace(96,"KPENTER");
+    mKeyMap.emplace(97,"RIGHTCTRL");
+    mKeyMap.emplace(98,"KPSLASH");
+    mKeyMap.emplace(99,"SYSRQ");
+    mKeyMap.emplace(100,"RIGHTALT");
+    mKeyMap.emplace(101,"LINEFEED");
+    mKeyMap.emplace(102,"HOME");
+    mKeyMap.emplace(103,"UP");
+    mKeyMap.emplace(104,"PAGEUP");
+    mKeyMap.emplace(105,"LEFT");
+    mKeyMap.emplace(106,"RIGHT");
+    mKeyMap.emplace(107,"END");
+    mKeyMap.emplace(108,"DOWN");
+    mKeyMap.emplace(109,"PAGEDOWN");
+    mKeyMap.emplace(110,"INSERT");
+    mKeyMap.emplace(111,"DELETE");
+    mKeyMap.emplace(112,"MACRO");
+    mKeyMap.emplace(113,"MUTE");
+    mKeyMap.emplace(114,"VOLUMEDOWN");
+    mKeyMap.emplace(115,"VOLUMEUP");
+    mKeyMap.emplace(116,"POWER");
+    mKeyMap.emplace(117,"KPEQUAL");
+    mKeyMap.emplace(118,"KPPLUSMINUS");
+    mKeyMap.emplace(119,"PAUSE");
+    mKeyMap.emplace(120,"SCALE");
+    mKeyMap.emplace(121,"KPCOMMA");
+    mKeyMap.emplace(122,"HANGEUL");
+    mKeyMap.emplace(123,"HANJA");
+    mKeyMap.emplace(124,"YEN");
+    mKeyMap.emplace(125,"LEFTMETA");
+    mKeyMap.emplace(126,"RIGHTMETA");
+    mKeyMap.emplace(127,"COMPOSE");
+    mKeyMap.emplace(128,"STOP");
+    mKeyMap.emplace(129,"AGAIN");
+    mKeyMap.emplace(130,"PROPS");
+    mKeyMap.emplace(131,"UNDO");
+    mKeyMap.emplace(132,"FRONT");
+    mKeyMap.emplace(133,"COPY");
+    mKeyMap.emplace(134,"OPEN");
+    mKeyMap.emplace(135,"PASTE");
+    mKeyMap.emplace(136,"FIND");
+    mKeyMap.emplace(137,"CUT");
+    mKeyMap.emplace(138,"HELP");
+    mKeyMap.emplace(139,"MENU");
+    mKeyMap.emplace(140,"CALC");
+    mKeyMap.emplace(141,"SETUP");
+    mKeyMap.emplace(142,"SLEEP");
+    mKeyMap.emplace(143,"WAKEUP");
+    mKeyMap.emplace(144,"FILE");
+    mKeyMap.emplace(145,"SENDFILE");
+    mKeyMap.emplace(146,"DELETEFILE");
+    mKeyMap.emplace(147,"XFER");
+    mKeyMap.emplace(148,"PROG1");
+    mKeyMap.emplace(149,"PROG2");
+    mKeyMap.emplace(150,"WWW");
+    mKeyMap.emplace(151,"MSDOS");
+    mKeyMap.emplace(152,"SCREENLOCK");
+    mKeyMap.emplace(153,"ROTATE_DISPLAY");
+    mKeyMap.emplace(154,"CYCLEWINDOWS");
+    mKeyMap.emplace(155,"MAIL");
+    mKeyMap.emplace(156,"BOOKMARKS");
+    mKeyMap.emplace(157,"COMPUTER");
+    mKeyMap.emplace(158,"BACK");
+    mKeyMap.emplace(159,"FORWARD");
+    mKeyMap.emplace(160,"CLOSECD");
+    mKeyMap.emplace(161,"EJECTCD");
+    mKeyMap.emplace(162,"EJECTCLOSECD");
+    mKeyMap.emplace(163,"NEXTSONG");
+    mKeyMap.emplace(164,"PLAYPAUSE");
+    mKeyMap.emplace(165,"PREVIOUSSONG");
+    mKeyMap.emplace(166,"STOPCD");
+    mKeyMap.emplace(167,"RECORD");
+    mKeyMap.emplace(168,"REWIND");
+    mKeyMap.emplace(169,"PHONE");
+    mKeyMap.emplace(170,"ISO");
+    mKeyMap.emplace(171,"CONFIG");
+    mKeyMap.emplace(172,"HOMEPAGE");
+    mKeyMap.emplace(173,"REFRESH");
+    mKeyMap.emplace(174,"EXIT");
+    mKeyMap.emplace(175,"MOVE");
+    mKeyMap.emplace(176,"EDIT");
+    mKeyMap.emplace(177,"SCROLLUP");
+    mKeyMap.emplace(178,"SCROLLDOWN");
+    mKeyMap.emplace(179,"KPLEFTPAREN");
+    mKeyMap.emplace(180,"KPRIGHTPAREN");
+    mKeyMap.emplace(181,"NEW");
+    mKeyMap.emplace(182,"REDO");
+    mKeyMap.emplace(183,"F13");
+    mKeyMap.emplace(184,"F14");
+    mKeyMap.emplace(185,"F15");
+    mKeyMap.emplace(186,"F16");
+    mKeyMap.emplace(187,"F17");
+    mKeyMap.emplace(188,"F18");
+    mKeyMap.emplace(189,"F19");
+    mKeyMap.emplace(190,"F20");
+    mKeyMap.emplace(191,"F21");
+    mKeyMap.emplace(192,"F22");
+    mKeyMap.emplace(193,"F23");
+    mKeyMap.emplace(194,"F24");
+    mKeyMap.emplace(200,"PLAYCD");
+    mKeyMap.emplace(201,"PAUSECD");
+    mKeyMap.emplace(202,"PROG3");
+    mKeyMap.emplace(203,"PROG4");
+    mKeyMap.emplace(204,"DASHBOARD");
+    mKeyMap.emplace(205,"SUSPEND");
+    mKeyMap.emplace(206,"CLOSE");
+    mKeyMap.emplace(207,"PLAY");
+    mKeyMap.emplace(208,"FASTFORWARD");
+    mKeyMap.emplace(209,"BASSBOOST");
+    mKeyMap.emplace(210,"PRINT");
+    mKeyMap.emplace(211,"HP");
+    mKeyMap.emplace(212,"CAMERA");
+    mKeyMap.emplace(213,"SOUND");
+    mKeyMap.emplace(214,"QUESTION");
+    mKeyMap.emplace(215,"EMAIL");
+    mKeyMap.emplace(216,"CHAT");
+    mKeyMap.emplace(217,"SEARCH");
+    mKeyMap.emplace(218,"CONNECT");
+    mKeyMap.emplace(219,"FINANCE");
+    mKeyMap.emplace(220,"SPORT");
+    mKeyMap.emplace(221,"SHOP");
+    mKeyMap.emplace(222,"ALTERASE");
+    mKeyMap.emplace(223,"CANCEL");
+    mKeyMap.emplace(224,"BRIGHTNESSDOWN");
+    mKeyMap.emplace(225,"BRIGHTNESSUP");
+    mKeyMap.emplace(226,"MEDIA");
+    mKeyMap.emplace(227,"SWITCHVIDEOMODE");
+    mKeyMap.emplace(228,"KBDILLUMTOGGLE");
+    mKeyMap.emplace(229,"KBDILLUMDOWN");
+    mKeyMap.emplace(230,"KBDILLUMUP");
+    mKeyMap.emplace(231,"SEND");
+    mKeyMap.emplace(232,"REPLY");
+    mKeyMap.emplace(233,"FORWARDMAIL");
+    mKeyMap.emplace(234,"SAVE");
+    mKeyMap.emplace(235,"DOCUMENTS");
+    mKeyMap.emplace(236,"BATTERY");
+    mKeyMap.emplace(237,"BLUETOOTH");
+    mKeyMap.emplace(238,"WLAN");
+    mKeyMap.emplace(239,"UWB");
+    mKeyMap.emplace(240,"UNKNOWN");
+    mKeyMap.emplace(241,"VIDEO_NEXT");
+    mKeyMap.emplace(242,"VIDEO_PREV");
+    mKeyMap.emplace(243,"BRIGHTNESS_CYCLE");
+    mKeyMap.emplace(244,"BRIGHTNESS_AUTO");
+    mKeyMap.emplace(245,"DISPLAY_OFF");
+    mKeyMap.emplace(246,"WWAN");
+    mKeyMap.emplace(247,"RFKILL");
+    mKeyMap.emplace(248,"MICMUTE");
+    mKeyMap.emplace(0x160,"OK");
+    mKeyMap.emplace(0x161,"SELECT");
+    mKeyMap.emplace(0x162,"GOTO");
+    mKeyMap.emplace(0x163,"CLEAR");
+    mKeyMap.emplace(0x164,"POWER2");
+    mKeyMap.emplace(0x165,"OPTION");
+    mKeyMap.emplace(0x166,"INFO");
+    mKeyMap.emplace(0x167,"TIME");
+    mKeyMap.emplace(0x168,"VENDOR");
+    mKeyMap.emplace(0x169,"ARCHIVE");
+    mKeyMap.emplace(0x16a,"PROGRAM");
+    mKeyMap.emplace(0x16b,"CHANNEL");
+    mKeyMap.emplace(0x16c,"FAVORITES");
+    mKeyMap.emplace(0x16d,"EPG");
+    mKeyMap.emplace(0x16e,"PVR");
+    mKeyMap.emplace(0x16f,"MHP");
+    mKeyMap.emplace(0x170,"LANGUAGE");
+    mKeyMap.emplace(0x171,"TITLE");
+    mKeyMap.emplace(0x172,"SUBTITLE");
+    mKeyMap.emplace(0x173,"ANGLE");
+    mKeyMap.emplace(0x174,"ZOOM");
+    mKeyMap.emplace(0x175,"MODE");
+    mKeyMap.emplace(0x176,"KEYBOARD");
+    mKeyMap.emplace(0x177,"SCREEN");
+    mKeyMap.emplace(0x178,"PC");
+    mKeyMap.emplace(0x179,"TV");
+    mKeyMap.emplace(0x17a,"TV2");
+    mKeyMap.emplace(0x17b,"VCR");
+    mKeyMap.emplace(0x17c,"VCR2");
+    mKeyMap.emplace(0x17d,"SAT");
+    mKeyMap.emplace(0x17e,"SAT2");
+    mKeyMap.emplace(0x17f,"CD");
+    mKeyMap.emplace(0x180,"TAPE");
+    mKeyMap.emplace(0x181,"RADIO");
+    mKeyMap.emplace(0x182,"TUNER");
+    mKeyMap.emplace(0x183,"PLAYER");
+    mKeyMap.emplace(0x184,"TEXT");
+    mKeyMap.emplace(0x185,"DVD");
+    mKeyMap.emplace(0x186,"AUX");
+    mKeyMap.emplace(0x187,"MP3");
+    mKeyMap.emplace(0x188,"AUDIO");
+    mKeyMap.emplace(0x189,"VIDEO");
+    mKeyMap.emplace(0x18a,"DIRECTORY");
+    mKeyMap.emplace(0x18b,"LIST");
+    mKeyMap.emplace(0x18c,"MEMO");
+    mKeyMap.emplace(0x18d,"CALENDAR");
+    mKeyMap.emplace(0x18e,"RED");
+    mKeyMap.emplace(0x18f,"GREEN");
+    mKeyMap.emplace(0x190,"YELLOW");
+    mKeyMap.emplace(0x191,"BLUE");
+    mKeyMap.emplace(0x192,"CHANNELUP");
+    mKeyMap.emplace(0x193,"CHANNELDOWN");
+    mKeyMap.emplace(0x194,"FIRST");
+    mKeyMap.emplace(0x195,"LAST");
+    mKeyMap.emplace(0x196,"AB");
+    mKeyMap.emplace(0x197,"NEXT");
+    mKeyMap.emplace(0x198,"RESTART");
+    mKeyMap.emplace(0x199,"SLOW");
+    mKeyMap.emplace(0x19a,"SHUFFLE");
+    mKeyMap.emplace(0x19b,"BREAK");
+    mKeyMap.emplace(0x19c,"PREVIOUS");
+    mKeyMap.emplace(0x19d,"DIGITS");
+    mKeyMap.emplace(0x19e,"TEEN");
+    mKeyMap.emplace(0x19f,"TWEN");
+    mKeyMap.emplace(0x1a0,"VIDEOPHONE");
+    mKeyMap.emplace(0x1a1,"GAMES");
+    mKeyMap.emplace(0x1a2,"ZOOMIN");
+    mKeyMap.emplace(0x1a3,"ZOOMOUT");
+    mKeyMap.emplace(0x1a4,"ZOOMRESET");
+    mKeyMap.emplace(0x1a5,"WORDPROCESSOR");
+    mKeyMap.emplace(0x1a6,"EDITOR");
+    mKeyMap.emplace(0x1a7,"SPREADSHEET");
+    mKeyMap.emplace(0x1a8,"GRAPHICSEDITOR");
+    mKeyMap.emplace(0x1a9,"PRESENTATION");
+    mKeyMap.emplace(0x1aa,"DATABASE");
+    mKeyMap.emplace(0x1ab,"NEWS");
+    mKeyMap.emplace(0x1ac,"VOICEMAIL");
+    mKeyMap.emplace(0x1ad,"ADDRESSBOOK");
+    mKeyMap.emplace(0x1ae,"MESSENGER");
+    mKeyMap.emplace(0x1af,"DISPLAYTOGGLE");
+    mKeyMap.emplace(0x1b0,"SPELLCHECK");
+    mKeyMap.emplace(0x1b1,"LOGOFF");
+    mKeyMap.emplace(0x1b2,"DOLLAR");
+    mKeyMap.emplace(0x1b3,"EURO");
+    mKeyMap.emplace(0x1b4,"FRAMEBACK");
+    mKeyMap.emplace(0x1b5,"FRAMEFORWARD");
+    mKeyMap.emplace(0x1b6,"CONTEXT_MENU");
+    mKeyMap.emplace(0x1b7,"MEDIA_REPEAT");
+    mKeyMap.emplace(0x1b8,"10CHANNELSUP");
+    mKeyMap.emplace(0x1b9,"10CHANNELSDOWN");
+    mKeyMap.emplace(0x1ba,"IMAGES");
+    mKeyMap.emplace(0x1c0,"DEL_EOL");
+    mKeyMap.emplace(0x1c1,"DEL_EOS");
+    mKeyMap.emplace(0x1c2,"INS_LINE");
+    mKeyMap.emplace(0x1c3,"DEL_LINE");
+    mKeyMap.emplace(0x1d0,"FN");
+    mKeyMap.emplace(0x1d1,"FN_ESC");
+    mKeyMap.emplace(0x1d2,"FN_F1");
+    mKeyMap.emplace(0x1d3,"FN_F2");
+    mKeyMap.emplace(0x1d4,"FN_F3");
+    mKeyMap.emplace(0x1d5,"FN_F4");
+    mKeyMap.emplace(0x1d6,"FN_F5");
+    mKeyMap.emplace(0x1d7,"FN_F6");
+    mKeyMap.emplace(0x1d8,"FN_F7");
+    mKeyMap.emplace(0x1d9,"FN_F8");
+    mKeyMap.emplace(0x1da,"FN_F9");
+    mKeyMap.emplace(0x1db,"FN_F10");
+    mKeyMap.emplace(0x1dc,"FN_F11");
+    mKeyMap.emplace(0x1dd,"FN_F12");
+    mKeyMap.emplace(0x1de,"FN_1");
+    mKeyMap.emplace(0x1df,"FN_2");
+    mKeyMap.emplace(0x1e0,"FN_D");
+    mKeyMap.emplace(0x1e1,"FN_E");
+    mKeyMap.emplace(0x1e2,"FN_F");
+    mKeyMap.emplace(0x1e3,"FN_S");
+    mKeyMap.emplace(0x1e4,"FN_B");
+    mKeyMap.emplace(0x1f1,"BRL_DOT1");
+    mKeyMap.emplace(0x1f2,"BRL_DOT2");
+    mKeyMap.emplace(0x1f3,"BRL_DOT3");
+    mKeyMap.emplace(0x1f4,"BRL_DOT4");
+    mKeyMap.emplace(0x1f5,"BRL_DOT5");
+    mKeyMap.emplace(0x1f6,"BRL_DOT6");
+    mKeyMap.emplace(0x1f7,"BRL_DOT7");
+    mKeyMap.emplace(0x1f8,"BRL_DOT8");
+    mKeyMap.emplace(0x1f9,"BRL_DOT9");
+    mKeyMap.emplace(0x1fa,"BRL_DOT10");
+    mKeyMap.emplace(0x200,"NUMERIC_0");
+    mKeyMap.emplace(0x201,"NUMERIC_1");
+    mKeyMap.emplace(0x202,"NUMERIC_2");
+    mKeyMap.emplace(0x203,"NUMERIC_3");
+    mKeyMap.emplace(0x204,"NUMERIC_4");
+    mKeyMap.emplace(0x205,"NUMERIC_5");
+    mKeyMap.emplace(0x206,"NUMERIC_6");
+    mKeyMap.emplace(0x207,"NUMERIC_7");
+    mKeyMap.emplace(0x208,"NUMERIC_8");
+    mKeyMap.emplace(0x209,"NUMERIC_9");
+    mKeyMap.emplace(0x20a,"NUMERIC_STAR");
+    mKeyMap.emplace(0x20b,"NUMERIC_POUND");
+    mKeyMap.emplace(0x20c,"NUMERIC_A");
+    mKeyMap.emplace(0x20d,"NUMERIC_B");
+    mKeyMap.emplace(0x20e,"NUMERIC_C");
+    mKeyMap.emplace(0x20f,"NUMERIC_D");
+    mKeyMap.emplace(0x210,"CAMERA_FOCUS");
+    mKeyMap.emplace(0x211,"WPS_BUTTON");
+    mKeyMap.emplace(0x212,"TOUCHPAD_TOGGLE");
+    mKeyMap.emplace(0x213,"TOUCHPAD_ON");
+    mKeyMap.emplace(0x214,"TOUCHPAD_OFF");
+    mKeyMap.emplace(0x215,"CAMERA_ZOOMIN");
+    mKeyMap.emplace(0x216,"CAMERA_ZOOMOUT");
+    mKeyMap.emplace(0x217,"CAMERA_UP");
+    mKeyMap.emplace(0x218,"CAMERA_DOWN");
+    mKeyMap.emplace(0x219,"CAMERA_LEFT");
+    mKeyMap.emplace(0x21a,"CAMERA_RIGHT");
+    mKeyMap.emplace(0x21b,"ATTENDANT_ON");
+    mKeyMap.emplace(0x21c,"ATTENDANT_OFF");
+    mKeyMap.emplace(0x21d,"ATTENDANT_TOGGLE");
+    mKeyMap.emplace(0x21e,"LIGHTS_TOGGLE");
+    mKeyMap.emplace(0x230,"ALS_TOGGLE");
+    mKeyMap.emplace(0x240,"BUTTONCONFIG");
+    mKeyMap.emplace(0x241,"TASKMANAGER");
+    mKeyMap.emplace(0x242,"JOURNAL");
+    mKeyMap.emplace(0x243,"CONTROLPANEL");
+    mKeyMap.emplace(0x244,"APPSELECT");
+    mKeyMap.emplace(0x245,"SCREENSAVER");
+    mKeyMap.emplace(0x246,"VOICECOMMAND");
+    mKeyMap.emplace(0x247,"ASSISTANT");
+    mKeyMap.emplace(0x250,"BRIGHTNESS_MIN");
+    mKeyMap.emplace(0x251,"BRIGHTNESS_MAX");
+    mKeyMap.emplace(0x260,"KBDINPUTASSIST_PREV");
+    mKeyMap.emplace(0x261,"KBDINPUTASSIST_NEXT");
+    mKeyMap.emplace(0x262,"KBDINPUTASSIST_PREVGROUP");
+    mKeyMap.emplace(0x263,"KBDINPUTASSIST_NEXTGROUP");
+    mKeyMap.emplace(0x264,"KBDINPUTASSIST_ACCEPT");
+    mKeyMap.emplace(0x265,"KBDINPUTASSIST_CANCEL");
+    mKeyMap.emplace(0x266,"RIGHT_UP");
+    mKeyMap.emplace(0x267,"RIGHT_DOWN");
+    mKeyMap.emplace(0x268,"LEFT_UP");
+    mKeyMap.emplace(0x269,"LEFT_DOWN");
+    mKeyMap.emplace(0x26a,"ROOT_MENU");
+    mKeyMap.emplace(0x26b,"MEDIA_TOP_MENU");
+    mKeyMap.emplace(0x26c,"NUMERIC_11");
+    mKeyMap.emplace(0x26d,"NUMERIC_12");
+    mKeyMap.emplace(0x26e,"AUDIO_DESC");
+    mKeyMap.emplace(0x26f,"3D_MODE");
+    mKeyMap.emplace(0x270,"NEXT_FAVORITE");
+    mKeyMap.emplace(0x271,"STOP_RECORD");
+    mKeyMap.emplace(0x272,"PAUSE_RECORD");
+    mKeyMap.emplace(0x273,"VOD");
+    mKeyMap.emplace(0x274,"UNMUTE");
+    mKeyMap.emplace(0x275,"FASTREVERSE");
+    mKeyMap.emplace(0x276,"SLOWREVERSE");
+    mKeyMap.emplace(0x277,"DATA");
+    mKeyMap.emplace(0x278,"ONSCREEN_KEYBOARD");
+    mKeyMap.emplace(113,"MIN_INTERESTING");
+    mKeyMap.emplace(0x2ff,"MAX");
+    mKeyMap.emplace(0x100,"MISC");
+    mKeyMap.emplace(0x100,"0");
+    mKeyMap.emplace(0x101,"1");
+    mKeyMap.emplace(0x102,"2");
+    mKeyMap.emplace(0x103,"3");
+    mKeyMap.emplace(0x104,"4");
+    mKeyMap.emplace(0x105,"5");
+    mKeyMap.emplace(0x106,"6");
+    mKeyMap.emplace(0x107,"7");
+    mKeyMap.emplace(0x108,"8");
+    mKeyMap.emplace(0x109,"9");
+    mKeyMap.emplace(0x110,"MOUSE");
+    mKeyMap.emplace(0x110,"LEFT");
+    mKeyMap.emplace(0x111,"RIGHT");
+    mKeyMap.emplace(0x112,"MIDDLE");
+    mKeyMap.emplace(0x113,"SIDE");
+    mKeyMap.emplace(0x114,"EXTRA");
+    mKeyMap.emplace(0x115,"FORWARD");
+    mKeyMap.emplace(0x116,"BACK");
+    mKeyMap.emplace(0x117,"TASK");
+    mKeyMap.emplace(0x120,"JOYSTICK");
+    mKeyMap.emplace(0x120,"TRIGGER");
+    mKeyMap.emplace(0x121,"THUMB");
+    mKeyMap.emplace(0x122,"THUMB2");
+    mKeyMap.emplace(0x123,"TOP");
+    mKeyMap.emplace(0x124,"TOP2");
+    mKeyMap.emplace(0x125,"PINKIE");
+    mKeyMap.emplace(0x126,"BASE");
+    mKeyMap.emplace(0x127,"BASE2");
+    mKeyMap.emplace(0x128,"BASE3");
+    mKeyMap.emplace(0x129,"BASE4");
+    mKeyMap.emplace(0x12a,"BASE5");
+    mKeyMap.emplace(0x12b,"BASE6");
+    mKeyMap.emplace(0x12f,"DEAD");
+    mKeyMap.emplace(0x130,"GAMEPAD");
+    mKeyMap.emplace(0x130,"SOUTH");
+    mKeyMap.emplace(0x131,"EAST");
+    mKeyMap.emplace(0x132,"C");
+    mKeyMap.emplace(0x133,"NORTH");
+    mKeyMap.emplace(0x134,"WEST");
+    mKeyMap.emplace(0x135,"Z");
+    mKeyMap.emplace(0x136,"TL");
+    mKeyMap.emplace(0x137,"TR");
+    mKeyMap.emplace(0x138,"TL2");
+    mKeyMap.emplace(0x139,"TR2");
+    mKeyMap.emplace(0x13a,"SELECT");
+    mKeyMap.emplace(0x13b,"START");
+    mKeyMap.emplace(0x13c,"MODE");
+    mKeyMap.emplace(0x13d,"THUMBL");
+    mKeyMap.emplace(0x13e,"THUMBR");
+    mKeyMap.emplace(0x140,"DIGI");
+    mKeyMap.emplace(0x140,"TOOL_PEN");
+    mKeyMap.emplace(0x141,"TOOL_RUBBER");
+    mKeyMap.emplace(0x142,"TOOL_BRUSH");
+    mKeyMap.emplace(0x143,"TOOL_PENCIL");
+    mKeyMap.emplace(0x144,"TOOL_AIRBRUSH");
+    mKeyMap.emplace(0x145,"TOOL_FINGER");
+    mKeyMap.emplace(0x146,"TOOL_MOUSE");
+    mKeyMap.emplace(0x147,"TOOL_LENS");
+    mKeyMap.emplace(0x148,"TOOL_QUINTTAP");
+    mKeyMap.emplace(0x149,"STYLUS3");
+    mKeyMap.emplace(0x14a,"TOUCH");
+    mKeyMap.emplace(0x14b,"STYLUS");
+    mKeyMap.emplace(0x14c,"STYLUS2");
+    mKeyMap.emplace(0x14d,"TOOL_DOUBLETAP");
+    mKeyMap.emplace(0x14e,"TOOL_TRIPLETAP");
+    mKeyMap.emplace(0x14f,"TOOL_QUADTAP");
+    mKeyMap.emplace(0x150,"WHEEL");
+    mKeyMap.emplace(0x150,"GEAR_DOWN");
+    mKeyMap.emplace(0x151,"GEAR_UP");
+    mKeyMap.emplace(0x220,"DPAD_UP");
+    mKeyMap.emplace(0x221,"DPAD_DOWN");
+    mKeyMap.emplace(0x222,"DPAD_LEFT");
+    mKeyMap.emplace(0x223,"DPAD_RIGHT");
+    mKeyMap.emplace(0x2c0,"TRIGGER_HAPPY");
+    mKeyMap.emplace(0x2c0,"TRIGGER_HAPPY1");
+    mKeyMap.emplace(0x2c1,"TRIGGER_HAPPY2");
+    mKeyMap.emplace(0x2c2,"TRIGGER_HAPPY3");
+    mKeyMap.emplace(0x2c3,"TRIGGER_HAPPY4");
+    mKeyMap.emplace(0x2c4,"TRIGGER_HAPPY5");
+    mKeyMap.emplace(0x2c5,"TRIGGER_HAPPY6");
+    mKeyMap.emplace(0x2c6,"TRIGGER_HAPPY7");
+    mKeyMap.emplace(0x2c7,"TRIGGER_HAPPY8");
+    mKeyMap.emplace(0x2c8,"TRIGGER_HAPPY9");
+    mKeyMap.emplace(0x2c9,"TRIGGER_HAPPY10");
+    mKeyMap.emplace(0x2ca,"TRIGGER_HAPPY11");
+    mKeyMap.emplace(0x2cb,"TRIGGER_HAPPY12");
+    mKeyMap.emplace(0x2cc,"TRIGGER_HAPPY13");
+    mKeyMap.emplace(0x2cd,"TRIGGER_HAPPY14");
+    mKeyMap.emplace(0x2ce,"TRIGGER_HAPPY15");
+    mKeyMap.emplace(0x2cf,"TRIGGER_HAPPY16");
+    mKeyMap.emplace(0x2d0,"TRIGGER_HAPPY17");
+    mKeyMap.emplace(0x2d1,"TRIGGER_HAPPY18");
+    mKeyMap.emplace(0x2d2,"TRIGGER_HAPPY19");
+    mKeyMap.emplace(0x2d3,"TRIGGER_HAPPY20");
+    mKeyMap.emplace(0x2d4,"TRIGGER_HAPPY21");
+    mKeyMap.emplace(0x2d5,"TRIGGER_HAPPY22");
+    mKeyMap.emplace(0x2d6,"TRIGGER_HAPPY23");
+    mKeyMap.emplace(0x2d7,"TRIGGER_HAPPY24");
+    mKeyMap.emplace(0x2d8,"TRIGGER_HAPPY25");
+    mKeyMap.emplace(0x2d9,"TRIGGER_HAPPY26");
+    mKeyMap.emplace(0x2da,"TRIGGER_HAPPY27");
+    mKeyMap.emplace(0x2db,"TRIGGER_HAPPY28");
+    mKeyMap.emplace(0x2dc,"TRIGGER_HAPPY29");
+    mKeyMap.emplace(0x2dd,"TRIGGER_HAPPY30");
+    mKeyMap.emplace(0x2de,"TRIGGER_HAPPY31");
+    mKeyMap.emplace(0x2df,"TRIGGER_HAPPY32");
+    mKeyMap.emplace(0x2e0,"TRIGGER_HAPPY33");
+    mKeyMap.emplace(0x2e1,"TRIGGER_HAPPY34");
+    mKeyMap.emplace(0x2e2,"TRIGGER_HAPPY35");
+    mKeyMap.emplace(0x2e3,"TRIGGER_HAPPY36");
+    mKeyMap.emplace(0x2e4,"TRIGGER_HAPPY37");
+    mKeyMap.emplace(0x2e5,"TRIGGER_HAPPY38");
+    mKeyMap.emplace(0x2e6,"TRIGGER_HAPPY39");
+    mKeyMap.emplace(0x2e7,"TRIGGER_HAPPY40");
+}
diff --git a/tools/keventreader/server/keymap.h b/tools/keventreader/server/keymap.h
new file mode 100644
index 0000000..28f68d6
--- /dev/null
+++ b/tools/keventreader/server/keymap.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+#ifndef CAR_KEVENTREADER_KEYMAP
+#define CAR_KEVENTREADER_KEYMAP
+
+#include <string>
+#include <map>
+
+namespace com::android::car::keventreader {
+    // this class provides output that is based on the Linux keycodes table
+    class Keymap {
+    public:
+        static Keymap& get();
+
+        std::string_view getDisplayName(int keycode);
+
+    private:
+        std::map<int, const char*> mKeyMap;
+        void fillMap();
+        Keymap();
+    };
+}
+
+#endif //CAR_KEVENTREADER_KEYMAP
diff --git a/tools/keventreader/server/main.cpp b/tools/keventreader/server/main.cpp
new file mode 100644
index 0000000..fdf9a1c
--- /dev/null
+++ b/tools/keventreader/server/main.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+#include "defines.h"
+
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <iostream>
+#include <signal.h>
+#include <utils/Log.h>
+#include "eventgatherer.h"
+#include "keymap.h"
+#include "eventprovider.h"
+
+
+using namespace android;
+using namespace com::android::car::keventreader;
+
+/**
+ * This tool expects to be able to read input events in the Linux kernel input_event format
+ * (see linux/input.h); that format is available by means of /dev/input/event* files (which are
+ * mapped 1-to-1 to input sources that provide keypresses as input (e.g. keyboards)
+ * The tool will hook up to each such file passed as input
+ */
+static const char* SYNTAX_INSTRUCTIONS =
+    "invalid command line arguments - provide one or more /dev/input/event files";
+
+static void error(int code) {
+    ALOGE("%s", SYNTAX_INSTRUCTIONS);
+    std::cerr << SYNTAX_INSTRUCTIONS << '\n';
+    exit(code);
+}
+
+int main(int argc, const char** argv) {
+    if (argc < 2 || argv[1] == nullptr || argv[1][0] == 0) {
+        error(1);
+    }
+
+    EventGatherer gatherer(argc, argv);
+    if (0 == gatherer.size()) {
+        error(2);
+    }
+
+    ALOGI("starting " LOG_TAG);
+    signal(SIGPIPE, SIG_IGN);
+
+    sp<ProcessState> processSelf(ProcessState::self());
+    sp<IServiceManager> serviceManager = defaultServiceManager();
+    auto service = std::make_unique<EventProviderImpl>(std::move(gatherer));
+    serviceManager->addService(String16(SERVICE_NAME), service.get());
+
+    processSelf->startThreadPool();
+
+    ALOGI(LOG_TAG " started");
+
+    auto svcthread = service->startLoop();
+    IPCThreadState::self()->joinThreadPool();
+
+    ALOGW(LOG_TAG " joined and going down");
+    exit(0);
+}