content: /*
   * 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>
       * &lt;receiver android:name=".InputDeviceReceiver"
       *         android:label="@string/keyboard_layouts_label">
       *     &lt;intent-filter>
       *         &lt;action android:name="android.hardware.input.action.QUERY_KEYBOARD_LAYOUTS" />
       *     &lt;/intent-filter>
       *     &lt;meta-data android:name="android.hardware.input.metadata.KEYBOARD_LAYOUTS"
       *             android:resource="@xml/keyboard_layouts" />
       * &lt;/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>&lt;keyboard-layouts></code> that
       * contains zero or more <code>&lt;keyboard-layout></code> elements.
       * Each <code>&lt;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>
       * &lt;?xml version="1.0" encoding="utf-8"?>
       * &lt;keyboard-layouts xmlns:android="http://schemas.android.com/apk/res/android">
       *     &lt;keyboard-layout android:name="keyboard_layout_english_us"
       *             android:label="@string/keyboard_layout_english_us_label"
       *             android:keyboardLayout="@raw/keyboard_layout_english_us" />
       * &lt;/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();
              }
          }
      }
  }
  