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