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