• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.hardware.input;
18 
19 import com.android.internal.util.ArrayUtils;
20 
21 import android.annotation.SdkConstant;
22 import android.annotation.SdkConstant.SdkConstantType;
23 import android.content.Context;
24 import android.os.Binder;
25 import android.os.Handler;
26 import android.os.IBinder;
27 import android.os.Looper;
28 import android.os.Message;
29 import android.os.RemoteException;
30 import android.os.ServiceManager;
31 import android.os.Vibrator;
32 import android.provider.Settings;
33 import android.provider.Settings.SettingNotFoundException;
34 import android.util.Log;
35 import android.util.SparseArray;
36 import android.view.InputDevice;
37 import android.view.InputEvent;
38 
39 import 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  */
50 public 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 
InputManager(IInputManager im)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      */
getInstance()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      */
getInputDevice(int id)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      */
getInputDeviceByDescriptor(String descriptor)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      */
getInputDeviceIds()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      */
registerInputDeviceListener(InputDeviceListener listener, Handler handler)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      */
unregisterInputDeviceListener(InputDeviceListener listener)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 
findInputDeviceListenerLocked(InputDeviceListener listener)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      */
getKeyboardLayouts()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      */
getKeyboardLayout(String keyboardLayoutDescriptor)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      */
getCurrentKeyboardLayoutForInputDevice(String inputDeviceDescriptor)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      */
setCurrentKeyboardLayoutForInputDevice(String inputDeviceDescriptor, String keyboardLayoutDescriptor)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      */
getKeyboardLayoutsForInputDevice(String inputDeviceDescriptor)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      */
addKeyboardLayoutForInputDevice(String inputDeviceDescriptor, String keyboardLayoutDescriptor)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      */
removeKeyboardLayoutForInputDevice(String inputDeviceDescriptor, String keyboardLayoutDescriptor)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      */
getPointerSpeed(Context context)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      */
setPointerSpeed(Context context, int speed)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      */
tryPointerSpeed(int speed)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      */
deviceHasKeys(int[] keyCodes)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      */
injectInputEvent(InputEvent event, int mode)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 
populateInputDevicesLocked()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 
onInputDevicesChanged(int[] deviceIdAndGeneration)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 
sendMessageToInputDeviceListenersLocked(int what, int deviceId)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 
containsDeviceId(int[] deviceIdAndGeneration, int deviceId)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      */
getInputDeviceVibrator(int deviceId)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          */
onInputDeviceAdded(int deviceId)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          */
onInputDeviceRemoved(int deviceId)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          */
onInputDeviceChanged(int deviceId)747         void onInputDeviceChanged(int deviceId);
748     }
749 
750     private final class InputDevicesChangedListener extends IInputDevicesChangedListener.Stub {
751         @Override
onInputDevicesChanged(int[] deviceIdAndGeneration)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 
InputDeviceListenerDelegate(InputDeviceListener listener, Handler handler)760         public InputDeviceListenerDelegate(InputDeviceListener listener, Handler handler) {
761             super(handler != null ? handler.getLooper() : Looper.myLooper());
762             mListener = listener;
763         }
764 
765         @Override
handleMessage(Message msg)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 
InputDeviceVibrator(int deviceId)785         public InputDeviceVibrator(int deviceId) {
786             mDeviceId = deviceId;
787             mToken = new Binder();
788         }
789 
790         @Override
hasVibrator()791         public boolean hasVibrator() {
792             return true;
793         }
794 
795         @Override
vibrate(long milliseconds)796         public void vibrate(long milliseconds) {
797             vibrate(new long[] { 0, milliseconds}, -1);
798         }
799 
800         @Override
vibrate(long[] pattern, int repeat)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
cancel()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