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