/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.hardware.input; import com.android.internal.os.SomeArgs; import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.content.Context; import android.media.AudioAttributes; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.Vibrator; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.util.Log; import android.util.SparseArray; import android.view.InputDevice; import android.view.InputEvent; import android.view.PointerIcon; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; /** * Provides information about input devices and available key layouts. *

* Get an instance of this class by calling * {@link android.content.Context#getSystemService(java.lang.String) * Context.getSystemService()} with the argument * {@link android.content.Context#INPUT_SERVICE}. *

*/ public final class InputManager { private static final String TAG = "InputManager"; private static final boolean DEBUG = false; private static final int MSG_DEVICE_ADDED = 1; private static final int MSG_DEVICE_REMOVED = 2; private static final int MSG_DEVICE_CHANGED = 3; private static InputManager sInstance; private final IInputManager mIm; // Guarded by mInputDevicesLock private final Object mInputDevicesLock = new Object(); private SparseArray mInputDevices; private InputDevicesChangedListener mInputDevicesChangedListener; private final ArrayList mInputDeviceListeners = new ArrayList(); // Guarded by mTabletModeLock private final Object mTabletModeLock = new Object(); private TabletModeChangedListener mTabletModeChangedListener; private List mOnTabletModeChangedListeners; /** * Broadcast Action: Query available keyboard layouts. *

* The input manager service locates available keyboard layouts * by querying broadcast receivers that are registered for this action. * An application can offer additional keyboard layouts to the user * by declaring a suitable broadcast receiver in its manifest. *

* Here is an example broadcast receiver declaration that an application * might include in its AndroidManifest.xml to advertise keyboard layouts. * The meta-data specifies a resource that contains a description of each keyboard * layout that is provided by the application. *


     * <receiver android:name=".InputDeviceReceiver"
     *         android:label="@string/keyboard_layouts_label">
     *     <intent-filter>
     *         <action android:name="android.hardware.input.action.QUERY_KEYBOARD_LAYOUTS" />
     *     </intent-filter>
     *     <meta-data android:name="android.hardware.input.metadata.KEYBOARD_LAYOUTS"
     *             android:resource="@xml/keyboard_layouts" />
     * </receiver>
     * 
*

* In the above example, the @xml/keyboard_layouts resource refers to * an XML resource whose root element is <keyboard-layouts> that * contains zero or more <keyboard-layout> elements. * Each <keyboard-layout> element specifies the name, label, and location * of a key character map for a particular keyboard layout. The label on the receiver * is used to name the collection of keyboard layouts provided by this receiver in the * keyboard layout settings. *


     * <?xml version="1.0" encoding="utf-8"?>
     * <keyboard-layouts xmlns:android="http://schemas.android.com/apk/res/android">
     *     <keyboard-layout android:name="keyboard_layout_english_us"
     *             android:label="@string/keyboard_layout_english_us_label"
     *             android:keyboardLayout="@raw/keyboard_layout_english_us" />
     * </keyboard-layouts>
     * 
*

* The android:name attribute specifies an identifier by which * the keyboard layout will be known in the package. * The android:label attribute specifies a human-readable descriptive * label to describe the keyboard layout in the user interface, such as "English (US)". * The android:keyboardLayout attribute refers to a * * key character map resource that defines the keyboard layout. *

*/ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_QUERY_KEYBOARD_LAYOUTS = "android.hardware.input.action.QUERY_KEYBOARD_LAYOUTS"; /** * Metadata Key: Keyboard layout metadata associated with * {@link #ACTION_QUERY_KEYBOARD_LAYOUTS}. *

* Specifies the resource id of a XML resource that describes the keyboard * layouts that are provided by the application. *

*/ public static final String META_DATA_KEYBOARD_LAYOUTS = "android.hardware.input.metadata.KEYBOARD_LAYOUTS"; /** * Pointer Speed: The minimum (slowest) pointer speed (-7). * @hide */ public static final int MIN_POINTER_SPEED = -7; /** * Pointer Speed: The maximum (fastest) pointer speed (7). * @hide */ public static final int MAX_POINTER_SPEED = 7; /** * Pointer Speed: The default pointer speed (0). * @hide */ public static final int DEFAULT_POINTER_SPEED = 0; /** * Input Event Injection Synchronization Mode: None. * Never blocks. Injection is asynchronous and is assumed always to be successful. * @hide */ public static final int INJECT_INPUT_EVENT_MODE_ASYNC = 0; // see InputDispatcher.h /** * Input Event Injection Synchronization Mode: Wait for result. * Waits for previous events to be dispatched so that the input dispatcher can * determine whether input event injection will be permitted based on the current * input focus. Does not wait for the input event to finish being handled * by the application. * @hide */ public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT = 1; // see InputDispatcher.h /** * Input Event Injection Synchronization Mode: Wait for finish. * Waits for the event to be delivered to the application and handled. * @hide */ public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2; // see InputDispatcher.h /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef({SWITCH_STATE_UNKNOWN, SWITCH_STATE_OFF, SWITCH_STATE_ON}) public @interface SwitchState {} /** * Switch State: Unknown. * * The system has yet to report a valid value for the switch. * @hide */ public static final int SWITCH_STATE_UNKNOWN = -1; /** * Switch State: Off. * @hide */ public static final int SWITCH_STATE_OFF = 0; /** * Switch State: On. * @hide */ public static final int SWITCH_STATE_ON = 1; private InputManager(IInputManager im) { mIm = im; } /** * Gets an instance of the input manager. * * @return The input manager instance. * * @hide */ public static InputManager getInstance() { synchronized (InputManager.class) { if (sInstance == null) { IBinder b = ServiceManager.getService(Context.INPUT_SERVICE); sInstance = new InputManager(IInputManager.Stub.asInterface(b)); } return sInstance; } } /** * Gets information about the input device with the specified id. * @param id The device id. * @return The input device or null if not found. */ public InputDevice getInputDevice(int id) { synchronized (mInputDevicesLock) { populateInputDevicesLocked(); int index = mInputDevices.indexOfKey(id); if (index < 0) { return null; } InputDevice inputDevice = mInputDevices.valueAt(index); if (inputDevice == null) { try { inputDevice = mIm.getInputDevice(id); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } if (inputDevice != null) { mInputDevices.setValueAt(index, inputDevice); } } return inputDevice; } } /** * Gets information about the input device with the specified descriptor. * @param descriptor The input device descriptor. * @return The input device or null if not found. * @hide */ public InputDevice getInputDeviceByDescriptor(String descriptor) { if (descriptor == null) { throw new IllegalArgumentException("descriptor must not be null."); } synchronized (mInputDevicesLock) { populateInputDevicesLocked(); int numDevices = mInputDevices.size(); for (int i = 0; i < numDevices; i++) { InputDevice inputDevice = mInputDevices.valueAt(i); if (inputDevice == null) { int id = mInputDevices.keyAt(i); try { inputDevice = mIm.getInputDevice(id); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } if (inputDevice == null) { continue; } mInputDevices.setValueAt(i, inputDevice); } if (descriptor.equals(inputDevice.getDescriptor())) { return inputDevice; } } return null; } } /** * Gets the ids of all input devices in the system. * @return The input device ids. */ public int[] getInputDeviceIds() { synchronized (mInputDevicesLock) { populateInputDevicesLocked(); final int count = mInputDevices.size(); final int[] ids = new int[count]; for (int i = 0; i < count; i++) { ids[i] = mInputDevices.keyAt(i); } return ids; } } /** * Registers an input device listener to receive notifications about when * input devices are added, removed or changed. * * @param listener The listener to register. * @param handler The handler on which the listener should be invoked, or null * if the listener should be invoked on the calling thread's looper. * * @see #unregisterInputDeviceListener */ public void registerInputDeviceListener(InputDeviceListener listener, Handler handler) { if (listener == null) { throw new IllegalArgumentException("listener must not be null"); } synchronized (mInputDevicesLock) { populateInputDevicesLocked(); int index = findInputDeviceListenerLocked(listener); if (index < 0) { mInputDeviceListeners.add(new InputDeviceListenerDelegate(listener, handler)); } } } /** * Unregisters an input device listener. * * @param listener The listener to unregister. * * @see #registerInputDeviceListener */ public void unregisterInputDeviceListener(InputDeviceListener listener) { if (listener == null) { throw new IllegalArgumentException("listener must not be null"); } synchronized (mInputDevicesLock) { int index = findInputDeviceListenerLocked(listener); if (index >= 0) { InputDeviceListenerDelegate d = mInputDeviceListeners.get(index); d.removeCallbacksAndMessages(null); mInputDeviceListeners.remove(index); } } } private int findInputDeviceListenerLocked(InputDeviceListener listener) { final int numListeners = mInputDeviceListeners.size(); for (int i = 0; i < numListeners; i++) { if (mInputDeviceListeners.get(i).mListener == listener) { return i; } } return -1; } /** * Queries whether the device is in tablet mode. * * @return The tablet switch state which is one of {@link #SWITCH_STATE_UNKNOWN}, * {@link #SWITCH_STATE_OFF} or {@link #SWITCH_STATE_ON}. * @hide */ @SwitchState public int isInTabletMode() { try { return mIm.isInTabletMode(); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } /** * Register a tablet mode changed listener. * * @param listener The listener to register. * @param handler The handler on which the listener should be invoked, or null * if the listener should be invoked on the calling thread's looper. * @hide */ public void registerOnTabletModeChangedListener( OnTabletModeChangedListener listener, Handler handler) { if (listener == null) { throw new IllegalArgumentException("listener must not be null"); } synchronized (mTabletModeLock) { if (mOnTabletModeChangedListeners == null) { initializeTabletModeListenerLocked(); } int idx = findOnTabletModeChangedListenerLocked(listener); if (idx < 0) { OnTabletModeChangedListenerDelegate d = new OnTabletModeChangedListenerDelegate(listener, handler); mOnTabletModeChangedListeners.add(d); } } } /** * Unregister a tablet mode changed listener. * * @param listener The listener to unregister. * @hide */ public void unregisterOnTabletModeChangedListener(OnTabletModeChangedListener listener) { if (listener == null) { throw new IllegalArgumentException("listener must not be null"); } synchronized (mTabletModeLock) { int idx = findOnTabletModeChangedListenerLocked(listener); if (idx >= 0) { OnTabletModeChangedListenerDelegate d = mOnTabletModeChangedListeners.remove(idx); d.removeCallbacksAndMessages(null); } } } private void initializeTabletModeListenerLocked() { final TabletModeChangedListener listener = new TabletModeChangedListener(); try { mIm.registerTabletModeChangedListener(listener); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } mTabletModeChangedListener = listener; mOnTabletModeChangedListeners = new ArrayList<>(); } private int findOnTabletModeChangedListenerLocked(OnTabletModeChangedListener listener) { final int N = mOnTabletModeChangedListeners.size(); for (int i = 0; i < N; i++) { if (mOnTabletModeChangedListeners.get(i).mListener == listener) { return i; } } return -1; } /** * Gets information about all supported keyboard layouts. *

* The input manager consults the built-in keyboard layouts as well * as all keyboard layouts advertised by applications using a * {@link #ACTION_QUERY_KEYBOARD_LAYOUTS} broadcast receiver. *

* * @return A list of all supported keyboard layouts. * * @hide */ public KeyboardLayout[] getKeyboardLayouts() { try { return mIm.getKeyboardLayouts(); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } /** * Gets information about all supported keyboard layouts appropriate * for a specific input device. *

* The input manager consults the built-in keyboard layouts as well * as all keyboard layouts advertised by applications using a * {@link #ACTION_QUERY_KEYBOARD_LAYOUTS} broadcast receiver. *

* * @return A list of all supported keyboard layouts for a specific * input device. * * @hide */ public KeyboardLayout[] getKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) { try { return mIm.getKeyboardLayoutsForInputDevice(identifier); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } /** * Gets the keyboard layout with the specified descriptor. * * @param keyboardLayoutDescriptor The keyboard layout descriptor, as returned by * {@link KeyboardLayout#getDescriptor()}. * @return The keyboard layout, or null if it could not be loaded. * * @hide */ public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) { if (keyboardLayoutDescriptor == null) { throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null"); } try { return mIm.getKeyboardLayout(keyboardLayoutDescriptor); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } /** * Gets the current keyboard layout descriptor for the specified input * device. * * @param identifier Identifier for the input device * @return The keyboard layout descriptor, or null if no keyboard layout has * been set. * @hide */ public String getCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier) { try { return mIm.getCurrentKeyboardLayoutForInputDevice(identifier); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } /** * Sets the current keyboard layout descriptor for the specified input * device. *

* This method may have the side-effect of causing the input device in * question to be reconfigured. *

* * @param identifier The identifier for the input device. * @param keyboardLayoutDescriptor The keyboard layout descriptor to use, * must not be null. * @hide */ public void setCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, String keyboardLayoutDescriptor) { if (identifier == null) { throw new IllegalArgumentException("identifier must not be null"); } if (keyboardLayoutDescriptor == null) { throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null"); } try { mIm.setCurrentKeyboardLayoutForInputDevice(identifier, keyboardLayoutDescriptor); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } /** * Gets all keyboard layout descriptors that are enabled for the specified * input device. * * @param identifier The identifier for the input device. * @return The keyboard layout descriptors. * @hide */ public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) { if (identifier == null) { throw new IllegalArgumentException("inputDeviceDescriptor must not be null"); } try { return mIm.getEnabledKeyboardLayoutsForInputDevice(identifier); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } /** * Adds the keyboard layout descriptor for the specified input device. *

* This method may have the side-effect of causing the input device in * question to be reconfigured. *

* * @param identifier The identifier for the input device. * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to * add. * @hide */ public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, String keyboardLayoutDescriptor) { if (identifier == null) { throw new IllegalArgumentException("inputDeviceDescriptor must not be null"); } if (keyboardLayoutDescriptor == null) { throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null"); } try { mIm.addKeyboardLayoutForInputDevice(identifier, keyboardLayoutDescriptor); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } /** * Removes the keyboard layout descriptor for the specified input device. *

* This method may have the side-effect of causing the input device in * question to be reconfigured. *

* * @param identifier The identifier for the input device. * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to * remove. * @hide */ public void removeKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, String keyboardLayoutDescriptor) { if (identifier == null) { throw new IllegalArgumentException("inputDeviceDescriptor must not be null"); } if (keyboardLayoutDescriptor == null) { throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null"); } try { mIm.removeKeyboardLayoutForInputDevice(identifier, keyboardLayoutDescriptor); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } /** * Gets the keyboard layout for the specified input device and IME subtype. * * @param identifier The identifier for the input device. * @param inputMethodInfo The input method. * @param inputMethodSubtype The input method subtype. {@code null} if this input method does * not support any subtype. * * @return The associated {@link KeyboardLayout}, or null if one has not been set. * * @hide */ @Nullable public KeyboardLayout getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, InputMethodInfo inputMethodInfo, @Nullable InputMethodSubtype inputMethodSubtype) { try { return mIm.getKeyboardLayoutForInputDevice( identifier, inputMethodInfo, inputMethodSubtype); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } /** * Sets the keyboard layout for the specified input device and IME subtype pair. * * @param identifier The identifier for the input device. * @param inputMethodInfo The input method with which to associate the keyboard layout. * @param inputMethodSubtype The input method subtype which which to associate the keyboard * layout. {@code null} if this input method does not support any subtype. * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to set * * @hide */ public void setKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, InputMethodInfo inputMethodInfo, @Nullable InputMethodSubtype inputMethodSubtype, String keyboardLayoutDescriptor) { try { mIm.setKeyboardLayoutForInputDevice(identifier, inputMethodInfo, inputMethodSubtype, keyboardLayoutDescriptor); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } /** * Gets the TouchCalibration applied to the specified input device's coordinates. * * @param inputDeviceDescriptor The input device descriptor. * @return The TouchCalibration currently assigned for use with the given * input device. If none is set, an identity TouchCalibration is returned. * * @hide */ public TouchCalibration getTouchCalibration(String inputDeviceDescriptor, int surfaceRotation) { try { return mIm.getTouchCalibrationForInputDevice(inputDeviceDescriptor, surfaceRotation); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } /** * Sets the TouchCalibration to apply to the specified input device's coordinates. *

* This method may have the side-effect of causing the input device in question * to be reconfigured. Requires {@link android.Manifest.permissions.SET_INPUT_CALIBRATION}. *

* * @param inputDeviceDescriptor The input device descriptor. * @param calibration The calibration to be applied * * @hide */ public void setTouchCalibration(String inputDeviceDescriptor, int surfaceRotation, TouchCalibration calibration) { try { mIm.setTouchCalibrationForInputDevice(inputDeviceDescriptor, surfaceRotation, calibration); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } /** * Gets the mouse pointer speed. *

* Only returns the permanent mouse pointer speed. Ignores any temporary pointer * speed set by {@link #tryPointerSpeed}. *

* * @param context The application context. * @return The pointer speed as a value between {@link #MIN_POINTER_SPEED} and * {@link #MAX_POINTER_SPEED}, or the default value {@link #DEFAULT_POINTER_SPEED}. * * @hide */ public int getPointerSpeed(Context context) { int speed = DEFAULT_POINTER_SPEED; try { speed = Settings.System.getInt(context.getContentResolver(), Settings.System.POINTER_SPEED); } catch (SettingNotFoundException snfe) { } return speed; } /** * Sets the mouse pointer speed. *

* Requires {@link android.Manifest.permissions.WRITE_SETTINGS}. *

* * @param context The application context. * @param speed The pointer speed as a value between {@link #MIN_POINTER_SPEED} and * {@link #MAX_POINTER_SPEED}, or the default value {@link #DEFAULT_POINTER_SPEED}. * * @hide */ public void setPointerSpeed(Context context, int speed) { if (speed < MIN_POINTER_SPEED || speed > MAX_POINTER_SPEED) { throw new IllegalArgumentException("speed out of range"); } Settings.System.putInt(context.getContentResolver(), Settings.System.POINTER_SPEED, speed); } /** * Changes the mouse pointer speed temporarily, but does not save the setting. *

* Requires {@link android.Manifest.permission.SET_POINTER_SPEED}. *

* * @param speed The pointer speed as a value between {@link #MIN_POINTER_SPEED} and * {@link #MAX_POINTER_SPEED}, or the default value {@link #DEFAULT_POINTER_SPEED}. * * @hide */ public void tryPointerSpeed(int speed) { if (speed < MIN_POINTER_SPEED || speed > MAX_POINTER_SPEED) { throw new IllegalArgumentException("speed out of range"); } try { mIm.tryPointerSpeed(speed); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } /** * Queries the framework about whether any physical keys exist on the * any keyboard attached to the device that are capable of producing the given * array of key codes. * * @param keyCodes The array of key codes to query. * @return A new array of the same size as the key codes array whose elements * are set to true if at least one attached keyboard supports the corresponding key code * at the same index in the key codes array. * * @hide */ public boolean[] deviceHasKeys(int[] keyCodes) { return deviceHasKeys(-1, keyCodes); } /** * Queries the framework about whether any physical keys exist on the * any keyboard attached to the device that are capable of producing the given * array of key codes. * * @param id The id of the device to query. * @param keyCodes The array of key codes to query. * @return A new array of the same size as the key codes array whose elements are set to true * if the given device could produce the corresponding key code at the same index in the key * codes array. * * @hide */ public boolean[] deviceHasKeys(int id, int[] keyCodes) { boolean[] ret = new boolean[keyCodes.length]; try { mIm.hasKeys(id, InputDevice.SOURCE_ANY, keyCodes, ret); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } return ret; } /** * Injects an input event into the event system on behalf of an application. * The synchronization mode determines whether the method blocks while waiting for * input injection to proceed. *

* Requires {@link android.Manifest.permission.INJECT_EVENTS} to inject into * windows that are owned by other applications. *

* Make sure you correctly set the event time and input source of the event * before calling this method. *

* * @param event The event to inject. * @param mode The synchronization mode. One of: * {@link #INJECT_INPUT_EVENT_MODE_ASYNC}, * {@link #INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT}, or * {@link #INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH}. * @return True if input event injection succeeded. * * @hide */ public boolean injectInputEvent(InputEvent event, int mode) { if (event == null) { throw new IllegalArgumentException("event must not be null"); } if (mode != INJECT_INPUT_EVENT_MODE_ASYNC && mode != INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH && mode != INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT) { throw new IllegalArgumentException("mode is invalid"); } try { return mIm.injectInputEvent(event, mode); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } /** * Changes the mouse pointer's icon shape into the specified id. * * @param iconId The id of the pointer graphic, as a value between * {@link PointerIcon.TYPE_ARROW} and {@link PointerIcon.TYPE_GRABBING}. * * @hide */ public void setPointerIconType(int iconId) { try { mIm.setPointerIconType(iconId); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } /** @hide */ public void setCustomPointerIcon(PointerIcon icon) { try { mIm.setCustomPointerIcon(icon); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } private void populateInputDevicesLocked() { if (mInputDevicesChangedListener == null) { final InputDevicesChangedListener listener = new InputDevicesChangedListener(); try { mIm.registerInputDevicesChangedListener(listener); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } mInputDevicesChangedListener = listener; } if (mInputDevices == null) { final int[] ids; try { ids = mIm.getInputDeviceIds(); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } mInputDevices = new SparseArray(); for (int i = 0; i < ids.length; i++) { mInputDevices.put(ids[i], null); } } } private void onInputDevicesChanged(int[] deviceIdAndGeneration) { if (DEBUG) { Log.d(TAG, "Received input devices changed."); } synchronized (mInputDevicesLock) { for (int i = mInputDevices.size(); --i > 0; ) { final int deviceId = mInputDevices.keyAt(i); if (!containsDeviceId(deviceIdAndGeneration, deviceId)) { if (DEBUG) { Log.d(TAG, "Device removed: " + deviceId); } mInputDevices.removeAt(i); sendMessageToInputDeviceListenersLocked(MSG_DEVICE_REMOVED, deviceId); } } for (int i = 0; i < deviceIdAndGeneration.length; i += 2) { final int deviceId = deviceIdAndGeneration[i]; int index = mInputDevices.indexOfKey(deviceId); if (index >= 0) { final InputDevice device = mInputDevices.valueAt(index); if (device != null) { final int generation = deviceIdAndGeneration[i + 1]; if (device.getGeneration() != generation) { if (DEBUG) { Log.d(TAG, "Device changed: " + deviceId); } mInputDevices.setValueAt(index, null); sendMessageToInputDeviceListenersLocked(MSG_DEVICE_CHANGED, deviceId); } } } else { if (DEBUG) { Log.d(TAG, "Device added: " + deviceId); } mInputDevices.put(deviceId, null); sendMessageToInputDeviceListenersLocked(MSG_DEVICE_ADDED, deviceId); } } } } private void sendMessageToInputDeviceListenersLocked(int what, int deviceId) { final int numListeners = mInputDeviceListeners.size(); for (int i = 0; i < numListeners; i++) { InputDeviceListenerDelegate listener = mInputDeviceListeners.get(i); listener.sendMessage(listener.obtainMessage(what, deviceId, 0)); } } private static boolean containsDeviceId(int[] deviceIdAndGeneration, int deviceId) { for (int i = 0; i < deviceIdAndGeneration.length; i += 2) { if (deviceIdAndGeneration[i] == deviceId) { return true; } } return false; } private void onTabletModeChanged(long whenNanos, boolean inTabletMode) { if (DEBUG) { Log.d(TAG, "Received tablet mode changed: " + "whenNanos=" + whenNanos + ", inTabletMode=" + inTabletMode); } synchronized (mTabletModeLock) { final int N = mOnTabletModeChangedListeners.size(); for (int i = 0; i < N; i++) { OnTabletModeChangedListenerDelegate listener = mOnTabletModeChangedListeners.get(i); listener.sendTabletModeChanged(whenNanos, inTabletMode); } } } /** * Gets a vibrator service associated with an input device, assuming it has one. * @return The vibrator, never null. * @hide */ public Vibrator getInputDeviceVibrator(int deviceId) { return new InputDeviceVibrator(deviceId); } /** * Listens for changes in input devices. */ public interface InputDeviceListener { /** * Called whenever an input device has been added to the system. * Use {@link InputManager#getInputDevice} to get more information about the device. * * @param deviceId The id of the input device that was added. */ void onInputDeviceAdded(int deviceId); /** * Called whenever an input device has been removed from the system. * * @param deviceId The id of the input device that was removed. */ void onInputDeviceRemoved(int deviceId); /** * Called whenever the properties of an input device have changed since they * were last queried. Use {@link InputManager#getInputDevice} to get * a fresh {@link InputDevice} object with the new properties. * * @param deviceId The id of the input device that changed. */ void onInputDeviceChanged(int deviceId); } private final class InputDevicesChangedListener extends IInputDevicesChangedListener.Stub { @Override public void onInputDevicesChanged(int[] deviceIdAndGeneration) throws RemoteException { InputManager.this.onInputDevicesChanged(deviceIdAndGeneration); } } private static final class InputDeviceListenerDelegate extends Handler { public final InputDeviceListener mListener; public InputDeviceListenerDelegate(InputDeviceListener listener, Handler handler) { super(handler != null ? handler.getLooper() : Looper.myLooper()); mListener = listener; } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_DEVICE_ADDED: mListener.onInputDeviceAdded(msg.arg1); break; case MSG_DEVICE_REMOVED: mListener.onInputDeviceRemoved(msg.arg1); break; case MSG_DEVICE_CHANGED: mListener.onInputDeviceChanged(msg.arg1); break; } } } /** @hide */ public interface OnTabletModeChangedListener { /** * Called whenever the device goes into or comes out of tablet mode. * * @param whenNanos The time at which the device transitioned into or * out of tablet mode. This is given in nanoseconds in the * {@link SystemClock#uptimeMillis} time base. */ void onTabletModeChanged(long whenNanos, boolean inTabletMode); } private final class TabletModeChangedListener extends ITabletModeChangedListener.Stub { @Override public void onTabletModeChanged(long whenNanos, boolean inTabletMode) { InputManager.this.onTabletModeChanged(whenNanos, inTabletMode); } } private static final class OnTabletModeChangedListenerDelegate extends Handler { private static final int MSG_TABLET_MODE_CHANGED = 0; public final OnTabletModeChangedListener mListener; public OnTabletModeChangedListenerDelegate( OnTabletModeChangedListener listener, Handler handler) { super(handler != null ? handler.getLooper() : Looper.myLooper()); mListener = listener; } public void sendTabletModeChanged(long whenNanos, boolean inTabletMode) { SomeArgs args = SomeArgs.obtain(); args.argi1 = (int) (whenNanos & 0xFFFFFFFF); args.argi2 = (int) (whenNanos >> 32); args.arg1 = (Boolean) inTabletMode; obtainMessage(MSG_TABLET_MODE_CHANGED, args).sendToTarget(); } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_TABLET_MODE_CHANGED: SomeArgs args = (SomeArgs) msg.obj; long whenNanos = (args.argi1 & 0xFFFFFFFFl) | ((long) args.argi2 << 32); boolean inTabletMode = (boolean) args.arg1; mListener.onTabletModeChanged(whenNanos, inTabletMode); break; } } } private final class InputDeviceVibrator extends Vibrator { private final int mDeviceId; private final Binder mToken; public InputDeviceVibrator(int deviceId) { mDeviceId = deviceId; mToken = new Binder(); } @Override public boolean hasVibrator() { return true; } /** * @hide */ @Override public void vibrate(int uid, String opPkg, long milliseconds, AudioAttributes attributes) { vibrate(new long[] { 0, milliseconds}, -1); } /** * @hide */ @Override public void vibrate(int uid, String opPkg, long[] pattern, int repeat, AudioAttributes attributes) { if (repeat >= pattern.length) { throw new ArrayIndexOutOfBoundsException(); } try { mIm.vibrate(mDeviceId, pattern, repeat, mToken); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } @Override public void cancel() { try { mIm.cancelVibrate(mDeviceId, mToken); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } } }