1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package android.widget.espresso;
18 
19 import static com.android.internal.util.Preconditions.checkNotNull;
20 
21 import android.annotation.IntDef;
22 import android.os.SystemClock;
23 import android.view.InputDevice;
24 import android.view.KeyEvent;
25 import android.view.MotionEvent;
26 
27 import androidx.test.espresso.InjectEventSecurityException;
28 import androidx.test.espresso.UiController;
29 
30 import java.lang.annotation.Retention;
31 import java.lang.annotation.RetentionPolicy;
32 import java.util.Iterator;
33 
34 /**
35  * Class to wrap an UiController to overwrite source of motion events to SOURCE_MOUSE.
36  * Note that this doesn't change the tool type.
37  */
38 public final class MouseUiController implements UiController {
39     @Retention(RetentionPolicy.SOURCE)
40     @IntDef({MotionEvent.BUTTON_PRIMARY, MotionEvent.BUTTON_SECONDARY, MotionEvent.BUTTON_TERTIARY})
41     public @interface MouseButton {}
42 
43     private final UiController mUiController;
44     @MouseButton
45     private final int mButton;
46 
MouseUiController(UiController uiController)47     public MouseUiController(UiController uiController) {
48         this(uiController, MotionEvent.BUTTON_PRIMARY);
49     }
50 
51     /**
52      * Constructs MouseUiController.
53      *
54      * @param uiController the uiController to wrap
55      * @param button the button to be used for generating input events.
56      */
MouseUiController(UiController uiController, @MouseButton int button)57     public MouseUiController(UiController uiController, @MouseButton int button) {
58         mUiController = checkNotNull(uiController);
59         mButton = button;
60     }
61 
62     @Override
injectKeyEvent(KeyEvent event)63     public boolean injectKeyEvent(KeyEvent event) throws InjectEventSecurityException {
64         return mUiController.injectKeyEvent(event);
65     }
66 
67     @Override
injectMotionEvent(MotionEvent event)68     public boolean injectMotionEvent(MotionEvent event) throws InjectEventSecurityException {
69         // Modify the event to mimic mouse event.
70         event.setSource(InputDevice.SOURCE_MOUSE);
71         if (event.getActionMasked() != MotionEvent.ACTION_UP) {
72             event.setButtonState(mButton);
73         } else {
74             event.setButtonState(0);
75         }
76         return mUiController.injectMotionEvent(event);
77     }
78 
79     /**
80      * Copied from latest {@link androidx.test.espresso.UiController}, since current
81      * {@link androidx.test.espresso.UiController#injectMotionEventSequence(Iterable)} seems not a
82      * default method.
83      */
84     @Override
injectMotionEventSequence(Iterable<MotionEvent> events)85     public boolean injectMotionEventSequence(Iterable<MotionEvent> events)
86             throws InjectEventSecurityException {
87         android.util.Log.w(
88                 "UIC",
89                 "Using default injectMotionEventSeq() - this may not inject a sequence properly. "
90                 + "If wrapping UIController please override this method and delegate.");
91         Iterator<MotionEvent> mei = events.iterator();
92         boolean success = true;
93         while (mei.hasNext()) {
94             MotionEvent me = mei.next();
95             if (me.getEventTime() - SystemClock.uptimeMillis() > 10) {
96                 // Because the loopMainThreadForAtLeast is overkill for waiting, intentially only
97                 // call it with a smaller amount of milliseconds as best effort
98                 loopMainThreadForAtLeast(10);
99             }
100             success &= injectMotionEvent(me);
101         }
102         return success;
103     }
104 
105     @Override
injectString(String str)106     public boolean injectString(String str) throws InjectEventSecurityException {
107         return mUiController.injectString(str);
108     }
109 
110     @Override
loopMainThreadForAtLeast(long millisDelay)111     public void loopMainThreadForAtLeast(long millisDelay) {
112         mUiController.loopMainThreadForAtLeast(millisDelay);
113     }
114 
115     @Override
loopMainThreadUntilIdle()116     public void loopMainThreadUntilIdle() {
117         mUiController.loopMainThreadUntilIdle();
118     }
119 }
120