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