Home | History | Annotate | Download | only in input
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.hardware.input;
     18 
     19 import com.android.internal.util.ArrayUtils;
     20 
     21 import android.annotation.SdkConstant;
     22 import android.annotation.SdkConstant.SdkConstantType;
     23 import android.content.Context;
     24 import android.os.Binder;
     25 import android.os.Handler;
     26 import android.os.IBinder;
     27 import android.os.Looper;
     28 import android.os.Message;
     29 import android.os.RemoteException;
     30 import android.os.ServiceManager;
     31 import android.os.Vibrator;
     32 import android.provider.Settings;
     33 import android.provider.Settings.SettingNotFoundException;
     34 import android.util.Log;
     35 import android.util.SparseArray;
     36 import android.view.InputDevice;
     37 import android.view.InputEvent;
     38 
     39 import java.util.ArrayList;
     40 
     41 /**
     42  * Provides information about input devices and available key layouts.
     43  * <p>
     44  * Get an instance of this class by calling
     45  * {@link android.content.Context#getSystemService(java.lang.String)
     46  * Context.getSystemService()} with the argument
     47  * {@link android.content.Context#INPUT_SERVICE}.
     48  * </p>
     49  */
     50 public final class InputManager {
     51     private static final String TAG = "InputManager";
     52     private static final boolean DEBUG = false;
     53 
     54     private static final int MSG_DEVICE_ADDED = 1;
     55     private static final int MSG_DEVICE_REMOVED = 2;
     56     private static final int MSG_DEVICE_CHANGED = 3;
     57 
     58     private static InputManager sInstance;
     59 
     60     private final IInputManager mIm;
     61 
     62     // Guarded by mInputDevicesLock
     63     private final Object mInputDevicesLock = new Object();
     64     private SparseArray<InputDevice> mInputDevices;
     65     private InputDevicesChangedListener mInputDevicesChangedListener;
     66     private final ArrayList<InputDeviceListenerDelegate> mInputDeviceListeners =
     67             new ArrayList<InputDeviceListenerDelegate>();
     68 
     69     /**
     70      * Broadcast Action: Query available keyboard layouts.
     71      * <p>
     72      * The input manager service locates available keyboard layouts
     73      * by querying broadcast receivers that are registered for this action.
     74      * An application can offer additional keyboard layouts to the user
     75      * by declaring a suitable broadcast receiver in its manifest.
     76      * </p><p>
     77      * Here is an example broadcast receiver declaration that an application
     78      * might include in its AndroidManifest.xml to advertise keyboard layouts.
     79      * The meta-data specifies a resource that contains a description of each keyboard
     80      * layout that is provided by the application.
     81      * <pre><code>
     82      * &lt;receiver android:name=".InputDeviceReceiver"
     83      *         android:label="@string/keyboard_layouts_label">
     84      *     &lt;intent-filter>
     85      *         &lt;action android:name="android.hardware.input.action.QUERY_KEYBOARD_LAYOUTS" />
     86      *     &lt;/intent-filter>
     87      *     &lt;meta-data android:name="android.hardware.input.metadata.KEYBOARD_LAYOUTS"
     88      *             android:resource="@xml/keyboard_layouts" />
     89      * &lt;/receiver>
     90      * </code></pre>
     91      * </p><p>
     92      * In the above example, the <code>@xml/keyboard_layouts</code> resource refers to
     93      * an XML resource whose root element is <code>&lt;keyboard-layouts></code> that
     94      * contains zero or more <code>&lt;keyboard-layout></code> elements.
     95      * Each <code>&lt;keyboard-layout></code> element specifies the name, label, and location
     96      * of a key character map for a particular keyboard layout.  The label on the receiver
     97      * is used to name the collection of keyboard layouts provided by this receiver in the
     98      * keyboard layout settings.
     99      * <pre></code>
    100      * &lt;?xml version="1.0" encoding="utf-8"?>
    101      * &lt;keyboard-layouts xmlns:android="http://schemas.android.com/apk/res/android">
    102      *     &lt;keyboard-layout android:name="keyboard_layout_english_us"
    103      *             android:label="@string/keyboard_layout_english_us_label"
    104      *             android:keyboardLayout="@raw/keyboard_layout_english_us" />
    105      * &lt;/keyboard-layouts>
    106      * </p><p>
    107      * The <code>android:name</code> attribute specifies an identifier by which
    108      * the keyboard layout will be known in the package.
    109      * The <code>android:label</code> attributes specifies a human-readable descriptive
    110      * label to describe the keyboard layout in the user interface, such as "English (US)".
    111      * The <code>android:keyboardLayout</code> attribute refers to a
    112      * <a href="http://source.android.com/tech/input/key-character-map-files.html">
    113      * key character map</a> resource that defines the keyboard layout.
    114      * </p>
    115      */
    116     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
    117     public static final String ACTION_QUERY_KEYBOARD_LAYOUTS =
    118             "android.hardware.input.action.QUERY_KEYBOARD_LAYOUTS";
    119 
    120     /**
    121      * Metadata Key: Keyboard layout metadata associated with
    122      * {@link #ACTION_QUERY_KEYBOARD_LAYOUTS}.
    123      * <p>
    124      * Specifies the resource id of a XML resource that describes the keyboard
    125      * layouts that are provided by the application.
    126      * </p>
    127      */
    128     public static final String META_DATA_KEYBOARD_LAYOUTS =
    129             "android.hardware.input.metadata.KEYBOARD_LAYOUTS";
    130 
    131     /**
    132      * Pointer Speed: The minimum (slowest) pointer speed (-7).
    133      * @hide
    134      */
    135     public static final int MIN_POINTER_SPEED = -7;
    136 
    137     /**
    138      * Pointer Speed: The maximum (fastest) pointer speed (7).
    139      * @hide
    140      */
    141     public static final int MAX_POINTER_SPEED = 7;
    142 
    143     /**
    144      * Pointer Speed: The default pointer speed (0).
    145      * @hide
    146      */
    147     public static final int DEFAULT_POINTER_SPEED = 0;
    148 
    149     /**
    150      * Input Event Injection Synchronization Mode: None.
    151      * Never blocks.  Injection is asynchronous and is assumed always to be successful.
    152      * @hide
    153      */
    154     public static final int INJECT_INPUT_EVENT_MODE_ASYNC = 0; // see InputDispatcher.h
    155 
    156     /**
    157      * Input Event Injection Synchronization Mode: Wait for result.
    158      * Waits for previous events to be dispatched so that the input dispatcher can
    159      * determine whether input event injection will be permitted based on the current
    160      * input focus.  Does not wait for the input event to finish being handled
    161      * by the application.
    162      * @hide
    163      */
    164     public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT = 1;  // see InputDispatcher.h
    165 
    166     /**
    167      * Input Event Injection Synchronization Mode: Wait for finish.
    168      * Waits for the event to be delivered to the application and handled.
    169      * @hide
    170      */
    171     public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2;  // see InputDispatcher.h
    172 
    173     private InputManager(IInputManager im) {
    174         mIm = im;
    175     }
    176 
    177     /**
    178      * Gets an instance of the input manager.
    179      *
    180      * @return The input manager instance.
    181      *
    182      * @hide
    183      */
    184     public static InputManager getInstance() {
    185         synchronized (InputManager.class) {
    186             if (sInstance == null) {
    187                 IBinder b = ServiceManager.getService(Context.INPUT_SERVICE);
    188                 sInstance = new InputManager(IInputManager.Stub.asInterface(b));
    189             }
    190             return sInstance;
    191         }
    192     }
    193 
    194     /**
    195      * Gets information about the input device with the specified id.
    196      * @param id The device id.
    197      * @return The input device or null if not found.
    198      */
    199     public InputDevice getInputDevice(int id) {
    200         synchronized (mInputDevicesLock) {
    201             populateInputDevicesLocked();
    202 
    203             int index = mInputDevices.indexOfKey(id);
    204             if (index < 0) {
    205                 return null;
    206             }
    207 
    208             InputDevice inputDevice = mInputDevices.valueAt(index);
    209             if (inputDevice == null) {
    210                 try {
    211                     inputDevice = mIm.getInputDevice(id);
    212                 } catch (RemoteException ex) {
    213                     throw new RuntimeException("Could not get input device information.", ex);
    214                 }
    215             }
    216             mInputDevices.setValueAt(index, inputDevice);
    217             return inputDevice;
    218         }
    219     }
    220 
    221     /**
    222      * Gets information about the input device with the specified descriptor.
    223      * @param descriptor The input device descriptor.
    224      * @return The input device or null if not found.
    225      * @hide
    226      */
    227     public InputDevice getInputDeviceByDescriptor(String descriptor) {
    228         if (descriptor == null) {
    229             throw new IllegalArgumentException("descriptor must not be null.");
    230         }
    231 
    232         synchronized (mInputDevicesLock) {
    233             populateInputDevicesLocked();
    234 
    235             int numDevices = mInputDevices.size();
    236             for (int i = 0; i < numDevices; i++) {
    237                 InputDevice inputDevice = mInputDevices.valueAt(i);
    238                 if (inputDevice == null) {
    239                     int id = mInputDevices.keyAt(i);
    240                     try {
    241                         inputDevice = mIm.getInputDevice(id);
    242                     } catch (RemoteException ex) {
    243                         // Ignore the problem for the purposes of this method.
    244                         continue;
    245                     }
    246                     mInputDevices.setValueAt(i, inputDevice);
    247                 }
    248                 if (descriptor.equals(inputDevice.getDescriptor())) {
    249                     return inputDevice;
    250                 }
    251             }
    252             return null;
    253         }
    254     }
    255 
    256     /**
    257      * Gets the ids of all input devices in the system.
    258      * @return The input device ids.
    259      */
    260     public int[] getInputDeviceIds() {
    261         synchronized (mInputDevicesLock) {
    262             populateInputDevicesLocked();
    263 
    264             final int count = mInputDevices.size();
    265             final int[] ids = new int[count];
    266             for (int i = 0; i < count; i++) {
    267                 ids[i] = mInputDevices.keyAt(i);
    268             }
    269             return ids;
    270         }
    271     }
    272 
    273     /**
    274      * Registers an input device listener to receive notifications about when
    275      * input devices are added, removed or changed.
    276      *
    277      * @param listener The listener to register.
    278      * @param handler The handler on which the listener should be invoked, or null
    279      * if the listener should be invoked on the calling thread's looper.
    280      *
    281      * @see #unregisterInputDeviceListener
    282      */
    283     public void registerInputDeviceListener(InputDeviceListener listener, Handler handler) {
    284         if (listener == null) {
    285             throw new IllegalArgumentException("listener must not be null");
    286         }
    287 
    288         synchronized (mInputDevicesLock) {
    289             int index = findInputDeviceListenerLocked(listener);
    290             if (index < 0) {
    291                 mInputDeviceListeners.add(new InputDeviceListenerDelegate(listener, handler));
    292             }
    293         }
    294     }
    295 
    296     /**
    297      * Unregisters an input device listener.
    298      *
    299      * @param listener The listener to unregister.
    300      *
    301      * @see #registerInputDeviceListener
    302      */
    303     public void unregisterInputDeviceListener(InputDeviceListener listener) {
    304         if (listener == null) {
    305             throw new IllegalArgumentException("listener must not be null");
    306         }
    307 
    308         synchronized (mInputDevicesLock) {
    309             int index = findInputDeviceListenerLocked(listener);
    310             if (index >= 0) {
    311                 InputDeviceListenerDelegate d = mInputDeviceListeners.get(index);
    312                 d.removeCallbacksAndMessages(null);
    313                 mInputDeviceListeners.remove(index);
    314             }
    315         }
    316     }
    317 
    318     private int findInputDeviceListenerLocked(InputDeviceListener listener) {
    319         final int numListeners = mInputDeviceListeners.size();
    320         for (int i = 0; i < numListeners; i++) {
    321             if (mInputDeviceListeners.get(i).mListener == listener) {
    322                 return i;
    323             }
    324         }
    325         return -1;
    326     }
    327 
    328     /**
    329      * Gets information about all supported keyboard layouts.
    330      * <p>
    331      * The input manager consults the built-in keyboard layouts as well
    332      * as all keyboard layouts advertised by applications using a
    333      * {@link #ACTION_QUERY_KEYBOARD_LAYOUTS} broadcast receiver.
    334      * </p>
    335      *
    336      * @return A list of all supported keyboard layouts.
    337      *
    338      * @hide
    339      */
    340     public KeyboardLayout[] getKeyboardLayouts() {
    341         try {
    342             return mIm.getKeyboardLayouts();
    343         } catch (RemoteException ex) {
    344             Log.w(TAG, "Could not get list of keyboard layout informations.", ex);
    345             return new KeyboardLayout[0];
    346         }
    347     }
    348 
    349     /**
    350      * Gets the keyboard layout with the specified descriptor.
    351      *
    352      * @param keyboardLayoutDescriptor The keyboard layout descriptor, as returned by
    353      * {@link KeyboardLayout#getDescriptor()}.
    354      * @return The keyboard layout, or null if it could not be loaded.
    355      *
    356      * @hide
    357      */
    358     public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) {
    359         if (keyboardLayoutDescriptor == null) {
    360             throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
    361         }
    362 
    363         try {
    364             return mIm.getKeyboardLayout(keyboardLayoutDescriptor);
    365         } catch (RemoteException ex) {
    366             Log.w(TAG, "Could not get keyboard layout information.", ex);
    367             return null;
    368         }
    369     }
    370 
    371     /**
    372      * Gets the current keyboard layout descriptor for the specified input device.
    373      *
    374      * @param inputDeviceDescriptor The input device descriptor.
    375      * @return The keyboard layout descriptor, or null if no keyboard layout has been set.
    376      *
    377      * @hide
    378      */
    379     public String getCurrentKeyboardLayoutForInputDevice(String inputDeviceDescriptor) {
    380         if (inputDeviceDescriptor == null) {
    381             throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
    382         }
    383 
    384         try {
    385             return mIm.getCurrentKeyboardLayoutForInputDevice(inputDeviceDescriptor);
    386         } catch (RemoteException ex) {
    387             Log.w(TAG, "Could not get current keyboard layout for input device.", ex);
    388             return null;
    389         }
    390     }
    391 
    392     /**
    393      * Sets the current keyboard layout descriptor for the specified input device.
    394      * <p>
    395      * This method may have the side-effect of causing the input device in question
    396      * to be reconfigured.
    397      * </p>
    398      *
    399      * @param inputDeviceDescriptor The input device descriptor.
    400      * @param keyboardLayoutDescriptor The keyboard layout descriptor to use, must not be null.
    401      *
    402      * @hide
    403      */
    404     public void setCurrentKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
    405             String keyboardLayoutDescriptor) {
    406         if (inputDeviceDescriptor == null) {
    407             throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
    408         }
    409         if (keyboardLayoutDescriptor == null) {
    410             throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
    411         }
    412 
    413         try {
    414             mIm.setCurrentKeyboardLayoutForInputDevice(inputDeviceDescriptor,
    415                     keyboardLayoutDescriptor);
    416         } catch (RemoteException ex) {
    417             Log.w(TAG, "Could not set current keyboard layout for input device.", ex);
    418         }
    419     }
    420 
    421     /**
    422      * Gets all keyboard layout descriptors that are enabled for the specified input device.
    423      *
    424      * @param inputDeviceDescriptor The input device descriptor.
    425      * @return The keyboard layout descriptors.
    426      *
    427      * @hide
    428      */
    429     public String[] getKeyboardLayoutsForInputDevice(String inputDeviceDescriptor) {
    430         if (inputDeviceDescriptor == null) {
    431             throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
    432         }
    433 
    434         try {
    435             return mIm.getKeyboardLayoutsForInputDevice(inputDeviceDescriptor);
    436         } catch (RemoteException ex) {
    437             Log.w(TAG, "Could not get keyboard layouts for input device.", ex);
    438             return ArrayUtils.emptyArray(String.class);
    439         }
    440     }
    441 
    442     /**
    443      * Adds the keyboard layout descriptor for the specified input device.
    444      * <p>
    445      * This method may have the side-effect of causing the input device in question
    446      * to be reconfigured.
    447      * </p>
    448      *
    449      * @param inputDeviceDescriptor The input device descriptor.
    450      * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to add.
    451      *
    452      * @hide
    453      */
    454     public void addKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
    455             String keyboardLayoutDescriptor) {
    456         if (inputDeviceDescriptor == null) {
    457             throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
    458         }
    459         if (keyboardLayoutDescriptor == null) {
    460             throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
    461         }
    462 
    463         try {
    464             mIm.addKeyboardLayoutForInputDevice(inputDeviceDescriptor, keyboardLayoutDescriptor);
    465         } catch (RemoteException ex) {
    466             Log.w(TAG, "Could not add keyboard layout for input device.", ex);
    467         }
    468     }
    469 
    470     /**
    471      * Removes the keyboard layout descriptor for the specified input device.
    472      * <p>
    473      * This method may have the side-effect of causing the input device in question
    474      * to be reconfigured.
    475      * </p>
    476      *
    477      * @param inputDeviceDescriptor The input device descriptor.
    478      * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to remove.
    479      *
    480      * @hide
    481      */
    482     public void removeKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
    483             String keyboardLayoutDescriptor) {
    484         if (inputDeviceDescriptor == null) {
    485             throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
    486         }
    487         if (keyboardLayoutDescriptor == null) {
    488             throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
    489         }
    490 
    491         try {
    492             mIm.removeKeyboardLayoutForInputDevice(inputDeviceDescriptor, keyboardLayoutDescriptor);
    493         } catch (RemoteException ex) {
    494             Log.w(TAG, "Could not remove keyboard layout for input device.", ex);
    495         }
    496     }
    497 
    498     /**
    499      * Gets the mouse pointer speed.
    500      * <p>
    501      * Only returns the permanent mouse pointer speed.  Ignores any temporary pointer
    502      * speed set by {@link #tryPointerSpeed}.
    503      * </p>
    504      *
    505      * @param context The application context.
    506      * @return The pointer speed as a value between {@link #MIN_POINTER_SPEED} and
    507      * {@link #MAX_POINTER_SPEED}, or the default value {@link #DEFAULT_POINTER_SPEED}.
    508      *
    509      * @hide
    510      */
    511     public int getPointerSpeed(Context context) {
    512         int speed = DEFAULT_POINTER_SPEED;
    513         try {
    514             speed = Settings.System.getInt(context.getContentResolver(),
    515                     Settings.System.POINTER_SPEED);
    516         } catch (SettingNotFoundException snfe) {
    517         }
    518         return speed;
    519     }
    520 
    521     /**
    522      * Sets the mouse pointer speed.
    523      * <p>
    524      * Requires {@link android.Manifest.permissions.WRITE_SETTINGS}.
    525      * </p>
    526      *
    527      * @param context The application context.
    528      * @param speed The pointer speed as a value between {@link #MIN_POINTER_SPEED} and
    529      * {@link #MAX_POINTER_SPEED}, or the default value {@link #DEFAULT_POINTER_SPEED}.
    530      *
    531      * @hide
    532      */
    533     public void setPointerSpeed(Context context, int speed) {
    534         if (speed < MIN_POINTER_SPEED || speed > MAX_POINTER_SPEED) {
    535             throw new IllegalArgumentException("speed out of range");
    536         }
    537 
    538         Settings.System.putInt(context.getContentResolver(),
    539                 Settings.System.POINTER_SPEED, speed);
    540     }
    541 
    542     /**
    543      * Changes the mouse pointer speed temporarily, but does not save the setting.
    544      * <p>
    545      * Requires {@link android.Manifest.permission.SET_POINTER_SPEED}.
    546      * </p>
    547      *
    548      * @param speed The pointer speed as a value between {@link #MIN_POINTER_SPEED} and
    549      * {@link #MAX_POINTER_SPEED}, or the default value {@link #DEFAULT_POINTER_SPEED}.
    550      *
    551      * @hide
    552      */
    553     public void tryPointerSpeed(int speed) {
    554         if (speed < MIN_POINTER_SPEED || speed > MAX_POINTER_SPEED) {
    555             throw new IllegalArgumentException("speed out of range");
    556         }
    557 
    558         try {
    559             mIm.tryPointerSpeed(speed);
    560         } catch (RemoteException ex) {
    561             Log.w(TAG, "Could not set temporary pointer speed.", ex);
    562         }
    563     }
    564 
    565     /**
    566      * Queries the framework about whether any physical keys exist on the
    567      * any keyboard attached to the device that are capable of producing the given
    568      * array of key codes.
    569      *
    570      * @param keyCodes The array of key codes to query.
    571      * @return A new array of the same size as the key codes array whose elements
    572      * are set to true if at least one attached keyboard supports the corresponding key code
    573      * at the same index in the key codes array.
    574      *
    575      * @hide
    576      */
    577     public boolean[] deviceHasKeys(int[] keyCodes) {
    578         boolean[] ret = new boolean[keyCodes.length];
    579         try {
    580             mIm.hasKeys(-1, InputDevice.SOURCE_ANY, keyCodes, ret);
    581         } catch (RemoteException e) {
    582             // no fallback; just return the empty array
    583         }
    584         return ret;
    585     }
    586 
    587     /**
    588      * Injects an input event into the event system on behalf of an application.
    589      * The synchronization mode determines whether the method blocks while waiting for
    590      * input injection to proceed.
    591      * <p>
    592      * Requires {@link android.Manifest.permission.INJECT_EVENTS} to inject into
    593      * windows that are owned by other applications.
    594      * </p><p>
    595      * Make sure you correctly set the event time and input source of the event
    596      * before calling this method.
    597      * </p>
    598      *
    599      * @param event The event to inject.
    600      * @param mode The synchronization mode.  One of:
    601      * {@link #INJECT_INPUT_EVENT_MODE_ASYNC},
    602      * {@link #INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT}, or
    603      * {@link #INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH}.
    604      * @return True if input event injection succeeded.
    605      *
    606      * @hide
    607      */
    608     public boolean injectInputEvent(InputEvent event, int mode) {
    609         if (event == null) {
    610             throw new IllegalArgumentException("event must not be null");
    611         }
    612         if (mode != INJECT_INPUT_EVENT_MODE_ASYNC
    613                 && mode != INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
    614                 && mode != INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT) {
    615             throw new IllegalArgumentException("mode is invalid");
    616         }
    617 
    618         try {
    619             return mIm.injectInputEvent(event, mode);
    620         } catch (RemoteException ex) {
    621             return false;
    622         }
    623     }
    624 
    625     private void populateInputDevicesLocked() {
    626         if (mInputDevicesChangedListener == null) {
    627             final InputDevicesChangedListener listener = new InputDevicesChangedListener();
    628             try {
    629                 mIm.registerInputDevicesChangedListener(listener);
    630             } catch (RemoteException ex) {
    631                 throw new RuntimeException(
    632                         "Could not get register input device changed listener", ex);
    633             }
    634             mInputDevicesChangedListener = listener;
    635         }
    636 
    637         if (mInputDevices == null) {
    638             final int[] ids;
    639             try {
    640                 ids = mIm.getInputDeviceIds();
    641             } catch (RemoteException ex) {
    642                 throw new RuntimeException("Could not get input device ids.", ex);
    643             }
    644 
    645             mInputDevices = new SparseArray<InputDevice>();
    646             for (int i = 0; i < ids.length; i++) {
    647                 mInputDevices.put(ids[i], null);
    648             }
    649         }
    650     }
    651 
    652     private void onInputDevicesChanged(int[] deviceIdAndGeneration) {
    653         if (DEBUG) {
    654             Log.d(TAG, "Received input devices changed.");
    655         }
    656 
    657         synchronized (mInputDevicesLock) {
    658             for (int i = mInputDevices.size(); --i > 0; ) {
    659                 final int deviceId = mInputDevices.keyAt(i);
    660                 if (!containsDeviceId(deviceIdAndGeneration, deviceId)) {
    661                     if (DEBUG) {
    662                         Log.d(TAG, "Device removed: " + deviceId);
    663                     }
    664                     mInputDevices.removeAt(i);
    665                     sendMessageToInputDeviceListenersLocked(MSG_DEVICE_REMOVED, deviceId);
    666                 }
    667             }
    668 
    669             for (int i = 0; i < deviceIdAndGeneration.length; i += 2) {
    670                 final int deviceId = deviceIdAndGeneration[i];
    671                 int index = mInputDevices.indexOfKey(deviceId);
    672                 if (index >= 0) {
    673                     final InputDevice device = mInputDevices.valueAt(index);
    674                     if (device != null) {
    675                         final int generation = deviceIdAndGeneration[i + 1];
    676                         if (device.getGeneration() != generation) {
    677                             if (DEBUG) {
    678                                 Log.d(TAG, "Device changed: " + deviceId);
    679                             }
    680                             mInputDevices.setValueAt(index, null);
    681                             sendMessageToInputDeviceListenersLocked(MSG_DEVICE_CHANGED, deviceId);
    682                         }
    683                     }
    684                 } else {
    685                     if (DEBUG) {
    686                         Log.d(TAG, "Device added: " + deviceId);
    687                     }
    688                     mInputDevices.put(deviceId, null);
    689                     sendMessageToInputDeviceListenersLocked(MSG_DEVICE_ADDED, deviceId);
    690                 }
    691             }
    692         }
    693     }
    694 
    695     private void sendMessageToInputDeviceListenersLocked(int what, int deviceId) {
    696         final int numListeners = mInputDeviceListeners.size();
    697         for (int i = 0; i < numListeners; i++) {
    698             InputDeviceListenerDelegate listener = mInputDeviceListeners.get(i);
    699             listener.sendMessage(listener.obtainMessage(what, deviceId, 0));
    700         }
    701     }
    702 
    703     private static boolean containsDeviceId(int[] deviceIdAndGeneration, int deviceId) {
    704         for (int i = 0; i < deviceIdAndGeneration.length; i += 2) {
    705             if (deviceIdAndGeneration[i] == deviceId) {
    706                 return true;
    707             }
    708         }
    709         return false;
    710     }
    711 
    712     /**
    713      * Gets a vibrator service associated with an input device, assuming it has one.
    714      * @return The vibrator, never null.
    715      * @hide
    716      */
    717     public Vibrator getInputDeviceVibrator(int deviceId) {
    718         return new InputDeviceVibrator(deviceId);
    719     }
    720 
    721     /**
    722      * Listens for changes in input devices.
    723      */
    724     public interface InputDeviceListener {
    725         /**
    726          * Called whenever an input device has been added to the system.
    727          * Use {@link InputManager#getInputDevice} to get more information about the device.
    728          *
    729          * @param deviceId The id of the input device that was added.
    730          */
    731         void onInputDeviceAdded(int deviceId);
    732 
    733         /**
    734          * Called whenever an input device has been removed from the system.
    735          *
    736          * @param deviceId The id of the input device that was removed.
    737          */
    738         void onInputDeviceRemoved(int deviceId);
    739 
    740         /**
    741          * Called whenever the properties of an input device have changed since they
    742          * were last queried.  Use {@link InputManager#getInputDevice} to get
    743          * a fresh {@link InputDevice} object with the new properties.
    744          *
    745          * @param deviceId The id of the input device that changed.
    746          */
    747         void onInputDeviceChanged(int deviceId);
    748     }
    749 
    750     private final class InputDevicesChangedListener extends IInputDevicesChangedListener.Stub {
    751         @Override
    752         public void onInputDevicesChanged(int[] deviceIdAndGeneration) throws RemoteException {
    753             InputManager.this.onInputDevicesChanged(deviceIdAndGeneration);
    754         }
    755     }
    756 
    757     private static final class InputDeviceListenerDelegate extends Handler {
    758         public final InputDeviceListener mListener;
    759 
    760         public InputDeviceListenerDelegate(InputDeviceListener listener, Handler handler) {
    761             super(handler != null ? handler.getLooper() : Looper.myLooper());
    762             mListener = listener;
    763         }
    764 
    765         @Override
    766         public void handleMessage(Message msg) {
    767             switch (msg.what) {
    768                 case MSG_DEVICE_ADDED:
    769                     mListener.onInputDeviceAdded(msg.arg1);
    770                     break;
    771                 case MSG_DEVICE_REMOVED:
    772                     mListener.onInputDeviceRemoved(msg.arg1);
    773                     break;
    774                 case MSG_DEVICE_CHANGED:
    775                     mListener.onInputDeviceChanged(msg.arg1);
    776                     break;
    777             }
    778         }
    779     }
    780 
    781     private final class InputDeviceVibrator extends Vibrator {
    782         private final int mDeviceId;
    783         private final Binder mToken;
    784 
    785         public InputDeviceVibrator(int deviceId) {
    786             mDeviceId = deviceId;
    787             mToken = new Binder();
    788         }
    789 
    790         @Override
    791         public boolean hasVibrator() {
    792             return true;
    793         }
    794 
    795         @Override
    796         public void vibrate(long milliseconds) {
    797             vibrate(new long[] { 0, milliseconds}, -1);
    798         }
    799 
    800         @Override
    801         public void vibrate(long[] pattern, int repeat) {
    802             if (repeat >= pattern.length) {
    803                 throw new ArrayIndexOutOfBoundsException();
    804             }
    805             try {
    806                 mIm.vibrate(mDeviceId, pattern, repeat, mToken);
    807             } catch (RemoteException ex) {
    808                 Log.w(TAG, "Failed to vibrate.", ex);
    809             }
    810         }
    811 
    812         @Override
    813         public void cancel() {
    814             try {
    815                 mIm.cancelVibrate(mDeviceId, mToken);
    816             } catch (RemoteException ex) {
    817                 Log.w(TAG, "Failed to cancel vibration.", ex);
    818             }
    819         }
    820     }
    821 }
    822