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 * <receiver android:name=".InputDeviceReceiver"> 85 * <intent-filter> 86 * <action android:name="android.hardware.input.action.QUERY_KEYBOARD_LAYOUTS" /> 87 * </intent-filter> 88 * <meta-data android:name="android.hardware.input.metadata.KEYBOARD_LAYOUTS" 89 * android:resource="@xml/keyboard_layouts" /> 90 * </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><keyboard-layouts></code> that 95 * contains zero or more <code><keyboard-layout></code> elements. 96 * Each <code><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 * <?xml version="1.0" encoding="utf-8"?> 100 * <keyboard-layouts xmlns:android="http://schemas.android.com/apk/res/android"> 101 * <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 * </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