• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.shadows;
2 
3 import static android.os.Build.VERSION.SDK_INT;
4 import static android.os.Build.VERSION_CODES.R;
5 import static android.os.Build.VERSION_CODES.TIRAMISU;
6 import static java.util.concurrent.TimeUnit.MILLISECONDS;
7 import static org.robolectric.util.reflector.Reflector.reflector;
8 
9 import android.hardware.input.InputManager;
10 import android.util.SparseArray;
11 import android.view.InputDevice;
12 import android.view.InputEvent;
13 import android.view.KeyEvent;
14 import android.view.MotionEvent;
15 import android.view.VerifiedKeyEvent;
16 import android.view.VerifiedMotionEvent;
17 import org.robolectric.annotation.ClassName;
18 import org.robolectric.annotation.Implementation;
19 import org.robolectric.annotation.Implements;
20 import org.robolectric.annotation.RealObject;
21 import org.robolectric.annotation.Resetter;
22 import org.robolectric.util.ReflectionHelpers;
23 import org.robolectric.util.reflector.Accessor;
24 import org.robolectric.util.reflector.ForType;
25 import org.robolectric.versioning.AndroidVersions.U;
26 
27 /** Shadow for {@link InputManager} */
28 @Implements(value = InputManager.class)
29 public class ShadowInputManager {
30 
31   @RealObject InputManager realInputManager;
32 
33   @Implementation
injectInputEvent(InputEvent event, int mode)34   protected boolean injectInputEvent(InputEvent event, int mode) {
35     // ignore
36     return true;
37   }
38 
39   @Implementation
deviceHasKeys(int id, int[] keyCodes)40   protected boolean[] deviceHasKeys(int id, int[] keyCodes) {
41     return new boolean[keyCodes.length];
42   }
43 
44   /** Used in {@link InputDevice#getDeviceIds()} */
45   @Implementation
getInputDeviceIds()46   protected int[] getInputDeviceIds() {
47     if (!ReflectionHelpers.hasField(InputManager.class, "mInputDevices")) {
48       return new int[0];
49     }
50 
51     SparseArray<InputDevice> inputDevices = getInputDevices();
52     if (inputDevices == null) {
53       return new int[0];
54     }
55 
56     int[] ids = new int[inputDevices.size()];
57     for (int i = 0; i < inputDevices.size(); i++) {
58       ids[i] = inputDevices.get(i).getId();
59     }
60 
61     return ids;
62   }
63 
64   @Implementation(maxSdk = TIRAMISU)
populateInputDevicesLocked()65   protected void populateInputDevicesLocked() throws ClassNotFoundException {
66     if (ReflectionHelpers.getField(realInputManager, "mInputDevicesChangedListener") == null) {
67       ReflectionHelpers.setField(
68           realInputManager,
69           "mInputDevicesChangedListener",
70           ReflectionHelpers.callConstructor(
71               Class.forName("android.hardware.input.InputManager$InputDevicesChangedListener")));
72     }
73 
74     if (getInputDevices() == null) {
75       final int[] ids = realInputManager.getInputDeviceIds();
76 
77       SparseArray<InputDevice> inputDevices = new SparseArray<>();
78       for (int i = 0; i < ids.length; i++) {
79         inputDevices.put(ids[i], null);
80       }
81       setInputDevices(inputDevices);
82     }
83   }
84 
getInputDevices()85   private SparseArray<InputDevice> getInputDevices() {
86     return reflector(InputManagerReflector.class, realInputManager).getInputDevices();
87   }
88 
setInputDevices(SparseArray<InputDevice> devices)89   private void setInputDevices(SparseArray<InputDevice> devices) {
90     reflector(InputManagerReflector.class, realInputManager).setInputDevices(devices);
91   }
92 
93   /**
94    * Provides a local java implementation, since the real implementation is in system server +
95    * native code.
96    */
97   @Implementation(minSdk = R)
verifyInputEvent( InputEvent inputEvent)98   protected @ClassName("android.view.VerifiedInputEvent") Object verifyInputEvent(
99       InputEvent inputEvent) {
100     if (inputEvent instanceof MotionEvent) {
101       MotionEvent motionEvent = (MotionEvent) inputEvent;
102       return new VerifiedMotionEvent(
103           motionEvent.getDeviceId(),
104           MILLISECONDS.toNanos(motionEvent.getEventTime()),
105           motionEvent.getSource(),
106           motionEvent.getDisplayId(),
107           motionEvent.getRawX(),
108           motionEvent.getRawY(),
109           motionEvent.getActionMasked(),
110           MILLISECONDS.toNanos(motionEvent.getDownTime()),
111           motionEvent.getFlags(),
112           motionEvent.getMetaState(),
113           motionEvent.getButtonState());
114     } else if (inputEvent instanceof KeyEvent) {
115       KeyEvent keyEvent = (KeyEvent) inputEvent;
116       return new VerifiedKeyEvent(
117           keyEvent.getDeviceId(),
118           MILLISECONDS.toNanos(keyEvent.getEventTime()),
119           keyEvent.getSource(),
120           keyEvent.getDisplayId(),
121           keyEvent.getAction(),
122           MILLISECONDS.toNanos(keyEvent.getDownTime()),
123           keyEvent.getFlags(),
124           keyEvent.getKeyCode(),
125           keyEvent.getScanCode(),
126           keyEvent.getMetaState(),
127           keyEvent.getRepeatCount());
128     } else {
129       throw new IllegalArgumentException("unknown input event: " + inputEvent.getClass().getName());
130     }
131   }
132 
133   @Resetter
reset()134   public static void reset() {
135     if (SDK_INT < U.SDK_INT) {
136       ReflectionHelpers.setStaticField(InputManager.class, "sInstance", null);
137     }
138   }
139 
140   @ForType(InputManager.class)
141   interface InputManagerReflector {
142     @Accessor("mInputDevices")
getInputDevices()143     SparseArray<InputDevice> getInputDevices();
144 
145     @Accessor("mInputDevices")
setInputDevices(SparseArray<InputDevice> devices)146     void setInputDevices(SparseArray<InputDevice> devices);
147   }
148 }
149