/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.uiautomator.core; import android.app.UiAutomation; import android.app.UiAutomation.AccessibilityEventFilter; import android.graphics.Point; import android.os.Build; import android.os.Environment; import android.os.RemoteException; import android.os.SystemClock; import android.util.DisplayMetrics; import android.util.Log; import android.view.Display; import android.view.KeyEvent; import android.view.Surface; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.concurrent.TimeoutException; /** * UiDevice provides access to state information about the device. * You can also use this class to simulate user actions on the device, * such as pressing the d-pad or pressing the Home and Menu buttons. * @since API Level 16 * @deprecated New tests should be written using UI Automator 2.0 which is available as part of the * Android Testing Support Library. */ @Deprecated public class UiDevice { private static final String LOG_TAG = UiDevice.class.getSimpleName(); // Sometimes HOME and BACK key presses will generate no events if already on // home page or there is nothing to go back to, Set low timeouts. private static final long KEY_PRESS_EVENT_TIMEOUT = 1 * 1000; // store for registered UiWatchers private final HashMap mWatchers = new HashMap(); private final List mWatchersTriggers = new ArrayList(); // remember if we're executing in the context of a UiWatcher private boolean mInWatcherContext = false; // provides access the {@link QueryController} and {@link InteractionController} private UiAutomatorBridge mUiAutomationBridge; // reference to self private static UiDevice sDevice; private UiDevice() { /* hide constructor */ } /** * @hide */ public void initialize(UiAutomatorBridge uiAutomatorBridge) { mUiAutomationBridge = uiAutomatorBridge; } boolean isInWatcherContext() { return mInWatcherContext; } /** * Provides access the {@link QueryController} and {@link InteractionController} * @return {@link ShellUiAutomatorBridge} */ UiAutomatorBridge getAutomatorBridge() { if (mUiAutomationBridge == null) { throw new RuntimeException("UiDevice not initialized"); } return mUiAutomationBridge; } /** * Enables or disables layout hierarchy compression. * * If compression is enabled, the layout hierarchy derived from the Acessibility * framework will only contain nodes that are important for uiautomator * testing. Any unnecessary surrounding layout nodes that make viewing * and searching the hierarchy inefficient are removed. * * @param compressed true to enable compression; else, false to disable * @since API Level 18 */ public void setCompressedLayoutHeirarchy(boolean compressed) { getAutomatorBridge().setCompressedLayoutHierarchy(compressed); } /** * Retrieves a singleton instance of UiDevice * * @return UiDevice instance * @since API Level 16 */ public static UiDevice getInstance() { if (sDevice == null) { sDevice = new UiDevice(); } return sDevice; } /** * Returns the display size in dp (device-independent pixel) * * The returned display size is adjusted per screen rotation. Also this will return the actual * size of the screen, rather than adjusted per system decorations (like status bar). * * @return a Point containing the display size in dp */ public Point getDisplaySizeDp() { Tracer.trace(); Display display = getAutomatorBridge().getDefaultDisplay(); Point p = new Point(); display.getRealSize(p); DisplayMetrics metrics = new DisplayMetrics(); display.getRealMetrics(metrics); float dpx = p.x / metrics.density; float dpy = p.y / metrics.density; p.x = Math.round(dpx); p.y = Math.round(dpy); return p; } /** * Retrieves the product name of the device. * * This method provides information on what type of device the test is running on. This value is * the same as returned by invoking #adb shell getprop ro.product.name. * * @return product name of the device * @since API Level 17 */ public String getProductName() { Tracer.trace(); return Build.PRODUCT; } /** * Retrieves the text from the last UI traversal event received. * * You can use this method to read the contents in a WebView container * because the accessibility framework fires events * as each text is highlighted. You can write a test to perform * directional arrow presses to focus on different elements inside a WebView, * and call this method to get the text from each traversed element. * If you are testing a view container that can return a reference to a * Document Object Model (DOM) object, your test should use the view's * DOM instead. * * @return text of the last traversal event, else return an empty string * @since API Level 16 */ public String getLastTraversedText() { Tracer.trace(); return getAutomatorBridge().getQueryController().getLastTraversedText(); } /** * Clears the text from the last UI traversal event. * See {@link #getLastTraversedText()}. * @since API Level 16 */ public void clearLastTraversedText() { Tracer.trace(); getAutomatorBridge().getQueryController().clearLastTraversedText(); } /** * Simulates a short press on the MENU button. * @return true if successful, else return false * @since API Level 16 */ public boolean pressMenu() { Tracer.trace(); waitForIdle(); return getAutomatorBridge().getInteractionController().sendKeyAndWaitForEvent( KeyEvent.KEYCODE_MENU, 0, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED, KEY_PRESS_EVENT_TIMEOUT); } /** * Simulates a short press on the BACK button. * @return true if successful, else return false * @since API Level 16 */ public boolean pressBack() { Tracer.trace(); waitForIdle(); return getAutomatorBridge().getInteractionController().sendKeyAndWaitForEvent( KeyEvent.KEYCODE_BACK, 0, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED, KEY_PRESS_EVENT_TIMEOUT); } /** * Simulates a short press on the HOME button. * @return true if successful, else return false * @since API Level 16 */ public boolean pressHome() { Tracer.trace(); waitForIdle(); return getAutomatorBridge().getInteractionController().sendKeyAndWaitForEvent( KeyEvent.KEYCODE_HOME, 0, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED, KEY_PRESS_EVENT_TIMEOUT); } /** * Simulates a short press on the SEARCH button. * @return true if successful, else return false * @since API Level 16 */ public boolean pressSearch() { Tracer.trace(); return pressKeyCode(KeyEvent.KEYCODE_SEARCH); } /** * Simulates a short press on the CENTER button. * @return true if successful, else return false * @since API Level 16 */ public boolean pressDPadCenter() { Tracer.trace(); return pressKeyCode(KeyEvent.KEYCODE_DPAD_CENTER); } /** * Simulates a short press on the DOWN button. * @return true if successful, else return false * @since API Level 16 */ public boolean pressDPadDown() { Tracer.trace(); return pressKeyCode(KeyEvent.KEYCODE_DPAD_DOWN); } /** * Simulates a short press on the UP button. * @return true if successful, else return false * @since API Level 16 */ public boolean pressDPadUp() { Tracer.trace(); return pressKeyCode(KeyEvent.KEYCODE_DPAD_UP); } /** * Simulates a short press on the LEFT button. * @return true if successful, else return false * @since API Level 16 */ public boolean pressDPadLeft() { Tracer.trace(); return pressKeyCode(KeyEvent.KEYCODE_DPAD_LEFT); } /** * Simulates a short press on the RIGHT button. * @return true if successful, else return false * @since API Level 16 */ public boolean pressDPadRight() { Tracer.trace(); return pressKeyCode(KeyEvent.KEYCODE_DPAD_RIGHT); } /** * Simulates a short press on the DELETE key. * @return true if successful, else return false * @since API Level 16 */ public boolean pressDelete() { Tracer.trace(); return pressKeyCode(KeyEvent.KEYCODE_DEL); } /** * Simulates a short press on the ENTER key. * @return true if successful, else return false * @since API Level 16 */ public boolean pressEnter() { Tracer.trace(); return pressKeyCode(KeyEvent.KEYCODE_ENTER); } /** * Simulates a short press using a key code. * * See {@link KeyEvent} * @return true if successful, else return false * @since API Level 16 */ public boolean pressKeyCode(int keyCode) { Tracer.trace(keyCode); waitForIdle(); return getAutomatorBridge().getInteractionController().sendKey(keyCode, 0); } /** * Simulates a short press using a key code. * * See {@link KeyEvent}. * @param keyCode the key code of the event. * @param metaState an integer in which each bit set to 1 represents a pressed meta key * @return true if successful, else return false * @since API Level 16 */ public boolean pressKeyCode(int keyCode, int metaState) { Tracer.trace(keyCode, metaState); waitForIdle(); return getAutomatorBridge().getInteractionController().sendKey(keyCode, metaState); } /** * Simulates a short press on the Recent Apps button. * * @return true if successful, else return false * @throws RemoteException * @since API Level 16 */ public boolean pressRecentApps() throws RemoteException { Tracer.trace(); waitForIdle(); return getAutomatorBridge().getInteractionController().toggleRecentApps(); } /** * Opens the notification shade. * * @return true if successful, else return false * @since API Level 18 */ public boolean openNotification() { Tracer.trace(); waitForIdle(); return getAutomatorBridge().getInteractionController().openNotification(); } /** * Opens the Quick Settings shade. * * @return true if successful, else return false * @since API Level 18 */ public boolean openQuickSettings() { Tracer.trace(); waitForIdle(); return getAutomatorBridge().getInteractionController().openQuickSettings(); } /** * Gets the width of the display, in pixels. The width and height details * are reported based on the current orientation of the display. * @return width in pixels or zero on failure * @since API Level 16 */ public int getDisplayWidth() { Tracer.trace(); Display display = getAutomatorBridge().getDefaultDisplay(); Point p = new Point(); display.getSize(p); return p.x; } /** * Gets the height of the display, in pixels. The size is adjusted based * on the current orientation of the display. * @return height in pixels or zero on failure * @since API Level 16 */ public int getDisplayHeight() { Tracer.trace(); Display display = getAutomatorBridge().getDefaultDisplay(); Point p = new Point(); display.getSize(p); return p.y; } /** * Perform a click at arbitrary coordinates specified by the user * * @param x coordinate * @param y coordinate * @return true if the click succeeded else false * @since API Level 16 */ public boolean click(int x, int y) { Tracer.trace(x, y); if (x >= getDisplayWidth() || y >= getDisplayHeight()) { return (false); } return getAutomatorBridge().getInteractionController().clickNoSync(x, y); } /** * 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. * * @param startX * @param startY * @param endX * @param endY * @param steps is the number of move steps sent to the system * @return false if the operation fails or the coordinates are invalid * @since API Level 16 */ public boolean swipe(int startX, int startY, int endX, int endY, int steps) { Tracer.trace(startX, startY, endX, endY, steps); return getAutomatorBridge().getInteractionController() .swipe(startX, startY, endX, endY, steps); } /** * 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. * * @param startX X-axis value for the starting coordinate * @param startY Y-axis value for the starting coordinate * @param endX X-axis value for the ending coordinate * @param endY Y-axis value for the ending coordinate * @param steps is the number of steps for the swipe action * @return true if swipe is performed, false if the operation fails * or the coordinates are invalid * @since API Level 18 */ public boolean drag(int startX, int startY, int endX, int endY, int steps) { Tracer.trace(startX, startY, endX, endY, steps); return getAutomatorBridge().getInteractionController() .swipe(startX, startY, endX, endY, steps, true); } /** * Performs a swipe between points in the Point array. Each step execution is throttled * to 5ms per step. So for a 100 steps, the swipe will take about 1/2 second to complete * * @param segments is Point array containing at least one Point object * @param segmentSteps steps to inject between two Points * @return true on success * @since API Level 16 */ public boolean swipe(Point[] segments, int segmentSteps) { Tracer.trace(segments, segmentSteps); return getAutomatorBridge().getInteractionController().swipe(segments, segmentSteps); } /** * Waits for the current application to idle. * Default wait timeout is 10 seconds * @since API Level 16 */ public void waitForIdle() { Tracer.trace(); waitForIdle(Configurator.getInstance().getWaitForIdleTimeout()); } /** * Waits for the current application to idle. * @param timeout in milliseconds * @since API Level 16 */ public void waitForIdle(long timeout) { Tracer.trace(timeout); getAutomatorBridge().waitForIdle(timeout); } /** * Retrieves the last activity to report accessibility events. * @deprecated The results returned should be considered unreliable * @return String name of activity * @since API Level 16 */ @Deprecated public String getCurrentActivityName() { Tracer.trace(); return getAutomatorBridge().getQueryController().getCurrentActivityName(); } /** * Retrieves the name of the last package to report accessibility events. * @return String name of package * @since API Level 16 */ public String getCurrentPackageName() { Tracer.trace(); return getAutomatorBridge().getQueryController().getCurrentPackageName(); } /** * Registers a {@link UiWatcher} to run automatically when the testing framework is unable to * find a match using a {@link UiSelector}. See {@link #runWatchers()} * * @param name to register the UiWatcher * @param watcher {@link UiWatcher} * @since API Level 16 */ public void registerWatcher(String name, UiWatcher watcher) { Tracer.trace(name, watcher); if (mInWatcherContext) { throw new IllegalStateException("Cannot register new watcher from within another"); } mWatchers.put(name, watcher); } /** * Removes a previously registered {@link UiWatcher}. * * See {@link #registerWatcher(String, UiWatcher)} * @param name used to register the UiWatcher * @since API Level 16 */ public void removeWatcher(String name) { Tracer.trace(name); if (mInWatcherContext) { throw new IllegalStateException("Cannot remove a watcher from within another"); } mWatchers.remove(name); } /** * This method forces all registered watchers to run. * See {@link #registerWatcher(String, UiWatcher)} * @since API Level 16 */ public void runWatchers() { Tracer.trace(); if (mInWatcherContext) { return; } for (String watcherName : mWatchers.keySet()) { UiWatcher watcher = mWatchers.get(watcherName); if (watcher != null) { try { mInWatcherContext = true; if (watcher.checkForCondition()) { setWatcherTriggered(watcherName); } } catch (Exception e) { Log.e(LOG_TAG, "Exceuting watcher: " + watcherName, e); } finally { mInWatcherContext = false; } } } } /** * Resets a {@link UiWatcher} that has been triggered. * If a UiWatcher runs and its {@link UiWatcher#checkForCondition()} call * returned true, then the UiWatcher is considered triggered. * See {@link #registerWatcher(String, UiWatcher)} * @since API Level 16 */ public void resetWatcherTriggers() { Tracer.trace(); mWatchersTriggers.clear(); } /** * Checks if a specific registered {@link UiWatcher} has triggered. * See {@link #registerWatcher(String, UiWatcher)}. If a UiWatcher runs and its * {@link UiWatcher#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. * * @param watcherName * @return true if triggered else false * @since API Level 16 */ public boolean hasWatcherTriggered(String watcherName) { Tracer.trace(watcherName); return mWatchersTriggers.contains(watcherName); } /** * Checks if any registered {@link UiWatcher} have triggered. * * See {@link #registerWatcher(String, UiWatcher)} * See {@link #hasWatcherTriggered(String)} * @since API Level 16 */ public boolean hasAnyWatcherTriggered() { Tracer.trace(); return mWatchersTriggers.size() > 0; } /** * Used internally by this class to set a {@link UiWatcher} state as triggered. * @param watcherName */ private void setWatcherTriggered(String watcherName) { Tracer.trace(watcherName); if (!hasWatcherTriggered(watcherName)) { mWatchersTriggers.add(watcherName); } } /** * Check if the device is in its natural orientation. This is determined by checking if the * orientation is at 0 or 180 degrees. * @return true if it is in natural orientation * @since API Level 17 */ public boolean isNaturalOrientation() { Tracer.trace(); waitForIdle(); int ret = getAutomatorBridge().getRotation(); return ret == UiAutomation.ROTATION_FREEZE_0 || ret == UiAutomation.ROTATION_FREEZE_180; } /** * Returns the current rotation of the display, as defined in {@link Surface} * @since API Level 17 */ public int getDisplayRotation() { Tracer.trace(); waitForIdle(); return getAutomatorBridge().getRotation(); } /** * Disables the sensors and freezes the device rotation at its * current rotation state. * @throws RemoteException * @since API Level 16 */ public void freezeRotation() throws RemoteException { Tracer.trace(); getAutomatorBridge().getInteractionController().freezeRotation(); } /** * Re-enables the sensors and un-freezes the device rotation allowing its contents * to rotate with the device physical rotation. During a test execution, it is best to * keep the device frozen in a specific orientation until the test case execution has completed. * @throws RemoteException */ public void unfreezeRotation() throws RemoteException { Tracer.trace(); getAutomatorBridge().getInteractionController().unfreezeRotation(); } /** * Simulates orienting the device to the left and also freezes rotation * by disabling the sensors. * * If you want to un-freeze the rotation and re-enable the sensors * see {@link #unfreezeRotation()}. * @throws RemoteException * @since API Level 17 */ public void setOrientationLeft() throws RemoteException { Tracer.trace(); getAutomatorBridge().getInteractionController().setRotationLeft(); waitForIdle(); // we don't need to check for idle on entry for this. We'll sync on exit } /** * Simulates orienting the device to the right and also freezes rotation * by disabling the sensors. * * If you want to un-freeze the rotation and re-enable the sensors * see {@link #unfreezeRotation()}. * @throws RemoteException * @since API Level 17 */ public void setOrientationRight() throws RemoteException { Tracer.trace(); getAutomatorBridge().getInteractionController().setRotationRight(); waitForIdle(); // we don't need to check for idle on entry for this. We'll sync on exit } /** * Simulates orienting the device into its natural orientation and also freezes rotation * by disabling the sensors. * * If you want to un-freeze the rotation and re-enable the sensors * see {@link #unfreezeRotation()}. * @throws RemoteException * @since API Level 17 */ public void setOrientationNatural() throws RemoteException { Tracer.trace(); getAutomatorBridge().getInteractionController().setRotationNatural(); waitForIdle(); // we don't need to check for idle on entry for this. We'll sync on exit } /** * 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. * @throws RemoteException * @since API Level 16 */ public void wakeUp() throws RemoteException { Tracer.trace(); if(getAutomatorBridge().getInteractionController().wakeDevice()) { // sync delay to allow the window manager to start accepting input // after the device is awakened. SystemClock.sleep(500); } } /** * Checks the power manager if the screen is ON. * * @return true if the screen is ON else false * @throws RemoteException * @since API Level 16 */ public boolean isScreenOn() throws RemoteException { Tracer.trace(); return getAutomatorBridge().getInteractionController().isScreenOn(); } /** * This method simply presses the power button if the screen is ON else * it does nothing if the screen is already OFF. * * @throws RemoteException * @since API Level 16 */ public void sleep() throws RemoteException { Tracer.trace(); getAutomatorBridge().getInteractionController().sleepDevice(); } /** * Helper method used for debugging to dump the current window's layout hierarchy. * The file root location is /data/local/tmp * * @param fileName * @since API Level 16 */ public void dumpWindowHierarchy(String fileName) { Tracer.trace(fileName); AccessibilityNodeInfo root = getAutomatorBridge().getQueryController().getAccessibilityRootNode(); if(root != null) { Display display = getAutomatorBridge().getDefaultDisplay(); Point size = new Point(); display.getSize(size); AccessibilityNodeInfoDumper.dumpWindowToFile(root, new File(new File(Environment.getDataDirectory(), "local/tmp"), fileName), display.getRotation(), size.x, size.y); } } /** * 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. * * @param packageName the specified window package name (can be null). * If null, a window update from any front-end window will end the wait * @param timeout the timeout for the wait * * @return true if a window update occurred, false if timeout has elapsed or if the current * window does not have the specified package name * @since API Level 16 */ public boolean waitForWindowUpdate(final String packageName, long timeout) { Tracer.trace(packageName, timeout); if (packageName != null) { if (!packageName.equals(getCurrentPackageName())) { return false; } } Runnable emptyRunnable = new Runnable() { @Override public void run() { } }; AccessibilityEventFilter checkWindowUpdate = new AccessibilityEventFilter() { @Override public boolean accept(AccessibilityEvent t) { if (t.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) { return packageName == null || packageName.equals(t.getPackageName()); } return false; } }; try { getAutomatorBridge().executeCommandAndWaitForAccessibilityEvent( emptyRunnable, checkWindowUpdate, timeout); } catch (TimeoutException e) { return false; } catch (Exception e) { Log.e(LOG_TAG, "waitForWindowUpdate: general exception from bridge", e); return false; } return true; } /** * Take a screenshot of current window and store it as PNG * * Default scale of 1.0f (original size) and 90% quality is used * The screenshot is adjusted per screen rotation * * @param storePath where the PNG should be written to * @return true if screen shot is created successfully, false otherwise * @since API Level 17 */ public boolean takeScreenshot(File storePath) { Tracer.trace(storePath); return takeScreenshot(storePath, 1.0f, 90); } /** * Take a screenshot of current window and store it as PNG * * The screenshot is adjusted per screen rotation * * @param storePath where the PNG should be written to * @param scale scale the screenshot down if needed; 1.0f for original size * @param quality quality of the PNG compression; range: 0-100 * @return true if screen shot is created successfully, false otherwise * @since API Level 17 */ public boolean takeScreenshot(File storePath, float scale, int quality) { Tracer.trace(storePath, scale, quality); return getAutomatorBridge().takeScreenshot(storePath, quality); } }