├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── ant.properties ├── build.xml ├── custom_rules.xml ├── libs ├── jackson-annotations-2.2.2.jar ├── jackson-core-2.2.2.jar ├── jackson-databind-2.2.2.jar ├── jsonrpc4j-0.28.jar ├── portlet-api-2.0.jar └── servlet-api-2.5.jar ├── local.properties ├── project.properties └── src └── com └── github └── uiautomatorstub ├── AutomatorHttpServer.java ├── AutomatorService.java ├── AutomatorServiceImpl.java ├── ConfiguratorInfo.java ├── DeviceInfo.java ├── Log.java ├── NotImplementedException.java ├── ObjInfo.java ├── Point.java ├── Rect.java ├── Selector.java ├── Stub.java └── watcher ├── ClickUiObjectWatcher.java ├── PressKeysWatcher.java └── SelectorWatcher.java /.gitignore: -------------------------------------------------------------------------------- 1 | .class 2 | bin 3 | .classpath 4 | .project 5 | .idea 6 | uiautomator-stub.iml 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "nanohttpd"] 2 | path = nanohttpd 3 | url = https://github.com/xiaocong/nanohttpd.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2013 Xiaocong He 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 20 | OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # It's moved to [android-uiautomator-server](https://github.com/xiaocong/android-uiautomator-server), which is based on android suppot library. 2 | 3 | # Purpose 4 | 5 | [UIAutomator](http://developer.android.com/tools/testing/testing_ui.html) is a 6 | great tool to perform Android UI testing, but to do it, you have to write java 7 | code, compile it, install the jar, and run. It's a complex steps for all 8 | testers... 9 | 10 | This project is to build a light weight jsonrpc server in Android device, so 11 | that we can just write PC side script to write UIAutomator tests. 12 | 13 | # Build 14 | 15 | - Update `local.properties` file with your android sdk path. 16 | - Set ANDROID_HOME env variable (`.bashrc` or `.bash_profile`). 17 | - Install Ant if you have not. 18 | - Run command: 19 | 20 | $ git submodule init 21 | $ git submodule update 22 | $ ant build # compile 23 | $ ant install # install jar file to device via adb 24 | 25 | # Run the jsonrcp server on Android device 26 | 27 | $ adb shell uiautomator runtest uiautomator-stub.jar bundle.jar -c com.github.uiautomatorstub.Stub 28 | $ adb forward tcp:9008 tcp:9008 # tcp forward 29 | 30 | # How to use 31 | 32 | Next is a python example using jsonrpclib. Before you run it, make sure install jsonrpclib via 33 | `pip install jsonrpclib`. 34 | 35 | ```python 36 | import jsonrpclib 37 | server = jsonrpclib.Server('http://localhost:9008/jsonrpc/0') 38 | 39 | server.wakeUp() 40 | server.pressKey("home") 41 | server.pressKey("back") 42 | ``` 43 | 44 | For convenicence, you can intall its python wrapper library [uiautomator](https://github.com/xiaocong/uiautomator). 45 | 46 | # Notes 47 | 48 | The jsonrpc API is still under discussion, so currently only some demo APIs have been implemented. 49 | If you have any idea, please email xiaocong@gmail.com or [submit tickets](https://github.com/xiaocong/uiautomator/issues/new). 50 | 51 | # Dependencies 52 | 53 | - [nanohttpd](https://github.com/NanoHttpd/nanohttpd) 54 | - [jsonrpc4j](https://code.google.com/p/jsonrpc4j/) 55 | -------------------------------------------------------------------------------- /ant.properties: -------------------------------------------------------------------------------- 1 | source.dir=src;nanohttpd/core/src/main/java 2 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 29 | 30 | 31 | 35 | 36 | 37 | 38 | 39 | 40 | 49 | 50 | 51 | 52 | 56 | 57 | 69 | 70 | 71 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /custom_rules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 42 | 43 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /libs/jackson-annotations-2.2.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaocong/android-uiautomator-jsonrpcserver/d16f938211435631ea25b2247685e8622c3376b6/libs/jackson-annotations-2.2.2.jar -------------------------------------------------------------------------------- /libs/jackson-core-2.2.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaocong/android-uiautomator-jsonrpcserver/d16f938211435631ea25b2247685e8622c3376b6/libs/jackson-core-2.2.2.jar -------------------------------------------------------------------------------- /libs/jackson-databind-2.2.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaocong/android-uiautomator-jsonrpcserver/d16f938211435631ea25b2247685e8622c3376b6/libs/jackson-databind-2.2.2.jar -------------------------------------------------------------------------------- /libs/jsonrpc4j-0.28.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaocong/android-uiautomator-jsonrpcserver/d16f938211435631ea25b2247685e8622c3376b6/libs/jsonrpc4j-0.28.jar -------------------------------------------------------------------------------- /libs/portlet-api-2.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaocong/android-uiautomator-jsonrpcserver/d16f938211435631ea25b2247685e8622c3376b6/libs/portlet-api-2.0.jar -------------------------------------------------------------------------------- /libs/servlet-api-2.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaocong/android-uiautomator-jsonrpcserver/d16f938211435631ea25b2247685e8622c3376b6/libs/servlet-api-2.5.jar -------------------------------------------------------------------------------- /local.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must *NOT* be checked into Version Control Systems, 5 | # as it contains information specific to your local configuration. 6 | 7 | # location of the SDK. This is only used by Ant 8 | # For customization when using a Version Control System, please read the 9 | # header note. 10 | sdk.dir=/opt/android-studio/sdk 11 | -------------------------------------------------------------------------------- /project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-19 15 | -------------------------------------------------------------------------------- /src/com/github/uiautomatorstub/AutomatorHttpServer.java: -------------------------------------------------------------------------------- 1 | package com.github.uiautomatorstub; 2 | 3 | import java.io.*; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | import android.os.Build; 8 | import com.android.uiautomator.core.UiDevice; 9 | import com.googlecode.jsonrpc4j.JsonRpcServer; 10 | 11 | import fi.iki.elonen.NanoHTTPD; 12 | 13 | public class AutomatorHttpServer extends NanoHTTPD { 14 | 15 | public AutomatorHttpServer(int port) { 16 | super(port); 17 | } 18 | 19 | private Map router = new HashMap(); 20 | 21 | public void route(String uri, JsonRpcServer rpc) { 22 | router.put(uri, rpc); 23 | } 24 | 25 | @Override 26 | public Response serve(String uri, Method method, 27 | Map headers, Map params, 28 | Map files) { 29 | Log.d(String.format("URI: %s, Method: %s, Header: %s, params, %s, files: %s", uri, method, headers, params, files)); 30 | 31 | if ("/stop".equals(uri)) { 32 | stop(); 33 | return new Response("Server stopped!!!"); 34 | } else if ("/0/screenshot".equals(uri)) { 35 | if (Build.VERSION.SDK_INT < 17) 36 | return new Response(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "API level less than 17."); 37 | File f = new File(AutomatorServiceImpl.STORAGE_PATH, "screenshot.png"); 38 | float scale = 1.0f; 39 | if (params.containsKey("scale")) { 40 | try { 41 | scale = Float.parseFloat(params.get("scale")); 42 | } catch (NumberFormatException e) { 43 | } 44 | } 45 | int quality = 100; 46 | if (params.containsKey("quality")) { 47 | try { 48 | quality = Integer.parseInt(params.get("quality")); 49 | } catch (NumberFormatException e) { 50 | } 51 | } 52 | UiDevice.getInstance().takeScreenshot(f, scale, quality); 53 | try { 54 | return new Response(Response.Status.OK, "image/png", new FileInputStream(f)); 55 | } catch (FileNotFoundException e) { 56 | Log.e(e.getMessage()); 57 | return new Response(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "Internal Server Error!!!"); 58 | } 59 | } else if (router.containsKey(uri)) { 60 | JsonRpcServer jsonRpcServer = router.get(uri); 61 | ByteArrayInputStream is = null; 62 | if (params.get("NanoHttpd.QUERY_STRING") != null) 63 | is = new ByteArrayInputStream(params.get("NanoHttpd.QUERY_STRING").getBytes()); 64 | else if (files.get("postData") != null) 65 | is = new ByteArrayInputStream(files.get("postData").getBytes()); 66 | else 67 | return new Response(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "Invalid http post data!"); 68 | ByteArrayOutputStream os = new ByteArrayOutputStream(); 69 | try { 70 | jsonRpcServer.handle(is, os); 71 | return new Response(Response.Status.OK, "application/json", new ByteArrayInputStream(os.toByteArray())); 72 | } catch (IOException e) { 73 | return new Response(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "Internal Server Error!!!"); 74 | } 75 | } else 76 | return new Response(Response.Status.NOT_FOUND, MIME_PLAINTEXT, "Not Found!!!"); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/com/github/uiautomatorstub/AutomatorService.java: -------------------------------------------------------------------------------- 1 | package com.github.uiautomatorstub; 2 | 3 | import android.os.RemoteException; 4 | 5 | import com.android.uiautomator.core.UiObjectNotFoundException; 6 | import com.googlecode.jsonrpc4j.JsonRpcErrors; 7 | import com.googlecode.jsonrpc4j.JsonRpcError; 8 | 9 | public interface AutomatorService { 10 | final static int ERROR_CODE_BASE = -32000; 11 | /** 12 | * It's to test if the service is alive. 13 | * @return 'pong' 14 | */ 15 | String ping(); 16 | 17 | /*************************************************************************** 18 | * Below section contains all methods from UiDevice. 19 | ***************************************************************************/ 20 | 21 | /** 22 | * Get the device info. 23 | * @return device info. 24 | */ 25 | DeviceInfo deviceInfo(); 26 | 27 | /** 28 | * Perform a click at arbitrary coordinates specified by the user. 29 | * @param x coordinate 30 | * @param y coordinate 31 | * @return true if the click succeeded else false 32 | */ 33 | boolean click(int x, int y); 34 | 35 | /** 36 | * Performs a swipe from one coordinate to another coordinate. You can control the smoothness and speed of the swipe by specifying the number of steps. Each step execution is throttled to 5 milliseconds per step, so for a 100 steps, the swipe will take around 0.5 seconds to complete. 37 | * @param startX X-axis value for the starting coordinate 38 | * @param startY Y-axis value for the starting coordinate 39 | * @param endX X-axis value for the ending coordinate 40 | * @param endY Y-axis value for the ending coordinate 41 | * @param steps is the number of steps for the swipe action 42 | * @return true if swipe is performed, false if the operation fails or the coordinates are invalid 43 | * @throws NotImplementedException 44 | */ 45 | @JsonRpcErrors({@JsonRpcError(exception=NotImplementedException.class, code=ERROR_CODE_BASE-3)}) 46 | boolean drag(int startX, int startY, int endX, int endY, int steps) throws NotImplementedException; 47 | 48 | /** 49 | * Performs a swipe from one coordinate to another using the number of steps to determine smoothness and speed. Each step execution is throttled to 5ms per step. So for a 100 steps, the swipe will take about 1/2 second to complete. 50 | * @param startX X-axis value for the starting coordinate 51 | * @param startY Y-axis value for the starting coordinate 52 | * @param endX X-axis value for the ending coordinate 53 | * @param endY Y-axis value for the ending coordinate 54 | * @param steps is the number of move steps sent to the system 55 | * @return false if the operation fails or the coordinates are invalid 56 | */ 57 | boolean swipe(int startX, int startY, int endX, int endY, int steps); 58 | 59 | /** 60 | * Helper method used for debugging to dump the current window's layout hierarchy. The file root location is /data/local/tmp 61 | * @param compressed use compressed layout hierarchy or not using setCompressedLayoutHeirarchy method. Ignore the parameter in case the API level lt 18. 62 | * @param filename the filename to be stored. 63 | * @return the absolute path name of dumped file. 64 | */ 65 | String dumpWindowHierarchy(boolean compressed, String filename); 66 | 67 | /** 68 | * Take a screenshot of current window and store it as PNG The screenshot is adjusted per screen rotation 69 | * @param filename where the PNG should be written to 70 | * @param scale scale the screenshot down if needed; 1.0f for original size 71 | * @param quality quality of the PNG compression; range: 0-100 72 | * @return the file name of the screenshot. null if failed. 73 | * @throws NotImplementedException 74 | */ 75 | @JsonRpcErrors({@JsonRpcError(exception=NotImplementedException.class, code=ERROR_CODE_BASE-3)}) 76 | String takeScreenshot(String filename, float scale, int quality) throws NotImplementedException; 77 | 78 | /** 79 | * Disables the sensors and freezes the device rotation at its current rotation state, or enable it. 80 | * @param freeze true to freeze the rotation, false to unfreeze the rotation. 81 | * @throws RemoteException 82 | */ 83 | @JsonRpcErrors({@JsonRpcError(exception=RemoteException.class, code=ERROR_CODE_BASE-1)}) 84 | void freezeRotation(boolean freeze) throws RemoteException; // freeze or unfreeze rotation, see also unfreezeRotation() 85 | 86 | /** 87 | * Simulates orienting the device to the left/right/natural and also freezes rotation by disabling the sensors. 88 | * @param dir Left or l, Right or r, Natural or n, case insensitive 89 | * @throws RemoteException 90 | * @throws NotImplementedException 91 | */ 92 | @JsonRpcErrors({@JsonRpcError(exception=RemoteException.class, code=ERROR_CODE_BASE-1), @JsonRpcError(exception=NotImplementedException.class, code=ERROR_CODE_BASE-3)}) 93 | void setOrientation(String dir) throws RemoteException, NotImplementedException; 94 | 95 | /** 96 | * Retrieves the text from the last UI traversal event received. 97 | * @return the text from the last UI traversal event received. 98 | */ 99 | String getLastTraversedText(); 100 | 101 | /** 102 | * Clears the text from the last UI traversal event. 103 | */ 104 | void clearLastTraversedText(); 105 | 106 | /** 107 | * Opens the notification shade. 108 | * @return true if successful, else return false 109 | * @throws NotImplementedException 110 | */ 111 | @JsonRpcErrors({@JsonRpcError(exception=NotImplementedException.class, code=ERROR_CODE_BASE-3)}) 112 | boolean openNotification() throws NotImplementedException; 113 | 114 | /** 115 | * Opens the Quick Settings shade. 116 | * @return true if successful, else return false 117 | * @throws NotImplementedException 118 | */ 119 | @JsonRpcErrors({@JsonRpcError(exception=NotImplementedException.class, code=ERROR_CODE_BASE-3)}) 120 | boolean openQuickSettings() throws NotImplementedException; 121 | 122 | /** 123 | * Checks if a specific registered UiWatcher has triggered. See registerWatcher(String, UiWatcher). If a UiWatcher runs and its checkForCondition() call returned true, then the UiWatcher is considered triggered. This is helpful if a watcher is detecting errors from ANR or crash dialogs and the test needs to know if a UiWatcher has been triggered. 124 | * @param watcherName the name of registered watcher. 125 | * @return true if triggered else false 126 | */ 127 | boolean hasWatcherTriggered(String watcherName); // We should implement some watchers to treat some blocking issues, e.g. force close dialog 128 | 129 | /** 130 | * Checks if any registered UiWatcher have triggered. 131 | * @return true if any UiWatcher have triggered else false. 132 | */ 133 | boolean hasAnyWatcherTriggered(); 134 | 135 | /** 136 | * Register a ClickUiObjectWatcher 137 | * @param name Watcher name 138 | * @param conditions If all UiObject in the conditions match, the watcher should be triggered. 139 | * @param target The target UiObject should be clicked if all conditions match. 140 | */ 141 | void registerClickUiObjectWatcher(String name, Selector[] conditions, Selector target); 142 | 143 | /** 144 | * Register a PressKeysWatcher 145 | * @param name Watcher name 146 | * @param conditions If all UiObject in the conditions match, the watcher should be triggered. 147 | * @param keys All keys will be pressed in sequence. 148 | */ 149 | void registerPressKeyskWatcher(String name, Selector[] conditions, String[] keys); 150 | 151 | /** 152 | * Removes a previously registered UiWatcher. 153 | * @param name Watcher name 154 | */ 155 | void removeWatcher(String name); 156 | 157 | /** 158 | * Resets a UiWatcher that has been triggered. If a UiWatcher runs and its checkForCondition() call returned true, then the UiWatcher is considered triggered. 159 | */ 160 | void resetWatcherTriggers(); 161 | 162 | /** 163 | * Force to run all watchers. 164 | */ 165 | void runWatchers(); 166 | 167 | /** 168 | * Get all registered UiWatchers 169 | * @return UiWatcher names 170 | */ 171 | String[] getWatchers(); 172 | 173 | /** 174 | * Simulates a short press using key name. 175 | * @param key possible key name is home, back, left, right, up, down, center, menu, search, enter, delete(or del), recent(recent apps), volume_up, volume_down, volume_mute, camera, power 176 | * @return true if successful, else return false 177 | * @throws RemoteException 178 | */ 179 | @JsonRpcErrors({@JsonRpcError(exception=RemoteException.class, code=ERROR_CODE_BASE-1)}) 180 | boolean pressKey(String key) throws RemoteException; 181 | 182 | /** 183 | * Simulates a short press using a key code. See KeyEvent. 184 | * @param keyCode the key code of the event. 185 | * @return true if successful, else return false 186 | */ 187 | boolean pressKeyCode(int keyCode); 188 | 189 | /** 190 | * Simulates a short press using a key code. See KeyEvent. 191 | * @param keyCode the key code of the event. 192 | * @param metaState an integer in which each bit set to 1 represents a pressed meta key 193 | * @return true if successful, else return false 194 | */ 195 | boolean pressKeyCode(int keyCode, int metaState); 196 | 197 | /** 198 | * This method simulates pressing the power button if the screen is OFF else it does nothing if the screen is already ON. If the screen was OFF and it just got turned ON, this method will insert a 500ms delay to allow the device time to wake up and accept input. 199 | * @throws RemoteException 200 | */ 201 | @JsonRpcErrors({@JsonRpcError(exception=RemoteException.class, code=ERROR_CODE_BASE-1)}) 202 | void wakeUp() throws RemoteException; 203 | 204 | /** 205 | * This method simply presses the power button if the screen is ON else it does nothing if the screen is already OFF. 206 | * @throws RemoteException 207 | */ 208 | @JsonRpcErrors({@JsonRpcError(exception=RemoteException.class, code=ERROR_CODE_BASE-1)}) 209 | void sleep() throws RemoteException; 210 | 211 | /** 212 | * Checks the power manager if the screen is ON. 213 | * @return true if the screen is ON else false 214 | * @throws RemoteException 215 | */ 216 | @JsonRpcErrors({@JsonRpcError(exception=RemoteException.class, code=ERROR_CODE_BASE-1)}) 217 | boolean isScreenOn() throws RemoteException; 218 | 219 | /** 220 | * Waits for the current application to idle. 221 | * @param timeout in milliseconds 222 | */ 223 | void waitForIdle(long timeout); 224 | 225 | /** 226 | * Waits for a window content update event to occur. If a package name for the window is specified, but the current window does not have the same package name, the function returns immediately. 227 | * @param packageName the specified window package name (can be null). If null, a window update from any front-end window will end the wait. 228 | * @param timeout the timeout for the wait 229 | * @return true if a window update occurred, false if timeout has elapsed or if the current window does not have the specified package name 230 | */ 231 | boolean waitForWindowUpdate (String packageName, long timeout); 232 | 233 | /*************************************************************************** 234 | * Below section contains all methods from UiObject. 235 | ***************************************************************************/ 236 | 237 | /** 238 | * Clears the existing text contents in an editable field. The UiSelector of this object must reference a UI element that is editable. When you call this method, the method first sets focus at the start edge of the field. The method then simulates a long-press to select the existing text, and deletes the selected text. If a "Select-All" option is displayed, the method will automatically attempt to use it to ensure full text selection. Note that it is possible that not all the text in the field is selected; for example, if the text contains separators such as spaces, slashes, at symbol etc. Also, not all editable fields support the long-press functionality. 239 | * @param obj the selector of the UiObject. 240 | * @throws UiObjectNotFoundException 241 | */ 242 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 243 | void clearTextField(Selector obj) throws UiObjectNotFoundException; 244 | 245 | /** 246 | * Reads the text property of the UI element 247 | * @param obj the selector of the UiObject. 248 | * @return text value of the current node represented by this UiObject 249 | * @throws UiObjectNotFoundException 250 | */ 251 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 252 | String getText(Selector obj) throws UiObjectNotFoundException; 253 | 254 | /** 255 | * Sets the text in an editable field, after clearing the field's content. The UiSelector selector of this object must reference a UI element that is editable. When you call this method, the method first simulates a click() on editable field to set focus. The method then clears the field's contents and injects your specified text into the field. If you want to capture the original contents of the field, call getText() first. You can then modify the text and use this method to update the field. 256 | * @param obj the selector of the UiObject. 257 | * @param text string to set 258 | * @return true if operation is successful 259 | * @throws UiObjectNotFoundException 260 | */ 261 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 262 | boolean setText(Selector obj, String text) throws UiObjectNotFoundException; 263 | 264 | /** 265 | * Performs a click at the center of the visible bounds of the UI element represented by this UiObject. 266 | * @param obj the target ui object. 267 | * @return true id successful else false 268 | * @throws UiObjectNotFoundException 269 | */ 270 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 271 | boolean click(Selector obj) throws UiObjectNotFoundException; 272 | 273 | /** 274 | * Clicks the bottom and right corner or top and left corner of the UI element 275 | * @param obj the target ui object. 276 | * @param corner "br"/"bottomright" means BottomRight, "tl"/"topleft" means TopLeft, "center" means Center. 277 | * @return true on success 278 | * @throws UiObjectNotFoundException 279 | */ 280 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 281 | boolean click(Selector obj, String corner) throws UiObjectNotFoundException; 282 | 283 | /** 284 | * Performs a click at the center of the visible bounds of the UI element represented by this UiObject and waits for window transitions. This method differ from click() only in that this method waits for a a new window transition as a result of the click. Some examples of a window transition: 285 | * - launching a new activity 286 | * - bringing up a pop-up menu 287 | * - bringing up a dialog 288 | * @param obj the target ui object. 289 | * @param timeout timeout before giving up on waiting for a new window 290 | * @return true if the event was triggered, else false 291 | * @throws UiObjectNotFoundException 292 | */ 293 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 294 | boolean clickAndWaitForNewWindow(Selector obj, long timeout) throws UiObjectNotFoundException; 295 | 296 | /** 297 | * Long clicks the center of the visible bounds of the UI element 298 | * @param obj the target ui object. 299 | * @return true if operation was successful 300 | * @throws UiObjectNotFoundException 301 | */ 302 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 303 | boolean longClick(Selector obj) throws UiObjectNotFoundException; 304 | 305 | /** 306 | * Long clicks bottom and right corner of the UI element 307 | * @param obj the target ui object. 308 | * @param corner "br"/"bottomright" means BottomRight, "tl"/"topleft" means TopLeft, "center" means Center. 309 | * @return true if operation was successful 310 | * @throws UiObjectNotFoundException 311 | */ 312 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 313 | boolean longClick(Selector obj, String corner) throws UiObjectNotFoundException; 314 | 315 | /** 316 | * Drags this object to a destination UiObject. The number of steps specified in your input parameter can influence the drag speed, and varying speeds may impact the results. Consider evaluating different speeds when using this method in your tests. 317 | * @param obj the ui object to be dragged. 318 | * @param destObj the ui object to be dragged to. 319 | * @param steps usually 40 steps. You can increase or decrease the steps to change the speed. 320 | * @return true if successful 321 | * @throws UiObjectNotFoundException 322 | * @throws NotImplementedException 323 | */ 324 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2), @JsonRpcError(exception=NotImplementedException.class, code=ERROR_CODE_BASE-3)}) 325 | boolean dragTo (Selector obj, Selector destObj, int steps) throws UiObjectNotFoundException, NotImplementedException; 326 | 327 | /** 328 | * Drags this object to arbitrary coordinates. The number of steps specified in your input parameter can influence the drag speed, and varying speeds may impact the results. Consider evaluating different speeds when using this method in your tests. 329 | * @param obj the ui object to be dragged. 330 | * @param destX the X-axis coordinate of destination. 331 | * @param destY the Y-axis coordinate of destination. 332 | * @param steps usually 40 steps. You can increase or decrease the steps to change the speed. 333 | * @return true if successful 334 | * @throws UiObjectNotFoundException 335 | * @throws NotImplementedException 336 | */ 337 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2), @JsonRpcError(exception=NotImplementedException.class, code=ERROR_CODE_BASE-3)}) 338 | boolean dragTo (Selector obj, int destX, int destY, int steps) throws UiObjectNotFoundException, NotImplementedException; 339 | 340 | /** 341 | * Check if view exists. This methods performs a waitForExists(long) with zero timeout. This basically returns immediately whether the view represented by this UiObject exists or not. 342 | * @param obj the ui object. 343 | * @return true if the view represented by this UiObject does exist 344 | */ 345 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 346 | boolean exist(Selector obj); 347 | 348 | /** 349 | * Get the object info. 350 | * @param obj the target ui object. 351 | * @return object info. 352 | * @throws UiObjectNotFoundException 353 | */ 354 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 355 | ObjInfo objInfo(Selector obj) throws UiObjectNotFoundException; 356 | 357 | /** 358 | * Get the count of the UiObject instances by the selector 359 | * @param obj the selector of the ui object 360 | * @return the count of instances. 361 | */ 362 | int count(Selector obj); 363 | 364 | /** 365 | * Get the info of all instance by the selector. 366 | * @param obj the selector of ui object. 367 | * @return array of object info. 368 | */ 369 | ObjInfo[] objInfoOfAllInstances(Selector obj); 370 | 371 | /** 372 | * Generates a two-pointer gesture with arbitrary starting and ending points. 373 | * @param obj the target ui object. ?? 374 | * @param startPoint1 start point of pointer 1 375 | * @param startPoint2 start point of pointer 2 376 | * @param endPoint1 end point of pointer 1 377 | * @param endPoint2 end point of pointer 2 378 | * @param steps the number of steps for the gesture. Steps are injected about 5 milliseconds apart, so 100 steps may take around 0.5 seconds to complete. 379 | * @return true if all touch events for this gesture are injected successfully, false otherwise 380 | * @throws UiObjectNotFoundException 381 | */ 382 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2), @JsonRpcError(exception=NotImplementedException.class, code=ERROR_CODE_BASE-3)}) 383 | boolean gesture(Selector obj, Point startPoint1, Point startPoint2, Point endPoint1, Point endPoint2, int steps) throws UiObjectNotFoundException, NotImplementedException; 384 | 385 | /** 386 | * Performs a two-pointer gesture, where each pointer moves diagonally toward the other, from the edges to the center of this UiObject . 387 | * @param obj the target ui object. 388 | * @param percent percentage of the object's diagonal length for the pinch gesture 389 | * @param steps the number of steps for the gesture. Steps are injected about 5 milliseconds apart, so 100 steps may take around 0.5 seconds to complete. 390 | * @return true if all touch events for this gesture are injected successfully, false otherwise 391 | * @throws UiObjectNotFoundException 392 | * @throws NotImplementedException 393 | */ 394 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2), @JsonRpcError(exception=NotImplementedException.class, code=ERROR_CODE_BASE-3)}) 395 | boolean pinchIn(Selector obj, int percent, int steps) throws UiObjectNotFoundException, NotImplementedException; 396 | 397 | /** 398 | * Performs a two-pointer gesture, where each pointer moves diagonally opposite across the other, from the center out towards the edges of the this UiObject. 399 | * @param obj the target ui object. 400 | * @param percent percentage of the object's diagonal length for the pinch gesture 401 | * @param steps the number of steps for the gesture. Steps are injected about 5 milliseconds apart, so 100 steps may take around 0.5 seconds to complete. 402 | * @return true if all touch events for this gesture are injected successfully, false otherwise 403 | * @throws UiObjectNotFoundException 404 | * @throws NotImplementedException 405 | */ 406 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2), @JsonRpcError(exception=NotImplementedException.class, code=ERROR_CODE_BASE-3)}) 407 | boolean pinchOut(Selector obj, int percent, int steps) throws UiObjectNotFoundException, NotImplementedException; 408 | 409 | /** 410 | * Performs the swipe up/down/left/right action on the UiObject 411 | * @param obj the target ui object. 412 | * @param dir "u"/"up", "d"/"down", "l"/"left", "r"/"right" 413 | * @param steps indicates the number of injected move steps into the system. Steps are injected about 5ms apart. So a 100 steps may take about 1/2 second to complete. 414 | * @return true of successful 415 | * @throws UiObjectNotFoundException 416 | */ 417 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 418 | boolean swipe(Selector obj, String dir, int steps) throws UiObjectNotFoundException; 419 | 420 | /** 421 | * Waits a specified length of time for a view to become visible. This method waits until the view becomes visible on the display, or until the timeout has elapsed. You can use this method in situations where the content that you want to select is not immediately displayed. 422 | * @param obj the target ui object 423 | * @param timeout time to wait (in milliseconds) 424 | * @return true if the view is displayed, else false if timeout elapsed while waiting 425 | */ 426 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 427 | boolean waitForExists (Selector obj, long timeout); 428 | 429 | /** 430 | * Waits a specified length of time for a view to become undetectable. This method waits until a view is no longer matchable, or until the timeout has elapsed. A view becomes undetectable when the UiSelector of the object is unable to find a match because the element has either changed its state or is no longer displayed. You can use this method when attempting to wait for some long operation to compete, such as downloading a large file or connecting to a remote server. 431 | * @param obj the target ui object 432 | * @param timeout time to wait (in milliseconds) 433 | * @return true if the element is gone before timeout elapsed, else false if timeout elapsed but a matching element is still found. 434 | */ 435 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 436 | boolean waitUntilGone (Selector obj, long timeout); 437 | 438 | /*************************************************************************** 439 | * Below section contains all methods from UiScrollable. 440 | ***************************************************************************/ 441 | 442 | /** 443 | * Performs a backwards fling action with the default number of fling steps (5). If the swipe direction is set to vertical, then the swipe will be performed from top to bottom. If the swipe direction is set to horizontal, then the swipes will be performed from left to right. Make sure to take into account devices configured with right-to-left languages like Arabic and Hebrew. 444 | * @param obj the selector of the scrollable object 445 | * @param isVertical vertical or horizontal 446 | * @return true if scrolled, and false if can't scroll anymore 447 | * @throws UiObjectNotFoundException 448 | */ 449 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 450 | boolean flingBackward(Selector obj, boolean isVertical) throws UiObjectNotFoundException; 451 | 452 | /** 453 | * Performs a forward fling with the default number of fling steps (5). If the swipe direction is set to vertical, then the swipes will be performed from bottom to top. If the swipe direction is set to horizontal, then the swipes will be performed from right to left. Make sure to take into account devices configured with right-to-left languages like Arabic and Hebrew. 454 | * @param obj the selector of the scrollable object 455 | * @param isVertical vertical or horizontal 456 | * @return true if scrolled, and false if can't scroll anymore 457 | * @throws UiObjectNotFoundException 458 | */ 459 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 460 | boolean flingForward(Selector obj, boolean isVertical) throws UiObjectNotFoundException; 461 | 462 | /** 463 | * Performs a fling gesture to reach the beginning of a scrollable layout element. The beginning can be at the top-most edge in the case of vertical controls, or the left-most edge for horizontal controls. Make sure to take into account devices configured with right-to-left languages like Arabic and Hebrew. 464 | * @param obj the selector of the scrollable object 465 | * @param isVertical vertical or horizontal 466 | * @param maxSwipes max swipes to achieve beginning. 467 | * @return true on scrolled, else false 468 | * @throws UiObjectNotFoundException 469 | */ 470 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 471 | boolean flingToBeginning (Selector obj, boolean isVertical, int maxSwipes) throws UiObjectNotFoundException; 472 | 473 | /** 474 | * Performs a fling gesture to reach the end of a scrollable layout element. The end can be at the bottom-most edge in the case of vertical controls, or the right-most edge for horizontal controls. Make sure to take into account devices configured with right-to-left languages like Arabic and Hebrew. 475 | * @param obj the selector of the scrollable object 476 | * @param isVertical vertical or horizontal 477 | * @param maxSwipes max swipes to achieve end. 478 | * @return true on scrolled, else false 479 | * @throws UiObjectNotFoundException 480 | */ 481 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 482 | boolean flingToEnd (Selector obj, boolean isVertical, int maxSwipes) throws UiObjectNotFoundException; 483 | 484 | /** 485 | * Performs a backward scroll. If the swipe direction is set to vertical, then the swipes will be performed from top to bottom. If the swipe direction is set to horizontal, then the swipes will be performed from left to right. Make sure to take into account devices configured with right-to-left languages like Arabic and Hebrew. 486 | * @param obj the selector of the scrollable object 487 | * @param isVertical vertical or horizontal 488 | * @param steps number of steps. Use this to control the speed of the scroll action. 489 | * @return true if scrolled, false if can't scroll anymore 490 | * @throws UiObjectNotFoundException 491 | */ 492 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 493 | boolean scrollBackward (Selector obj, boolean isVertical, int steps) throws UiObjectNotFoundException; 494 | 495 | /** 496 | * Performs a forward scroll with the default number of scroll steps (55). If the swipe direction is set to vertical, then the swipes will be performed from bottom to top. If the swipe direction is set to horizontal, then the swipes will be performed from right to left. Make sure to take into account devices configured with right-to-left languages like Arabic and Hebrew. 497 | * @param obj the selector of the scrollable object 498 | * @param isVertical vertical or horizontal 499 | * @param steps number of steps. Use this to control the speed of the scroll action. 500 | * @return true on scrolled, else false 501 | * @throws UiObjectNotFoundException 502 | */ 503 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 504 | boolean scrollForward (Selector obj, boolean isVertical, int steps) throws UiObjectNotFoundException; 505 | 506 | /** 507 | * Scrolls to the beginning of a scrollable layout element. The beginning can be at the top-most edge in the case of vertical controls, or the left-most edge for horizontal controls. Make sure to take into account devices configured with right-to-left languages like Arabic and Hebrew. 508 | * @param obj the selector of the scrollable object 509 | * @param isVertical vertical or horizontal 510 | * @param maxSwipes max swipes to be performed. 511 | * @param steps use steps to control the speed, so that it may be a scroll, or fling 512 | * @return true on scrolled else false 513 | * @throws UiObjectNotFoundException 514 | */ 515 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 516 | boolean scrollToBeginning (Selector obj, boolean isVertical, int maxSwipes, int steps) throws UiObjectNotFoundException; 517 | 518 | /** 519 | * Scrolls to the end of a scrollable layout element. The end can be at the bottom-most edge in the case of vertical controls, or the right-most edge for horizontal controls. Make sure to take into account devices configured with right-to-left languages like Arabic and Hebrew. 520 | * @param obj the selector of the scrollable object 521 | * @param isVertical vertical or horizontal 522 | * @param maxSwipes max swipes to be performed. 523 | * @param steps use steps to control the speed, so that it may be a scroll, or fling 524 | * @return true on scrolled, else false 525 | * @throws UiObjectNotFoundException 526 | */ 527 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 528 | boolean scrollToEnd (Selector obj, boolean isVertical, int maxSwipes, int steps) throws UiObjectNotFoundException; 529 | 530 | /** 531 | * Perform a scroll forward action to move through the scrollable layout element until a visible item that matches the selector is found. 532 | * @param obj the selector of the scrollable object 533 | * @param targetObj the item matches the selector to be found. 534 | * @param isVertical vertical or horizontal 535 | * @return true on scrolled, else false 536 | * @throws UiObjectNotFoundException 537 | */ 538 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 539 | boolean scrollTo (Selector obj, Selector targetObj, boolean isVertical) throws UiObjectNotFoundException; 540 | 541 | /*************************************************************************** 542 | * Some time we have to use chained selection, e.g. 543 | * new UiCollection(...).getChildByText(...).getChild().... 544 | * So we should have a mechanism to save the previous UiObject. 545 | ***************************************************************************/ 546 | 547 | /** 548 | * Searches for child UI element within the constraints of this UiSelector selector. It looks for any child matching the childPattern argument that has a child UI element anywhere within its sub hierarchy that has a text attribute equal to text. The returned UiObject will point at the childPattern instance that matched the search and not at the identifying child element that matched the text attribute. 549 | * @param collection Selector of UiCollection or UiScrollable. 550 | * @param text String of the identifying child contents of of the childPattern 551 | * @param child UiSelector selector of the child pattern to match and return 552 | * @return A string ID represent the returned UiObject. 553 | */ 554 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 555 | String childByText(Selector collection, Selector child, String text) throws UiObjectNotFoundException; 556 | 557 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 558 | String childByText(Selector collection, Selector child, String text, boolean allowScrollSearch) throws UiObjectNotFoundException; 559 | 560 | /** 561 | * Searches for child UI element within the constraints of this UiSelector selector. It looks for any child matching the childPattern argument that has a child UI element anywhere within its sub hierarchy that has content-description text. The returned UiObject will point at the childPattern instance that matched the search and not at the identifying child element that matched the content description. 562 | * @param collection Selector of UiCollection or UiScrollable 563 | * @param child UiSelector selector of the child pattern to match and return 564 | * @param text String of the identifying child contents of of the childPattern 565 | * @return A string ID represent the returned UiObject. 566 | */ 567 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 568 | String childByDescription(Selector collection, Selector child, String text) throws UiObjectNotFoundException; 569 | 570 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 571 | String childByDescription(Selector collection, Selector child, String text, boolean allowScrollSearch) throws UiObjectNotFoundException; 572 | 573 | /** 574 | * Searches for child UI element within the constraints of this UiSelector. It looks for any child matching the childPattern argument that has a child UI element anywhere within its sub hierarchy that is at the instance specified. The operation is performed only on the visible items and no scrolling is performed in this case. 575 | * @param collection Selector of UiCollection or UiScrollable 576 | * @param child UiSelector selector of the child pattern to match and return 577 | * @param instance int the desired matched instance of this childPattern 578 | * @return A string ID represent the returned UiObject. 579 | */ 580 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 581 | String childByInstance(Selector collection, Selector child, int instance) throws UiObjectNotFoundException; 582 | 583 | /** 584 | * Creates a new UiObject for a child view that is under the present UiObject. 585 | * @param obj The ID string represent the parent UiObject. 586 | * @param selector UiSelector selector of the child pattern to match and return 587 | * @return A string ID represent the returned UiObject. 588 | */ 589 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 590 | String getChild(String obj, Selector selector) throws UiObjectNotFoundException; 591 | 592 | /** 593 | * Creates a new UiObject for a sibling view or a child of the sibling view, relative to the present UiObject. 594 | * @param obj The ID string represent the source UiObject. 595 | * @param selector for a sibling view or children of the sibling view 596 | * @return A string ID represent the returned UiObject. 597 | */ 598 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 599 | String getFromParent(String obj, Selector selector) throws UiObjectNotFoundException; 600 | 601 | /** 602 | * Get a new UiObject from the selector. 603 | * @param selector Selector of the UiObject 604 | * @return A string ID represent the returned UiObject. 605 | * @throws UiObjectNotFoundException 606 | */ 607 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 608 | String getUiObject(Selector selector) throws UiObjectNotFoundException; 609 | 610 | /** 611 | * Remove the UiObject from memory. 612 | */ 613 | void removeUiObject(String obj); 614 | 615 | 616 | /** 617 | * Get all named UiObjects. 618 | * @return all names 619 | */ 620 | String[] getUiObjects(); 621 | 622 | /** 623 | * Clears the existing text contents in an editable field. The UiSelector of this object must reference a UI element that is editable. When you call this method, the method first sets focus at the start edge of the field. The method then simulates a long-press to select the existing text, and deletes the selected text. If a "Select-All" option is displayed, the method will automatically attempt to use it to ensure full text selection. Note that it is possible that not all the text in the field is selected; for example, if the text contains separators such as spaces, slashes, at symbol etc. Also, not all editable fields support the long-press functionality. 624 | * @param obj the id of the UiObject. 625 | * @throws UiObjectNotFoundException 626 | */ 627 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 628 | void clearTextField(String obj) throws UiObjectNotFoundException; 629 | 630 | /** 631 | * Reads the text property of the UI element 632 | * @param obj the id of the UiObject. 633 | * @return text value of the current node represented by this UiObject 634 | * @throws UiObjectNotFoundException 635 | */ 636 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 637 | String getText(String obj) throws UiObjectNotFoundException; 638 | 639 | /** 640 | * Sets the text in an editable field, after clearing the field's content. The UiSelector selector of this object must reference a UI element that is editable. When you call this method, the method first simulates a click() on editable field to set focus. The method then clears the field's contents and injects your specified text into the field. If you want to capture the original contents of the field, call getText() first. You can then modify the text and use this method to update the field. 641 | * @param obj the id of the UiObject. 642 | * @param text string to set 643 | * @return true if operation is successful 644 | * @throws UiObjectNotFoundException 645 | */ 646 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 647 | boolean setText(String obj, String text) throws UiObjectNotFoundException; 648 | 649 | /** 650 | * Performs a click at the center of the visible bounds of the UI element represented by this UiObject. 651 | * @param obj the id of target ui object. 652 | * @return true id successful else false 653 | * @throws UiObjectNotFoundException 654 | */ 655 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 656 | boolean click(String obj) throws UiObjectNotFoundException; 657 | 658 | /** 659 | * Clicks the bottom and right corner or top and left corner of the UI element 660 | * @param obj the id of target ui object. 661 | * @param corner "br"/"bottomright" means BottomRight, "tl"/"topleft" means TopLeft, "center" means Center. 662 | * @return true on success 663 | * @throws UiObjectNotFoundException 664 | */ 665 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 666 | boolean click(String obj, String corner) throws UiObjectNotFoundException; 667 | 668 | /** 669 | * Performs a click at the center of the visible bounds of the UI element represented by this UiObject and waits for window transitions. This method differ from click() only in that this method waits for a a new window transition as a result of the click. Some examples of a window transition: 670 | * - launching a new activity 671 | * - bringing up a pop-up menu 672 | * - bringing up a dialog 673 | * @param obj the id of target ui object. 674 | * @param timeout timeout before giving up on waiting for a new window 675 | * @return true if the event was triggered, else false 676 | * @throws UiObjectNotFoundException 677 | */ 678 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 679 | boolean clickAndWaitForNewWindow(String obj, long timeout) throws UiObjectNotFoundException; 680 | 681 | /** 682 | * Long clicks the center of the visible bounds of the UI element 683 | * @param obj the id of target ui object. 684 | * @return true if operation was successful 685 | * @throws UiObjectNotFoundException 686 | */ 687 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 688 | boolean longClick(String obj) throws UiObjectNotFoundException; 689 | 690 | /** 691 | * Long clicks bottom and right corner of the UI element 692 | * @param obj the id of target ui object. 693 | * @param corner "br"/"bottomright" means BottomRight, "tl"/"topleft" means TopLeft, "center" means Center. 694 | * @return true if operation was successful 695 | * @throws UiObjectNotFoundException 696 | */ 697 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 698 | boolean longClick(String obj, String corner) throws UiObjectNotFoundException; 699 | 700 | /** 701 | * Drags this object to a destination UiObject. The number of steps specified in your input parameter can influence the drag speed, and varying speeds may impact the results. Consider evaluating different speeds when using this method in your tests. 702 | * @param obj the id of ui object to be dragged. 703 | * @param destObj the ui object to be dragged to. 704 | * @param steps usually 40 steps. You can increase or decrease the steps to change the speed. 705 | * @return true if successful 706 | * @throws UiObjectNotFoundException 707 | * @throws NotImplementedException 708 | */ 709 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2), @JsonRpcError(exception=NotImplementedException.class, code=ERROR_CODE_BASE-3)}) 710 | boolean dragTo (String obj, Selector destObj, int steps) throws UiObjectNotFoundException, NotImplementedException; 711 | 712 | /** 713 | * Drags this object to arbitrary coordinates. The number of steps specified in your input parameter can influence the drag speed, and varying speeds may impact the results. Consider evaluating different speeds when using this method in your tests. 714 | * @param obj the id of ui object to be dragged. 715 | * @param destX the X-axis coordinate of destination. 716 | * @param destY the Y-axis coordinate of destination. 717 | * @param steps usually 40 steps. You can increase or decrease the steps to change the speed. 718 | * @return true if successful 719 | * @throws UiObjectNotFoundException 720 | * @throws NotImplementedException 721 | */ 722 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2), @JsonRpcError(exception=NotImplementedException.class, code=ERROR_CODE_BASE-3)}) 723 | boolean dragTo (String obj, int destX, int destY, int steps) throws UiObjectNotFoundException, NotImplementedException; 724 | 725 | /** 726 | * Check if view exists. This methods performs a waitForExists(long) with zero timeout. This basically returns immediately whether the view represented by this UiObject exists or not. 727 | * @param obj the id of ui object. 728 | * @return true if the view represented by this UiObject does exist 729 | */ 730 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 731 | boolean exist(String obj); 732 | 733 | /** 734 | * Get the object info. 735 | * @param obj the id of target ui object. 736 | * @return object info. 737 | * @throws UiObjectNotFoundException 738 | */ 739 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 740 | ObjInfo objInfo(String obj) throws UiObjectNotFoundException; 741 | 742 | /** 743 | * Generates a two-pointer gesture with arbitrary starting and ending points. 744 | * @param obj the id of target ui object. ?? 745 | * @param startPoint1 start point of pointer 1 746 | * @param startPoint2 start point of pointer 2 747 | * @param endPoint1 end point of pointer 1 748 | * @param endPoint2 end point of pointer 2 749 | * @param steps the number of steps for the gesture. Steps are injected about 5 milliseconds apart, so 100 steps may take around 0.5 seconds to complete. 750 | * @return true if all touch events for this gesture are injected successfully, false otherwise 751 | * @throws UiObjectNotFoundException 752 | */ 753 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2), @JsonRpcError(exception=NotImplementedException.class, code=ERROR_CODE_BASE-3)}) 754 | boolean gesture(String obj, Point startPoint1, Point startPoint2, Point endPoint1, Point endPoint2, int steps) throws UiObjectNotFoundException, NotImplementedException; 755 | 756 | /** 757 | * Performs a two-pointer gesture, where each pointer moves diagonally toward the other, from the edges to the center of this UiObject . 758 | * @param obj the id of target ui object. 759 | * @param percent percentage of the object's diagonal length for the pinch gesture 760 | * @param steps the number of steps for the gesture. Steps are injected about 5 milliseconds apart, so 100 steps may take around 0.5 seconds to complete. 761 | * @return true if all touch events for this gesture are injected successfully, false otherwise 762 | * @throws UiObjectNotFoundException 763 | * @throws NotImplementedException 764 | */ 765 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2), @JsonRpcError(exception=NotImplementedException.class, code=ERROR_CODE_BASE-3)}) 766 | boolean pinchIn(String obj, int percent, int steps) throws UiObjectNotFoundException, NotImplementedException; 767 | 768 | /** 769 | * Performs a two-pointer gesture, where each pointer moves diagonally opposite across the other, from the center out towards the edges of the this UiObject. 770 | * @param obj the id of target ui object. 771 | * @param percent percentage of the object's diagonal length for the pinch gesture 772 | * @param steps the number of steps for the gesture. Steps are injected about 5 milliseconds apart, so 100 steps may take around 0.5 seconds to complete. 773 | * @return true if all touch events for this gesture are injected successfully, false otherwise 774 | * @throws UiObjectNotFoundException 775 | * @throws NotImplementedException 776 | */ 777 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2), @JsonRpcError(exception=NotImplementedException.class, code=ERROR_CODE_BASE-3)}) 778 | boolean pinchOut(String obj, int percent, int steps) throws UiObjectNotFoundException, NotImplementedException; 779 | 780 | /** 781 | * Performs the swipe up/down/left/right action on the UiObject 782 | * @param obj the id of target ui object. 783 | * @param dir "u"/"up", "d"/"down", "l"/"left", "r"/"right" 784 | * @param steps indicates the number of injected move steps into the system. Steps are injected about 5ms apart. So a 100 steps may take about 1/2 second to complete. 785 | * @return true of successful 786 | * @throws UiObjectNotFoundException 787 | */ 788 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 789 | boolean swipe(String obj, String dir, int steps) throws UiObjectNotFoundException; 790 | 791 | /** 792 | * Waits a specified length of time for a view to become visible. This method waits until the view becomes visible on the display, or until the timeout has elapsed. You can use this method in situations where the content that you want to select is not immediately displayed. 793 | * @param obj the id of target ui object 794 | * @param timeout time to wait (in milliseconds) 795 | * @return true if the view is displayed, else false if timeout elapsed while waiting 796 | */ 797 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 798 | boolean waitForExists (String obj, long timeout) throws UiObjectNotFoundException; 799 | 800 | /** 801 | * Waits a specified length of time for a view to become undetectable. This method waits until a view is no longer matchable, or until the timeout has elapsed. A view becomes undetectable when the UiSelector of the object is unable to find a match because the element has either changed its state or is no longer displayed. You can use this method when attempting to wait for some long operation to compete, such as downloading a large file or connecting to a remote server. 802 | * @param obj the id of target ui object 803 | * @param timeout time to wait (in milliseconds) 804 | * @return true if the element is gone before timeout elapsed, else false if timeout elapsed but a matching element is still found. 805 | */ 806 | @JsonRpcErrors({@JsonRpcError(exception=UiObjectNotFoundException.class, code=ERROR_CODE_BASE-2)}) 807 | boolean waitUntilGone (String obj, long timeout) throws UiObjectNotFoundException; 808 | 809 | /** 810 | * Get Configurator 811 | * @return Configurator information. 812 | * @throws NotImplementedException 813 | */ 814 | @JsonRpcErrors({@JsonRpcError(exception=NotImplementedException.class, code=ERROR_CODE_BASE-3)}) 815 | ConfiguratorInfo getConfigurator() throws NotImplementedException; 816 | 817 | /** 818 | * Set Configurator. 819 | * @param info the configurator information to be set. 820 | * @throws NotImplementedException 821 | */ 822 | @JsonRpcErrors({@JsonRpcError(exception=NotImplementedException.class, code=ERROR_CODE_BASE-3)}) 823 | ConfiguratorInfo setConfigurator(ConfiguratorInfo info) throws NotImplementedException; 824 | } 825 | -------------------------------------------------------------------------------- /src/com/github/uiautomatorstub/AutomatorServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.uiautomatorstub; 2 | 3 | import android.os.Build; 4 | import android.os.Environment; 5 | import android.os.RemoteException; 6 | 7 | import java.io.*; 8 | import java.lang.reflect.InvocationTargetException; 9 | import java.util.*; 10 | import java.util.concurrent.ConcurrentHashMap; 11 | 12 | import android.view.KeyEvent; 13 | import com.android.uiautomator.core.*; 14 | import com.github.uiautomatorstub.watcher.ClickUiObjectWatcher; 15 | import com.github.uiautomatorstub.watcher.PressKeysWatcher; 16 | 17 | 18 | public class AutomatorServiceImpl implements AutomatorService { 19 | 20 | final public static String STORAGE_PATH = "/data/local/tmp/"; 21 | private final HashSet watchers = new HashSet(); 22 | private final ConcurrentHashMap uiObjects = new ConcurrentHashMap(); 23 | 24 | public AutomatorServiceImpl() { 25 | } 26 | 27 | /** 28 | * Walk around to avoid backforward compatibility issue on uiautomator between api level 16/17. 29 | */ 30 | static void setAsHorizontalList(UiScrollable obj) { 31 | Class noparams[] = {}; 32 | Object nullparmas[] = {}; 33 | try { 34 | Class.forName("com.android.uiautomator.core.UiScrollable").getDeclaredMethod("setAsHorizontalList", noparams).invoke(obj, nullparmas); 35 | } catch (NoSuchMethodException e) { 36 | Log.d(e.getMessage()); 37 | } catch (ClassNotFoundException e) { 38 | Log.d(e.getMessage()); 39 | } catch (InvocationTargetException e) { 40 | Log.d(e.getMessage()); 41 | } catch (IllegalAccessException e) { 42 | Log.d(e.getMessage()); 43 | } 44 | } 45 | 46 | /** 47 | * Walk around to avoid backforward compatibility issue on uiautomator between api level 16/17. 48 | */ 49 | static void setAsVerticalList(UiScrollable obj) { 50 | Class noparams[] = {}; 51 | Object nullparmas[] = {}; 52 | try { 53 | Class.forName("com.android.uiautomator.core.UiScrollable").getDeclaredMethod("setAsVerticalList", noparams).invoke(obj, nullparmas); 54 | } catch (NoSuchMethodException e) { 55 | Log.d(e.getMessage()); 56 | } catch (ClassNotFoundException e) { 57 | Log.d(e.getMessage()); 58 | } catch (InvocationTargetException e) { 59 | Log.d(e.getMessage()); 60 | } catch (IllegalAccessException e) { 61 | Log.d(e.getMessage()); 62 | } 63 | } 64 | 65 | /** 66 | * It's to test if the service is alive. 67 | * 68 | * @return 'pong' 69 | */ 70 | @Override 71 | public String ping() { 72 | //new UiObject(new UiSelector()).exists(); // here we call the method just for checking if the UiAutomationService is ok, else it will throw IllegalStateException. 73 | return "pong"; 74 | } 75 | 76 | /** 77 | * Get the device info. 78 | * 79 | * @return device info. 80 | */ 81 | @Override 82 | public DeviceInfo deviceInfo() { 83 | return DeviceInfo.getDeviceInfo(); 84 | } 85 | 86 | /** 87 | * Perform a click at arbitrary coordinates specified by the user. 88 | * 89 | * @param x coordinate 90 | * @param y coordinate 91 | * @return true if the click succeeded else false 92 | */ 93 | @Override 94 | public boolean click(int x, int y) { 95 | return UiDevice.getInstance().click(x, y); 96 | } 97 | 98 | /** 99 | * Performs a swipe from one coordinate to another coordinate. You can control the smoothness and speed of the swipe by specifying the number of steps. Each step execution is throttled to 5 milliseconds per step, so for a 100 steps, the swipe will take around 0.5 seconds to complete. 100 | * 101 | * @param startX X-axis value for the starting coordinate 102 | * @param startY Y-axis value for the starting coordinate 103 | * @param endX X-axis value for the ending coordinate 104 | * @param endY Y-axis value for the ending coordinate 105 | * @param steps is the number of steps for the swipe action 106 | * @return true if swipe is performed, false if the operation fails or the coordinates are invalid 107 | * @throws com.github.uiautomatorstub.NotImplementedException 108 | * 109 | */ 110 | @Override 111 | public boolean drag(int startX, int startY, int endX, int endY, int steps) throws NotImplementedException { 112 | if (Build.VERSION.SDK_INT < 18) 113 | throw new NotImplementedException(); 114 | 115 | return UiDevice.getInstance().drag(startX, startY, endX, endY, steps); 116 | } 117 | 118 | /** 119 | * Performs a swipe from one coordinate to another using the number of steps to determine smoothness and speed. Each step execution is throttled to 5ms per step. So for a 100 steps, the swipe will take about 1/2 second to complete. 120 | * 121 | * @param startX X-axis value for the starting coordinate 122 | * @param startY Y-axis value for the starting coordinate 123 | * @param endX X-axis value for the ending coordinate 124 | * @param endY Y-axis value for the ending coordinate 125 | * @param steps is the number of move steps sent to the system 126 | * @return false if the operation fails or the coordinates are invalid 127 | */ 128 | @Override 129 | public boolean swipe(int startX, int startY, int endX, int endY, int steps) { 130 | return UiDevice.getInstance().swipe(startX,startY, endX, endY, steps); 131 | } 132 | 133 | /** 134 | * Helper method used for debugging to dump the current window's layout hierarchy. The file root location is /data/local/tmp 135 | * @param compressed use compressed layout hierarchy or not using setCompressedLayoutHeirarchy method. Ignore the parameter in case the API level lt 18. 136 | * @param filename the filename to be stored. 137 | * @return the absolute path name of dumped file. 138 | */ 139 | @Override 140 | public String dumpWindowHierarchy(boolean compressed, String filename) { 141 | if (Build.VERSION.SDK_INT >= 18) 142 | UiDevice.getInstance().setCompressedLayoutHeirarchy(compressed); 143 | File parent = new File(Environment.getDataDirectory(), "local/tmp"); // Environment.getDataDirectory() return /data/local/tmp in android 4.3 but not expected /data 144 | if (!parent.exists()) 145 | parent.mkdirs(); 146 | boolean return_value = false; 147 | if (filename == null || filename == "") { 148 | filename = "dump.xml"; 149 | return_value = true; 150 | } 151 | File dumpFile = new File(parent, filename).getAbsoluteFile(); 152 | UiDevice.getInstance().dumpWindowHierarchy(filename); 153 | File f = new File(STORAGE_PATH, filename); // It should be this one, but in Android4.3, it is "/data/local/tmp/local/tmp"...... 154 | if (!f.exists()) f = dumpFile; 155 | if (f.exists()) { 156 | if (return_value) { 157 | BufferedReader reader = null; 158 | try { 159 | StringBuilder sb = new StringBuilder(); 160 | reader = new BufferedReader(new FileReader(f)); 161 | char[] buffer = new char[4096]; 162 | int len = 0; 163 | while ((len = reader.read(buffer)) != -1) { 164 | sb.append(new String(buffer, 0, len)); 165 | } 166 | reader.close(); 167 | reader = null; 168 | return sb.toString(); 169 | } catch (IOException e) { 170 | Log.e(e.toString()); 171 | } finally { 172 | if (reader != null) { 173 | try { 174 | reader.close(); 175 | reader = null; 176 | } catch (IOException e1) { 177 | } 178 | } 179 | } 180 | return null; 181 | } else 182 | return f.getAbsolutePath(); 183 | } else 184 | return null; 185 | } 186 | 187 | /** 188 | * Take a screenshot of current window and store it as PNG The screenshot is adjusted per screen rotation 189 | * 190 | * @param filename where the PNG should be written to 191 | * @param scale scale the screenshot down if needed; 1.0f for original size 192 | * @param quality quality of the PNG compression; range: 0-100 193 | * @return the file name of the screenshot. null if failed. 194 | * @throws com.github.uiautomatorstub.NotImplementedException 195 | * 196 | */ 197 | @Override 198 | public String takeScreenshot(String filename, float scale, int quality) throws NotImplementedException { 199 | if (Build.VERSION.SDK_INT < 17) 200 | throw new NotImplementedException("takeScreenshot"); 201 | File f = new File(STORAGE_PATH, filename); 202 | UiDevice.getInstance().takeScreenshot(f, scale, quality); 203 | if (f.exists()) 204 | return f.getAbsolutePath(); 205 | return null; 206 | } 207 | 208 | /** 209 | * Disables the sensors and freezes the device rotation at its current rotation state, or enable it. 210 | * 211 | * @param freeze true to freeze the rotation, false to unfreeze the rotation. 212 | * @throws android.os.RemoteException 213 | */ 214 | @Override 215 | public void freezeRotation(boolean freeze) throws RemoteException { 216 | if (freeze) 217 | UiDevice.getInstance().freezeRotation(); 218 | else 219 | UiDevice.getInstance().unfreezeRotation(); 220 | } 221 | 222 | /** 223 | * Simulates orienting the device to the left/right/natural and also freezes rotation by disabling the sensors. 224 | * 225 | * @param dir Left or l, Right or r, Natural or n, case insensitive 226 | * @throws android.os.RemoteException 227 | * @throws com.github.uiautomatorstub.NotImplementedException 228 | * 229 | */ 230 | @Override 231 | public void setOrientation(String dir) throws RemoteException, NotImplementedException { 232 | if (Build.VERSION.SDK_INT < 17) 233 | throw new NotImplementedException("setOrientation"); 234 | dir = dir.toLowerCase(); 235 | if ("left".equals(dir) || "l".equals(dir)) 236 | UiDevice.getInstance().setOrientationLeft(); 237 | else if ("right".equals(dir) || "r".equals(dir)) 238 | UiDevice.getInstance().setOrientationRight(); 239 | else if ("natural".equals(dir) || "n".equals(dir)) 240 | UiDevice.getInstance().setOrientationNatural(); 241 | } 242 | 243 | /** 244 | * Retrieves the text from the last UI traversal event received. 245 | * 246 | * @return the text from the last UI traversal event received. 247 | */ 248 | @Override 249 | public String getLastTraversedText() { 250 | return UiDevice.getInstance().getLastTraversedText(); 251 | } 252 | 253 | /** 254 | * Clears the text from the last UI traversal event. 255 | */ 256 | @Override 257 | public void clearLastTraversedText() { 258 | UiDevice.getInstance().clearLastTraversedText(); 259 | } 260 | 261 | /** 262 | * Opens the notification shade. 263 | * 264 | * @return true if successful, else return false 265 | * @throws com.github.uiautomatorstub.NotImplementedException 266 | * 267 | */ 268 | @Override 269 | public boolean openNotification() throws NotImplementedException { 270 | if (Build.VERSION.SDK_INT < 18) 271 | throw new NotImplementedException("openNotification"); 272 | return UiDevice.getInstance().openNotification(); 273 | } 274 | 275 | /** 276 | * Opens the Quick Settings shade. 277 | * 278 | * @return true if successful, else return false 279 | * @throws com.github.uiautomatorstub.NotImplementedException 280 | * 281 | */ 282 | @Override 283 | public boolean openQuickSettings() throws NotImplementedException { 284 | if (Build.VERSION.SDK_INT < 18) 285 | throw new NotImplementedException("openQuickSettings"); 286 | return UiDevice.getInstance().openQuickSettings(); 287 | } 288 | 289 | /** 290 | * Checks if a specific registered UiWatcher has triggered. See registerWatcher(String, UiWatcher). If a UiWatcher runs and its checkForCondition() call returned true, then the UiWatcher is considered triggered. This is helpful if a watcher is detecting errors from ANR or crash dialogs and the test needs to know if a UiWatcher has been triggered. 291 | * 292 | * @param watcherName the name of registered watcher. 293 | * @return true if triggered else false 294 | */ 295 | @Override 296 | public boolean hasWatcherTriggered(String watcherName) { 297 | return UiDevice.getInstance().hasWatcherTriggered(watcherName); 298 | } 299 | 300 | /** 301 | * Checks if any registered UiWatcher have triggered. 302 | * 303 | * @return true if any UiWatcher have triggered else false. 304 | */ 305 | @Override 306 | public boolean hasAnyWatcherTriggered() { 307 | return UiDevice.getInstance().hasAnyWatcherTriggered(); 308 | } 309 | 310 | /** 311 | * Register a ClickUiObjectWatcher 312 | * 313 | * @param name Watcher name 314 | * @param conditions If all UiObject in the conditions match, the watcher should be triggered. 315 | * @param target The target UiObject should be clicked if all conditions match. 316 | */ 317 | @Override 318 | public void registerClickUiObjectWatcher(String name, Selector[] conditions, Selector target) { 319 | synchronized (watchers) { 320 | if (watchers.contains(name)) { 321 | UiDevice.getInstance().removeWatcher(name); 322 | watchers.remove(name); 323 | } 324 | 325 | UiSelector[] selectors = new UiSelector[conditions.length]; 326 | for (int i = 0; i < conditions.length; i++) { 327 | selectors[i] = conditions[i].toUiSelector(); 328 | } 329 | UiDevice.getInstance().registerWatcher(name, new ClickUiObjectWatcher(selectors, target.toUiSelector())); 330 | watchers.add(name); 331 | } 332 | } 333 | 334 | /** 335 | * Register a PressKeysWatcher 336 | * 337 | * @param name Watcher name 338 | * @param conditions If all UiObject in the conditions match, the watcher should be triggered. 339 | * @param keys All keys will be pressed in sequence. 340 | */ 341 | @Override 342 | public void registerPressKeyskWatcher(String name, Selector[] conditions, String[] keys) { 343 | synchronized (watchers) { 344 | if (watchers.contains(name)) { 345 | UiDevice.getInstance().removeWatcher(name); 346 | watchers.remove(name); 347 | } 348 | 349 | UiSelector[] selectors = new UiSelector[conditions.length]; 350 | for (int i = 0; i < conditions.length; i++) { 351 | selectors[i] = conditions[i].toUiSelector(); 352 | } 353 | UiDevice.getInstance().registerWatcher(name, new PressKeysWatcher(selectors, keys)); 354 | watchers.add(name); 355 | } 356 | } 357 | 358 | /** 359 | * Removes a previously registered UiWatcher. 360 | * 361 | * @param name Watcher name 362 | */ 363 | @Override 364 | public void removeWatcher(String name) { 365 | synchronized (watchers) { 366 | if (watchers.contains(name)) { 367 | UiDevice.getInstance().removeWatcher(name); 368 | watchers.remove(name); 369 | } 370 | } 371 | } 372 | 373 | /** 374 | * Resets a UiWatcher that has been triggered. If a UiWatcher runs and its checkForCondition() call returned true, then the UiWatcher is considered triggered. 375 | */ 376 | @Override 377 | public void resetWatcherTriggers() { 378 | UiDevice.getInstance().resetWatcherTriggers(); 379 | } 380 | 381 | /** 382 | * Force to run all watchers. 383 | */ 384 | @Override 385 | public void runWatchers() { 386 | UiDevice.getInstance().runWatchers(); 387 | } 388 | 389 | /** 390 | * Get all registered UiWatchers 391 | * 392 | * @return UiWatcher names 393 | */ 394 | @Override 395 | public String[] getWatchers() { 396 | synchronized (watchers) { 397 | return watchers.toArray(new String[watchers.size()]); 398 | } 399 | } 400 | 401 | /** 402 | * Simulates a short press using key name. 403 | * 404 | * @param key possible key name is home, back, left, right, up, down, center, menu, search, enter, delete(or del), recent(recent apps), volume_up, volume_down, volume_mute, camera, power 405 | * @return true if successful, else return false 406 | * @throws android.os.RemoteException 407 | */ 408 | @Override 409 | public boolean pressKey(String key) throws RemoteException { 410 | boolean result; 411 | key = key.toLowerCase(); 412 | if ("home".equals(key)) 413 | result = UiDevice.getInstance().pressHome(); 414 | else if ("back".equals(key)) 415 | result = UiDevice.getInstance().pressBack(); 416 | else if ("left".equals(key)) 417 | result = UiDevice.getInstance().pressDPadLeft(); 418 | else if ("right".equals(key)) 419 | result = UiDevice.getInstance().pressDPadRight(); 420 | else if ("up".equals(key)) 421 | result = UiDevice.getInstance().pressDPadUp(); 422 | else if ("down".equals(key)) 423 | result = UiDevice.getInstance().pressDPadDown(); 424 | else if ("center".equals(key)) 425 | result = UiDevice.getInstance().pressDPadCenter(); 426 | else if ("menu".equals(key)) 427 | result = UiDevice.getInstance().pressMenu(); 428 | else if ("search".equals(key)) 429 | result = UiDevice.getInstance().pressSearch(); 430 | else if ("enter".equals(key)) 431 | result = UiDevice.getInstance().pressEnter(); 432 | else if ("delete".equals(key) || "del".equals(key)) 433 | result = UiDevice.getInstance().pressDelete(); 434 | else if ("recent".equals(key)) 435 | result = UiDevice.getInstance().pressRecentApps(); 436 | else if ("volume_up".equals(key)) 437 | result = UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_VOLUME_UP); 438 | else if ("volume_down".equals(key)) 439 | result = UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_VOLUME_DOWN); 440 | else if ("volume_mute".equals(key)) 441 | result = UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_VOLUME_MUTE); 442 | else if ("camera".equals(key)) 443 | result = UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_CAMERA); 444 | else result = "power".equals(key) && UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_POWER); 445 | 446 | return result; 447 | } 448 | 449 | /** 450 | * Simulates a short press using a key code. See KeyEvent. 451 | * 452 | * @param keyCode the key code of the event. 453 | * @return true if successful, else return false 454 | */ 455 | @Override 456 | public boolean pressKeyCode(int keyCode) { 457 | return UiDevice.getInstance().pressKeyCode(keyCode); 458 | } 459 | 460 | /** 461 | * Simulates a short press using a key code. See KeyEvent. 462 | * 463 | * @param keyCode the key code of the event. 464 | * @param metaState an integer in which each bit set to 1 represents a pressed meta key 465 | * @return true if successful, else return false 466 | */ 467 | @Override 468 | public boolean pressKeyCode(int keyCode, int metaState) { 469 | return UiDevice.getInstance().pressKeyCode(keyCode, metaState); 470 | } 471 | 472 | /** 473 | * This method simulates pressing the power button if the screen is OFF else it does nothing if the screen is already ON. If the screen was OFF and it just got turned ON, this method will insert a 500ms delay to allow the device time to wake up and accept input. 474 | * 475 | * @throws android.os.RemoteException 476 | */ 477 | @Override 478 | public void wakeUp() throws RemoteException { 479 | UiDevice.getInstance().wakeUp(); 480 | } 481 | 482 | /** 483 | * This method simply presses the power button if the screen is ON else it does nothing if the screen is already OFF. 484 | * 485 | * @throws android.os.RemoteException 486 | */ 487 | @Override 488 | public void sleep() throws RemoteException { 489 | UiDevice.getInstance().sleep(); 490 | } 491 | 492 | /** 493 | * Checks the power manager if the screen is ON. 494 | * 495 | * @return true if the screen is ON else false 496 | * @throws android.os.RemoteException 497 | */ 498 | @Override 499 | public boolean isScreenOn() throws RemoteException { 500 | return UiDevice.getInstance().isScreenOn(); 501 | } 502 | 503 | /** 504 | * Waits for the current application to idle. 505 | * 506 | * @param timeout in milliseconds 507 | */ 508 | @Override 509 | public void waitForIdle(long timeout) { 510 | UiDevice.getInstance().waitForIdle(timeout); 511 | } 512 | 513 | /** 514 | * Waits for a window content update event to occur. If a package name for the window is specified, but the current window does not have the same package name, the function returns immediately. 515 | * 516 | * @param packageName the specified window package name (can be null). If null, a window update from any front-end window will end the wait. 517 | * @param timeout the timeout for the wait 518 | * @return true if a window update occurred, false if timeout has elapsed or if the current window does not have the specified package name 519 | */ 520 | @Override 521 | public boolean waitForWindowUpdate(String packageName, long timeout) { 522 | return UiDevice.getInstance().waitForWindowUpdate(packageName, timeout); 523 | } 524 | 525 | /** 526 | * Clears the existing text contents in an editable field. The UiSelector of this object must reference a UI element that is editable. When you call this method, the method first sets focus at the start edge of the field. The method then simulates a long-press to select the existing text, and deletes the selected text. If a "Select-All" option is displayed, the method will automatically attempt to use it to ensure full text selection. Note that it is possible that not all the text in the field is selected; for example, if the text contains separators such as spaces, slashes, at symbol etc. Also, not all editable fields support the long-press functionality. 527 | * 528 | * @param obj the selector of the UiObject. 529 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 530 | * 531 | */ 532 | @Override 533 | public void clearTextField(Selector obj) throws UiObjectNotFoundException { 534 | new UiObject(obj.toUiSelector()).clearTextField(); 535 | } 536 | 537 | /** 538 | * Reads the text property of the UI element 539 | * 540 | * @param obj the selector of the UiObject. 541 | * @return text value of the current node represented by this UiObject 542 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 543 | * 544 | */ 545 | @Override 546 | public String getText(Selector obj) throws UiObjectNotFoundException { 547 | return new UiObject(obj.toUiSelector()).getText(); 548 | } 549 | 550 | /** 551 | * Sets the text in an editable field, after clearing the field's content. The UiSelector selector of this object must reference a UI element that is editable. When you call this method, the method first simulates a click() on editable field to set focus. The method then clears the field's contents and injects your specified text into the field. If you want to capture the original contents of the field, call getText() first. You can then modify the text and use this method to update the field. 552 | * 553 | * @param obj the selector of the UiObject. 554 | * @param text string to set 555 | * @return true if operation is successful 556 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 557 | * 558 | */ 559 | @Override 560 | public boolean setText(Selector obj, String text) throws UiObjectNotFoundException { 561 | return new UiObject(obj.toUiSelector()).setText(text); 562 | } 563 | 564 | /** 565 | * Performs a click at the center of the visible bounds of the UI element represented by this UiObject. 566 | * 567 | * @param obj the target ui object. 568 | * @return true id successful else false 569 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 570 | * 571 | */ 572 | @Override 573 | public boolean click(Selector obj) throws UiObjectNotFoundException { 574 | return new UiObject(obj.toUiSelector()).click(); 575 | } 576 | 577 | /** 578 | * Clicks the bottom and right corner or top and left corner of the UI element 579 | * 580 | * @param obj the target ui object. 581 | * @param corner "br"/"bottomright" means BottomRight, "tl"/"topleft" means TopLeft, "center" means Center. 582 | * @return true on success 583 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 584 | * 585 | */ 586 | @Override 587 | public boolean click(Selector obj, String corner) throws UiObjectNotFoundException { 588 | return click(new UiObject(obj.toUiSelector()), corner); 589 | } 590 | 591 | private boolean click(UiObject obj, String corner) throws UiObjectNotFoundException { 592 | if (corner == null) 593 | corner = "center"; 594 | corner = corner.toLowerCase(); 595 | if ("br".equals(corner) || "bottomright".equals(corner)) 596 | return obj.clickBottomRight(); 597 | else if ("tl".equals(corner) || "topleft".equals(corner)) 598 | return obj.clickTopLeft(); 599 | else if ("c".equals(corner) || "center".equals(corner)) 600 | return obj.click(); 601 | return false; 602 | } 603 | 604 | /** 605 | * Performs a click at the center of the visible bounds of the UI element represented by this UiObject and waits for window transitions. This method differ from click() only in that this method waits for a a new window transition as a result of the click. Some examples of a window transition: 606 | * - launching a new activity 607 | * - bringing up a pop-up menu 608 | * - bringing up a dialog 609 | * 610 | * @param obj the target ui object. 611 | * @param timeout timeout before giving up on waiting for a new window 612 | * @return true if the event was triggered, else false 613 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 614 | * 615 | */ 616 | @Override 617 | public boolean clickAndWaitForNewWindow(Selector obj, long timeout) throws UiObjectNotFoundException { 618 | return new UiObject(obj.toUiSelector()).clickAndWaitForNewWindow(timeout); 619 | } 620 | 621 | /** 622 | * Long clicks the center of the visible bounds of the UI element 623 | * 624 | * @param obj the target ui object. 625 | * @return true if operation was successful 626 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 627 | * 628 | */ 629 | @Override 630 | public boolean longClick(Selector obj) throws UiObjectNotFoundException { 631 | return new UiObject(obj.toUiSelector()).longClick(); 632 | } 633 | 634 | /** 635 | * Long clicks bottom and right corner of the UI element 636 | * 637 | * @param obj the target ui object. 638 | * @param corner "br"/"bottomright" means BottomRight, "tl"/"topleft" means TopLeft, "center" means Center. 639 | * @return true if operation was successful 640 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 641 | * 642 | */ 643 | @Override 644 | public boolean longClick(Selector obj, String corner) throws UiObjectNotFoundException { 645 | return longClick(new UiObject(obj.toUiSelector()), corner); 646 | } 647 | 648 | private boolean longClick(UiObject obj, String corner) throws UiObjectNotFoundException { 649 | if (corner == null) 650 | corner = "center"; 651 | 652 | corner = corner.toLowerCase(); 653 | if ("br".equals(corner) || "bottomright".equals(corner)) 654 | return obj.longClickBottomRight(); 655 | else if ("tl".equals(corner) || "topleft".equals(corner)) 656 | return obj.longClickTopLeft(); 657 | else if ("c".equals(corner) || "center".equals(corner)) 658 | return obj.longClick(); 659 | 660 | return false; 661 | } 662 | 663 | /** 664 | * Drags this object to a destination UiObject. The number of steps specified in your input parameter can influence the drag speed, and varying speeds may impact the results. Consider evaluating different speeds when using this method in your tests. 665 | * 666 | * @param obj the ui object to be dragged. 667 | * @param destObj the ui object to be dragged to. 668 | * @param steps usually 40 steps. You can increase or decrease the steps to change the speed. 669 | * @return true if successful 670 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 671 | * 672 | * @throws com.github.uiautomatorstub.NotImplementedException 673 | * 674 | */ 675 | @Override 676 | public boolean dragTo(Selector obj, Selector destObj, int steps) throws UiObjectNotFoundException, NotImplementedException { 677 | return dragTo(new UiObject(obj.toUiSelector()), destObj, steps); 678 | } 679 | 680 | private boolean dragTo(UiObject obj, Selector destObj, int steps) throws UiObjectNotFoundException, NotImplementedException { 681 | if (Build.VERSION.SDK_INT < 18) 682 | throw new NotImplementedException("dragTo"); 683 | return obj.dragTo(new UiObject(destObj.toUiSelector()), steps); 684 | } 685 | 686 | /** 687 | * Drags this object to arbitrary coordinates. The number of steps specified in your input parameter can influence the drag speed, and varying speeds may impact the results. Consider evaluating different speeds when using this method in your tests. 688 | * 689 | * @param obj the ui object to be dragged. 690 | * @param destX the X-axis coordinate of destination. 691 | * @param destY the Y-axis coordinate of destination. 692 | * @param steps usually 40 steps. You can increase or decrease the steps to change the speed. 693 | * @return true if successful 694 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 695 | * 696 | * @throws com.github.uiautomatorstub.NotImplementedException 697 | * 698 | */ 699 | @Override 700 | public boolean dragTo(Selector obj, int destX, int destY, int steps) throws UiObjectNotFoundException, NotImplementedException { 701 | return dragTo(new UiObject(obj.toUiSelector()), destX, destY, steps); 702 | } 703 | 704 | private boolean dragTo(UiObject obj, int destX, int destY, int steps) throws UiObjectNotFoundException, NotImplementedException { 705 | if (Build.VERSION.SDK_INT < 18) 706 | throw new NotImplementedException("dragTo"); 707 | return obj.dragTo(destX, destY, steps); 708 | } 709 | /** 710 | * Check if view exists. This methods performs a waitForExists(long) with zero timeout. This basically returns immediately whether the view represented by this UiObject exists or not. 711 | * 712 | * @param obj the ui object. 713 | * @return true if the view represented by this UiObject does exist 714 | */ 715 | @Override 716 | public boolean exist(Selector obj) { 717 | return new UiObject(obj.toUiSelector()).exists(); 718 | } 719 | 720 | /** 721 | * Get the object info. 722 | * 723 | * @param obj the target ui object. 724 | * @return object info. 725 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 726 | * 727 | */ 728 | @Override 729 | public ObjInfo objInfo(Selector obj) throws UiObjectNotFoundException { 730 | return ObjInfo.getObjInfo(obj.toUiSelector()); 731 | } 732 | 733 | /** 734 | * Get the count of the UiObject instances by the selector 735 | * 736 | * @param obj the selector of the ui object 737 | * @return the count of instances. 738 | */ 739 | @Override 740 | public int count(Selector obj) { 741 | if ((obj.getMask() & Selector.MASK_INSTANCE) > 0) { 742 | if (new UiObject(obj.toUiSelector()).exists()) 743 | return 1; 744 | else 745 | return 0; 746 | } else { 747 | UiSelector sel = obj.toUiSelector(); 748 | if (! new UiObject(sel).exists()) 749 | return 0; 750 | int low = 1; 751 | int high = 2; 752 | sel = sel.instance(high - 1); 753 | while (new UiObject(sel).exists()) { 754 | low = high; 755 | high = high * 2; 756 | sel = sel.instance(high - 1); 757 | } 758 | while (high > low + 1) { 759 | int mid = (low + high)/2; 760 | sel = sel.instance(mid - 1); 761 | if (new UiObject(sel).exists()) 762 | low = mid; 763 | else 764 | high = mid; 765 | } 766 | return low; 767 | } 768 | } 769 | 770 | /** 771 | * Get the info of all instance by the selector. 772 | * 773 | * @param obj the selector of ui object. 774 | * @return array of object info. 775 | * 776 | */ 777 | @Override 778 | public ObjInfo[] objInfoOfAllInstances(Selector obj) { 779 | int total = count(obj); 780 | ObjInfo objs [] = new ObjInfo[total]; 781 | if ((obj.getMask() & Selector.MASK_INSTANCE) > 0 && total > 0) { 782 | try { 783 | objs[0] = objInfo(obj); 784 | } catch (UiObjectNotFoundException e) { 785 | } 786 | } else { 787 | UiSelector sel = obj.toUiSelector(); 788 | for (int i = 0; i < total; i++) { 789 | try { 790 | objs[i] = ObjInfo.getObjInfo(sel.instance(i)); 791 | } catch (UiObjectNotFoundException e) { 792 | } 793 | } 794 | } 795 | return objs; 796 | } 797 | 798 | /** 799 | * Generates a two-pointer gesture with arbitrary starting and ending points. 800 | * 801 | * @param obj the target ui object. ?? 802 | * @param startPoint1 start point of pointer 1 803 | * @param startPoint2 start point of pointer 2 804 | * @param endPoint1 end point of pointer 1 805 | * @param endPoint2 end point of pointer 2 806 | * @param steps the number of steps for the gesture. Steps are injected about 5 milliseconds apart, so 100 steps may take around 0.5 seconds to complete. 807 | * @return true if all touch events for this gesture are injected successfully, false otherwise 808 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 809 | * 810 | */ 811 | @Override 812 | public boolean gesture(Selector obj, Point startPoint1, Point startPoint2, Point endPoint1, Point endPoint2, int steps) throws UiObjectNotFoundException, NotImplementedException { 813 | return gesture(new UiObject(obj.toUiSelector()), 814 | startPoint1, startPoint2, 815 | endPoint1, endPoint2, steps); 816 | } 817 | 818 | private boolean gesture(UiObject obj, Point startPoint1, Point startPoint2, Point endPoint1, Point endPoint2, int steps) throws UiObjectNotFoundException, NotImplementedException { 819 | if (Build.VERSION.SDK_INT < 18) 820 | throw new NotImplementedException("gesture(performTwoPointerGesture)"); 821 | return obj.performTwoPointerGesture( 822 | startPoint1.toPoint(), startPoint2.toPoint(), 823 | endPoint1.toPoint(), endPoint2.toPoint(), steps); 824 | } 825 | 826 | /** 827 | * Performs a two-pointer gesture, where each pointer moves diagonally toward the other, from the edges to the center of this UiObject . 828 | * 829 | * @param obj the target ui object. 830 | * @param percent percentage of the object's diagonal length for the pinch gesture 831 | * @param steps the number of steps for the gesture. Steps are injected about 5 milliseconds apart, so 100 steps may take around 0.5 seconds to complete. 832 | * @return true if all touch events for this gesture are injected successfully, false otherwise 833 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 834 | * 835 | * @throws com.github.uiautomatorstub.NotImplementedException 836 | * 837 | */ 838 | @Override 839 | public boolean pinchIn(Selector obj, int percent, int steps) throws UiObjectNotFoundException, NotImplementedException { 840 | return pinchIn(new UiObject(obj.toUiSelector()), percent, steps); 841 | } 842 | 843 | private boolean pinchIn(UiObject obj, int percent, int steps) throws UiObjectNotFoundException, NotImplementedException { 844 | if (Build.VERSION.SDK_INT < 18) 845 | throw new NotImplementedException("pinchIn"); 846 | return obj.pinchIn(percent, steps); 847 | } 848 | 849 | /** 850 | * Performs a two-pointer gesture, where each pointer moves diagonally opposite across the other, from the center out towards the edges of the this UiObject. 851 | * 852 | * @param obj the target ui object. 853 | * @param percent percentage of the object's diagonal length for the pinch gesture 854 | * @param steps the number of steps for the gesture. Steps are injected about 5 milliseconds apart, so 100 steps may take around 0.5 seconds to complete. 855 | * @return true if all touch events for this gesture are injected successfully, false otherwise 856 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 857 | * 858 | * @throws com.github.uiautomatorstub.NotImplementedException 859 | * 860 | */ 861 | @Override 862 | public boolean pinchOut(Selector obj, int percent, int steps) throws UiObjectNotFoundException, NotImplementedException { 863 | return pinchOut(new UiObject(obj.toUiSelector()), percent, steps); 864 | } 865 | 866 | private boolean pinchOut(UiObject obj, int percent, int steps) throws UiObjectNotFoundException, NotImplementedException { 867 | if (Build.VERSION.SDK_INT < 18) 868 | throw new NotImplementedException("pinchOut"); 869 | return obj.pinchOut(percent, steps); 870 | } 871 | 872 | /** 873 | * Performs the swipe up/down/left/right action on the UiObject 874 | * 875 | * @param obj the target ui object. 876 | * @param dir "u"/"up", "d"/"down", "l"/"left", "r"/"right" 877 | * @param steps indicates the number of injected move steps into the system. Steps are injected about 5ms apart. So a 100 steps may take about 1/2 second to complete. 878 | * @return true of successful 879 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 880 | * 881 | */ 882 | @Override 883 | public boolean swipe(Selector obj, String dir, int steps) throws UiObjectNotFoundException { 884 | return swipe(new UiObject(obj.toUiSelector()), dir, steps); 885 | } 886 | 887 | private boolean swipe(UiObject item, String dir, int steps) throws UiObjectNotFoundException { 888 | dir = dir.toLowerCase(); 889 | boolean result = false; 890 | if ("u".equals(dir) || "up".equals(dir)) 891 | result = item.swipeUp(steps); 892 | else if ("d".equals(dir) || "down".equals(dir)) 893 | result = item.swipeDown(steps); 894 | else if ("l".equals(dir) || "left".equals(dir)) 895 | result = item.swipeLeft(steps); 896 | else if ("r".equals(dir) || "right".equals(dir)) 897 | result = item.swipeRight(steps); 898 | return result; 899 | } 900 | 901 | /** 902 | * Waits a specified length of time for a view to become visible. This method waits until the view becomes visible on the display, or until the timeout has elapsed. You can use this method in situations where the content that you want to select is not immediately displayed. 903 | * 904 | * @param obj the target ui object 905 | * @param timeout time to wait (in milliseconds) 906 | * @return true if the view is displayed, else false if timeout elapsed while waiting 907 | */ 908 | @Override 909 | public boolean waitForExists(Selector obj, long timeout) { 910 | return new UiObject(obj.toUiSelector()).waitForExists(timeout); 911 | } 912 | 913 | /** 914 | * Waits a specified length of time for a view to become undetectable. This method waits until a view is no longer matchable, or until the timeout has elapsed. A view becomes undetectable when the UiSelector of the object is unable to find a match because the element has either changed its state or is no longer displayed. You can use this method when attempting to wait for some long operation to compete, such as downloading a large file or connecting to a remote server. 915 | * 916 | * @param obj the target ui object 917 | * @param timeout time to wait (in milliseconds) 918 | * @return true if the element is gone before timeout elapsed, else false if timeout elapsed but a matching element is still found. 919 | */ 920 | @Override 921 | public boolean waitUntilGone(Selector obj, long timeout) { 922 | return new UiObject(obj.toUiSelector()).waitUntilGone(timeout); 923 | } 924 | 925 | /** 926 | * Performs a backwards fling action with the default number of fling steps (5). If the swipe direction is set to vertical, then the swipe will be performed from top to bottom. If the swipe direction is set to horizontal, then the swipes will be performed from left to right. Make sure to take into account devices configured with right-to-left languages like Arabic and Hebrew. 927 | * @param obj the selector of the scrollable object 928 | * @param isVertical vertical or horizontal 929 | * @return true if scrolled, and false if can't scroll anymore 930 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 931 | * 932 | */ 933 | @Override 934 | public boolean flingBackward(Selector obj, boolean isVertical) throws UiObjectNotFoundException { 935 | UiScrollable scrollable = new UiScrollable(obj.toUiSelector()); 936 | if (isVertical) 937 | setAsVerticalList(scrollable); 938 | else 939 | setAsHorizontalList(scrollable); 940 | return scrollable.flingBackward(); 941 | } 942 | 943 | /** 944 | * Performs a forward fling with the default number of fling steps (5). If the swipe direction is set to vertical, then the swipes will be performed from bottom to top. If the swipe direction is set to horizontal, then the swipes will be performed from right to left. Make sure to take into account devices configured with right-to-left languages like Arabic and Hebrew. 945 | * @param obj the selector of the scrollable object 946 | * @param isVertical vertical or horizontal 947 | * @return true if scrolled, and false if can't scroll anymore 948 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 949 | * 950 | */ 951 | @Override 952 | public boolean flingForward(Selector obj, boolean isVertical) throws UiObjectNotFoundException { 953 | UiScrollable scrollable = new UiScrollable(obj.toUiSelector()); 954 | if (isVertical) 955 | setAsVerticalList(scrollable); 956 | else 957 | setAsHorizontalList(scrollable); 958 | return scrollable.flingForward(); 959 | } 960 | 961 | /** 962 | * Performs a fling gesture to reach the beginning of a scrollable layout element. The beginning can be at the top-most edge in the case of vertical controls, or the left-most edge for horizontal controls. Make sure to take into account devices configured with right-to-left languages like Arabic and Hebrew. 963 | * 964 | * @param obj the selector of the scrollable object 965 | * @param isVertical vertical or horizontal 966 | * @param maxSwipes max swipes to achieve beginning. 967 | * @return true on scrolled, else false 968 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 969 | * 970 | */ 971 | @Override 972 | public boolean flingToBeginning(Selector obj, boolean isVertical, int maxSwipes) throws UiObjectNotFoundException { 973 | UiScrollable scrollable = new UiScrollable(obj.toUiSelector()); 974 | if (isVertical) 975 | setAsVerticalList(scrollable); 976 | else 977 | setAsHorizontalList(scrollable); 978 | return scrollable.flingToBeginning(maxSwipes); 979 | } 980 | 981 | /** 982 | * Performs a fling gesture to reach the end of a scrollable layout element. The end can be at the bottom-most edge in the case of vertical controls, or the right-most edge for horizontal controls. Make sure to take into account devices configured with right-to-left languages like Arabic and Hebrew. 983 | * 984 | * @param obj the selector of the scrollable object 985 | * @param isVertical vertical or horizontal 986 | * @param maxSwipes max swipes to achieve end. 987 | * @return true on scrolled, else false 988 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 989 | * 990 | */ 991 | @Override 992 | public boolean flingToEnd(Selector obj, boolean isVertical, int maxSwipes) throws UiObjectNotFoundException { 993 | UiScrollable scrollable = new UiScrollable(obj.toUiSelector()); 994 | if (isVertical) 995 | setAsVerticalList(scrollable); 996 | else 997 | setAsHorizontalList(scrollable); 998 | return scrollable.flingToEnd(maxSwipes); 999 | } 1000 | 1001 | /** 1002 | * Performs a backward scroll. If the swipe direction is set to vertical, then the swipes will be performed from top to bottom. If the swipe direction is set to horizontal, then the swipes will be performed from left to right. Make sure to take into account devices configured with right-to-left languages like Arabic and Hebrew. 1003 | * 1004 | * @param obj the selector of the scrollable object 1005 | * @param isVertical vertical or horizontal 1006 | * @param steps number of steps. Use this to control the speed of the scroll action. 1007 | * @return true if scrolled, false if can't scroll anymore 1008 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 1009 | * 1010 | */ 1011 | @Override 1012 | public boolean scrollBackward(Selector obj, boolean isVertical, int steps) throws UiObjectNotFoundException { 1013 | UiScrollable scrollable = new UiScrollable(obj.toUiSelector()); 1014 | if (isVertical) 1015 | setAsVerticalList(scrollable); 1016 | else 1017 | setAsHorizontalList(scrollable); 1018 | return scrollable.scrollBackward(steps); 1019 | } 1020 | 1021 | /** 1022 | * Performs a forward scroll with the default number of scroll steps (55). If the swipe direction is set to vertical, then the swipes will be performed from bottom to top. If the swipe direction is set to horizontal, then the swipes will be performed from right to left. Make sure to take into account devices configured with right-to-left languages like Arabic and Hebrew. 1023 | * 1024 | * @param obj the selector of the scrollable object 1025 | * @param isVertical vertical or horizontal 1026 | * @param steps number of steps. Use this to control the speed of the scroll action. 1027 | * @return true on scrolled, else false 1028 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 1029 | * 1030 | */ 1031 | @Override 1032 | public boolean scrollForward(Selector obj, boolean isVertical, int steps) throws UiObjectNotFoundException { 1033 | UiScrollable scrollable = new UiScrollable(obj.toUiSelector()); 1034 | if (isVertical) 1035 | setAsVerticalList(scrollable); 1036 | else 1037 | setAsHorizontalList(scrollable); 1038 | return scrollable.scrollForward(steps); 1039 | } 1040 | 1041 | /** 1042 | * Scrolls to the beginning of a scrollable layout element. The beginning can be at the top-most edge in the case of vertical controls, or the left-most edge for horizontal controls. Make sure to take into account devices configured with right-to-left languages like Arabic and Hebrew. 1043 | * 1044 | * @param obj the selector of the scrollable object 1045 | * @param isVertical vertical or horizontal 1046 | * @param maxSwipes max swipes to be performed. 1047 | * @param steps use steps to control the speed, so that it may be a scroll, or fling 1048 | * @return true on scrolled else false 1049 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 1050 | * 1051 | */ 1052 | @Override 1053 | public boolean scrollToBeginning(Selector obj, boolean isVertical, int maxSwipes, int steps) throws UiObjectNotFoundException { 1054 | UiScrollable scrollable = new UiScrollable(obj.toUiSelector()); 1055 | if (isVertical) 1056 | setAsVerticalList(scrollable); 1057 | else 1058 | setAsHorizontalList(scrollable); 1059 | return scrollable.scrollToBeginning(maxSwipes, steps); 1060 | } 1061 | 1062 | /** 1063 | * Scrolls to the end of a scrollable layout element. The end can be at the bottom-most edge in the case of vertical controls, or the right-most edge for horizontal controls. Make sure to take into account devices configured with right-to-left languages like Arabic and Hebrew. 1064 | * 1065 | * @param obj the selector of the scrollable object 1066 | * @param isVertical vertical or horizontal 1067 | * @param maxSwipes max swipes to be performed. 1068 | * @param steps use steps to control the speed, so that it may be a scroll, or fling 1069 | * @return true on scrolled, else false 1070 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 1071 | * 1072 | */ 1073 | @Override 1074 | public boolean scrollToEnd(Selector obj, boolean isVertical, int maxSwipes, int steps) throws UiObjectNotFoundException { 1075 | UiScrollable scrollable = new UiScrollable(obj.toUiSelector()); 1076 | if (isVertical) 1077 | setAsVerticalList(scrollable); 1078 | else 1079 | setAsHorizontalList(scrollable); 1080 | return scrollable.scrollToEnd(maxSwipes, steps); 1081 | } 1082 | 1083 | /** 1084 | * Perform a scroll forward action to move through the scrollable layout element until a visible item that matches the selector is found. 1085 | * 1086 | * @param obj the selector of the scrollable object 1087 | * @param targetObj the item matches the selector to be found. 1088 | * @param isVertical vertical or horizontal 1089 | * @return true on scrolled, else false 1090 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 1091 | * 1092 | */ 1093 | @Override 1094 | public boolean scrollTo(Selector obj, Selector targetObj, boolean isVertical) throws UiObjectNotFoundException { 1095 | UiScrollable scrollable = new UiScrollable(obj.toUiSelector()); 1096 | if (isVertical) 1097 | setAsVerticalList(scrollable); 1098 | else 1099 | setAsHorizontalList(scrollable); 1100 | return scrollable.scrollIntoView(targetObj.toUiSelector()); 1101 | } 1102 | 1103 | 1104 | /** 1105 | * Name an UiObject and cache it. 1106 | * @param obj UiObject 1107 | * @return the name of the UiObject 1108 | */ 1109 | private String addUiObject(UiObject obj) { 1110 | String key = UUID.randomUUID().toString(); 1111 | uiObjects.put(key, obj); 1112 | // schedule the clear timer. 1113 | Timer clearTimer = new Timer(); 1114 | clearTimer.schedule(new ClearUiObjectTimerTask(key), 60000); 1115 | return key; 1116 | } 1117 | 1118 | class ClearUiObjectTimerTask extends TimerTask { 1119 | String name; 1120 | 1121 | public ClearUiObjectTimerTask(String name) { 1122 | this.name = name; 1123 | } 1124 | 1125 | public void run() { 1126 | uiObjects.remove(name); 1127 | } 1128 | } 1129 | /** 1130 | * Searches for child UI element within the constraints of this UiSelector selector. It looks for any child matching the childPattern argument that has a child UI element anywhere within its sub hierarchy that has a text attribute equal to text. The returned UiObject will point at the childPattern instance that matched the search and not at the identifying child element that matched the text attribute. 1131 | * 1132 | * @param collection Selector of UiCollection or UiScrollable. 1133 | * @param text String of the identifying child contents of of the childPattern 1134 | * @param child UiSelector selector of the child pattern to match and return 1135 | * @return A string ID represent the returned UiObject. 1136 | */ 1137 | @Override 1138 | public String childByText(Selector collection, Selector child, String text) throws UiObjectNotFoundException{ 1139 | UiObject obj; 1140 | if (exist(collection) && objInfo(collection).isScrollable()) { 1141 | obj = new UiScrollable(collection.toUiSelector()).getChildByText(child.toUiSelector(), text); 1142 | } else { 1143 | obj = new UiCollection(collection.toUiSelector()).getChildByText(child.toUiSelector(), text); 1144 | } 1145 | return addUiObject(obj); 1146 | } 1147 | 1148 | @Override 1149 | public String childByText(Selector collection, Selector child, String text, boolean allowScrollSearch) throws UiObjectNotFoundException { 1150 | UiObject obj = new UiScrollable(collection.toUiSelector()).getChildByText(child.toUiSelector(), text, allowScrollSearch); 1151 | return addUiObject(obj); 1152 | } 1153 | 1154 | /** 1155 | * Searches for child UI element within the constraints of this UiSelector selector. It looks for any child matching the childPattern argument that has a child UI element anywhere within its sub hierarchy that has content-description text. The returned UiObject will point at the childPattern instance that matched the search and not at the identifying child element that matched the content description. 1156 | * 1157 | * @param collection Selector of UiCollection or UiScrollable 1158 | * @param child UiSelector selector of the child pattern to match and return 1159 | * @param text String of the identifying child contents of of the childPattern 1160 | * @return A string ID represent the returned UiObject. 1161 | */ 1162 | @Override 1163 | public String childByDescription(Selector collection, Selector child, String text) throws UiObjectNotFoundException { 1164 | UiObject obj; 1165 | if (exist(collection) && objInfo(collection).isScrollable()) { 1166 | obj = new UiScrollable(collection.toUiSelector()).getChildByDescription(child.toUiSelector(), text); 1167 | } else { 1168 | obj = new UiCollection(collection.toUiSelector()).getChildByDescription(child.toUiSelector(), text); 1169 | } 1170 | return addUiObject(obj); 1171 | } 1172 | 1173 | @Override 1174 | public String childByDescription(Selector collection, Selector child, String text, boolean allowScrollSearch) throws UiObjectNotFoundException { 1175 | UiObject obj = new UiScrollable(collection.toUiSelector()).getChildByDescription(child.toUiSelector(), text, allowScrollSearch); 1176 | return addUiObject(obj); 1177 | } 1178 | 1179 | /** 1180 | * Searches for child UI element within the constraints of this UiSelector. It looks for any child matching the childPattern argument that has a child UI element anywhere within its sub hierarchy that is at the instance specified. The operation is performed only on the visible items and no scrolling is performed in this case. 1181 | * 1182 | * @param collection Selector of UiCollection or UiScrollable 1183 | * @param child UiSelector selector of the child pattern to match and return 1184 | * @param instance int the desired matched instance of this childPattern 1185 | * @return A string ID represent the returned UiObject. 1186 | */ 1187 | @Override 1188 | public String childByInstance(Selector collection, Selector child, int instance) throws UiObjectNotFoundException { 1189 | UiObject obj; 1190 | if (exist(collection) && objInfo(collection).isScrollable()) { 1191 | obj = new UiScrollable(collection.toUiSelector()).getChildByInstance(child.toUiSelector(), instance); 1192 | } else { 1193 | obj = new UiCollection(collection.toUiSelector()).getChildByInstance(child.toUiSelector(), instance); 1194 | } 1195 | return addUiObject(obj); 1196 | } 1197 | 1198 | /** 1199 | * Creates a new UiObject for a child view that is under the present UiObject. 1200 | * 1201 | * @param obj The ID string represent the parent UiObject. 1202 | * @param selector UiSelector selector of the child pattern to match and return 1203 | * @return A string ID represent the returned UiObject. 1204 | */ 1205 | @Override 1206 | public String getChild(String obj, Selector selector) throws UiObjectNotFoundException { 1207 | UiObject ui = uiObjects.get(obj); 1208 | if (ui != null) { 1209 | return addUiObject(ui.getChild(selector.toUiSelector())); 1210 | } 1211 | return null; 1212 | } 1213 | 1214 | /** 1215 | * Creates a new UiObject for a sibling view or a child of the sibling view, relative to the present UiObject. 1216 | * 1217 | * @param obj The ID string represent the source UiObject. 1218 | * @param selector for a sibling view or children of the sibling view 1219 | * @return A string ID represent the returned UiObject. 1220 | */ 1221 | @Override 1222 | public String getFromParent(String obj, Selector selector) throws UiObjectNotFoundException { 1223 | UiObject ui = uiObjects.get(obj); 1224 | if (ui != null) { 1225 | return addUiObject(ui.getFromParent(selector.toUiSelector())); 1226 | } 1227 | return null; 1228 | } 1229 | 1230 | /** 1231 | * Get a new UiObject from the selector. 1232 | * 1233 | * @param selector Selector of the UiObject 1234 | * @return A string ID represent the returned UiObject. 1235 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 1236 | * 1237 | */ 1238 | @Override 1239 | public String getUiObject(Selector selector) throws UiObjectNotFoundException { 1240 | return addUiObject(new UiObject(selector.toUiSelector())); 1241 | } 1242 | 1243 | /** 1244 | * Remove the UiObject from memory. 1245 | */ 1246 | @Override 1247 | public void removeUiObject(String obj) { 1248 | uiObjects.remove(obj); 1249 | } 1250 | 1251 | /** 1252 | * Get all named UiObjects. 1253 | * 1254 | * @return all names 1255 | */ 1256 | @Override 1257 | public String[] getUiObjects() { 1258 | Set strings = uiObjects.keySet(); 1259 | return strings.toArray(new String[strings.size()]); 1260 | } 1261 | 1262 | private UiObject getUiObject(String name) throws UiObjectNotFoundException{ 1263 | if (uiObjects.containsKey(name)) { 1264 | return uiObjects.get(name); 1265 | } else { 1266 | throw new UiObjectNotFoundException("UiObject " + name + " not found!"); 1267 | } 1268 | } 1269 | 1270 | /** 1271 | * Clears the existing text contents in an editable field. The UiSelector of this object must reference a UI element that is editable. When you call this method, the method first sets focus at the start edge of the field. The method then simulates a long-press to select the existing text, and deletes the selected text. If a "Select-All" option is displayed, the method will automatically attempt to use it to ensure full text selection. Note that it is possible that not all the text in the field is selected; for example, if the text contains separators such as spaces, slashes, at symbol etc. Also, not all editable fields support the long-press functionality. 1272 | * 1273 | * @param obj the id of the UiObject. 1274 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 1275 | * 1276 | */ 1277 | @Override 1278 | public void clearTextField(String obj) throws UiObjectNotFoundException { 1279 | getUiObject(obj).clearTextField(); 1280 | } 1281 | 1282 | /** 1283 | * Reads the text property of the UI element 1284 | * 1285 | * @param obj the id of the UiObject. 1286 | * @return text value of the current node represented by this UiObject 1287 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 1288 | * 1289 | */ 1290 | @Override 1291 | public String getText(String obj) throws UiObjectNotFoundException { 1292 | return getUiObject(obj).getText(); 1293 | } 1294 | 1295 | /** 1296 | * Sets the text in an editable field, after clearing the field's content. The UiSelector selector of this object must reference a UI element that is editable. When you call this method, the method first simulates a click() on editable field to set focus. The method then clears the field's contents and injects your specified text into the field. If you want to capture the original contents of the field, call getText() first. You can then modify the text and use this method to update the field. 1297 | * 1298 | * @param obj the id of the UiObject. 1299 | * @param text string to set 1300 | * @return true if operation is successful 1301 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 1302 | * 1303 | */ 1304 | @Override 1305 | public boolean setText(String obj, String text) throws UiObjectNotFoundException { 1306 | return getUiObject(obj).setText(text); 1307 | } 1308 | 1309 | /** 1310 | * Performs a click at the center of the visible bounds of the UI element represented by this UiObject. 1311 | * 1312 | * @param obj the id of target ui object. 1313 | * @return true id successful else false 1314 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 1315 | * 1316 | */ 1317 | @Override 1318 | public boolean click(String obj) throws UiObjectNotFoundException { 1319 | return getUiObject(obj).click(); 1320 | } 1321 | 1322 | /** 1323 | * Clicks the bottom and right corner or top and left corner of the UI element 1324 | * 1325 | * @param obj the id of target ui object. 1326 | * @param corner "br"/"bottomright" means BottomRight, "tl"/"topleft" means TopLeft, "center" means Center. 1327 | * @return true on success 1328 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 1329 | * 1330 | */ 1331 | @Override 1332 | public boolean click(String obj, String corner) throws UiObjectNotFoundException { 1333 | return click(getUiObject(obj), corner); 1334 | } 1335 | 1336 | /** 1337 | * Performs a click at the center of the visible bounds of the UI element represented by this UiObject and waits for window transitions. This method differ from click() only in that this method waits for a a new window transition as a result of the click. Some examples of a window transition: 1338 | * - launching a new activity 1339 | * - bringing up a pop-up menu 1340 | * - bringing up a dialog 1341 | * 1342 | * @param obj the id of target ui object. 1343 | * @param timeout timeout before giving up on waiting for a new window 1344 | * @return true if the event was triggered, else false 1345 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 1346 | * 1347 | */ 1348 | @Override 1349 | public boolean clickAndWaitForNewWindow(String obj, long timeout) throws UiObjectNotFoundException { 1350 | return getUiObject(obj).clickAndWaitForNewWindow(timeout); 1351 | } 1352 | 1353 | /** 1354 | * Long clicks the center of the visible bounds of the UI element 1355 | * 1356 | * @param obj the id of target ui object. 1357 | * @return true if operation was successful 1358 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 1359 | * 1360 | */ 1361 | @Override 1362 | public boolean longClick(String obj) throws UiObjectNotFoundException { 1363 | return getUiObject(obj).longClick(); 1364 | } 1365 | 1366 | /** 1367 | * Long clicks bottom and right corner of the UI element 1368 | * 1369 | * @param obj the id of target ui object. 1370 | * @param corner "br"/"bottomright" means BottomRight, "tl"/"topleft" means TopLeft, "center" means Center. 1371 | * @return true if operation was successful 1372 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 1373 | * 1374 | */ 1375 | @Override 1376 | public boolean longClick(String obj, String corner) throws UiObjectNotFoundException { 1377 | return longClick(getUiObject(obj), corner); 1378 | } 1379 | 1380 | /** 1381 | * Drags this object to a destination UiObject. The number of steps specified in your input parameter can influence the drag speed, and varying speeds may impact the results. Consider evaluating different speeds when using this method in your tests. 1382 | * 1383 | * @param obj the id of ui object to be dragged. 1384 | * @param destObj the ui object to be dragged to. 1385 | * @param steps usually 40 steps. You can increase or decrease the steps to change the speed. 1386 | * @return true if successful 1387 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 1388 | * 1389 | * @throws com.github.uiautomatorstub.NotImplementedException 1390 | * 1391 | */ 1392 | @Override 1393 | public boolean dragTo(String obj, Selector destObj, int steps) throws UiObjectNotFoundException, NotImplementedException { 1394 | return dragTo(getUiObject(obj), destObj, steps); 1395 | } 1396 | 1397 | /** 1398 | * Drags this object to arbitrary coordinates. The number of steps specified in your input parameter can influence the drag speed, and varying speeds may impact the results. Consider evaluating different speeds when using this method in your tests. 1399 | * 1400 | * @param obj the id of ui object to be dragged. 1401 | * @param destX the X-axis coordinate of destination. 1402 | * @param destY the Y-axis coordinate of destination. 1403 | * @param steps usually 40 steps. You can increase or decrease the steps to change the speed. 1404 | * @return true if successful 1405 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 1406 | * 1407 | * @throws com.github.uiautomatorstub.NotImplementedException 1408 | * 1409 | */ 1410 | @Override 1411 | public boolean dragTo(String obj, int destX, int destY, int steps) throws UiObjectNotFoundException, NotImplementedException { 1412 | return dragTo(getUiObject(obj), destX, destY, steps); 1413 | } 1414 | 1415 | /** 1416 | * Check if view exists. This methods performs a waitForExists(long) with zero timeout. This basically returns immediately whether the view represented by this UiObject exists or not. 1417 | * 1418 | * @param obj the id of ui object. 1419 | * @return true if the view represented by this UiObject does exist 1420 | */ 1421 | @Override 1422 | public boolean exist(String obj) { 1423 | try { 1424 | return getUiObject(obj).exists(); 1425 | } catch (UiObjectNotFoundException e) { 1426 | return false; 1427 | } 1428 | } 1429 | 1430 | /** 1431 | * Get the object info. 1432 | * 1433 | * @param obj the id of target ui object. 1434 | * @return object info. 1435 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 1436 | * 1437 | */ 1438 | @Override 1439 | public ObjInfo objInfo(String obj) throws UiObjectNotFoundException { 1440 | return ObjInfo.getObjInfo(getUiObject(obj)); 1441 | } 1442 | 1443 | /** 1444 | * Generates a two-pointer gesture with arbitrary starting and ending points. 1445 | * 1446 | * @param obj the id of target ui object. ?? 1447 | * @param startPoint1 start point of pointer 1 1448 | * @param startPoint2 start point of pointer 2 1449 | * @param endPoint1 end point of pointer 1 1450 | * @param endPoint2 end point of pointer 2 1451 | * @param steps the number of steps for the gesture. Steps are injected about 5 milliseconds apart, so 100 steps may take around 0.5 seconds to complete. 1452 | * @return true if all touch events for this gesture are injected successfully, false otherwise 1453 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 1454 | * 1455 | */ 1456 | @Override 1457 | public boolean gesture(String obj, Point startPoint1, Point startPoint2, Point endPoint1, Point endPoint2, int steps) throws UiObjectNotFoundException, NotImplementedException { 1458 | return gesture(getUiObject(obj), startPoint1, startPoint2, endPoint1, endPoint2, steps); 1459 | } 1460 | 1461 | /** 1462 | * Performs a two-pointer gesture, where each pointer moves diagonally toward the other, from the edges to the center of this UiObject . 1463 | * 1464 | * @param obj the id of target ui object. 1465 | * @param percent percentage of the object's diagonal length for the pinch gesture 1466 | * @param steps the number of steps for the gesture. Steps are injected about 5 milliseconds apart, so 100 steps may take around 0.5 seconds to complete. 1467 | * @return true if all touch events for this gesture are injected successfully, false otherwise 1468 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 1469 | * 1470 | * @throws com.github.uiautomatorstub.NotImplementedException 1471 | * 1472 | */ 1473 | @Override 1474 | public boolean pinchIn(String obj, int percent, int steps) throws UiObjectNotFoundException, NotImplementedException { 1475 | return pinchIn(getUiObject(obj), percent, steps); 1476 | } 1477 | 1478 | /** 1479 | * Performs a two-pointer gesture, where each pointer moves diagonally opposite across the other, from the center out towards the edges of the this UiObject. 1480 | * 1481 | * @param obj the id of target ui object. 1482 | * @param percent percentage of the object's diagonal length for the pinch gesture 1483 | * @param steps the number of steps for the gesture. Steps are injected about 5 milliseconds apart, so 100 steps may take around 0.5 seconds to complete. 1484 | * @return true if all touch events for this gesture are injected successfully, false otherwise 1485 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 1486 | * 1487 | * @throws com.github.uiautomatorstub.NotImplementedException 1488 | * 1489 | */ 1490 | @Override 1491 | public boolean pinchOut(String obj, int percent, int steps) throws UiObjectNotFoundException, NotImplementedException { 1492 | return pinchOut(getUiObject(obj), percent, steps); 1493 | } 1494 | 1495 | /** 1496 | * Performs the swipe up/down/left/right action on the UiObject 1497 | * 1498 | * @param obj the id of target ui object. 1499 | * @param dir "u"/"up", "d"/"down", "l"/"left", "r"/"right" 1500 | * @param steps indicates the number of injected move steps into the system. Steps are injected about 5ms apart. So a 100 steps may take about 1/2 second to complete. 1501 | * @return true of successful 1502 | * @throws com.android.uiautomator.core.UiObjectNotFoundException 1503 | * 1504 | */ 1505 | @Override 1506 | public boolean swipe(String obj, String dir, int steps) throws UiObjectNotFoundException { 1507 | return swipe(getUiObject(obj), dir, steps); 1508 | } 1509 | 1510 | /** 1511 | * Waits a specified length of time for a view to become visible. This method waits until the view becomes visible on the display, or until the timeout has elapsed. You can use this method in situations where the content that you want to select is not immediately displayed. 1512 | * 1513 | * @param obj the id of target ui object 1514 | * @param timeout time to wait (in milliseconds) 1515 | * @return true if the view is displayed, else false if timeout elapsed while waiting 1516 | */ 1517 | @Override 1518 | public boolean waitForExists(String obj, long timeout) throws UiObjectNotFoundException { 1519 | return getUiObject(obj).waitForExists(timeout); 1520 | } 1521 | 1522 | /** 1523 | * Waits a specified length of time for a view to become undetectable. This method waits until a view is no longer matchable, or until the timeout has elapsed. A view becomes undetectable when the UiSelector of the object is unable to find a match because the element has either changed its state or is no longer displayed. You can use this method when attempting to wait for some long operation to compete, such as downloading a large file or connecting to a remote server. 1524 | * 1525 | * @param obj the id of target ui object 1526 | * @param timeout time to wait (in milliseconds) 1527 | * @return true if the element is gone before timeout elapsed, else false if timeout elapsed but a matching element is still found. 1528 | */ 1529 | @Override 1530 | public boolean waitUntilGone(String obj, long timeout) throws UiObjectNotFoundException { 1531 | return getUiObject(obj).waitUntilGone(timeout); 1532 | } 1533 | 1534 | /** 1535 | * Get Configurator 1536 | * 1537 | * @return Configurator information. 1538 | * @throws com.github.uiautomatorstub.NotImplementedException 1539 | */ 1540 | @Override 1541 | public ConfiguratorInfo getConfigurator() throws NotImplementedException { 1542 | if (Build.VERSION.SDK_INT < 18) 1543 | throw new NotImplementedException(); 1544 | 1545 | return new ConfiguratorInfo(); 1546 | } 1547 | 1548 | /** 1549 | * Set Configurator. 1550 | * 1551 | * @param info the configurator information to be set. 1552 | * @throws com.github.uiautomatorstub.NotImplementedException 1553 | */ 1554 | @Override 1555 | public ConfiguratorInfo setConfigurator(ConfiguratorInfo info) throws NotImplementedException { 1556 | if (Build.VERSION.SDK_INT < 18) 1557 | throw new NotImplementedException(); 1558 | 1559 | ConfiguratorInfo.setConfigurator(info); 1560 | return new ConfiguratorInfo(); 1561 | } 1562 | } 1563 | -------------------------------------------------------------------------------- /src/com/github/uiautomatorstub/ConfiguratorInfo.java: -------------------------------------------------------------------------------- 1 | package com.github.uiautomatorstub; 2 | 3 | import java.lang.reflect.InvocationTargetException; 4 | 5 | /** 6 | * Created by b036 on 12/26/13. 7 | */ 8 | public class ConfiguratorInfo { 9 | 10 | public ConfiguratorInfo() { 11 | try { 12 | Class clz = Class.forName("com.android.uiautomator.core.Configurator"); 13 | Object conf = clz.getMethod("getInstance").invoke(null); 14 | this._actionAcknowledgmentTimeout = (Long)clz.getMethod("getActionAcknowledgmentTimeout").invoke(conf); 15 | this._keyInjectionDelay = (Long)clz.getMethod("getKeyInjectionDelay").invoke(conf); 16 | this._scrollAcknowledgmentTimeout = (Long)clz.getMethod("getScrollAcknowledgmentTimeout").invoke(conf); 17 | this._waitForIdleTimeout = (Long)clz.getMethod("getWaitForIdleTimeout").invoke(conf); 18 | this._waitForSelectorTimeout = (Long)clz.getMethod("getWaitForSelectorTimeout").invoke(conf); 19 | } catch (IllegalAccessException e) { 20 | e.printStackTrace(); 21 | } catch (InvocationTargetException e) { 22 | e.printStackTrace(); 23 | } catch (NoSuchMethodException e) { 24 | e.printStackTrace(); 25 | } catch (ClassNotFoundException e) { 26 | e.printStackTrace(); 27 | } 28 | } 29 | 30 | public long getActionAcknowledgmentTimeout() { 31 | return _actionAcknowledgmentTimeout; 32 | } 33 | 34 | public void setActionAcknowledgmentTimeout(long _actionAcknowledgmentTimeout) { 35 | this._actionAcknowledgmentTimeout = _actionAcknowledgmentTimeout; 36 | } 37 | 38 | public long getKeyInjectionDelay() { 39 | return _keyInjectionDelay; 40 | } 41 | 42 | public void setKeyInjectionDelay(long _keyInjectionDelay) { 43 | this._keyInjectionDelay = _keyInjectionDelay; 44 | } 45 | 46 | public long getScrollAcknowledgmentTimeout() { 47 | return _scrollAcknowledgmentTimeout; 48 | } 49 | 50 | public void setScrollAcknowledgmentTimeout(long _scrollAcknowledgmentTimeout) { 51 | this._scrollAcknowledgmentTimeout = _scrollAcknowledgmentTimeout; 52 | } 53 | 54 | public long getWaitForIdleTimeout() { 55 | return _waitForIdleTimeout; 56 | } 57 | 58 | public void setWaitForIdleTimeout(long _waitForIdleTimeout) { 59 | this._waitForIdleTimeout = _waitForIdleTimeout; 60 | } 61 | 62 | public long getWaitForSelectorTimeout() { 63 | return _waitForSelectorTimeout; 64 | } 65 | 66 | public void setWaitForSelectorTimeout(long _waitForSelectorTimeout) { 67 | this._waitForSelectorTimeout = _waitForSelectorTimeout; 68 | } 69 | 70 | public static void setConfigurator(ConfiguratorInfo info) { 71 | try { 72 | Class clz = Class.forName("com.android.uiautomator.core.Configurator"); 73 | Object conf = clz.getMethod("getInstance").invoke(null); 74 | clz.getMethod("setActionAcknowledgmentTimeout", Long.TYPE).invoke(conf, info.getActionAcknowledgmentTimeout()); 75 | clz.getMethod("setKeyInjectionDelay", Long.TYPE).invoke(conf, info.getKeyInjectionDelay()); 76 | clz.getMethod("setScrollAcknowledgmentTimeout", Long.TYPE).invoke(conf, info.getScrollAcknowledgmentTimeout()); 77 | clz.getMethod("setWaitForIdleTimeout", Long.TYPE).invoke(conf, info.getWaitForIdleTimeout()); 78 | clz.getMethod("setWaitForSelectorTimeout", Long.TYPE).invoke(conf, info.getWaitForSelectorTimeout()); 79 | } catch (IllegalAccessException e) { 80 | Log.d(e.getMessage()); 81 | } catch (InvocationTargetException e) { 82 | Log.d(e.getMessage()); 83 | } catch (NoSuchMethodException e) { 84 | Log.d(e.getMessage()); 85 | } catch (ClassNotFoundException e) { 86 | Log.d(e.getMessage()); 87 | } 88 | } 89 | 90 | private long _actionAcknowledgmentTimeout; 91 | private long _keyInjectionDelay; 92 | private long _scrollAcknowledgmentTimeout; 93 | private long _waitForIdleTimeout; 94 | private long _waitForSelectorTimeout; 95 | } 96 | -------------------------------------------------------------------------------- /src/com/github/uiautomatorstub/DeviceInfo.java: -------------------------------------------------------------------------------- 1 | package com.github.uiautomatorstub; 2 | 3 | import android.os.RemoteException; 4 | 5 | import com.android.uiautomator.core.UiDevice; 6 | 7 | public class DeviceInfo { 8 | private String _currentPackageName; 9 | private int _displayWidth; 10 | private int _displayHeight; 11 | private int _displayRotation; 12 | private int _displaySizeDpX; 13 | private int _displaySizeDpY; 14 | private String _productName; 15 | private boolean _naturalOrientation; 16 | 17 | private int _sdkInt; 18 | 19 | public final static DeviceInfo getDeviceInfo() { 20 | return new DeviceInfo(); 21 | } 22 | 23 | private DeviceInfo() { 24 | this._sdkInt = android.os.Build.VERSION.SDK_INT; 25 | 26 | UiDevice ud = UiDevice.getInstance(); 27 | this._currentPackageName = ud.getCurrentPackageName(); 28 | this._displayWidth = ud.getDisplayWidth(); 29 | this._displayHeight = ud.getDisplayHeight(); 30 | if (_sdkInt >= 17) { 31 | this._displayRotation = ud.getDisplayRotation(); 32 | this._productName = ud.getProductName(); 33 | this._naturalOrientation = ud.isNaturalOrientation(); 34 | } 35 | if (_sdkInt >= 18) { 36 | this._displaySizeDpX = ud.getDisplaySizeDp().x; 37 | this._displaySizeDpY = ud.getDisplaySizeDp().y; 38 | } 39 | } 40 | 41 | public String getCurrentPackageName() { 42 | return _currentPackageName; 43 | } 44 | 45 | public void setCurrentPackageName(String currentPackageName) { 46 | this._currentPackageName = currentPackageName; 47 | } 48 | 49 | public int getDisplayWidth() { 50 | return _displayWidth; 51 | } 52 | 53 | public void setDisplayWidth(int displayWidth) { 54 | this._displayWidth = displayWidth; 55 | } 56 | 57 | public int getDisplayHeight() { 58 | return _displayHeight; 59 | } 60 | 61 | public void setDisplayHeight(int displayHeight) { 62 | this._displayHeight = displayHeight; 63 | } 64 | 65 | public int getDisplayRotation() { 66 | return _displayRotation; 67 | } 68 | 69 | public void setDisplayRotation(int displayRotation) { 70 | this._displayRotation = displayRotation; 71 | } 72 | 73 | public int getDisplaySizeDpX() { 74 | return _displaySizeDpX; 75 | } 76 | 77 | public void setDisplaySizeDpX(int displaySizeDpX) { 78 | this._displaySizeDpX = displaySizeDpX; 79 | } 80 | 81 | public int getDisplaySizeDpY() { 82 | return _displaySizeDpY; 83 | } 84 | 85 | public void setDisplaySizeDpY(int displaySizeDpY) { 86 | this._displaySizeDpY = displaySizeDpY; 87 | } 88 | 89 | public String getProductName() { 90 | return _productName; 91 | } 92 | 93 | public void setProductName(String productName) { 94 | this._productName = productName; 95 | } 96 | 97 | public boolean isNaturalOrientation() { 98 | return _naturalOrientation; 99 | } 100 | 101 | public void setNaturalOrientation(boolean naturalOrientation) { 102 | this._naturalOrientation = naturalOrientation; 103 | } 104 | 105 | public int getSdkInt() { 106 | return _sdkInt; 107 | } 108 | 109 | public void setSdkInt(int sdkInt) { 110 | this._sdkInt = sdkInt; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/com/github/uiautomatorstub/Log.java: -------------------------------------------------------------------------------- 1 | package com.github.uiautomatorstub; 2 | 3 | public class Log { 4 | public static final String TAG = "UIAutomatorStub"; 5 | 6 | public static void d(String msg) { 7 | android.util.Log.d(TAG, msg); 8 | } 9 | 10 | public static void i(String msg) { 11 | android.util.Log.i(TAG, msg); 12 | } 13 | 14 | public static void e(String msg) { 15 | android.util.Log.e(TAG, msg); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/com/github/uiautomatorstub/NotImplementedException.java: -------------------------------------------------------------------------------- 1 | package com.github.uiautomatorstub; 2 | 3 | import android.os.Build; 4 | 5 | /** 6 | * Created with IntelliJ IDEA. 7 | * User: b036 8 | * Date: 8/13/13 9 | * Time: 10:45 AM 10 | * To change this template use File | Settings | File Templates. 11 | */ 12 | public class NotImplementedException extends Exception { 13 | public NotImplementedException() { 14 | super("This method is not yet implemented in API level " + Build.VERSION.SDK_INT + "."); 15 | } 16 | 17 | public NotImplementedException(String method) { 18 | super(method + " is not yet implemented in API level " + Build.VERSION.SDK_INT + "."); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/com/github/uiautomatorstub/ObjInfo.java: -------------------------------------------------------------------------------- 1 | package com.github.uiautomatorstub; 2 | 3 | import com.android.uiautomator.core.UiObject; 4 | import com.android.uiautomator.core.UiObjectNotFoundException; 5 | import com.android.uiautomator.core.UiSelector; 6 | 7 | public class ObjInfo { 8 | 9 | public static final ObjInfo getObjInfo(UiObject obj) throws UiObjectNotFoundException { 10 | return new ObjInfo(obj); 11 | } 12 | 13 | public static final ObjInfo getObjInfo(UiSelector selector) throws UiObjectNotFoundException { 14 | return new ObjInfo(new UiObject(selector)); 15 | } 16 | 17 | private ObjInfo(UiObject obj) throws UiObjectNotFoundException { 18 | this._bounds = Rect.from(obj.getBounds()); 19 | this._checkable = obj.isCheckable(); 20 | this._checked = obj.isChecked(); 21 | this._childCount = obj.getChildCount(); 22 | this._clickable = obj.isClickable(); 23 | this._contentDescription = obj.getContentDescription(); 24 | this._enabled = obj.isEnabled(); 25 | this._focusable = obj.isFocusable(); 26 | this._focused = obj.isFocused(); 27 | this._longClickable = obj.isLongClickable(); 28 | this._packageName = obj.getPackageName(); 29 | this._scrollable = obj.isScrollable(); 30 | this._selected = obj.isSelected(); 31 | this._text = obj.getText(); 32 | if (android.os.Build.VERSION.SDK_INT >= 17) { 33 | this._visibleBounds = Rect.from(obj.getVisibleBounds()); 34 | } 35 | if (android.os.Build.VERSION.SDK_INT >= 18) { 36 | this._className = obj.getClassName(); 37 | } 38 | } 39 | 40 | private Rect _bounds; 41 | private Rect _visibleBounds; 42 | private int _childCount; 43 | private String _className; 44 | private String _contentDescription; 45 | private String _packageName; 46 | private String _text; 47 | private boolean _checkable; 48 | private boolean _checked; 49 | private boolean _clickable; 50 | private boolean _enabled; 51 | private boolean _focusable; 52 | private boolean _focused; 53 | private boolean _longClickable; 54 | private boolean _scrollable; 55 | private boolean _selected; 56 | 57 | public Rect getBounds() { 58 | return _bounds; 59 | } 60 | 61 | public void setBounds(Rect bounds) { 62 | this._bounds = bounds; 63 | } 64 | 65 | public Rect getVisibleBounds() { 66 | return _visibleBounds; 67 | } 68 | 69 | public void setVisibleBounds(Rect visibleBounds) { 70 | this._visibleBounds = visibleBounds; 71 | } 72 | 73 | public int getChildCount() { 74 | return _childCount; 75 | } 76 | 77 | public void setChildCount(int childCount) { 78 | this._childCount = childCount; 79 | } 80 | 81 | public String getClassName() { 82 | return _className; 83 | } 84 | 85 | public void setClassName(String className) { 86 | this._className = className; 87 | } 88 | 89 | public String getContentDescription() { 90 | return _contentDescription; 91 | } 92 | 93 | public void setContentDescription(String contentDescription) { 94 | this._contentDescription = contentDescription; 95 | } 96 | 97 | public String getPackageName() { 98 | return _packageName; 99 | } 100 | 101 | public void setPackageName(String packageName) { 102 | this._packageName = packageName; 103 | } 104 | 105 | public String getText() { 106 | return _text; 107 | } 108 | 109 | public void setText(String text) { 110 | this._text = text; 111 | } 112 | 113 | public boolean isCheckable() { 114 | return _checkable; 115 | } 116 | 117 | public void setCheckable(boolean checkable) { 118 | this._checkable = checkable; 119 | } 120 | 121 | public boolean isChecked() { 122 | return _checked; 123 | } 124 | 125 | public void setChecked(boolean checked) { 126 | this._checked = checked; 127 | } 128 | 129 | public boolean isClickable() { 130 | return _clickable; 131 | } 132 | 133 | public void setClickable(boolean clickable) { 134 | this._clickable = clickable; 135 | } 136 | 137 | public boolean isEnabled() { 138 | return _enabled; 139 | } 140 | 141 | public void setEnabled(boolean enabled) { 142 | this._enabled = enabled; 143 | } 144 | 145 | public boolean isFocusable() { 146 | return _focusable; 147 | } 148 | 149 | public void setFocusable(boolean focusable) { 150 | this._focusable = focusable; 151 | } 152 | 153 | public boolean isFocused() { 154 | return _focused; 155 | } 156 | 157 | public void setFocused(boolean focused) { 158 | this._focused = focused; 159 | } 160 | 161 | public boolean isLongClickable() { 162 | return _longClickable; 163 | } 164 | 165 | public void setLongClickable(boolean longClickable) { 166 | this._longClickable = longClickable; 167 | } 168 | 169 | public boolean isScrollable() { 170 | return _scrollable; 171 | } 172 | 173 | public void setScrollable(boolean scrollable) { 174 | this._scrollable = scrollable; 175 | } 176 | 177 | public boolean isSelected() { 178 | return _selected; 179 | } 180 | 181 | public void setSelected(boolean selected) { 182 | this._selected = selected; 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/com/github/uiautomatorstub/Point.java: -------------------------------------------------------------------------------- 1 | package com.github.uiautomatorstub; 2 | 3 | /** 4 | * Created with IntelliJ IDEA. 5 | * User: b036 6 | * Date: 8/13/13 7 | * Time: 10:11 AM 8 | * To change this template use File | Settings | File Templates. 9 | */ 10 | public class Point { 11 | private int _x; 12 | private int _y; 13 | 14 | public int getX() { 15 | return _x; 16 | } 17 | 18 | public void setX(int x) { 19 | this._x = x; 20 | } 21 | 22 | public int getY() { 23 | return _y; 24 | } 25 | 26 | public void setY(int y) { 27 | this._y = y; 28 | } 29 | 30 | public android.graphics.Point toPoint() { 31 | return new android.graphics.Point(_x, _y); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/com/github/uiautomatorstub/Rect.java: -------------------------------------------------------------------------------- 1 | package com.github.uiautomatorstub; 2 | 3 | /** 4 | * Created with IntelliJ IDEA. 5 | * User: b036 6 | * Date: 8/13/13 7 | * Time: 10:13 AM 8 | * To change this template use File | Settings | File Templates. 9 | */ 10 | public class Rect { 11 | private int _top; 12 | private int _bottom; 13 | private int _left; 14 | private int _right; 15 | 16 | public static Rect from(android.graphics.Rect r) { 17 | Rect rect = new Rect(); 18 | rect._top = r.top; 19 | rect._bottom = r.bottom; 20 | rect._left = r.left; 21 | rect._right = r.right; 22 | 23 | return rect; 24 | } 25 | 26 | public int getTop() { 27 | return _top; 28 | } 29 | 30 | public void setTop(int top) { 31 | this._top = top; 32 | } 33 | 34 | public int getBottom() { 35 | return _bottom; 36 | } 37 | 38 | public void setBottom(int bottom) { 39 | this._bottom = bottom; 40 | } 41 | 42 | public int getLeft() { 43 | return _left; 44 | } 45 | 46 | public void setLeft(int left) { 47 | this._left = left; 48 | } 49 | 50 | public int getRight() { 51 | return _right; 52 | } 53 | 54 | public void setRight(int right) { 55 | this._right = right; 56 | } 57 | 58 | public android.graphics.Rect toRect() { 59 | return new android.graphics.Rect(_left, _top, _right, _bottom); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/com/github/uiautomatorstub/Selector.java: -------------------------------------------------------------------------------- 1 | package com.github.uiautomatorstub; 2 | 3 | import com.android.uiautomator.core.UiSelector; 4 | 5 | public class Selector { 6 | private String _text; 7 | private String _textContains; 8 | private String _textMatches; 9 | private String _textStartsWith; 10 | private String _className; 11 | private String _classNameMatches; 12 | private String _description; 13 | private String _descriptionContains; 14 | private String _descriptionMatches; 15 | private String _descriptionStartsWith; 16 | private boolean _checkable; 17 | private boolean _checked; 18 | private boolean _clickable; 19 | private boolean _longClickable; 20 | private boolean _scrollable; 21 | private boolean _enabled; 22 | private boolean _focusable; 23 | private boolean _focused; 24 | private boolean _selected; 25 | private String _packageName; 26 | private String _packageNameMatches; 27 | private String _resourceId; 28 | private String _resourceIdMatches; 29 | private int _index; 30 | private int _instance; 31 | private Selector[] _childOrSiblingSelector = new Selector[]{}; 32 | private String[] _childOrSibling = new String[]{}; 33 | 34 | private long _mask; 35 | 36 | public static final long MASK_TEXT = 0x01; 37 | public static final long MASK_TEXTCONTAINS = 0x02; 38 | public static final long MASK_TEXTMATCHES = 0x04; 39 | public static final long MASK_TEXTSTARTSWITH = 0x08; 40 | public static final long MASK_CLASSNAME = 0x10; 41 | public static final long MASK_CLASSNAMEMATCHES = 0x20; 42 | public static final long MASK_DESCRIPTION = 0x40; 43 | public static final long MASK_DESCRIPTIONCONTAINS = 0x80; 44 | public static final long MASK_DESCRIPTIONMATCHES = 0x0100; 45 | public static final long MASK_DESCRIPTIONSTARTSWITH = 0x0200; 46 | public static final long MASK_CHECKABLE = 0x0400; 47 | public static final long MASK_CHECKED = 0x0800; 48 | public static final long MASK_CLICKABLE = 0x1000; 49 | public static final long MASK_LONGCLICKABLE = 0x2000; 50 | public static final long MASK_SCROLLABLE = 0x4000; 51 | public static final long MASK_ENABLED = 0x8000; 52 | public static final long MASK_FOCUSABLE = 0x010000; 53 | public static final long MASK_FOCUSED = 0x020000; 54 | public static final long MASK_SELECTED = 0x040000; 55 | public static final long MASK_PACKAGENAME = 0x080000; 56 | public static final long MASK_PACKAGENAMEMATCHES = 0x100000; 57 | public static final long MASK_RESOURCEID = 0x200000; 58 | public static final long MASK_RESOURCEIDMATCHES = 0x400000; 59 | public static final long MASK_INDEX = 0x800000; 60 | public static final long MASK_INSTANCE = 0x01000000; 61 | 62 | public UiSelector toUiSelector() { 63 | UiSelector s = new UiSelector(); 64 | if ((getMask() & Selector.MASK_CHECKABLE) > 0 && android.os.Build.VERSION.SDK_INT >= 18) 65 | s = s.checkable(this.isCheckable()); 66 | if ((getMask() & Selector.MASK_CHECKED) > 0) 67 | s = s.checked(isChecked()); 68 | if ((getMask() & Selector.MASK_CLASSNAME) > 0) 69 | s = s.className(getClassName()); // API level 16 should support it.... wrong in Android Java Doc 70 | if ((getMask() & Selector.MASK_CLASSNAMEMATCHES) > 0 && android.os.Build.VERSION.SDK_INT >= 17) 71 | s = s.classNameMatches(getClassNameMatches()); 72 | if ((getMask() & Selector.MASK_CLICKABLE) > 0) 73 | s = s.clickable(isClickable()); 74 | if ((getMask() & Selector.MASK_DESCRIPTION) > 0) 75 | s = s.description(getDescription()); 76 | if ((getMask() & Selector.MASK_DESCRIPTIONCONTAINS) > 0) 77 | s = s.descriptionContains(getDescriptionContains()); 78 | if ((getMask() & Selector.MASK_DESCRIPTIONMATCHES) > 0 && android.os.Build.VERSION.SDK_INT >= 17) 79 | s = s.descriptionMatches(getDescriptionMatches()); 80 | if ((getMask() & Selector.MASK_DESCRIPTIONSTARTSWITH) > 0) 81 | s = s.descriptionStartsWith(getDescriptionStartsWith()); 82 | if ((getMask() & Selector.MASK_ENABLED) > 0) 83 | s = s.enabled(isEnabled()); 84 | if ((getMask() & Selector.MASK_FOCUSABLE) > 0) 85 | s = s.focusable(isFocusable()); 86 | if ((getMask() & Selector.MASK_FOCUSED) > 0) 87 | s = s.focused(isFocused()); 88 | if ((getMask() & Selector.MASK_INDEX) > 0) 89 | s = s.index(getIndex()); 90 | if ((getMask() & Selector.MASK_INSTANCE) > 0) 91 | s = s .instance(getInstance()); 92 | if ((getMask() & Selector.MASK_LONGCLICKABLE) > 0 && android.os.Build.VERSION.SDK_INT >= 17) 93 | s = s.longClickable(isLongClickable()); 94 | if ((getMask() & Selector.MASK_PACKAGENAME) > 0) 95 | s = s.packageName(getPackageName()); 96 | if ((getMask() & Selector.MASK_PACKAGENAMEMATCHES) > 0 && android.os.Build.VERSION.SDK_INT >= 17) 97 | s = s.packageNameMatches(getPackageNameMatches()); 98 | if ((getMask() & Selector.MASK_RESOURCEID) > 0 && android.os.Build.VERSION.SDK_INT >= 18) 99 | s = s.resourceId(getResourceId()); 100 | if ((getMask() & Selector.MASK_RESOURCEIDMATCHES) > 0 && android.os.Build.VERSION.SDK_INT >= 18) 101 | s = s.resourceIdMatches(getResourceIdMatches()); 102 | if ((getMask() & Selector.MASK_SCROLLABLE) > 0) 103 | s = s.scrollable(isScrollable()); 104 | if ((getMask() & Selector.MASK_SELECTED) > 0) 105 | s = s.selected(isSelected()); 106 | if ((getMask() & Selector.MASK_TEXT) > 0) 107 | s = s.text(getText()); 108 | if ((getMask() & Selector.MASK_TEXTCONTAINS) > 0) 109 | s = s.textContains(getTextContains()); 110 | if ((getMask() & Selector.MASK_TEXTSTARTSWITH) > 0) 111 | s = s.textStartsWith(getTextStartsWith()); 112 | if ((getMask() & Selector.MASK_TEXTMATCHES) > 0 && android.os.Build.VERSION.SDK_INT >= 17) 113 | s = s.textMatches(getTextMatches()); 114 | 115 | for (int i = 0; i < this.getChildOrSibling().length && i < this.getChildOrSiblingSelector().length; i++) { 116 | if (this.getChildOrSibling()[i].toLowerCase().equals("child")) 117 | s = s.childSelector(getChildOrSiblingSelector()[i].toUiSelector()); 118 | else if (this.getChildOrSibling()[i].toLowerCase().equals("sibling")) 119 | s = s.fromParent((getChildOrSiblingSelector()[i].toUiSelector())); 120 | } 121 | 122 | return s; 123 | } 124 | 125 | public String getText() { 126 | return _text; 127 | } 128 | 129 | public void setText(String text) { 130 | this._text = text; 131 | } 132 | 133 | public String getClassName() { 134 | return _className; 135 | } 136 | 137 | public void setClassName(String className) { 138 | this._className = className; 139 | } 140 | 141 | public String getDescription() { 142 | return _description; 143 | } 144 | 145 | public void setDescription(String description) { 146 | this._description = description; 147 | } 148 | 149 | public String getTextContains() { 150 | return _textContains; 151 | } 152 | 153 | public void setTextContains(String _textContains) { 154 | this._textContains = _textContains; 155 | } 156 | 157 | public String getTextMatches() { 158 | return _textMatches; 159 | } 160 | 161 | public void setTextMatches(String _textMatches) { 162 | this._textMatches = _textMatches; 163 | } 164 | 165 | public String getTextStartsWith() { 166 | return _textStartsWith; 167 | } 168 | 169 | public void setTextStartsWith(String _textStartsWith) { 170 | this._textStartsWith = _textStartsWith; 171 | } 172 | 173 | public String getClassNameMatches() { 174 | return _classNameMatches; 175 | } 176 | 177 | public void setClassNameMatches(String _classNameMatches) { 178 | this._classNameMatches = _classNameMatches; 179 | } 180 | 181 | public String getDescriptionContains() { 182 | return _descriptionContains; 183 | } 184 | 185 | public void setDescriptionContains(String _descriptionContains) { 186 | this._descriptionContains = _descriptionContains; 187 | } 188 | 189 | public String getDescriptionMatches() { 190 | return _descriptionMatches; 191 | } 192 | 193 | public void setDescriptionMatches(String _descriptionMatches) { 194 | this._descriptionMatches = _descriptionMatches; 195 | } 196 | 197 | public String getDescriptionStartsWith() { 198 | return _descriptionStartsWith; 199 | } 200 | 201 | public void setDescriptionStartsWith(String _descriptionStartsWith) { 202 | this._descriptionStartsWith = _descriptionStartsWith; 203 | } 204 | 205 | public boolean isCheckable() { 206 | return _checkable; 207 | } 208 | 209 | public void setCheckable(boolean _checkable) { 210 | this._checkable = _checkable; 211 | } 212 | 213 | public boolean isChecked() { 214 | return _checked; 215 | } 216 | 217 | public void setChecked(boolean _checked) { 218 | this._checked = _checked; 219 | } 220 | 221 | public boolean isClickable() { 222 | return _clickable; 223 | } 224 | 225 | public void setClickable(boolean _clickable) { 226 | this._clickable = _clickable; 227 | } 228 | 229 | public boolean isScrollable() { 230 | return _scrollable; 231 | } 232 | 233 | public void setScrollable(boolean _scrollable) { 234 | this._scrollable = _scrollable; 235 | } 236 | 237 | public boolean isLongClickable() { 238 | return _longClickable; 239 | } 240 | 241 | public void setLongClickable(boolean _longClickable) { 242 | this._longClickable = _longClickable; 243 | } 244 | 245 | public boolean isEnabled() { 246 | return _enabled; 247 | } 248 | 249 | public void setEnabled(boolean _enabled) { 250 | this._enabled = _enabled; 251 | } 252 | 253 | public boolean isFocusable() { 254 | return _focusable; 255 | } 256 | 257 | public void setFocusable(boolean _focusable) { 258 | this._focusable = _focusable; 259 | } 260 | 261 | public boolean isFocused() { 262 | return _focused; 263 | } 264 | 265 | public void setFocused(boolean _focused) { 266 | this._focused = _focused; 267 | } 268 | 269 | public boolean isSelected() { 270 | return _selected; 271 | } 272 | 273 | public void setSelected(boolean _selected) { 274 | this._selected = _selected; 275 | } 276 | 277 | public String getPackageName() { 278 | return _packageName; 279 | } 280 | 281 | public void setPackageName(String _packageName) { 282 | this._packageName = _packageName; 283 | } 284 | 285 | public String getPackageNameMatches() { 286 | return _packageNameMatches; 287 | } 288 | 289 | public void setPackageNameMatches(String _packageNameMatches) { 290 | this._packageNameMatches = _packageNameMatches; 291 | } 292 | 293 | public String getResourceId() { 294 | return _resourceId; 295 | } 296 | 297 | public void setResourceId(String _resourceId) { 298 | this._resourceId = _resourceId; 299 | } 300 | 301 | public String getResourceIdMatches() { 302 | return _resourceIdMatches; 303 | } 304 | 305 | public void setResourceIdMatches(String _resourceIdMatches) { 306 | this._resourceIdMatches = _resourceIdMatches; 307 | } 308 | 309 | public int getIndex() { 310 | return _index; 311 | } 312 | 313 | public void setIndex(int _index) { 314 | this._index = _index; 315 | } 316 | 317 | public int getInstance() { 318 | return _instance; 319 | } 320 | 321 | public void setInstance(int _instance) { 322 | this._instance = _instance; 323 | } 324 | 325 | public long getMask() { 326 | return _mask; 327 | } 328 | 329 | public void setMask(long _mask) { 330 | this._mask = _mask; 331 | } 332 | 333 | public Selector[] getChildOrSiblingSelector() { 334 | return _childOrSiblingSelector; 335 | } 336 | 337 | public void setChildOrSiblingSelector(Selector[] _childOrSiblingSelector) { 338 | this._childOrSiblingSelector = _childOrSiblingSelector; 339 | } 340 | 341 | public String[] getChildOrSibling() { 342 | return _childOrSibling; 343 | } 344 | 345 | public void setChildOrSibling(String[] _childOrSibling) { 346 | this._childOrSibling = _childOrSibling; 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /src/com/github/uiautomatorstub/Stub.java: -------------------------------------------------------------------------------- 1 | package com.github.uiautomatorstub; 2 | 3 | import android.test.FlakyTest; 4 | import android.test.suitebuilder.annotation.LargeTest; 5 | 6 | import com.android.uiautomator.testrunner.UiAutomatorTestCase; 7 | import com.fasterxml.jackson.databind.ObjectMapper; 8 | import com.googlecode.jsonrpc4j.JsonRpcServer; 9 | 10 | /** 11 | * A working example of a ui automator test. 12 | * 13 | * @author SNI 14 | */ 15 | public class Stub extends UiAutomatorTestCase { 16 | 17 | private static final int TEST_TOLERANCE = 3; 18 | private static final int PORT = 9008; 19 | private AutomatorHttpServer server = null; 20 | 21 | @Override 22 | protected void setUp() throws Exception { 23 | super.setUp(); 24 | server = new AutomatorHttpServer(PORT); 25 | server.route("/jsonrpc/0", new JsonRpcServer(new ObjectMapper(), 26 | new AutomatorServiceImpl(), AutomatorService.class)); 27 | server.start(); 28 | } 29 | 30 | @Override 31 | protected void tearDown() throws Exception { 32 | server.stop(); 33 | server = null; 34 | super.tearDown(); 35 | } 36 | 37 | @LargeTest 38 | @FlakyTest(tolerance = TEST_TOLERANCE) 39 | public void testUIAutomatorStub() throws InterruptedException { 40 | while (server.isAlive()) 41 | Thread.sleep(100); 42 | } 43 | } -------------------------------------------------------------------------------- /src/com/github/uiautomatorstub/watcher/ClickUiObjectWatcher.java: -------------------------------------------------------------------------------- 1 | package com.github.uiautomatorstub.watcher; 2 | 3 | import com.android.uiautomator.core.UiObject; 4 | import com.android.uiautomator.core.UiObjectNotFoundException; 5 | import com.android.uiautomator.core.UiSelector; 6 | import com.github.uiautomatorstub.Log; 7 | 8 | /** 9 | * Created with IntelliJ IDEA. 10 | * User: b036 11 | * Date: 8/21/13 12 | * Time: 4:18 PM 13 | * To change this template use File | Settings | File Templates. 14 | */ 15 | public class ClickUiObjectWatcher extends SelectorWatcher { 16 | 17 | private UiSelector target = null; 18 | 19 | public ClickUiObjectWatcher(UiSelector[] conditions, UiSelector target) { 20 | super(conditions); 21 | this.target = target; 22 | } 23 | 24 | @Override 25 | public void action() { 26 | Log.d("ClickUiObjectWatcher triggered!"); 27 | if (target != null) { 28 | try { 29 | new UiObject(target).click(); 30 | } catch (UiObjectNotFoundException e) { 31 | Log.d(e.getMessage()); 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/com/github/uiautomatorstub/watcher/PressKeysWatcher.java: -------------------------------------------------------------------------------- 1 | package com.github.uiautomatorstub.watcher; 2 | 3 | import android.os.RemoteException; 4 | import android.view.KeyEvent; 5 | import com.android.uiautomator.core.UiDevice; 6 | import com.android.uiautomator.core.UiSelector; 7 | import com.github.uiautomatorstub.Log; 8 | 9 | /** 10 | * Created with IntelliJ IDEA. 11 | * User: b036 12 | * Date: 8/21/13 13 | * Time: 4:24 PM 14 | * To change this template use File | Settings | File Templates. 15 | */ 16 | public class PressKeysWatcher extends SelectorWatcher{ 17 | private String[] keys = new String[]{}; 18 | 19 | public PressKeysWatcher(UiSelector[] conditions, String[] keys) { 20 | super(conditions); 21 | this.keys = keys; 22 | } 23 | 24 | @Override 25 | public void action() { 26 | Log.d("PressKeysWatcher triggered!"); 27 | for (String key: keys) { 28 | key = key.toLowerCase(); 29 | if ("home".equals(key)) 30 | UiDevice.getInstance().pressHome(); 31 | else if ("back".equals(key)) 32 | UiDevice.getInstance().pressBack(); 33 | else if ("left".equals(key)) 34 | UiDevice.getInstance().pressDPadLeft(); 35 | else if ("right".equals(key)) 36 | UiDevice.getInstance().pressDPadRight(); 37 | else if ("up".equals(key)) 38 | UiDevice.getInstance().pressDPadUp(); 39 | else if ("down".equals(key)) 40 | UiDevice.getInstance().pressDPadDown(); 41 | else if ("center".equals(key)) 42 | UiDevice.getInstance().pressDPadCenter(); 43 | else if ("menu".equals(key)) 44 | UiDevice.getInstance().pressMenu(); 45 | else if ("search".equals(key)) 46 | UiDevice.getInstance().pressSearch(); 47 | else if ("enter".equals(key)) 48 | UiDevice.getInstance().pressEnter(); 49 | else if ("delete".equals(key) || "del".equals(key)) 50 | UiDevice.getInstance().pressDelete(); 51 | else if ("recent".equals(key)) 52 | try { 53 | UiDevice.getInstance().pressRecentApps(); 54 | } catch (RemoteException e) { 55 | Log.d(e.getMessage()); 56 | } 57 | else if ("volume_up".equals(key)) 58 | UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_VOLUME_UP); 59 | else if ("volume_down".equals(key)) 60 | UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_VOLUME_DOWN); 61 | else if ("volume_mute".equals(key)) 62 | UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_VOLUME_MUTE); 63 | else if ("camera".equals(key)) 64 | UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_CAMERA); 65 | else if ("power".equals(key)) 66 | UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_POWER); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/com/github/uiautomatorstub/watcher/SelectorWatcher.java: -------------------------------------------------------------------------------- 1 | package com.github.uiautomatorstub.watcher; 2 | 3 | import com.android.uiautomator.core.UiObject; 4 | import com.android.uiautomator.core.UiSelector; 5 | import com.android.uiautomator.core.UiWatcher; 6 | 7 | /** 8 | * Created with IntelliJ IDEA. 9 | * User: b036 10 | * Date: 8/21/13 11 | * Time: 1:57 PM 12 | * To change this template use File | Settings | File Templates. 13 | */ 14 | public abstract class SelectorWatcher implements UiWatcher { 15 | private UiSelector[] conditions = null; 16 | 17 | public SelectorWatcher(UiSelector[] conditions) { 18 | this.conditions = conditions; 19 | } 20 | 21 | @Override 22 | public boolean checkForCondition() { 23 | for (UiSelector s : conditions) { 24 | UiObject obj = new UiObject(s); 25 | if (!obj.exists()) return false; 26 | } 27 | action(); 28 | return true; 29 | } 30 | 31 | public abstract void action(); 32 | } 33 | --------------------------------------------------------------------------------