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