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