• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.simulator;
2 
3 import android.app.UiAutomation;
4 import android.os.Handler;
5 import android.os.Looper;
6 import android.os.SystemClock;
7 import android.view.KeyEvent;
8 import android.view.MotionEvent;
9 import androidx.test.platform.app.InstrumentationRegistry;
10 import java.awt.event.MouseAdapter;
11 import java.awt.event.MouseEvent;
12 import java.time.Duration;
13 import java.time.Instant;
14 import javax.swing.JMenuItem;
15 import javax.swing.JPopupMenu;
16 import javax.swing.SwingUtilities;
17 import org.robolectric.shadows.ShadowUiAutomation;
18 import org.robolectric.simulator.pluginapi.MenuCustomizer;
19 import org.robolectric.util.inject.Injector;
20 
21 /** A {@link MouseAdapter} that triggers Android {@link MotionEvent}s when the mouse is pressed. */
22 public class MouseHandler extends MouseAdapter {
23   private final UiAutomation uiAutomation =
24       InstrumentationRegistry.getInstrumentation().getUiAutomation();
25 
26   private boolean isPressed;
27   private Duration androidSystemClockTimeDelta;
28   private Instant downTime;
29 
30   private final Handler handler = new Handler(Looper.getMainLooper());
31 
32   private final JPopupMenu rightClickMenu;
33 
MouseHandler()34   public MouseHandler() {
35     rightClickMenu = new JPopupMenu();
36     rightClickMenu.add(getBackMenuItem());
37 
38     // Allow plugins to customize the right click menu.
39     Injector injector = new Injector.Builder(Looper.class.getClassLoader()).build();
40     MenuCustomizer menuCustomizer = injector.getInstance(MenuCustomizer.class);
41     menuCustomizer.customizePopupMenu(rightClickMenu);
42   }
43 
getBackMenuItem()44   private JMenuItem getBackMenuItem() {
45     JMenuItem sendBackMenuItem = new JMenuItem("Press back");
46     sendBackMenuItem.addActionListener(
47         e -> {
48           handler.post(
49               () -> {
50                 long eventTime = SystemClock.uptimeMillis();
51                 KeyEvent backKeyDown =
52                     new KeyEvent(
53                         eventTime,
54                         eventTime,
55                         KeyEvent.ACTION_DOWN,
56                         KeyEvent.KEYCODE_BACK,
57                         /* repeat= */ 0,
58                         0);
59                 ShadowUiAutomation.injectInputEvent(backKeyDown);
60 
61                 KeyEvent backKeyUp =
62                     new KeyEvent(
63                         eventTime,
64                         eventTime,
65                         KeyEvent.ACTION_UP,
66                         KeyEvent.KEYCODE_BACK,
67                         /* repeat= */ 0,
68                         0);
69                 ShadowUiAutomation.injectInputEvent(backKeyUp);
70               });
71         });
72 
73     return sendBackMenuItem;
74   }
75 
76   @Override
mousePressed(MouseEvent mouseEvent)77   public void mousePressed(MouseEvent mouseEvent) {
78     if (shouldHandle(mouseEvent)) {
79       isPressed = true;
80       androidSystemClockTimeDelta =
81           Duration.ofMillis(SystemClock.uptimeMillis()).minus(Duration.ofNanos(System.nanoTime()));
82       downTime = Instant.ofEpochMilli(mouseEvent.getWhen());
83       postMotionEvent(mouseEvent, MotionEvent.ACTION_DOWN);
84     }
85   }
86 
87   @Override
mouseDragged(MouseEvent mouseEvent)88   public void mouseDragged(MouseEvent mouseEvent) {
89     if (isPressed) {
90       postMotionEvent(mouseEvent, MotionEvent.ACTION_MOVE);
91     }
92   }
93 
94   @Override
mouseReleased(MouseEvent mouseEvent)95   public void mouseReleased(MouseEvent mouseEvent) {
96     if (shouldHandle(mouseEvent)) {
97       isPressed = false;
98       postMotionEvent(mouseEvent, MotionEvent.ACTION_UP);
99     } else if (SwingUtilities.isRightMouseButton(mouseEvent)) {
100       rightClickMenu.show(mouseEvent.getComponent(), mouseEvent.getX(), mouseEvent.getY());
101     }
102   }
103 
shouldHandle(MouseEvent mouseEvent)104   private boolean shouldHandle(MouseEvent mouseEvent) {
105     return !mouseEvent.isPopupTrigger() && SwingUtilities.isLeftMouseButton(mouseEvent);
106   }
107 
postMotionEvent(MouseEvent mouseEvent, int action)108   private void postMotionEvent(MouseEvent mouseEvent, int action) {
109     MotionEvent androidEvent = obtainMotionEvent(mouseEvent, action);
110     handler.post(() -> uiAutomation.injectInputEvent(androidEvent, true));
111     mouseEvent.consume();
112   }
113 
obtainMotionEvent(MouseEvent mouseEvent, int action)114   private MotionEvent obtainMotionEvent(MouseEvent mouseEvent, int action) {
115     return MotionEvent.obtain(
116         toAndroidTime(downTime),
117         toAndroidTime(Instant.ofEpochMilli(mouseEvent.getWhen())),
118         action,
119         mouseEvent.getX(),
120         mouseEvent.getY(),
121         /* metaState= */ 0);
122   }
123 
toAndroidTime(Instant instant)124   private long toAndroidTime(Instant instant) {
125     return instant.minus(androidSystemClockTimeDelta).toEpochMilli();
126   }
127 }
128