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