Notify applications when input devices change.

This change allows the InputManager to keep track of what input
devices are registered with the system and when they change.
It needs to do this so that it can properly clear its cache of
input device properties (especially the key map!) when changes
occur.

Added new API so that applications can register listeners for
input device changes.

Fixed a minor bug in EventHub where it didn't handle EPOLLHUP
properly so it would spam the log about unsupposed epoll events
until inotify noticed that the device was gone and removed it.

Change-Id: I937d8c601f7185d4299038bce6a2934fe4fdd2b3
diff --git a/Android.mk b/Android.mk
index cacdee9..4e3929c 100644
--- a/Android.mk
+++ b/Android.mk
@@ -111,6 +111,7 @@
 	core/java/android/database/IContentObserver.aidl \
 	core/java/android/hardware/ISerialManager.aidl \
 	core/java/android/hardware/input/IInputManager.aidl \
+	core/java/android/hardware/input/IInputDevicesChangedListener.aidl \
 	core/java/android/hardware/usb/IUsbManager.aidl \
 	core/java/android/net/IConnectivityManager.aidl \
 	core/java/android/net/INetworkManagementEventObserver.aidl \
diff --git a/api/current.txt b/api/current.txt
index ee964f7..3f79136 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9820,10 +9820,20 @@
 package android.hardware.input {
 
   public final class InputManager {
+    method public android.view.InputDevice getInputDevice(int);
+    method public int[] getInputDeviceIds();
+    method public void registerInputDeviceListener(android.hardware.input.InputManager.InputDeviceListener, android.os.Handler);
+    method public void unregisterInputDeviceListener(android.hardware.input.InputManager.InputDeviceListener);
     field public static final java.lang.String ACTION_QUERY_KEYBOARD_LAYOUTS = "android.hardware.input.action.QUERY_KEYBOARD_LAYOUTS";
     field public static final java.lang.String META_DATA_KEYBOARD_LAYOUTS = "android.hardware.input.metadata.KEYBOARD_LAYOUTS";
   }
 
+  public static abstract interface InputManager.InputDeviceListener {
+    method public abstract void onInputDeviceAdded(int);
+    method public abstract void onInputDeviceChanged(int);
+    method public abstract void onInputDeviceRemoved(int);
+  }
+
 }
 
 package android.hardware.usb {
diff --git a/core/java/android/hardware/input/IInputDevicesChangedListener.aidl b/core/java/android/hardware/input/IInputDevicesChangedListener.aidl
new file mode 100644
index 0000000..5d8ada1
--- /dev/null
+++ b/core/java/android/hardware/input/IInputDevicesChangedListener.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2012 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;
+
+/** @hide */
+interface IInputDevicesChangedListener {
+    /* Called when input devices changed, such as a device being added,
+     * removed or changing configuration.
+     *
+     * The parameter is an array of pairs (deviceId, generation) indicating the current
+     * device id and generation of all input devices.  The client can determine what
+     * has happened by comparing the result to its prior observations.
+     */
+    oneway void onInputDevicesChanged(in int[] deviceIdAndGeneration);
+}
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 47e0d1e..ca8321f 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -17,6 +17,7 @@
 package android.hardware.input;
 
 import android.hardware.input.KeyboardLayout;
+import android.hardware.input.IInputDevicesChangedListener;
 import android.view.InputDevice;
 import android.view.InputEvent;
 
@@ -42,4 +43,7 @@
     String getKeyboardLayoutForInputDevice(String inputDeviceDescriptor);
     void setKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
             String keyboardLayoutDescriptor);
+
+    // Registers an input devices changed listener.
+    void registerInputDevicesChangedListener(IInputDevicesChangedListener listener);
 }
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 3b3c237..35c49a1 100755
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -19,7 +19,10 @@
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.content.Context;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.provider.Settings;
@@ -29,6 +32,8 @@
 import android.view.InputDevice;
 import android.view.InputEvent;
 
+import java.util.ArrayList;
+
 /**
  * Provides information about input devices and available key layouts.
  * <p>
@@ -40,11 +45,22 @@
  */
 public final class InputManager {
     private static final String TAG = "InputManager";
+    private static final boolean DEBUG = false;
+
+    private static final int MSG_DEVICE_ADDED = 1;
+    private static final int MSG_DEVICE_REMOVED = 2;
+    private static final int MSG_DEVICE_CHANGED = 3;
 
     private static InputManager sInstance;
 
     private final IInputManager mIm;
-    private final SparseArray<InputDevice> mInputDevices = new SparseArray<InputDevice>();
+
+    // Guarded by mInputDevicesLock
+    private final Object mInputDevicesLock = new Object();
+    private SparseArray<InputDevice> mInputDevices;
+    private InputDevicesChangedListener mInputDevicesChangedListener;
+    private final ArrayList<InputDeviceListenerDelegate> mInputDeviceListeners =
+            new ArrayList<InputDeviceListenerDelegate>();
 
     /**
      * Broadcast Action: Query available keyboard layouts.
@@ -169,6 +185,103 @@
     }
 
     /**
+     * Gets information about the input device with the specified id.
+     * @param id The device id.
+     * @return The input device or null if not found.
+     */
+    public InputDevice getInputDevice(int id) {
+        synchronized (mInputDevicesLock) {
+            populateInputDevicesLocked();
+
+            int index = mInputDevices.indexOfKey(id);
+            if (index < 0) {
+                return null;
+            }
+
+            InputDevice inputDevice = mInputDevices.valueAt(index);
+            if (inputDevice == null) {
+                try {
+                    inputDevice = mIm.getInputDevice(id);
+                } catch (RemoteException ex) {
+                    throw new RuntimeException("Could not get input device information.", ex);
+                }
+            }
+            mInputDevices.setValueAt(index, inputDevice);
+            return inputDevice;
+        }
+    }
+
+    /**
+     * Gets the ids of all input devices in the system.
+     * @return The input device ids.
+     */
+    public int[] getInputDeviceIds() {
+        synchronized (mInputDevicesLock) {
+            populateInputDevicesLocked();
+
+            final int count = mInputDevices.size();
+            final int[] ids = new int[count];
+            for (int i = 0; i < count; i++) {
+                ids[i] = mInputDevices.keyAt(i);
+            }
+            return ids;
+        }
+    }
+
+    /**
+     * Registers an input device listener to receive notifications about when
+     * input devices are added, removed or changed.
+     *
+     * @param listener The listener to register.
+     * @param handler The handler on which the listener should be invoked, or null
+     * if the listener should be invoked on the calling thread's looper.
+     *
+     * @see #unregisterInputDeviceListener
+     */
+    public void registerInputDeviceListener(InputDeviceListener listener, Handler handler) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+
+        synchronized (mInputDevicesLock) {
+            int index = findInputDeviceListenerLocked(listener);
+            if (index < 0) {
+                mInputDeviceListeners.add(new InputDeviceListenerDelegate(listener, handler));
+            }
+        }
+    }
+
+    /**
+     * Unregisters an input device listener.
+     *
+     * @param listener The listener to unregister.
+     *
+     * @see #registerInputDeviceListener
+     */
+    public void unregisterInputDeviceListener(InputDeviceListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+
+        synchronized (mInputDevicesLock) {
+            int index = findInputDeviceListenerLocked(listener);
+            if (index >= 0) {
+                mInputDeviceListeners.remove(index);
+            }
+        }
+    }
+
+    private int findInputDeviceListenerLocked(InputDeviceListener listener) {
+        final int numListeners = mInputDeviceListeners.size();
+        for (int i = 0; i < numListeners; i++) {
+            if (mInputDeviceListeners.get(i).mListener == listener) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
      * Gets information about all supported keyboard layouts.
      * <p>
      * The input manager consults the built-in keyboard layouts as well
@@ -327,50 +440,6 @@
     }
 
     /**
-     * Gets information about the input device with the specified id.
-     * @param id The device id.
-     * @return The input device or null if not found.
-     *
-     * @hide
-     */
-    public InputDevice getInputDevice(int id) {
-        synchronized (mInputDevices) {
-            InputDevice inputDevice = mInputDevices.get(id);
-            if (inputDevice != null) {
-                return inputDevice;
-            }
-        }
-        final InputDevice newInputDevice;
-        try {
-            newInputDevice = mIm.getInputDevice(id);
-        } catch (RemoteException ex) {
-            throw new RuntimeException("Could not get input device information.", ex);
-        }
-        synchronized (mInputDevices) {
-            InputDevice inputDevice = mInputDevices.get(id);
-            if (inputDevice != null) {
-                return inputDevice;
-            }
-            mInputDevices.put(id, newInputDevice);
-            return newInputDevice;
-        }
-    }
-
-    /**
-     * Gets the ids of all input devices in the system.
-     * @return The input device ids.
-     *
-     * @hide
-     */
-    public int[] getInputDeviceIds() {
-        try {
-            return mIm.getInputDeviceIds();
-        } catch (RemoteException ex) {
-            throw new RuntimeException("Could not get input device ids.", ex);
-        }
-    }
-
-    /**
      * Queries the framework about whether any physical keys exist on the
      * any keyboard attached to the device that are capable of producing the given
      * array of key codes.
@@ -429,4 +498,151 @@
             return false;
         }
     }
+
+    private void populateInputDevicesLocked() {
+        if (mInputDevicesChangedListener == null) {
+            final InputDevicesChangedListener listener = new InputDevicesChangedListener();
+            try {
+                mIm.registerInputDevicesChangedListener(listener);
+            } catch (RemoteException ex) {
+                throw new RuntimeException(
+                        "Could not get register input device changed listener", ex);
+            }
+            mInputDevicesChangedListener = listener;
+        }
+
+        if (mInputDevices == null) {
+            final int[] ids;
+            try {
+                ids = mIm.getInputDeviceIds();
+            } catch (RemoteException ex) {
+                throw new RuntimeException("Could not get input device ids.", ex);
+            }
+
+            mInputDevices = new SparseArray<InputDevice>();
+            for (int i = 0; i < ids.length; i++) {
+                mInputDevices.put(ids[i], null);
+            }
+        }
+    }
+
+    private void onInputDevicesChanged(int[] deviceIdAndGeneration) {
+        if (DEBUG) {
+            Log.d(TAG, "Received input devices changed.");
+        }
+
+        synchronized (mInputDevicesLock) {
+            for (int i = mInputDevices.size(); --i > 0; ) {
+                final int deviceId = mInputDevices.keyAt(i);
+                if (!containsDeviceId(deviceIdAndGeneration, deviceId)) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Device removed: " + deviceId);
+                    }
+                    mInputDevices.removeAt(i);
+                    sendMessageToInputDeviceListenersLocked(MSG_DEVICE_REMOVED, deviceId);
+                }
+            }
+
+            for (int i = 0; i < deviceIdAndGeneration.length; i += 2) {
+                final int deviceId = deviceIdAndGeneration[i];
+                int index = mInputDevices.indexOfKey(deviceId);
+                if (index >= 0) {
+                    final InputDevice device = mInputDevices.valueAt(index);
+                    if (device != null) {
+                        final int generation = deviceIdAndGeneration[i + 1];
+                        if (device.getGeneration() != generation) {
+                            if (DEBUG) {
+                                Log.d(TAG, "Device changed: " + deviceId);
+                            }
+                            mInputDevices.setValueAt(index, null);
+                            sendMessageToInputDeviceListenersLocked(MSG_DEVICE_CHANGED, deviceId);
+                        }
+                    }
+                } else {
+                    if (DEBUG) {
+                        Log.d(TAG, "Device added: " + deviceId);
+                    }
+                    mInputDevices.put(deviceId, null);
+                    sendMessageToInputDeviceListenersLocked(MSG_DEVICE_ADDED, deviceId);
+                }
+            }
+        }
+    }
+
+    private void sendMessageToInputDeviceListenersLocked(int what, int deviceId) {
+        final int numListeners = mInputDeviceListeners.size();
+        for (int i = 0; i < numListeners; i++) {
+            InputDeviceListenerDelegate listener = mInputDeviceListeners.get(i);
+            listener.sendMessage(listener.obtainMessage(what, deviceId, 0));
+        }
+    }
+
+    private static boolean containsDeviceId(int[] deviceIdAndGeneration, int deviceId) {
+        for (int i = 0; i < deviceIdAndGeneration.length; i += 2) {
+            if (deviceIdAndGeneration[i] == deviceId) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Listens for changes in input devices.
+     */
+    public interface InputDeviceListener {
+        /**
+         * Called whenever an input device has been added to the system.
+         * Use {@link InputManager#getInputDevice} to get more information about the device.
+         *
+         * @param deviceId The id of the input device that was added.
+         */
+        void onInputDeviceAdded(int deviceId);
+
+        /**
+         * Called whenever an input device has been removed from the system.
+         *
+         * @param deviceId The id of the input device that was removed.
+         */
+        void onInputDeviceRemoved(int deviceId);
+
+        /**
+         * Called whenever the properties of an input device have changed since they
+         * were last queried.  Use {@link InputManager#getInputDevice} to get
+         * a fresh {@link InputDevice} object with the new properties.
+         *
+         * @param deviceId The id of the input device that changed.
+         */
+        void onInputDeviceChanged(int deviceId);
+    }
+
+    private final class InputDevicesChangedListener extends IInputDevicesChangedListener.Stub {
+        @Override
+        public void onInputDevicesChanged(int[] deviceIdAndGeneration) throws RemoteException {
+            InputManager.this.onInputDevicesChanged(deviceIdAndGeneration);
+        }
+    }
+
+    private static final class InputDeviceListenerDelegate extends Handler {
+        public final InputDeviceListener mListener;
+
+        public InputDeviceListenerDelegate(InputDeviceListener listener, Handler handler) {
+            super(handler != null ? handler.getLooper() : Looper.myLooper());
+            mListener = listener;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_DEVICE_ADDED:
+                    mListener.onInputDeviceAdded(msg.arg1);
+                    break;
+                case MSG_DEVICE_REMOVED:
+                    mListener.onInputDeviceRemoved(msg.arg1);
+                    break;
+                case MSG_DEVICE_CHANGED:
+                    mListener.onInputDeviceChanged(msg.arg1);
+                    break;
+            }
+        }
+    }
 }
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 75b2c746..4ebb679 100755
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -40,6 +40,7 @@
  */
 public final class InputDevice implements Parcelable {
     private final int mId;
+    private final int mGeneration;
     private final String mName;
     private final String mDescriptor;
     private final int mSources;
@@ -302,9 +303,10 @@
     };
 
     // Called by native code.
-    private InputDevice(int id, String name, String descriptor, int sources,
+    private InputDevice(int id, int generation, String name, String descriptor, int sources,
             int keyboardType, KeyCharacterMap keyCharacterMap) {
         mId = id;
+        mGeneration = generation;
         mName = name;
         mDescriptor = descriptor;
         mSources = sources;
@@ -314,6 +316,7 @@
 
     private InputDevice(Parcel in) {
         mId = in.readInt();
+        mGeneration = in.readInt();
         mName = in.readString();
         mDescriptor = in.readString();
         mSources = in.readInt();
@@ -364,6 +367,19 @@
     }
 
     /**
+     * Gets a generation number for this input device.
+     * The generation number is incremented whenever the device is reconfigured and its
+     * properties may have changed.
+     *
+     * @return The generation number.
+     *
+     * @hide
+     */
+    public int getGeneration() {
+        return mGeneration;
+    }
+
+    /**
      * Gets the input device descriptor, which is a stable identifier for an input device.
      * <p>
      * An input device descriptor uniquely identifies an input device.  Its value
@@ -595,6 +611,7 @@
     @Override
     public void writeToParcel(Parcel out, int flags) {
         out.writeInt(mId);
+        out.writeInt(mGeneration);
         out.writeString(mName);
         out.writeString(mDescriptor);
         out.writeInt(mSources);
@@ -624,6 +641,7 @@
         StringBuilder description = new StringBuilder();
         description.append("Input Device ").append(mId).append(": ").append(mName).append("\n");
         description.append("  Descriptor: ").append(mDescriptor).append("\n");
+        description.append("  Generation: ").append(mGeneration).append("\n");
 
         description.append("  Keyboard Type: ");
         switch (mKeyboardType) {
diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java
index 3d165ea..12d7b12 100644
--- a/core/java/android/view/KeyCharacterMap.java
+++ b/core/java/android/view/KeyCharacterMap.java
@@ -22,8 +22,6 @@
 import android.util.AndroidRuntimeException;
 import android.util.SparseIntArray;
 import android.hardware.input.InputManager;
-import android.util.SparseArray;
-import android.view.InputDevice.MotionRange;
 
 import java.lang.Character;
 
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index 0524c25..1d6af90 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -21,6 +21,8 @@
 import android.graphics.Paint;
 import android.graphics.RectF;
 import android.graphics.Paint.FontMetricsInt;
+import android.hardware.input.InputManager;
+import android.hardware.input.InputManager.InputDeviceListener;
 import android.util.Log;
 import android.view.InputDevice;
 import android.view.KeyEvent;
@@ -32,7 +34,7 @@
 
 import java.util.ArrayList;
 
-public class PointerLocationView extends View {
+public class PointerLocationView extends View implements InputDeviceListener {
     private static final String TAG = "Pointer";
     
     public static class PointerState {
@@ -82,6 +84,8 @@
     private final int ESTIMATE_FUTURE_POINTS = 2;
     private final float ESTIMATE_INTERVAL = 0.02f;
 
+    private final InputManager mIm;
+
     private final ViewConfiguration mVC;
     private final Paint mTextPaint;
     private final Paint mTextBackgroundPaint;
@@ -108,6 +112,8 @@
         super(c);
         setFocusableInTouchMode(true);
 
+        mIm = (InputManager)c.getSystemService(Context.INPUT_SERVICE);
+
         mVC = ViewConfiguration.get(c);
         mTextPaint = new Paint();
         mTextPaint.setAntiAlias(true);
@@ -139,18 +145,6 @@
         mActivePointerId = 0;
         
         mVelocity = VelocityTracker.obtain();
-        
-        logInputDeviceCapabilities();
-    }
-    
-    private void logInputDeviceCapabilities() {
-        int[] deviceIds = InputDevice.getDeviceIds();
-        for (int i = 0; i < deviceIds.length; i++) {
-            InputDevice device = InputDevice.getDevice(deviceIds[i]);
-            if (device != null) {
-                Log.i(TAG, device.toString());
-            }
-        }
     }
 
     public void setPrintCoords(boolean state) {
@@ -631,7 +625,53 @@
         logMotionEvent("Trackball", event);
         return true;
     }
-    
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        mIm.registerInputDeviceListener(this, getHandler());
+        logInputDevices();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        mIm.unregisterInputDeviceListener(this);
+    }
+
+    @Override
+    public void onInputDeviceAdded(int deviceId) {
+        logInputDeviceState(deviceId, "Device Added");
+    }
+
+    @Override
+    public void onInputDeviceChanged(int deviceId) {
+        logInputDeviceState(deviceId, "Device Changed");
+    }
+
+    @Override
+    public void onInputDeviceRemoved(int deviceId) {
+        logInputDeviceState(deviceId, "Device Removed");
+    }
+
+    private void logInputDevices() {
+        int[] deviceIds = InputDevice.getDeviceIds();
+        for (int i = 0; i < deviceIds.length; i++) {
+            logInputDeviceState(deviceIds[i], "Device Enumerated");
+        }
+    }
+
+    private void logInputDeviceState(int deviceId, String state) {
+        InputDevice device = mIm.getInputDevice(deviceId);
+        if (device != null) {
+            Log.i(TAG, state + ": " + device);
+        } else {
+            Log.i(TAG, state + ": " + deviceId);
+        }
+    }
+
     // HACK
     // A quick and dirty string builder implementation optimized for GC.
     // Using String.format causes the application grind to a halt when
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index d7d476a..e8a3a3b 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -54,8 +54,9 @@
     }
 
     ScopedLocalRef<jobject> inputDeviceObj(env, env->NewObject(gInputDeviceClassInfo.clazz,
-            gInputDeviceClassInfo.ctor, deviceInfo.getId(), nameObj.get(),
-            descriptorObj.get(), deviceInfo.getSources(), deviceInfo.getKeyboardType(),
+            gInputDeviceClassInfo.ctor, deviceInfo.getId(), deviceInfo.getGeneration(),
+            nameObj.get(), descriptorObj.get(),
+            deviceInfo.getSources(), deviceInfo.getKeyboardType(),
             kcmObj.get()));
 
     const Vector<InputDeviceInfo::MotionRange>& ranges = deviceInfo.getMotionRanges();
@@ -86,7 +87,7 @@
     gInputDeviceClassInfo.clazz = jclass(env->NewGlobalRef(gInputDeviceClassInfo.clazz));
 
     GET_METHOD_ID(gInputDeviceClassInfo.ctor, gInputDeviceClassInfo.clazz,
-            "<init>", "(ILjava/lang/String;Ljava/lang/String;IILandroid/view/KeyCharacterMap;)V");
+            "<init>", "(IILjava/lang/String;Ljava/lang/String;IILandroid/view/KeyCharacterMap;)V");
 
     GET_METHOD_ID(gInputDeviceClassInfo.addMotionRange, gInputDeviceClassInfo.clazz,
             "addMotionRange", "(IIFFFF)V");
diff --git a/include/androidfw/InputDevice.h b/include/androidfw/InputDevice.h
index c9554dc..2eac544 100644
--- a/include/androidfw/InputDevice.h
+++ b/include/androidfw/InputDevice.h
@@ -66,9 +66,11 @@
         float fuzz;
     };
 
-    void initialize(int32_t id, const String8& name, const String8& descriptor);
+    void initialize(int32_t id, int32_t generation,
+            const String8& name, const String8& descriptor);
 
     inline int32_t getId() const { return mId; }
+    inline int32_t getGeneration() const { return mGeneration; }
     inline const String8 getName() const { return mName; }
     inline const String8 getDescriptor() const { return mDescriptor; }
     inline uint32_t getSources() const { return mSources; }
@@ -97,6 +99,7 @@
 
 private:
     int32_t mId;
+    int32_t mGeneration;
     String8 mName;
     String8 mDescriptor;
     uint32_t mSources;
diff --git a/libs/androidfw/InputDevice.cpp b/libs/androidfw/InputDevice.cpp
index 698feb6..6bb06a9 100644
--- a/libs/androidfw/InputDevice.cpp
+++ b/libs/androidfw/InputDevice.cpp
@@ -127,11 +127,12 @@
 // --- InputDeviceInfo ---
 
 InputDeviceInfo::InputDeviceInfo() {
-    initialize(-1, String8("uninitialized device info"), String8("unknown"));
+    initialize(-1, -1, String8("uninitialized device info"), String8("unknown"));
 }
 
 InputDeviceInfo::InputDeviceInfo(const InputDeviceInfo& other) :
-        mId(other.mId), mName(other.mName), mDescriptor(other.mDescriptor),
+        mId(other.mId), mGeneration(other.mGeneration),
+        mName(other.mName), mDescriptor(other.mDescriptor),
         mSources(other.mSources),
         mKeyboardType(other.mKeyboardType),
         mKeyCharacterMap(other.mKeyCharacterMap),
@@ -141,8 +142,10 @@
 InputDeviceInfo::~InputDeviceInfo() {
 }
 
-void InputDeviceInfo::initialize(int32_t id, const String8& name, const String8& descriptor) {
+void InputDeviceInfo::initialize(int32_t id, int32_t generation,
+        const String8& name, const String8& descriptor) {
     mId = id;
+    mGeneration = generation;
     mName = name;
     mDescriptor = descriptor;
     mSources = 0;
diff --git a/services/input/EventHub.cpp b/services/input/EventHub.cpp
index 2ba821e..fbffc94 100644
--- a/services/input/EventHub.cpp
+++ b/services/input/EventHub.cpp
@@ -720,6 +720,11 @@
                         break;
                     }
                 }
+            } else if (eventItem.events & EPOLLHUP) {
+                ALOGI("Removing device %s due to epoll hang-up event.",
+                        device->identifier.name.string());
+                deviceChanged = true;
+                closeDeviceLocked(device);
             } else {
                 ALOGW("Received unexpected epoll event 0x%08x for device %s.",
                         eventItem.events, device->identifier.name.string());
diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp
index ea614ad..71eba52 100644
--- a/services/input/InputReader.cpp
+++ b/services/input/InputReader.cpp
@@ -237,7 +237,8 @@
         const sp<InputReaderPolicyInterface>& policy,
         const sp<InputListenerInterface>& listener) :
         mContext(this), mEventHub(eventHub), mPolicy(policy),
-        mGlobalMetaState(0), mDisableVirtualKeysTimeout(LLONG_MIN), mNextTimeout(LLONG_MAX),
+        mGlobalMetaState(0), mGeneration(1),
+        mDisableVirtualKeysTimeout(LLONG_MIN), mNextTimeout(LLONG_MAX),
         mConfigurationChangesToRefresh(0) {
     mQueuedListener = new QueuedInputListener(listener);
 
@@ -257,18 +258,24 @@
 }
 
 void InputReader::loopOnce() {
+    int32_t oldGeneration;
     int32_t timeoutMillis;
+    bool inputDevicesChanged = false;
+    Vector<InputDeviceInfo> inputDevices;
     { // acquire lock
         AutoMutex _l(mLock);
 
+        oldGeneration = mGeneration;
+        timeoutMillis = -1;
+
         uint32_t changes = mConfigurationChangesToRefresh;
         if (changes) {
             mConfigurationChangesToRefresh = 0;
+            timeoutMillis = 0;
             refreshConfigurationLocked(changes);
         }
 
-        timeoutMillis = -1;
-        if (mNextTimeout != LLONG_MAX) {
+        if (timeoutMillis < 0 && mNextTimeout != LLONG_MAX) {
             nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
             timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout);
         }
@@ -283,7 +290,8 @@
         if (count) {
             processEventsLocked(mEventBuffer, count);
         }
-        if (!count || timeoutMillis == 0) {
+
+        if (mNextTimeout != LLONG_MAX) {
             nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
             if (now >= mNextTimeout) {
 #if DEBUG_RAW_EVENTS
@@ -293,8 +301,18 @@
                 timeoutExpiredLocked(now);
             }
         }
+
+        if (oldGeneration != mGeneration) {
+            inputDevicesChanged = true;
+            getInputDevicesLocked(inputDevices);
+        }
     } // release lock
 
+    // Send out a message that the describes the changed input devices.
+    if (inputDevicesChanged) {
+        mPolicy->notifyInputDevicesChanged(inputDevices);
+    }
+
     // Flush queued events out to the listener.
     // This must happen outside of the lock because the listener could potentially call
     // back into the InputReader's methods, such as getScanCodeState, or become blocked
@@ -344,6 +362,12 @@
 }
 
 void InputReader::addDeviceLocked(nsecs_t when, int32_t deviceId) {
+    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+    if (deviceIndex >= 0) {
+        ALOGW("Ignoring spurious device added event for deviceId %d.", deviceId);
+        return;
+    }
+
     InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(deviceId);
     uint32_t classes = mEventHub->getDeviceClasses(deviceId);
 
@@ -359,27 +383,22 @@
                 identifier.name.string(), device->getSources());
     }
 
-    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
-    if (deviceIndex < 0) {
-        mDevices.add(deviceId, device);
-    } else {
-        ALOGW("Ignoring spurious device added event for deviceId %d.", deviceId);
-        delete device;
-        return;
-    }
+    mDevices.add(deviceId, device);
+    bumpGenerationLocked();
 }
 
 void InputReader::removeDeviceLocked(nsecs_t when, int32_t deviceId) {
     InputDevice* device = NULL;
     ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
-    if (deviceIndex >= 0) {
-        device = mDevices.valueAt(deviceIndex);
-        mDevices.removeItemsAt(deviceIndex, 1);
-    } else {
+    if (deviceIndex < 0) {
         ALOGW("Ignoring spurious device removed event for deviceId %d.", deviceId);
         return;
     }
 
+    device = mDevices.valueAt(deviceIndex);
+    mDevices.removeItemsAt(deviceIndex, 1);
+    bumpGenerationLocked();
+
     if (device->isIgnored()) {
         ALOGI("Device removed: id=%d, name='%s' (ignored non-input device)",
                 device->getId(), device->getName().string());
@@ -394,7 +413,8 @@
 
 InputDevice* InputReader::createDeviceLocked(int32_t deviceId,
         const InputDeviceIdentifier& identifier, uint32_t classes) {
-    InputDevice* device = new InputDevice(&mContext, deviceId, identifier, classes);
+    InputDevice* device = new InputDevice(&mContext, deviceId, bumpGenerationLocked(),
+            identifier, classes);
 
     // External devices.
     if (classes & INPUT_DEVICE_CLASS_EXTERNAL) {
@@ -577,39 +597,30 @@
     }
 }
 
+int32_t InputReader::bumpGenerationLocked() {
+    return ++mGeneration;
+}
+
 void InputReader::getInputConfiguration(InputConfiguration* outConfiguration) {
     AutoMutex _l(mLock);
 
     *outConfiguration = mInputConfiguration;
 }
 
-status_t InputReader::getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo) {
+void InputReader::getInputDevices(Vector<InputDeviceInfo>& outInputDevices) {
     AutoMutex _l(mLock);
-
-    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
-    if (deviceIndex < 0) {
-        return NAME_NOT_FOUND;
-    }
-
-    InputDevice* device = mDevices.valueAt(deviceIndex);
-    if (device->isIgnored()) {
-        return NAME_NOT_FOUND;
-    }
-
-    device->getDeviceInfo(outDeviceInfo);
-    return OK;
+    getInputDevicesLocked(outInputDevices);
 }
 
-void InputReader::getInputDeviceIds(Vector<int32_t>& outDeviceIds) {
-    AutoMutex _l(mLock);
-
-    outDeviceIds.clear();
+void InputReader::getInputDevicesLocked(Vector<InputDeviceInfo>& outInputDevices) {
+    outInputDevices.clear();
 
     size_t numDevices = mDevices.size();
     for (size_t i = 0; i < numDevices; i++) {
         InputDevice* device = mDevices.valueAt(i);
         if (!device->isIgnored()) {
-            outDeviceIds.add(device->getId());
+            outInputDevices.push();
+            device->getDeviceInfo(&outInputDevices.editTop());
         }
     }
 }
@@ -824,6 +835,11 @@
     mReader->requestTimeoutAtTimeLocked(when);
 }
 
+int32_t InputReader::ContextImpl::bumpGeneration() {
+    // lock is already held by the input loop
+    return mReader->bumpGenerationLocked();
+}
+
 InputReaderPolicyInterface* InputReader::ContextImpl::getPolicy() {
     return mReader->mPolicy.get();
 }
@@ -854,9 +870,10 @@
 
 // --- InputDevice ---
 
-InputDevice::InputDevice(InputReaderContext* context, int32_t id,
+InputDevice::InputDevice(InputReaderContext* context, int32_t id, int32_t generation,
         const InputDeviceIdentifier& identifier, uint32_t classes) :
-        mContext(context), mId(id), mIdentifier(identifier), mClasses(classes),
+        mContext(context), mId(id), mGeneration(generation),
+        mIdentifier(identifier), mClasses(classes),
         mSources(0), mIsExternal(false), mDropUntilNextSync(false) {
 }
 
@@ -874,6 +891,7 @@
 
     dump.appendFormat(INDENT "Device %d: %s\n", deviceInfo.getId(),
             deviceInfo.getName().string());
+    dump.appendFormat(INDENT2 "Generation: %d\n", mGeneration);
     dump.appendFormat(INDENT2 "IsExternal: %s\n", toString(mIsExternal));
     dump.appendFormat(INDENT2 "Sources: 0x%08x\n", deviceInfo.getSources());
     dump.appendFormat(INDENT2 "KeyboardType: %d\n", deviceInfo.getKeyboardType());
@@ -983,7 +1001,7 @@
 }
 
 void InputDevice::getDeviceInfo(InputDeviceInfo* outDeviceInfo) {
-    outDeviceInfo->initialize(mId, mIdentifier.name, mIdentifier.descriptor);
+    outDeviceInfo->initialize(mId, mGeneration, mIdentifier.name, mIdentifier.descriptor);
 
     size_t numMappers = mMappers.size();
     for (size_t i = 0; i < numMappers; i++) {
@@ -1054,6 +1072,10 @@
     }
 }
 
+void InputDevice::bumpGeneration() {
+    mGeneration = mContext->bumpGeneration();
+}
+
 void InputDevice::notifyReset(nsecs_t when) {
     NotifyDeviceResetArgs args(when, mId);
     mContext->getListener()->notifyDeviceReset(&args);
@@ -1728,6 +1750,10 @@
     return getEventHub()->getAbsoluteAxisInfo(getDeviceId(), axis, axisInfo);
 }
 
+void InputMapper::bumpGeneration() {
+    mDevice->bumpGeneration();
+}
+
 void InputMapper::dumpRawAbsoluteAxisInfo(String8& dump,
         const RawAbsoluteAxisInfo& axis, const char* name) {
     if (axis.valid) {
@@ -2137,6 +2163,7 @@
         } else {
             mOrientation = DISPLAY_ORIENTATION_0;
         }
+        bumpGeneration();
     }
 }
 
@@ -2998,6 +3025,7 @@
 
         // Inform the dispatcher about the changes.
         *outResetNeeded = true;
+        bumpGeneration();
     }
 }
 
diff --git a/services/input/InputReader.h b/services/input/InputReader.h
index 8ab5905..d29776d8 100644
--- a/services/input/InputReader.h
+++ b/services/input/InputReader.h
@@ -209,6 +209,11 @@
 
     /* Gets a pointer controller associated with the specified cursor device (ie. a mouse). */
     virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId) = 0;
+
+    /* Notifies the input reader policy that some input devices have changed
+     * and provides information about all current input devices.
+     */
+    virtual void notifyInputDevicesChanged(const Vector<InputDeviceInfo>& inputDevices) = 0;
 };
 
 
@@ -240,16 +245,11 @@
      */
     virtual void getInputConfiguration(InputConfiguration* outConfiguration) = 0;
 
-    /* Gets information about the specified input device.
-     * Returns OK if the device information was obtained or NAME_NOT_FOUND if there
-     * was no such device.
+    /* Gets information about all input devices.
      *
      * This method may be called on any thread (usually by the input manager).
      */
-    virtual status_t getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo) = 0;
-
-    /* Gets the list of all registered device ids. */
-    virtual void getInputDeviceIds(Vector<int32_t>& outDeviceIds) = 0;
+    virtual void getInputDevices(Vector<InputDeviceInfo>& outInputDevices) = 0;
 
     /* Query current input state. */
     virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask,
@@ -288,6 +288,7 @@
     virtual void fadePointer() = 0;
 
     virtual void requestTimeoutAtTime(nsecs_t when) = 0;
+    virtual int32_t bumpGeneration() = 0;
 
     virtual InputReaderPolicyInterface* getPolicy() = 0;
     virtual InputListenerInterface* getListener() = 0;
@@ -319,9 +320,7 @@
     virtual void loopOnce();
 
     virtual void getInputConfiguration(InputConfiguration* outConfiguration);
-
-    virtual status_t getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo);
-    virtual void getInputDeviceIds(Vector<int32_t>& outDeviceIds);
+    virtual void getInputDevices(Vector<InputDeviceInfo>& outInputDevices);
 
     virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask,
             int32_t scanCode);
@@ -353,6 +352,7 @@
                 InputDevice* device, int32_t keyCode, int32_t scanCode);
         virtual void fadePointer();
         virtual void requestTimeoutAtTime(nsecs_t when);
+        virtual int32_t bumpGeneration();
         virtual InputReaderPolicyInterface* getPolicy();
         virtual InputListenerInterface* getListener();
         virtual EventHubInterface* getEventHub();
@@ -393,9 +393,14 @@
 
     void fadePointerLocked();
 
+    int32_t mGeneration;
+    int32_t bumpGenerationLocked();
+
     InputConfiguration mInputConfiguration;
     void updateInputConfigurationLocked();
 
+    void getInputDevicesLocked(Vector<InputDeviceInfo>& outInputDevices);
+
     nsecs_t mDisableVirtualKeysTimeout;
     void disableVirtualKeysUntilLocked(nsecs_t time);
     bool shouldDropVirtualKeyLocked(nsecs_t now,
@@ -432,12 +437,13 @@
 /* Represents the state of a single input device. */
 class InputDevice {
 public:
-    InputDevice(InputReaderContext* context, int32_t id,
+    InputDevice(InputReaderContext* context, int32_t id, int32_t generation,
             const InputDeviceIdentifier& identifier, uint32_t classes);
     ~InputDevice();
 
     inline InputReaderContext* getContext() { return mContext; }
     inline int32_t getId() { return mId; }
+    inline int32_t getGeneration() { return mGeneration; }
     inline const String8& getName() { return mIdentifier.name; }
     inline uint32_t getClasses() { return mClasses; }
     inline uint32_t getSources() { return mSources; }
@@ -465,6 +471,8 @@
 
     void fadePointer();
 
+    void bumpGeneration();
+
     void notifyReset(nsecs_t when);
 
     inline const PropertyMap& getConfiguration() { return mConfiguration; }
@@ -487,6 +495,7 @@
 private:
     InputReaderContext* mContext;
     int32_t mId;
+    int32_t mGeneration;
     InputDeviceIdentifier mIdentifier;
     uint32_t mClasses;
 
@@ -849,6 +858,7 @@
     InputReaderContext* mContext;
 
     status_t getAbsoluteAxisInfo(int32_t axis, RawAbsoluteAxisInfo* axisInfo);
+    void bumpGeneration();
 
     static void dumpRawAbsoluteAxisInfo(String8& dump,
             const RawAbsoluteAxisInfo& axis, const char* name);
diff --git a/services/input/tests/InputReader_test.cpp b/services/input/tests/InputReader_test.cpp
index eac9a1c..e59af4e 100644
--- a/services/input/tests/InputReader_test.cpp
+++ b/services/input/tests/InputReader_test.cpp
@@ -127,6 +127,7 @@
 class FakeInputReaderPolicy : public InputReaderPolicyInterface {
     InputReaderConfiguration mConfig;
     KeyedVector<int32_t, sp<FakePointerController> > mPointerControllers;
+    Vector<InputDeviceInfo> mInputDevices;
 
 protected:
     virtual ~FakeInputReaderPolicy() { }
@@ -141,10 +142,6 @@
         mConfig.setDisplayInfo(displayId, true /*external*/, width, height, orientation);
     }
 
-    virtual nsecs_t getVirtualKeyQuietTime() {
-        return 0;
-    }
-
     void addExcludedDeviceName(const String8& deviceName) {
         mConfig.excludedDeviceNames.push(deviceName);
     }
@@ -157,6 +154,10 @@
         return &mConfig;
     }
 
+    const Vector<InputDeviceInfo>& getInputDevices() const {
+        return mInputDevices;
+    }
+
 private:
     virtual void getReaderConfiguration(InputReaderConfiguration* outConfig) {
         *outConfig = mConfig;
@@ -165,6 +166,10 @@
     virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId) {
         return mPointerControllers.valueFor(deviceId);
     }
+
+    virtual void notifyInputDevicesChanged(const Vector<InputDeviceInfo>& inputDevices) {
+        mInputDevices = inputDevices;
+    }
 };
 
 
@@ -667,6 +672,7 @@
     sp<InputListenerInterface> mListener;
     int32_t mGlobalMetaState;
     bool mUpdateGlobalMetaStateWasCalled;
+    int32_t mGeneration;
 
 public:
     FakeInputReaderContext(const sp<EventHubInterface>& eventHub,
@@ -722,6 +728,10 @@
 
     virtual void requestTimeoutAtTime(nsecs_t when) {
     }
+
+    virtual int32_t bumpGeneration() {
+        return ++mGeneration;
+    }
 };
 
 
@@ -887,7 +897,8 @@
     InputDevice* newDevice(int32_t deviceId, const String8& name, uint32_t classes) {
         InputDeviceIdentifier identifier;
         identifier.name = name;
-        return new InputDevice(&mContext, deviceId, identifier, classes);
+        int32_t generation = deviceId + 1;
+        return new InputDevice(&mContext, deviceId, generation, identifier, classes);
     }
 
 protected:
@@ -1045,52 +1056,30 @@
     ASSERT_EQ(InputConfiguration::TOUCHSCREEN_NOTOUCH, config.touchScreen);
 }
 
-TEST_F(InputReaderTest, GetInputDeviceInfo_WhenDeviceIdIsValid) {
+TEST_F(InputReaderTest, GetInputDevices) {
     ASSERT_NO_FATAL_FAILURE(addDevice(1, String8("keyboard"),
             INPUT_DEVICE_CLASS_KEYBOARD, NULL));
+    ASSERT_NO_FATAL_FAILURE(addDevice(2, String8("ignored"),
+            0, NULL)); // no classes so device will be ignored
 
-    InputDeviceInfo info;
-    status_t result = mReader->getInputDeviceInfo(1, &info);
+    Vector<InputDeviceInfo> inputDevices;
+    mReader->getInputDevices(inputDevices);
 
-    ASSERT_EQ(OK, result);
-    ASSERT_EQ(1, info.getId());
-    ASSERT_STREQ("keyboard", info.getName().string());
-    ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, info.getKeyboardType());
-    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, info.getSources());
-    ASSERT_EQ(size_t(0), info.getMotionRanges().size());
-}
+    ASSERT_EQ(1U, inputDevices.size());
+    ASSERT_EQ(1, inputDevices[0].getId());
+    ASSERT_STREQ("keyboard", inputDevices[0].getName().string());
+    ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, inputDevices[0].getKeyboardType());
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, inputDevices[0].getSources());
+    ASSERT_EQ(size_t(0), inputDevices[0].getMotionRanges().size());
 
-TEST_F(InputReaderTest, GetInputDeviceInfo_WhenDeviceIdIsInvalid) {
-    InputDeviceInfo info;
-    status_t result = mReader->getInputDeviceInfo(-1, &info);
-
-    ASSERT_EQ(NAME_NOT_FOUND, result);
-}
-
-TEST_F(InputReaderTest, GetInputDeviceInfo_WhenDeviceIdIsIgnored) {
-    addDevice(1, String8("ignored"), 0, NULL); // no classes so device will be ignored
-
-    InputDeviceInfo info;
-    status_t result = mReader->getInputDeviceInfo(1, &info);
-
-    ASSERT_EQ(NAME_NOT_FOUND, result);
-}
-
-TEST_F(InputReaderTest, GetInputDeviceIds) {
-    sp<FakePointerController> controller = new FakePointerController();
-    mFakePolicy->setPointerController(2, controller);
-
-    ASSERT_NO_FATAL_FAILURE(addDevice(1, String8("keyboard"),
-            INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_ALPHAKEY, NULL));
-    ASSERT_NO_FATAL_FAILURE(addDevice(2, String8("mouse"),
-            INPUT_DEVICE_CLASS_CURSOR, NULL));
-
-    Vector<int32_t> ids;
-    mReader->getInputDeviceIds(ids);
-
-    ASSERT_EQ(size_t(2), ids.size());
-    ASSERT_EQ(1, ids[0]);
-    ASSERT_EQ(2, ids[1]);
+    // Should also have received a notification describing the new input devices.
+    inputDevices = mFakePolicy->getInputDevices();
+    ASSERT_EQ(1U, inputDevices.size());
+    ASSERT_EQ(1, inputDevices[0].getId());
+    ASSERT_STREQ("keyboard", inputDevices[0].getName().string());
+    ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, inputDevices[0].getKeyboardType());
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, inputDevices[0].getSources());
+    ASSERT_EQ(size_t(0), inputDevices[0].getMotionRanges().size());
 }
 
 TEST_F(InputReaderTest, GetKeyCodeState_ForwardsRequestsToMappers) {
@@ -1243,6 +1232,7 @@
 protected:
     static const char* DEVICE_NAME;
     static const int32_t DEVICE_ID;
+    static const int32_t DEVICE_GENERATION;
     static const uint32_t DEVICE_CLASSES;
 
     sp<FakeEventHub> mFakeEventHub;
@@ -1261,7 +1251,8 @@
         mFakeEventHub->addDevice(DEVICE_ID, String8(DEVICE_NAME), 0);
         InputDeviceIdentifier identifier;
         identifier.name = DEVICE_NAME;
-        mDevice = new InputDevice(mFakeContext, DEVICE_ID, identifier, DEVICE_CLASSES);
+        mDevice = new InputDevice(mFakeContext, DEVICE_ID, DEVICE_GENERATION,
+                identifier, DEVICE_CLASSES);
     }
 
     virtual void TearDown() {
@@ -1276,6 +1267,7 @@
 
 const char* InputDeviceTest::DEVICE_NAME = "device";
 const int32_t InputDeviceTest::DEVICE_ID = 1;
+const int32_t InputDeviceTest::DEVICE_GENERATION = 2;
 const uint32_t InputDeviceTest::DEVICE_CLASSES = INPUT_DEVICE_CLASS_KEYBOARD
         | INPUT_DEVICE_CLASS_TOUCH | INPUT_DEVICE_CLASS_JOYSTICK;
 
@@ -1428,6 +1420,7 @@
 protected:
     static const char* DEVICE_NAME;
     static const int32_t DEVICE_ID;
+    static const int32_t DEVICE_GENERATION;
     static const uint32_t DEVICE_CLASSES;
 
     sp<FakeEventHub> mFakeEventHub;
@@ -1443,7 +1436,8 @@
         mFakeContext = new FakeInputReaderContext(mFakeEventHub, mFakePolicy, mFakeListener);
         InputDeviceIdentifier identifier;
         identifier.name = DEVICE_NAME;
-        mDevice = new InputDevice(mFakeContext, DEVICE_ID, identifier, DEVICE_CLASSES);
+        mDevice = new InputDevice(mFakeContext, DEVICE_ID, DEVICE_GENERATION,
+                identifier, DEVICE_CLASSES);
 
         mFakeEventHub->addDevice(DEVICE_ID, String8(DEVICE_NAME), 0);
     }
@@ -1522,6 +1516,7 @@
 
 const char* InputMapperTest::DEVICE_NAME = "device";
 const int32_t InputMapperTest::DEVICE_ID = 1;
+const int32_t InputMapperTest::DEVICE_GENERATION = 2;
 const uint32_t InputMapperTest::DEVICE_CLASSES = 0; // not needed for current tests
 
 
diff --git a/services/java/com/android/server/input/InputManagerService.java b/services/java/com/android/server/input/InputManagerService.java
index 2f25df1..ce7671f 100644
--- a/services/java/com/android/server/input/InputManagerService.java
+++ b/services/java/com/android/server/input/InputManagerService.java
@@ -34,18 +34,23 @@
 import android.content.res.XmlResourceParser;
 import android.database.ContentObserver;
 import android.hardware.input.IInputManager;
+import android.hardware.input.IInputDevicesChangedListener;
 import android.hardware.input.InputManager;
 import android.hardware.input.KeyboardLayout;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
 import android.os.MessageQueue;
 import android.os.Process;
+import android.os.RemoteException;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.util.Log;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.Xml;
 import android.view.InputChannel;
 import android.view.InputDevice;
@@ -77,12 +82,33 @@
 
     private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml";
 
+    private static final int MSG_DELIVER_INPUT_DEVICES_CHANGED = 1;
+
     // Pointer to native input manager service object.
     private final int mPtr;
 
     private final Context mContext;
     private final Callbacks mCallbacks;
-    private final Handler mHandler;
+    private final InputManagerHandler mHandler;
+
+    // Used to simulate a persistent data store for keyboard layouts.
+    // TODO: Replace with the real thing.
+    private final HashMap<String, String> mFakeRegistry = new HashMap<String, String>();
+
+    // List of currently registered input devices changed listeners by process id.
+    private Object mInputDevicesLock = new Object();
+    private boolean mInputDevicesChangedPending; // guarded by mInputDevicesLock
+    private InputDevice[] mInputDevices = new InputDevice[0];
+    private final SparseArray<InputDevicesChangedListenerRecord> mInputDevicesChangedListeners =
+            new SparseArray<InputDevicesChangedListenerRecord>(); // guarded by mInputDevicesLock
+    private final ArrayList<InputDevicesChangedListenerRecord>
+            mTempInputDevicesChangedListenersToNotify =
+                    new ArrayList<InputDevicesChangedListenerRecord>(); // handler thread only
+
+    // State for the currently installed input filter.
+    final Object mInputFilterLock = new Object();
+    InputFilter mInputFilter; // guarded by mInputFilterLock
+    InputFilterHost mInputFilterHost; // guarded by mInputFilterLock
 
     private static native int nativeInit(InputManagerService service,
             Context context, MessageQueue messageQueue);
@@ -111,9 +137,7 @@
     private static native void nativeSetSystemUiVisibility(int ptr, int visibility);
     private static native void nativeSetFocusedApplication(int ptr,
             InputApplicationHandle application);
-    private static native InputDevice nativeGetInputDevice(int ptr, int deviceId);
     private static native void nativeGetInputConfiguration(int ptr, Configuration configuration);
-    private static native int[] nativeGetInputDeviceIds(int ptr);
     private static native boolean nativeTransferTouchFocus(int ptr,
             InputChannel fromChannel, InputChannel toChannel);
     private static native void nativeSetPointerSpeed(int ptr, int speed);
@@ -145,19 +169,10 @@
     /** The key is down but is a virtual key press that is being emulated by the system. */
     public static final int KEY_STATE_VIRTUAL = 2;
 
-    // Used to simulate a persistent data store for keyboard layouts.
-    // TODO: Replace with the real thing.
-    private final HashMap<String, String> mFakeRegistry = new HashMap<String, String>();
-
-    // State for the currently installed input filter.
-    final Object mInputFilterLock = new Object();
-    InputFilter mInputFilter;
-    InputFilterHost mInputFilterHost;
-
     public InputManagerService(Context context, Callbacks callbacks) {
         this.mContext = context;
         this.mCallbacks = callbacks;
-        this.mHandler = new Handler();
+        this.mHandler = new InputManagerHandler();
 
         Slog.i(TAG, "Initializing input manager");
         mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
@@ -396,16 +411,98 @@
      */
     @Override // Binder call
     public InputDevice getInputDevice(int deviceId) {
-        return nativeGetInputDevice(mPtr, deviceId);
+        synchronized (mInputDevicesLock) {
+            final int count = mInputDevices.length;
+            for (int i = 0; i < count; i++) {
+                final InputDevice inputDevice = mInputDevices[i];
+                if (inputDevice.getId() == deviceId) {
+                    return inputDevice;
+                }
+            }
+        }
+        return null;
     }
-    
+
     /**
      * Gets the ids of all input devices in the system.
      * @return The input device ids.
      */
     @Override // Binder call
     public int[] getInputDeviceIds() {
-        return nativeGetInputDeviceIds(mPtr);
+        synchronized (mInputDevicesLock) {
+            final int count = mInputDevices.length;
+            int[] ids = new int[count];
+            for (int i = 0; i < count; i++) {
+                ids[i] = mInputDevices[i].getId();
+            }
+            return ids;
+        }
+    }
+
+    @Override // Binder call
+    public void registerInputDevicesChangedListener(IInputDevicesChangedListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+
+        synchronized (mInputDevicesLock) {
+            int callingPid = Binder.getCallingPid();
+            if (mInputDevicesChangedListeners.get(callingPid) != null) {
+                throw new SecurityException("The calling process has already "
+                        + "registered an InputDevicesChangedListener.");
+            }
+
+            InputDevicesChangedListenerRecord record =
+                    new InputDevicesChangedListenerRecord(callingPid, listener);
+            try {
+                IBinder binder = listener.asBinder();
+                binder.linkToDeath(record, 0);
+            } catch (RemoteException ex) {
+                // give up
+                throw new RuntimeException(ex);
+            }
+
+            mInputDevicesChangedListeners.put(callingPid, record);
+        }
+    }
+
+    private void onInputDevicesChangedListenerDied(int pid) {
+        synchronized (mInputDevicesLock) {
+            mInputDevicesChangedListeners.remove(pid);
+        }
+    }
+
+    // Must be called on handler.
+    private void deliverInputDevicesChanged() {
+        mTempInputDevicesChangedListenersToNotify.clear();
+
+        final int numListeners;
+        final int[] deviceIdAndGeneration;
+        synchronized (mInputDevicesLock) {
+            if (!mInputDevicesChangedPending) {
+                return;
+            }
+            mInputDevicesChangedPending = false;
+
+            numListeners = mInputDevicesChangedListeners.size();
+            for (int i = 0; i < numListeners; i++) {
+                mTempInputDevicesChangedListenersToNotify.add(
+                        mInputDevicesChangedListeners.valueAt(i));
+            }
+
+            final int numDevices = mInputDevices.length;
+            deviceIdAndGeneration = new int[numDevices * 2];
+            for (int i = 0; i < numDevices; i++) {
+                final InputDevice inputDevice = mInputDevices[i];
+                deviceIdAndGeneration[i * 2] = inputDevice.getId();
+                deviceIdAndGeneration[i * 2 + 1] = inputDevice.getGeneration();
+            }
+        }
+
+        for (int i = 0; i < numListeners; i++) {
+            mTempInputDevicesChangedListenersToNotify.get(i).notifyInputDevicesChanged(
+                    deviceIdAndGeneration);
+        }
     }
 
     @Override // Binder call
@@ -741,6 +838,18 @@
     }
 
     // Native callback.
+    private void notifyInputDevicesChanged(InputDevice[] inputDevices) {
+        synchronized (mInputDevicesLock) {
+            mInputDevices = inputDevices;
+
+            if (!mInputDevicesChangedPending) {
+                mInputDevicesChangedPending = true;
+                mHandler.sendEmptyMessage(MSG_DELIVER_INPUT_DEVICES_CHANGED);
+            }
+        }
+    }
+
+    // Native callback.
     private void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) {
         mCallbacks.notifyLidSwitchChanged(whenNanos, lidOpen);
     }
@@ -906,6 +1015,20 @@
     }
 
     /**
+     * Private handler for the input manager.
+     */
+    private final class InputManagerHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_DELIVER_INPUT_DEVICES_CHANGED:
+                    deliverInputDevicesChanged();
+                    break;
+            }
+        }
+    }
+
+    /**
      * Hosting interface for input filters to call back into the input manager.
      */
     private final class InputFilterHost implements InputFilter.Host {
@@ -957,4 +1080,32 @@
             return result;
         }
     }
+
+    private final class InputDevicesChangedListenerRecord implements DeathRecipient {
+        private final int mPid;
+        private final IInputDevicesChangedListener mListener;
+
+        public InputDevicesChangedListenerRecord(int pid, IInputDevicesChangedListener listener) {
+            mPid = pid;
+            mListener = listener;
+        }
+
+        @Override
+        public void binderDied() {
+            if (DEBUG) {
+                Slog.d(TAG, "Input devices changed listener for pid " + mPid + " died.");
+            }
+            onInputDevicesChangedListenerDied(mPid);
+        }
+
+        public void notifyInputDevicesChanged(int[] info) {
+            try {
+                mListener.onInputDevicesChanged(info);
+            } catch (RemoteException ex) {
+                Slog.w(TAG, "Failed to notify process "
+                        + mPid + " that input devices changed, assuming it died.", ex);
+                binderDied();
+            }
+        }
+    }
 }
diff --git a/services/jni/com_android_server_input_InputManagerService.cpp b/services/jni/com_android_server_input_InputManagerService.cpp
index 85d6e11..f1536fd 100644
--- a/services/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/jni/com_android_server_input_InputManagerService.cpp
@@ -59,6 +59,7 @@
 
 static struct {
     jmethodID notifyConfigurationChanged;
+    jmethodID notifyInputDevicesChanged;
     jmethodID notifyLidSwitchChanged;
     jmethodID notifyInputChannelBroken;
     jmethodID notifyANR;
@@ -82,6 +83,10 @@
 
 static struct {
     jclass clazz;
+} gInputDeviceClassInfo;
+
+static struct {
+    jclass clazz;
 } gKeyEventClassInfo;
 
 static struct {
@@ -179,6 +184,7 @@
 
     virtual void getReaderConfiguration(InputReaderConfiguration* outConfig);
     virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId);
+    virtual void notifyInputDevicesChanged(const Vector<InputDeviceInfo>& inputDevices);
 
     /* --- InputDispatcherPolicyInterface implementation --- */
 
@@ -486,6 +492,36 @@
     }
 }
 
+void NativeInputManager::notifyInputDevicesChanged(const Vector<InputDeviceInfo>& inputDevices) {
+    JNIEnv* env = jniEnv();
+
+    size_t count = inputDevices.size();
+    jobjectArray inputDevicesObjArray = env->NewObjectArray(
+            count, gInputDeviceClassInfo.clazz, NULL);
+    if (inputDevicesObjArray) {
+        bool error = false;
+        for (size_t i = 0; i < count; i++) {
+            jobject inputDeviceObj = android_view_InputDevice_create(env, inputDevices.itemAt(i));
+            if (!inputDeviceObj) {
+                error = true;
+                break;
+            }
+
+            env->SetObjectArrayElement(inputDevicesObjArray, i, inputDeviceObj);
+            env->DeleteLocalRef(inputDeviceObj);
+        }
+
+        if (!error) {
+            env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyInputDevicesChanged,
+                    inputDevicesObjArray);
+        }
+
+        env->DeleteLocalRef(inputDevicesObjArray);
+    }
+
+    checkAndClearExceptionFromCallback(env, "notifyInputDevicesChanged");
+}
+
 void NativeInputManager::notifySwitch(nsecs_t when, int32_t switchCode,
         int32_t switchValue, uint32_t policyFlags) {
 #if DEBUG_INPUT_DISPATCHER_POLICY
@@ -1147,36 +1183,6 @@
     im->setSystemUiVisibility(visibility);
 }
 
-static jobject nativeGetInputDevice(JNIEnv* env,
-        jclass clazz, jint ptr, jint deviceId) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
-
-    InputDeviceInfo deviceInfo;
-    status_t status = im->getInputManager()->getReader()->getInputDeviceInfo(
-            deviceId, & deviceInfo);
-    if (status) {
-        return NULL;
-    }
-
-    return android_view_InputDevice_create(env, deviceInfo);
-}
-
-static jintArray nativeGetInputDeviceIds(JNIEnv* env,
-        jclass clazz, jint ptr) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
-
-    Vector<int> deviceIds;
-    im->getInputManager()->getReader()->getInputDeviceIds(deviceIds);
-
-    jintArray deviceIdsObj = env->NewIntArray(deviceIds.size());
-    if (! deviceIdsObj) {
-        return NULL;
-    }
-
-    env->SetIntArrayRegion(deviceIdsObj, 0, deviceIds.size(), deviceIds.array());
-    return deviceIdsObj;
-}
-
 static void nativeGetInputConfiguration(JNIEnv* env,
         jclass clazz, jint ptr, jobject configObj) {
     NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -1273,10 +1279,6 @@
             (void*) nativeSetInputDispatchMode },
     { "nativeSetSystemUiVisibility", "(II)V",
             (void*) nativeSetSystemUiVisibility },
-    { "nativeGetInputDevice", "(II)Landroid/view/InputDevice;",
-            (void*) nativeGetInputDevice },
-    { "nativeGetInputDeviceIds", "(I)[I",
-            (void*) nativeGetInputDeviceIds },
     { "nativeGetInputConfiguration", "(ILandroid/content/res/Configuration;)V",
             (void*) nativeGetInputConfiguration },
     { "nativeTransferTouchFocus", "(ILandroid/view/InputChannel;Landroid/view/InputChannel;)Z",
@@ -1316,6 +1318,9 @@
     GET_METHOD_ID(gServiceClassInfo.notifyConfigurationChanged, clazz,
             "notifyConfigurationChanged", "(J)V");
 
+    GET_METHOD_ID(gServiceClassInfo.notifyInputDevicesChanged, clazz,
+            "notifyInputDevicesChanged", "([Landroid/view/InputDevice;)V");
+
     GET_METHOD_ID(gServiceClassInfo.notifyLidSwitchChanged, clazz,
             "notifyLidSwitchChanged", "(JZ)V");
 
@@ -1377,6 +1382,11 @@
     GET_METHOD_ID(gServiceClassInfo.getPointerIcon, clazz,
             "getPointerIcon", "()Landroid/view/PointerIcon;");
 
+    // InputDevice
+
+    FIND_CLASS(gInputDeviceClassInfo.clazz, "android/view/InputDevice");
+    gInputDeviceClassInfo.clazz = jclass(env->NewGlobalRef(gInputDeviceClassInfo.clazz));
+
     // KeyEvent
 
     FIND_CLASS(gKeyEventClassInfo.clazz, "android/view/KeyEvent");