InputManager.java revision 08c7116ab9cd04ad6dd3c04aa1017237e7f409ac
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.media.AudioAttributes;
25import android.os.Binder;
26import android.os.Handler;
27import android.os.IBinder;
28import android.os.Looper;
29import android.os.Message;
30import android.os.RemoteException;
31import android.os.ServiceManager;
32import android.os.Vibrator;
33import android.provider.Settings;
34import android.provider.Settings.SettingNotFoundException;
35import android.util.Log;
36import android.util.SparseArray;
37import android.view.InputDevice;
38import android.view.InputEvent;
39
40import java.util.ArrayList;
41
42/**
43 * Provides information about input devices and available key layouts.
44 * <p>
45 * Get an instance of this class by calling
46 * {@link android.content.Context#getSystemService(java.lang.String)
47 * Context.getSystemService()} with the argument
48 * {@link android.content.Context#INPUT_SERVICE}.
49 * </p>
50 */
51public final class InputManager {
52    private static final String TAG = "InputManager";
53    private static final boolean DEBUG = false;
54
55    private static final int MSG_DEVICE_ADDED = 1;
56    private static final int MSG_DEVICE_REMOVED = 2;
57    private static final int MSG_DEVICE_CHANGED = 3;
58
59    private static InputManager sInstance;
60
61    private final IInputManager mIm;
62
63    // Guarded by mInputDevicesLock
64    private final Object mInputDevicesLock = new Object();
65    private SparseArray<InputDevice> mInputDevices;
66    private InputDevicesChangedListener mInputDevicesChangedListener;
67    private final ArrayList<InputDeviceListenerDelegate> mInputDeviceListeners =
68            new ArrayList<InputDeviceListenerDelegate>();
69
70    /**
71     * Broadcast Action: Query available keyboard layouts.
72     * <p>
73     * The input manager service locates available keyboard layouts
74     * by querying broadcast receivers that are registered for this action.
75     * An application can offer additional keyboard layouts to the user
76     * by declaring a suitable broadcast receiver in its manifest.
77     * </p><p>
78     * Here is an example broadcast receiver declaration that an application
79     * might include in its AndroidManifest.xml to advertise keyboard layouts.
80     * The meta-data specifies a resource that contains a description of each keyboard
81     * layout that is provided by the application.
82     * <pre><code>
83     * &lt;receiver android:name=".InputDeviceReceiver"
84     *         android:label="@string/keyboard_layouts_label">
85     *     &lt;intent-filter>
86     *         &lt;action android:name="android.hardware.input.action.QUERY_KEYBOARD_LAYOUTS" />
87     *     &lt;/intent-filter>
88     *     &lt;meta-data android:name="android.hardware.input.metadata.KEYBOARD_LAYOUTS"
89     *             android:resource="@xml/keyboard_layouts" />
90     * &lt;/receiver>
91     * </code></pre>
92     * </p><p>
93     * In the above example, the <code>@xml/keyboard_layouts</code> resource refers to
94     * an XML resource whose root element is <code>&lt;keyboard-layouts></code> that
95     * contains zero or more <code>&lt;keyboard-layout></code> elements.
96     * Each <code>&lt;keyboard-layout></code> element specifies the name, label, and location
97     * of a key character map for a particular keyboard layout.  The label on the receiver
98     * is used to name the collection of keyboard layouts provided by this receiver in the
99     * keyboard layout settings.
100     * <pre></code>
101     * &lt;?xml version="1.0" encoding="utf-8"?>
102     * &lt;keyboard-layouts xmlns:android="http://schemas.android.com/apk/res/android">
103     *     &lt;keyboard-layout android:name="keyboard_layout_english_us"
104     *             android:label="@string/keyboard_layout_english_us_label"
105     *             android:keyboardLayout="@raw/keyboard_layout_english_us" />
106     * &lt;/keyboard-layouts>
107     * </p><p>
108     * The <code>android:name</code> attribute specifies an identifier by which
109     * the keyboard layout will be known in the package.
110     * The <code>android:label</code> attributes specifies a human-readable descriptive
111     * label to describe the keyboard layout in the user interface, such as "English (US)".
112     * The <code>android:keyboardLayout</code> attribute refers to a
113     * <a href="http://source.android.com/tech/input/key-character-map-files.html">
114     * key character map</a> resource that defines the keyboard layout.
115     * </p>
116     */
117    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
118    public static final String ACTION_QUERY_KEYBOARD_LAYOUTS =
119            "android.hardware.input.action.QUERY_KEYBOARD_LAYOUTS";
120
121    /**
122     * Metadata Key: Keyboard layout metadata associated with
123     * {@link #ACTION_QUERY_KEYBOARD_LAYOUTS}.
124     * <p>
125     * Specifies the resource id of a XML resource that describes the keyboard
126     * layouts that are provided by the application.
127     * </p>
128     */
129    public static final String META_DATA_KEYBOARD_LAYOUTS =
130            "android.hardware.input.metadata.KEYBOARD_LAYOUTS";
131
132    /**
133     * Pointer Speed: The minimum (slowest) pointer speed (-7).
134     * @hide
135     */
136    public static final int MIN_POINTER_SPEED = -7;
137
138    /**
139     * Pointer Speed: The maximum (fastest) pointer speed (7).
140     * @hide
141     */
142    public static final int MAX_POINTER_SPEED = 7;
143
144    /**
145     * Pointer Speed: The default pointer speed (0).
146     * @hide
147     */
148    public static final int DEFAULT_POINTER_SPEED = 0;
149
150    /**
151     * Input Event Injection Synchronization Mode: None.
152     * Never blocks.  Injection is asynchronous and is assumed always to be successful.
153     * @hide
154     */
155    public static final int INJECT_INPUT_EVENT_MODE_ASYNC = 0; // see InputDispatcher.h
156
157    /**
158     * Input Event Injection Synchronization Mode: Wait for result.
159     * Waits for previous events to be dispatched so that the input dispatcher can
160     * determine whether input event injection will be permitted based on the current
161     * input focus.  Does not wait for the input event to finish being handled
162     * by the application.
163     * @hide
164     */
165    public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT = 1;  // see InputDispatcher.h
166
167    /**
168     * Input Event Injection Synchronization Mode: Wait for finish.
169     * Waits for the event to be delivered to the application and handled.
170     * @hide
171     */
172    public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2;  // see InputDispatcher.h
173
174    private InputManager(IInputManager im) {
175        mIm = im;
176    }
177
178    /**
179     * Gets an instance of the input manager.
180     *
181     * @return The input manager instance.
182     *
183     * @hide
184     */
185    public static InputManager getInstance() {
186        synchronized (InputManager.class) {
187            if (sInstance == null) {
188                IBinder b = ServiceManager.getService(Context.INPUT_SERVICE);
189                sInstance = new InputManager(IInputManager.Stub.asInterface(b));
190            }
191            return sInstance;
192        }
193    }
194
195    /**
196     * Gets information about the input device with the specified id.
197     * @param id The device id.
198     * @return The input device or null if not found.
199     */
200    public InputDevice getInputDevice(int id) {
201        synchronized (mInputDevicesLock) {
202            populateInputDevicesLocked();
203
204            int index = mInputDevices.indexOfKey(id);
205            if (index < 0) {
206                return null;
207            }
208
209            InputDevice inputDevice = mInputDevices.valueAt(index);
210            if (inputDevice == null) {
211                try {
212                    inputDevice = mIm.getInputDevice(id);
213                } catch (RemoteException ex) {
214                    throw new RuntimeException("Could not get input device information.", ex);
215                }
216                if (inputDevice != null) {
217                    mInputDevices.setValueAt(index, inputDevice);
218                }
219            }
220            return inputDevice;
221        }
222    }
223
224    /**
225     * Gets information about the input device with the specified descriptor.
226     * @param descriptor The input device descriptor.
227     * @return The input device or null if not found.
228     * @hide
229     */
230    public InputDevice getInputDeviceByDescriptor(String descriptor) {
231        if (descriptor == null) {
232            throw new IllegalArgumentException("descriptor must not be null.");
233        }
234
235        synchronized (mInputDevicesLock) {
236            populateInputDevicesLocked();
237
238            int numDevices = mInputDevices.size();
239            for (int i = 0; i < numDevices; i++) {
240                InputDevice inputDevice = mInputDevices.valueAt(i);
241                if (inputDevice == null) {
242                    int id = mInputDevices.keyAt(i);
243                    try {
244                        inputDevice = mIm.getInputDevice(id);
245                    } catch (RemoteException ex) {
246                        // Ignore the problem for the purposes of this method.
247                    }
248                    if (inputDevice == null) {
249                        continue;
250                    }
251                    mInputDevices.setValueAt(i, inputDevice);
252                }
253                if (descriptor.equals(inputDevice.getDescriptor())) {
254                    return inputDevice;
255                }
256            }
257            return null;
258        }
259    }
260
261    /**
262     * Gets the ids of all input devices in the system.
263     * @return The input device ids.
264     */
265    public int[] getInputDeviceIds() {
266        synchronized (mInputDevicesLock) {
267            populateInputDevicesLocked();
268
269            final int count = mInputDevices.size();
270            final int[] ids = new int[count];
271            for (int i = 0; i < count; i++) {
272                ids[i] = mInputDevices.keyAt(i);
273            }
274            return ids;
275        }
276    }
277
278    /**
279     * Registers an input device listener to receive notifications about when
280     * input devices are added, removed or changed.
281     *
282     * @param listener The listener to register.
283     * @param handler The handler on which the listener should be invoked, or null
284     * if the listener should be invoked on the calling thread's looper.
285     *
286     * @see #unregisterInputDeviceListener
287     */
288    public void registerInputDeviceListener(InputDeviceListener listener, Handler handler) {
289        if (listener == null) {
290            throw new IllegalArgumentException("listener must not be null");
291        }
292
293        synchronized (mInputDevicesLock) {
294            int index = findInputDeviceListenerLocked(listener);
295            if (index < 0) {
296                mInputDeviceListeners.add(new InputDeviceListenerDelegate(listener, handler));
297            }
298        }
299    }
300
301    /**
302     * Unregisters an input device listener.
303     *
304     * @param listener The listener to unregister.
305     *
306     * @see #registerInputDeviceListener
307     */
308    public void unregisterInputDeviceListener(InputDeviceListener listener) {
309        if (listener == null) {
310            throw new IllegalArgumentException("listener must not be null");
311        }
312
313        synchronized (mInputDevicesLock) {
314            int index = findInputDeviceListenerLocked(listener);
315            if (index >= 0) {
316                InputDeviceListenerDelegate d = mInputDeviceListeners.get(index);
317                d.removeCallbacksAndMessages(null);
318                mInputDeviceListeners.remove(index);
319            }
320        }
321    }
322
323    private int findInputDeviceListenerLocked(InputDeviceListener listener) {
324        final int numListeners = mInputDeviceListeners.size();
325        for (int i = 0; i < numListeners; i++) {
326            if (mInputDeviceListeners.get(i).mListener == listener) {
327                return i;
328            }
329        }
330        return -1;
331    }
332
333    /**
334     * Gets information about all supported keyboard layouts.
335     * <p>
336     * The input manager consults the built-in keyboard layouts as well
337     * as all keyboard layouts advertised by applications using a
338     * {@link #ACTION_QUERY_KEYBOARD_LAYOUTS} broadcast receiver.
339     * </p>
340     *
341     * @return A list of all supported keyboard layouts.
342     *
343     * @hide
344     */
345    public KeyboardLayout[] getKeyboardLayouts() {
346        try {
347            return mIm.getKeyboardLayouts();
348        } catch (RemoteException ex) {
349            Log.w(TAG, "Could not get list of keyboard layout informations.", ex);
350            return new KeyboardLayout[0];
351        }
352    }
353
354    /**
355     * Gets the keyboard layout with the specified descriptor.
356     *
357     * @param keyboardLayoutDescriptor The keyboard layout descriptor, as returned by
358     * {@link KeyboardLayout#getDescriptor()}.
359     * @return The keyboard layout, or null if it could not be loaded.
360     *
361     * @hide
362     */
363    public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) {
364        if (keyboardLayoutDescriptor == null) {
365            throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
366        }
367
368        try {
369            return mIm.getKeyboardLayout(keyboardLayoutDescriptor);
370        } catch (RemoteException ex) {
371            Log.w(TAG, "Could not get keyboard layout information.", ex);
372            return null;
373        }
374    }
375
376    /**
377     * Gets the current keyboard layout descriptor for the specified input
378     * device.
379     *
380     * @param identifier Identifier for the input device
381     * @return The keyboard layout descriptor, or null if no keyboard layout has
382     *         been set.
383     * @hide
384     */
385    public String getCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier) {
386        try {
387            return mIm.getCurrentKeyboardLayoutForInputDevice(identifier);
388        } catch (RemoteException ex) {
389            Log.w(TAG, "Could not get current keyboard layout for input device.", ex);
390            return null;
391        }
392    }
393
394    /**
395     * Sets the current keyboard layout descriptor for the specified input
396     * device.
397     * <p>
398     * This method may have the side-effect of causing the input device in
399     * question to be reconfigured.
400     * </p>
401     *
402     * @param identifier The identifier for the input device.
403     * @param keyboardLayoutDescriptor The keyboard layout descriptor to use,
404     *            must not be null.
405     * @hide
406     */
407    public void setCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
408            String keyboardLayoutDescriptor) {
409        if (identifier == null) {
410            throw new IllegalArgumentException("identifier must not be null");
411        }
412        if (keyboardLayoutDescriptor == null) {
413            throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
414        }
415
416        try {
417            mIm.setCurrentKeyboardLayoutForInputDevice(identifier,
418                    keyboardLayoutDescriptor);
419        } catch (RemoteException ex) {
420            Log.w(TAG, "Could not set current keyboard layout for input device.", ex);
421        }
422    }
423
424    /**
425     * Gets all keyboard layout descriptors that are enabled for the specified
426     * input device.
427     *
428     * @param identifier The identifier for the input device.
429     * @return The keyboard layout descriptors.
430     * @hide
431     */
432    public String[] getKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
433        if (identifier == null) {
434            throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
435        }
436
437        try {
438            return mIm.getKeyboardLayoutsForInputDevice(identifier);
439        } catch (RemoteException ex) {
440            Log.w(TAG, "Could not get keyboard layouts for input device.", ex);
441            return ArrayUtils.emptyArray(String.class);
442        }
443    }
444
445    /**
446     * Adds the keyboard layout descriptor for the specified input device.
447     * <p>
448     * This method may have the side-effect of causing the input device in
449     * question to be reconfigured.
450     * </p>
451     *
452     * @param identifier The identifier for the input device.
453     * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to
454     *            add.
455     * @hide
456     */
457    public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
458            String keyboardLayoutDescriptor) {
459        if (identifier == null) {
460            throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
461        }
462        if (keyboardLayoutDescriptor == null) {
463            throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
464        }
465
466        try {
467            mIm.addKeyboardLayoutForInputDevice(identifier, keyboardLayoutDescriptor);
468        } catch (RemoteException ex) {
469            Log.w(TAG, "Could not add keyboard layout for input device.", ex);
470        }
471    }
472
473    /**
474     * Removes the keyboard layout descriptor for the specified input device.
475     * <p>
476     * This method may have the side-effect of causing the input device in
477     * question to be reconfigured.
478     * </p>
479     *
480     * @param identifier The identifier for the input device.
481     * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to
482     *            remove.
483     * @hide
484     */
485    public void removeKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
486            String keyboardLayoutDescriptor) {
487        if (identifier == null) {
488            throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
489        }
490        if (keyboardLayoutDescriptor == null) {
491            throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
492        }
493
494        try {
495            mIm.removeKeyboardLayoutForInputDevice(identifier, keyboardLayoutDescriptor);
496        } catch (RemoteException ex) {
497            Log.w(TAG, "Could not remove keyboard layout for input device.", ex);
498        }
499    }
500
501    /**
502     * Gets the TouchCalibration applied to the specified input device's coordinates.
503     *
504     * @param inputDeviceDescriptor The input device descriptor.
505     * @return The TouchCalibration currently assigned for use with the given
506     * input device. If none is set, an identity TouchCalibration is returned.
507     *
508     * @hide
509     */
510    public TouchCalibration getTouchCalibration(String inputDeviceDescriptor, int surfaceRotation) {
511        try {
512            return mIm.getTouchCalibrationForInputDevice(inputDeviceDescriptor, surfaceRotation);
513        } catch (RemoteException ex) {
514            Log.w(TAG, "Could not get calibration matrix for input device.", ex);
515            return TouchCalibration.IDENTITY;
516        }
517    }
518
519    /**
520     * Sets the TouchCalibration to apply to the specified input device's coordinates.
521     * <p>
522     * This method may have the side-effect of causing the input device in question
523     * to be reconfigured. Requires {@link android.Manifest.permissions.SET_INPUT_CALIBRATION}.
524     * </p>
525     *
526     * @param inputDeviceDescriptor The input device descriptor.
527     * @param calibration The calibration to be applied
528     *
529     * @hide
530     */
531    public void setTouchCalibration(String inputDeviceDescriptor, int surfaceRotation,
532            TouchCalibration calibration) {
533        try {
534            mIm.setTouchCalibrationForInputDevice(inputDeviceDescriptor, surfaceRotation, 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        /**
856         * @hide
857         */
858        @Override
859        public void vibrate(int uid, String opPkg, long milliseconds, AudioAttributes attributes) {
860            vibrate(new long[] { 0, milliseconds}, -1);
861        }
862
863        /**
864         * @hide
865         */
866        @Override
867        public void vibrate(int uid, String opPkg, long[] pattern, int repeat,
868                AudioAttributes attributes) {
869            if (repeat >= pattern.length) {
870                throw new ArrayIndexOutOfBoundsException();
871            }
872            try {
873                mIm.vibrate(mDeviceId, pattern, repeat, mToken);
874            } catch (RemoteException ex) {
875                Log.w(TAG, "Failed to vibrate.", ex);
876            }
877        }
878
879        @Override
880        public void cancel() {
881            try {
882                mIm.cancelVibrate(mDeviceId, mToken);
883            } catch (RemoteException ex) {
884                Log.w(TAG, "Failed to cancel vibration.", ex);
885            }
886        }
887    }
888}
889