InputManager.java revision d6396d67201fb2b64d13070324bb115c9c23b08a
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 TouchCalibration applied to the specified input device's coordinates.
504     *
505     * @param inputDeviceDescriptor The input device descriptor.
506     * @return The TouchCalibration currently assigned for use with the given
507     * input device. If none is set, an identity TouchCalibration is returned.
508     *
509     * @hide
510     */
511    public TouchCalibration getTouchCalibration(String inputDeviceDescriptor) {
512        try {
513            return mIm.getTouchCalibrationForInputDevice(inputDeviceDescriptor);
514        } catch (RemoteException ex) {
515            Log.w(TAG, "Could not get calibration matrix for input device.", ex);
516            return TouchCalibration.IDENTITY;
517        }
518    }
519
520    /**
521     * Sets the TouchCalibration to apply to the specified input device's coordinates.
522     * <p>
523     * This method may have the side-effect of causing the input device in question
524     * to be reconfigured. Requires {@link android.Manifest.permissions.SET_INPUT_CALIBRATION}.
525     * </p>
526     *
527     * @param inputDeviceDescriptor The input device descriptor.
528     * @param calibration The calibration to be applied
529     *
530     * @hide
531     */
532    public void setTouchCalibration(String inputDeviceDescriptor, TouchCalibration calibration) {
533        try {
534            mIm.setTouchCalibrationForInputDevice(inputDeviceDescriptor, calibration);
535        } catch (RemoteException ex) {
536            Log.w(TAG, "Could not set calibration matrix for input device.", ex);
537        }
538    }
539
540    /**
541     * Gets the mouse pointer speed.
542     * <p>
543     * Only returns the permanent mouse pointer speed.  Ignores any temporary pointer
544     * speed set by {@link #tryPointerSpeed}.
545     * </p>
546     *
547     * @param context The application context.
548     * @return 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 int getPointerSpeed(Context context) {
554        int speed = DEFAULT_POINTER_SPEED;
555        try {
556            speed = Settings.System.getInt(context.getContentResolver(),
557                    Settings.System.POINTER_SPEED);
558        } catch (SettingNotFoundException snfe) {
559        }
560        return speed;
561    }
562
563    /**
564     * Sets the mouse pointer speed.
565     * <p>
566     * Requires {@link android.Manifest.permissions.WRITE_SETTINGS}.
567     * </p>
568     *
569     * @param context The application context.
570     * @param speed The pointer speed as a value between {@link #MIN_POINTER_SPEED} and
571     * {@link #MAX_POINTER_SPEED}, or the default value {@link #DEFAULT_POINTER_SPEED}.
572     *
573     * @hide
574     */
575    public void setPointerSpeed(Context context, int speed) {
576        if (speed < MIN_POINTER_SPEED || speed > MAX_POINTER_SPEED) {
577            throw new IllegalArgumentException("speed out of range");
578        }
579
580        Settings.System.putInt(context.getContentResolver(),
581                Settings.System.POINTER_SPEED, speed);
582    }
583
584    /**
585     * Changes the mouse pointer speed temporarily, but does not save the setting.
586     * <p>
587     * Requires {@link android.Manifest.permission.SET_POINTER_SPEED}.
588     * </p>
589     *
590     * @param speed The pointer speed as a value between {@link #MIN_POINTER_SPEED} and
591     * {@link #MAX_POINTER_SPEED}, or the default value {@link #DEFAULT_POINTER_SPEED}.
592     *
593     * @hide
594     */
595    public void tryPointerSpeed(int speed) {
596        if (speed < MIN_POINTER_SPEED || speed > MAX_POINTER_SPEED) {
597            throw new IllegalArgumentException("speed out of range");
598        }
599
600        try {
601            mIm.tryPointerSpeed(speed);
602        } catch (RemoteException ex) {
603            Log.w(TAG, "Could not set temporary pointer speed.", ex);
604        }
605    }
606
607    /**
608     * Queries the framework about whether any physical keys exist on the
609     * any keyboard attached to the device that are capable of producing the given
610     * array of key codes.
611     *
612     * @param keyCodes The array of key codes to query.
613     * @return A new array of the same size as the key codes array whose elements
614     * are set to true if at least one attached keyboard supports the corresponding key code
615     * at the same index in the key codes array.
616     *
617     * @hide
618     */
619    public boolean[] deviceHasKeys(int[] keyCodes) {
620        return deviceHasKeys(-1, keyCodes);
621    }
622
623    /**
624     * Queries the framework about whether any physical keys exist on the
625     * any keyboard attached to the device that are capable of producing the given
626     * array of key codes.
627     *
628     * @param id The id of the device to query.
629     * @param keyCodes The array of key codes to query.
630     * @return A new array of the same size as the key codes array whose elements are set to true
631     * if the given device could produce the corresponding key code at the same index in the key
632     * codes array.
633     *
634     * @hide
635     */
636    public boolean[] deviceHasKeys(int id, int[] keyCodes) {
637        boolean[] ret = new boolean[keyCodes.length];
638        try {
639            mIm.hasKeys(id, InputDevice.SOURCE_ANY, keyCodes, ret);
640        } catch (RemoteException e) {
641            // no fallback; just return the empty array
642        }
643        return ret;
644    }
645
646
647    /**
648     * Injects an input event into the event system on behalf of an application.
649     * The synchronization mode determines whether the method blocks while waiting for
650     * input injection to proceed.
651     * <p>
652     * Requires {@link android.Manifest.permission.INJECT_EVENTS} to inject into
653     * windows that are owned by other applications.
654     * </p><p>
655     * Make sure you correctly set the event time and input source of the event
656     * before calling this method.
657     * </p>
658     *
659     * @param event The event to inject.
660     * @param mode The synchronization mode.  One of:
661     * {@link #INJECT_INPUT_EVENT_MODE_ASYNC},
662     * {@link #INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT}, or
663     * {@link #INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH}.
664     * @return True if input event injection succeeded.
665     *
666     * @hide
667     */
668    public boolean injectInputEvent(InputEvent event, int mode) {
669        if (event == null) {
670            throw new IllegalArgumentException("event must not be null");
671        }
672        if (mode != INJECT_INPUT_EVENT_MODE_ASYNC
673                && mode != INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
674                && mode != INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT) {
675            throw new IllegalArgumentException("mode is invalid");
676        }
677
678        try {
679            return mIm.injectInputEvent(event, mode);
680        } catch (RemoteException ex) {
681            return false;
682        }
683    }
684
685    private void populateInputDevicesLocked() {
686        if (mInputDevicesChangedListener == null) {
687            final InputDevicesChangedListener listener = new InputDevicesChangedListener();
688            try {
689                mIm.registerInputDevicesChangedListener(listener);
690            } catch (RemoteException ex) {
691                throw new RuntimeException(
692                        "Could not get register input device changed listener", ex);
693            }
694            mInputDevicesChangedListener = listener;
695        }
696
697        if (mInputDevices == null) {
698            final int[] ids;
699            try {
700                ids = mIm.getInputDeviceIds();
701            } catch (RemoteException ex) {
702                throw new RuntimeException("Could not get input device ids.", ex);
703            }
704
705            mInputDevices = new SparseArray<InputDevice>();
706            for (int i = 0; i < ids.length; i++) {
707                mInputDevices.put(ids[i], null);
708            }
709        }
710    }
711
712    private void onInputDevicesChanged(int[] deviceIdAndGeneration) {
713        if (DEBUG) {
714            Log.d(TAG, "Received input devices changed.");
715        }
716
717        synchronized (mInputDevicesLock) {
718            for (int i = mInputDevices.size(); --i > 0; ) {
719                final int deviceId = mInputDevices.keyAt(i);
720                if (!containsDeviceId(deviceIdAndGeneration, deviceId)) {
721                    if (DEBUG) {
722                        Log.d(TAG, "Device removed: " + deviceId);
723                    }
724                    mInputDevices.removeAt(i);
725                    sendMessageToInputDeviceListenersLocked(MSG_DEVICE_REMOVED, deviceId);
726                }
727            }
728
729            for (int i = 0; i < deviceIdAndGeneration.length; i += 2) {
730                final int deviceId = deviceIdAndGeneration[i];
731                int index = mInputDevices.indexOfKey(deviceId);
732                if (index >= 0) {
733                    final InputDevice device = mInputDevices.valueAt(index);
734                    if (device != null) {
735                        final int generation = deviceIdAndGeneration[i + 1];
736                        if (device.getGeneration() != generation) {
737                            if (DEBUG) {
738                                Log.d(TAG, "Device changed: " + deviceId);
739                            }
740                            mInputDevices.setValueAt(index, null);
741                            sendMessageToInputDeviceListenersLocked(MSG_DEVICE_CHANGED, deviceId);
742                        }
743                    }
744                } else {
745                    if (DEBUG) {
746                        Log.d(TAG, "Device added: " + deviceId);
747                    }
748                    mInputDevices.put(deviceId, null);
749                    sendMessageToInputDeviceListenersLocked(MSG_DEVICE_ADDED, deviceId);
750                }
751            }
752        }
753    }
754
755    private void sendMessageToInputDeviceListenersLocked(int what, int deviceId) {
756        final int numListeners = mInputDeviceListeners.size();
757        for (int i = 0; i < numListeners; i++) {
758            InputDeviceListenerDelegate listener = mInputDeviceListeners.get(i);
759            listener.sendMessage(listener.obtainMessage(what, deviceId, 0));
760        }
761    }
762
763    private static boolean containsDeviceId(int[] deviceIdAndGeneration, int deviceId) {
764        for (int i = 0; i < deviceIdAndGeneration.length; i += 2) {
765            if (deviceIdAndGeneration[i] == deviceId) {
766                return true;
767            }
768        }
769        return false;
770    }
771
772    /**
773     * Gets a vibrator service associated with an input device, assuming it has one.
774     * @return The vibrator, never null.
775     * @hide
776     */
777    public Vibrator getInputDeviceVibrator(int deviceId) {
778        return new InputDeviceVibrator(deviceId);
779    }
780
781    /**
782     * Listens for changes in input devices.
783     */
784    public interface InputDeviceListener {
785        /**
786         * Called whenever an input device has been added to the system.
787         * Use {@link InputManager#getInputDevice} to get more information about the device.
788         *
789         * @param deviceId The id of the input device that was added.
790         */
791        void onInputDeviceAdded(int deviceId);
792
793        /**
794         * Called whenever an input device has been removed from the system.
795         *
796         * @param deviceId The id of the input device that was removed.
797         */
798        void onInputDeviceRemoved(int deviceId);
799
800        /**
801         * Called whenever the properties of an input device have changed since they
802         * were last queried.  Use {@link InputManager#getInputDevice} to get
803         * a fresh {@link InputDevice} object with the new properties.
804         *
805         * @param deviceId The id of the input device that changed.
806         */
807        void onInputDeviceChanged(int deviceId);
808    }
809
810    private final class InputDevicesChangedListener extends IInputDevicesChangedListener.Stub {
811        @Override
812        public void onInputDevicesChanged(int[] deviceIdAndGeneration) throws RemoteException {
813            InputManager.this.onInputDevicesChanged(deviceIdAndGeneration);
814        }
815    }
816
817    private static final class InputDeviceListenerDelegate extends Handler {
818        public final InputDeviceListener mListener;
819
820        public InputDeviceListenerDelegate(InputDeviceListener listener, Handler handler) {
821            super(handler != null ? handler.getLooper() : Looper.myLooper());
822            mListener = listener;
823        }
824
825        @Override
826        public void handleMessage(Message msg) {
827            switch (msg.what) {
828                case MSG_DEVICE_ADDED:
829                    mListener.onInputDeviceAdded(msg.arg1);
830                    break;
831                case MSG_DEVICE_REMOVED:
832                    mListener.onInputDeviceRemoved(msg.arg1);
833                    break;
834                case MSG_DEVICE_CHANGED:
835                    mListener.onInputDeviceChanged(msg.arg1);
836                    break;
837            }
838        }
839    }
840
841    private final class InputDeviceVibrator extends Vibrator {
842        private final int mDeviceId;
843        private final Binder mToken;
844
845        public InputDeviceVibrator(int deviceId) {
846            mDeviceId = deviceId;
847            mToken = new Binder();
848        }
849
850        @Override
851        public boolean hasVibrator() {
852            return true;
853        }
854
855        @Override
856        public void vibrate(long milliseconds) {
857            vibrate(new long[] { 0, milliseconds}, -1);
858        }
859
860        @Override
861        public void vibrate(long[] pattern, int repeat) {
862            if (repeat >= pattern.length) {
863                throw new ArrayIndexOutOfBoundsException();
864            }
865            try {
866                mIm.vibrate(mDeviceId, pattern, repeat, mToken);
867            } catch (RemoteException ex) {
868                Log.w(TAG, "Failed to vibrate.", ex);
869            }
870        }
871
872        /**
873         * @hide
874         */
875        @Override
876        public void vibrate(int owningUid, String owningPackage, long milliseconds) {
877            vibrate(milliseconds);
878        }
879
880        /**
881         * @hide
882         */
883        @Override
884        public void vibrate(int owningUid, String owningPackage, long[] pattern, int repeat) {
885            vibrate(pattern, repeat);
886        }
887
888        @Override
889        public void cancel() {
890            try {
891                mIm.cancelVibrate(mDeviceId, mToken);
892            } catch (RemoteException ex) {
893                Log.w(TAG, "Failed to cancel vibration.", ex);
894            }
895        }
896    }
897}
898