/*
|
* 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 android.annotation.IntDef;
|
import android.annotation.SdkConstant;
|
import android.annotation.SdkConstant.SdkConstantType;
|
import android.annotation.SystemService;
|
import android.annotation.UnsupportedAppUsage;
|
import android.content.Context;
|
import android.media.AudioAttributes;
|
import android.os.Binder;
|
import android.os.Build;
|
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.ServiceManager.ServiceNotFoundException;
|
import android.os.SystemClock;
|
import android.os.VibrationEffect;
|
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.InputMonitor;
|
import android.view.MotionEvent;
|
import android.view.PointerIcon;
|
|
import com.android.internal.os.SomeArgs;
|
|
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.
|
*/
|
@SystemService(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;
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
|
private final IInputManager mIm;
|
|
// Guarded by mInputDevicesLock
|
private final Object mInputDevicesLock = new Object();
|
private SparseArray<InputDevice> mInputDevices;
|
private InputDevicesChangedListener mInputDevicesChangedListener;
|
private final ArrayList<InputDeviceListenerDelegate> mInputDeviceListeners =
|
new ArrayList<InputDeviceListenerDelegate>();
|
|
// Guarded by mTabletModeLock
|
private final Object mTabletModeLock = new Object();
|
private TabletModeChangedListener mTabletModeChangedListener;
|
private List<OnTabletModeChangedListenerDelegate> mOnTabletModeChangedListeners;
|
|
/**
|
* Broadcast Action: Query available keyboard layouts.
|
* <p>
|
* 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.
|
* </p><p>
|
* 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.
|
* <pre><code>
|
* <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>
|
* </code></pre>
|
* </p><p>
|
* In the above example, the <code>@xml/keyboard_layouts</code> resource refers to
|
* an XML resource whose root element is <code><keyboard-layouts></code> that
|
* contains zero or more <code><keyboard-layout></code> elements.
|
* Each <code><keyboard-layout></code> 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.
|
* <pre><code>
|
* <?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>
|
* </pre></code>
|
* </p><p>
|
* The <code>android:name</code> attribute specifies an identifier by which
|
* the keyboard layout will be known in the package.
|
* The <code>android:label</code> attribute specifies a human-readable descriptive
|
* label to describe the keyboard layout in the user interface, such as "English (US)".
|
* The <code>android:keyboardLayout</code> attribute refers to a
|
* <a href="http://source.android.com/tech/input/key-character-map-files.html">
|
* key character map</a> resource that defines the keyboard layout.
|
* </p>
|
*/
|
@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}.
|
* <p>
|
* Specifies the resource id of a XML resource that describes the keyboard
|
* layouts that are provided by the application.
|
* </p>
|
*/
|
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
|
*/
|
@UnsupportedAppUsage
|
public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2; // see InputDispatcher.h
|
|
/** @hide */
|
@Retention(RetentionPolicy.SOURCE)
|
@IntDef(prefix = { "SWITCH_STATE_" }, value = {
|
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
|
*/
|
@UnsupportedAppUsage
|
public static InputManager getInstance() {
|
synchronized (InputManager.class) {
|
if (sInstance == null) {
|
try {
|
sInstance = new InputManager(IInputManager.Stub
|
.asInterface(ServiceManager.getServiceOrThrow(Context.INPUT_SERVICE)));
|
} catch (ServiceNotFoundException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
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;
|
}
|
}
|
|
/**
|
* Returns true if an input device is enabled. Should return true for most
|
* situations. Some system apps may disable an input device, for
|
* example to prevent unwanted touch events.
|
*
|
* @param id The input device Id.
|
*
|
* @hide
|
*/
|
public boolean isInputDeviceEnabled(int id) {
|
try {
|
return mIm.isInputDeviceEnabled(id);
|
} catch (RemoteException ex) {
|
Log.w(TAG, "Could not check enabled status of input device with id = " + id);
|
throw ex.rethrowFromSystemServer();
|
}
|
}
|
|
/**
|
* Enables an InputDevice.
|
* <p>
|
* Requires {@link android.Manifest.permissions.DISABLE_INPUT_DEVICE}.
|
* </p>
|
*
|
* @param id The input device Id.
|
*
|
* @hide
|
*/
|
public void enableInputDevice(int id) {
|
try {
|
mIm.enableInputDevice(id);
|
} catch (RemoteException ex) {
|
Log.w(TAG, "Could not enable input device with id = " + id);
|
throw ex.rethrowFromSystemServer();
|
}
|
}
|
|
/**
|
* Disables an InputDevice.
|
* <p>
|
* Requires {@link android.Manifest.permissions.DISABLE_INPUT_DEVICE}.
|
* </p>
|
*
|
* @param id The input device Id.
|
*
|
* @hide
|
*/
|
public void disableInputDevice(int id) {
|
try {
|
mIm.disableInputDevice(id);
|
} catch (RemoteException ex) {
|
Log.w(TAG, "Could not disable input device with id = " + id);
|
throw ex.rethrowFromSystemServer();
|
}
|
}
|
|
/**
|
* 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.
|
* <p>
|
* 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.
|
* </p>
|
*
|
* @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.
|
* <p>
|
* 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.
|
* </p>
|
*
|
* @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.
|
* <p>
|
* This method may have the side-effect of causing the input device in
|
* question to be reconfigured.
|
* </p>
|
*
|
* @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.
|
* <p>
|
* This method may have the side-effect of causing the input device in
|
* question to be reconfigured.
|
* </p>
|
*
|
* @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.
|
* <p>
|
* This method may have the side-effect of causing the input device in
|
* question to be reconfigured.
|
* </p>
|
*
|
* @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 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.
|
* <p>
|
* 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}.
|
* </p>
|
*
|
* @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.
|
* <p>
|
* Only returns the permanent mouse pointer speed. Ignores any temporary pointer
|
* speed set by {@link #tryPointerSpeed}.
|
* </p>
|
*
|
* @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.
|
* <p>
|
* Requires {@link android.Manifest.permissions.WRITE_SETTINGS}.
|
* </p>
|
*
|
* @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.
|
* <p>
|
* Requires {@link android.Manifest.permission.SET_POINTER_SPEED}.
|
* </p>
|
*
|
* @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.
|
* <p>
|
* Requires {@link android.Manifest.permission.INJECT_EVENTS} to inject into
|
* windows that are owned by other applications.
|
* </p><p>
|
* Make sure you correctly set the event time and input source of the event
|
* before calling this method.
|
* </p>
|
*
|
* @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
|
*/
|
@UnsupportedAppUsage
|
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
|
*/
|
@UnsupportedAppUsage
|
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();
|
}
|
}
|
|
/**
|
* Request or release pointer capture.
|
* <p>
|
* When in capturing mode, the pointer icon disappears and all mouse events are dispatched to
|
* the window which has requested the capture. Relative position changes are available through
|
* {@link MotionEvent#getX} and {@link MotionEvent#getY}.
|
*
|
* @param enable true when requesting pointer capture, false when releasing.
|
*
|
* @hide
|
*/
|
public void requestPointerCapture(IBinder windowToken, boolean enable) {
|
try {
|
mIm.requestPointerCapture(windowToken, enable);
|
} catch (RemoteException ex) {
|
throw ex.rethrowFromSystemServer();
|
}
|
}
|
|
/**
|
* Monitor input on the specified display for gestures.
|
*
|
* @hide
|
*/
|
public InputMonitor monitorGestureInput(String name, int displayId) {
|
try {
|
return mIm.monitorGestureInput(name, displayId);
|
} 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<InputDevice>();
|
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;
|
}
|
|
@Override
|
public boolean hasAmplitudeControl() {
|
return false;
|
}
|
|
/**
|
* @hide
|
*/
|
@Override
|
public void vibrate(int uid, String opPkg, VibrationEffect effect,
|
String reason, AudioAttributes attributes) {
|
long[] pattern;
|
int repeat;
|
if (effect instanceof VibrationEffect.OneShot) {
|
VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect;
|
pattern = new long[] { 0, oneShot.getDuration() };
|
repeat = -1;
|
} else if (effect instanceof VibrationEffect.Waveform) {
|
VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect;
|
pattern = waveform.getTimings();
|
repeat = waveform.getRepeatIndex();
|
} else {
|
// TODO: Add support for prebaked effects
|
Log.w(TAG, "Pre-baked effects aren't supported on input devices");
|
return;
|
}
|
|
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();
|
}
|
}
|
}
|
}
|