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