All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tascape.qa.th.android.driver.UiAutomatorDevice Maven / Gradle / Ivy

There is a newer version: 1.1.15
Show newest version
/*
 * Copyright 2016 tascape.
 *
 * 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.tascape.qa.th.android.driver;

import com.android.uiautomator.stub.IUiCollection;
import com.android.uiautomator.stub.IUiDevice;
import com.android.uiautomator.stub.IUiObject;
import com.android.uiautomator.stub.IUiScrollable;
import com.android.uiautomator.stub.Point;
import com.android.uiautomator.stub.UiSelector;
import com.android.uiautomator.stub.UiWatcher;
import com.google.common.collect.Lists;
import com.tascape.qa.th.Utils;
import com.tascape.qa.th.android.comm.Adb;
import java.awt.Dimension;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.sf.lipermi.exception.LipeRMIException;
import net.sf.lipermi.handler.CallHandler;
import net.sf.lipermi.net.Client;
import org.apache.commons.exec.ExecuteWatchdog;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author linsong wang
 */
public class UiAutomatorDevice extends AdbDevice implements IUiDevice {
    private static final Logger LOG = LoggerFactory.getLogger(UiAutomatorDevice.class);

    private static final long serialVersionUID = 1L;

    public static final String UIA_SERVER_JAR = "uia-server.jar";

    public static final String UIA_BUNDLE_JAR = "bundle.jar";

    private static final String UIA_SERVER_PATH;

    private static final String UIA_BUNDLE_PATH;

    public static final long WAIT_FOR_EXISTS = 30000;

    static {
        try {
            File server = Paths.get(File.createTempFile("uias", ".jar").getParent(), UIA_SERVER_JAR).toFile();
            File bundle = Paths.get(File.createTempFile("uias", ".jar").getParent(), UIA_BUNDLE_JAR).toFile();
            UIA_SERVER_PATH = server.getAbsolutePath();
            UIA_BUNDLE_PATH = bundle.getAbsolutePath();
            LOG.debug("uia server {}", UIA_SERVER_PATH);
            LOG.debug("uia bundle {}", UIA_BUNDLE_PATH);
            server.createNewFile();
            bundle.createNewFile();

            OutputStream out = new FileOutputStream(server);
            IOUtils.copy(UiAutomatorDevice.class.getResourceAsStream("/uias/" + UIA_SERVER_JAR), out);
            out = new FileOutputStream(bundle);
            IOUtils.copy(UiAutomatorDevice.class.getResourceAsStream("/uias/" + UIA_BUNDLE_JAR), out);
        } catch (IOException ex) {
            throw new RuntimeException("Cannot get uia server/bundle jar files", ex);
        }
    }

    private final String ip = "localhost";

    private int port = IUiDevice.UIAUTOMATOR_RMI_PORT;

    private ExecuteWatchdog uiautomatorDog;

    private Client client;

    private IUiDevice uiDevice;

    private IUiObject uiObject;

    private IUiCollection uiCollection;

    private IUiScrollable uiScrollable;

    private final Dimension screenDimension = new Dimension(0, 0);

    private static Set LOCAL_PORTS = Collections.synchronizedSet(new HashSet<>());

    public void start() throws IOException, InterruptedException {
        uiautomatorDog = this.setupUiAutomatorRmiServer();
        this.port = this.setupRmiPortForward();

        CallHandler callHandler = new CallHandler();
        this.client = new Client(this.ip, this.port, callHandler);
        this.uiDevice = IUiDevice.class.cast(client.getGlobal(IUiDevice.class));
        this.uiObject = IUiObject.class.cast(client.getGlobal(IUiObject.class));
        this.uiCollection = IUiCollection.class.cast(client.getGlobal(IUiCollection.class));
        this.uiScrollable = IUiScrollable.class.cast(client.getGlobal(IUiScrollable.class));
        LOG.debug("Device product name '{}'", this.uiDevice.getProductName());

        screenDimension.width = uiDevice.getDisplayWidth();
        screenDimension.height = uiDevice.getDisplayHeight();
        LOG.debug("Device screen dimension '{}'", screenDimension);
    }

    public void stop() {
        try {
            client.close();
        } catch (IOException ex) {
            LOG.warn("{}", ex.getMessage());
        }
        if (uiautomatorDog != null) {
            uiautomatorDog.stop();
            uiautomatorDog.killedProcess();
        }
    }

    private synchronized int setupRmiPortForward() throws IOException, InterruptedException {
        int remote = IUiDevice.UIAUTOMATOR_RMI_PORT;
        int local = IUiDevice.UIAUTOMATOR_RMI_PORT;
        while (LOCAL_PORTS.contains(local)) {
            local++;
        }
        this.getAdb().setupAdbPortForward(local, remote);
        LOCAL_PORTS.add(local);
        return local;
    }

    @Override
    public String getName() {
        return UiAutomatorDevice.class.getSimpleName();
    }

    @Override
    public void reset() {
        throw new UnsupportedOperationException();
    }

    public IUiObject getUiObject() {
        return uiObject;
    }

    public IUiCollection getUiCollection() {
        return uiCollection;
    }

    public IUiScrollable getUiScrollable() {
        return uiScrollable;
    }

    public void install(String apkPath) throws IOException, InterruptedException {
        this.backToHome();
        ExecuteWatchdog dog = this.getAdb().adbAsync(Lists.newArrayList("install", "-rg", apkPath), 60000);
        Utils.sleep(10000, "wait for app push");
        this.takeDeviceScreenshot();

        String pkg = uiDevice.getCurrentPackageName();
        if (pkg.equals("com.android.packageinstaller")) {
            Utils.sleep(10000, "wait for allow");
            if (this.resourceIdExists("android:id/button1")) {
                this.clickByResourceId("android:id/button1");
            }
        }
        pkg = uiDevice.getCurrentPackageName();
        if (pkg.equals("com.android.packageinstaller")) {
            this.clickByResourceId("com.android.packageinstaller:id/ok_button");
        }
        if (dog.isWatching()) {
            dog.killedProcess();
        }
    }

    public Dimension getScreenDimension() {
        return screenDimension;
    }

    public void home() {
        LOG.debug("press home");
        uiDevice.pressHome();
    }

    public void back() {
        LOG.debug("press back");
        uiDevice.pressBack();
    }

    public void enter() {
        LOG.debug("press enter");
        uiDevice.pressEnter();
    }

    public void backToHome() {
        for (int i = 0; i < 5; i++) {
            back();
        }
        home();
        home();
    }

    public void dragHalfScreenUp() {
        Dimension dimension = this.getScreenDimension();
        this.swipe(dimension.width / 2, dimension.height / 2, dimension.width / 2, 0, 10);
    }

    public void dragHalfScreenDown() {
        Dimension dimension = this.getScreenDimension();
        this.swipe(dimension.width / 2, dimension.height / 2, dimension.width / 2, dimension.height, 10);
    }

    public boolean resourceIdExists(String resouceId) {
        LOG.debug("look for {}", resouceId);
        uiObject.useUiObjectSelector(new UiSelector().resourceId(resouceId));
        return uiObject.exists();
    }

    public boolean textExists(String text) {
        LOG.debug("look for {}", text);
        uiObject.useUiObjectSelector(new UiSelector().text(text));
        return uiObject.exists();
    }

    public boolean waitForResourceId(String resouceId) {
        LOG.debug("wait for {}", resouceId);
        uiObject.useUiObjectSelector(new UiSelector().resourceId(resouceId));
        uiObject.waitForExists(WAIT_FOR_EXISTS);
        return uiObject.exists();
    }

    public boolean waitForText(String text) {
        LOG.debug("wait for {}", text);
        uiObject.useUiObjectSelector(new UiSelector().text(text));
        uiObject.waitForExists(WAIT_FOR_EXISTS);
        return uiObject.exists();
    }

    public boolean waitForTextContains(String text) {
        LOG.debug("wait for {}", text);
        uiObject.useUiObjectSelector(new UiSelector().textContains(text));
        uiObject.waitForExists(WAIT_FOR_EXISTS);
        return uiObject.exists();
    }

    public void clickByResourceId(String resouceId) {
        LOG.debug("click {}", resouceId);
        uiObject.useUiObjectSelector(new UiSelector().resourceId(resouceId));
        uiObject.click();
        uiDevice.waitForIdle();
    }

    public void clickByText(String text) {
        LOG.debug("click {}", text);
        uiObject.useUiObjectSelector(new UiSelector().text(text));
        uiObject.click();
        uiDevice.waitForIdle();
    }

    public void clickByTextContains(String text) {
        LOG.debug("click {}", text);
        uiObject.useUiObjectSelector(new UiSelector().textContains(text));
        uiObject.click();
        uiDevice.waitForIdle();
    }

    public void clearTextByResourceId(String resouceId) {
        LOG.debug("clear {}", resouceId);
        uiObject.useUiObjectSelector(new UiSelector().resourceId(resouceId));
        uiObject.clearTextField();
        String text = uiObject.getText();
        if (text.isEmpty()) {
            return;
        }
        uiObject.clickBottomRight();
        for (int i = 0; i < text.length(); i++) {
            uiDevice.pressDelete();
        }
    }

    public void setTextByResourceId(String resouceId, String text) {
        LOG.debug("type {} into {}", text, resouceId);
        uiObject.useUiObjectSelector(new UiSelector().resourceId(resouceId));
        clearTextByResourceId(resouceId);
        uiObject.setText(text);
        this.back();
    }

    public String getTextByResourceId(String resouceId) {
        if (resourceIdExists(resouceId)) {
            uiObject.useUiObjectSelector(new UiSelector().resourceId(resouceId));
            return uiObject.getText();
        }
        return null;
    }

    public File takeDeviceScreenshot() throws IOException {
        String f = "/data/local/tmp/ff.png";
        this.uiDevice.takeScreenshot(new File(f));
        File png = this.getLogPath().resolve("ss-" + System.currentTimeMillis() + ".png").toFile();
        this.getAdb().pull(f, png);
        LOG.debug("Save screenshot to {}", png.getAbsolutePath());
        return png;
    }

    @Override
    public void clearLastTraversedText() {
        this.uiDevice.clearLastTraversedText();
    }

    @Override
    public boolean click(int x, int y) {
        return this.uiDevice.click(x, y);
    }

    @Override
    public void dumpWindowHierarchy(String fileName) {
        this.uiDevice.dumpWindowHierarchy(fileName);
    }

    @Override
    public void freezeRotation() throws LipeRMIException {
        this.uiDevice.freezeRotation();
    }

    @Override
    public String getCurrentActivityName() {
        return this.uiDevice.getCurrentActivityName();
    }

    @Override
    public String getCurrentPackageName() {
        return this.uiDevice.getCurrentPackageName();
    }

    @Override
    public int getDisplayHeight() {
        return this.uiDevice.getDisplayHeight();
    }

    @Override
    public int getDisplayRotation() {
        return this.uiDevice.getDisplayRotation();
    }

    @Override
    public Point getDisplaySizeDp() {
        return this.uiDevice.getDisplaySizeDp();
    }

    @Override
    public int getDisplayWidthDp() {
        return this.uiDevice.getDisplayWidthDp();
    }

    @Override
    public int getDisplayHeightDp() {
        return this.uiDevice.getDisplayHeightDp();
    }

    @Override
    public int getDisplayWidth() {
        return this.uiDevice.getDisplayWidth();
    }

    @Override
    public String getLastTraversedText() {
        return this.uiDevice.getLastTraversedText();
    }

    @Override
    public String getProductName() {
        return this.uiDevice.getProductName();
    }

    @Override
    public boolean hasAnyWatcherTriggered() {
        return this.uiDevice.hasAnyWatcherTriggered();
    }

    @Override
    public boolean hasWatcherTriggered(String watcherName) {
        return this.uiDevice.hasWatcherTriggered(watcherName);
    }

    @Override
    public boolean isNaturalOrientation() {
        return this.uiDevice.isNaturalOrientation();
    }

    @Override
    public boolean isScreenOn() throws LipeRMIException {
        return this.uiDevice.isScreenOn();
    }

    @Override
    public boolean pressBack() {
        return this.uiDevice.pressBack();
    }

    @Override
    public boolean pressDPadCenter() {
        return this.uiDevice.pressDPadCenter();
    }

    @Override
    public boolean pressDPadDown() {
        return this.uiDevice.pressDPadDown();
    }

    @Override
    public boolean pressDPadLeft() {
        return this.uiDevice.pressDPadLeft();
    }

    @Override
    public boolean pressDPadRight() {
        return this.uiDevice.pressDPadRight();
    }

    @Override
    public boolean pressDPadUp() {
        return this.uiDevice.pressDPadUp();
    }

    @Override
    public boolean pressDelete() {
        return this.uiDevice.pressDelete();
    }

    @Override
    public boolean pressEnter() {
        return this.uiDevice.pressEnter();
    }

    @Override
    public boolean pressHome() {
        return this.uiDevice.pressHome();
    }

    @Override
    public boolean pressKeyCode(int keyCode) {
        return this.uiDevice.pressKeyCode(keyCode);
    }

    @Override
    public boolean pressKeyCode(int keyCode, int metaState) {
        return this.uiDevice.pressKeyCode(keyCode, metaState);
    }

    @Override
    public boolean pressMenu() {
        return this.uiDevice.pressMenu();
    }

    @Override
    public boolean pressRecentApps() throws LipeRMIException {
        return this.uiDevice.pressRecentApps();
    }

    @Override
    public boolean pressSearch() {
        return this.uiDevice.pressSearch();
    }

    @Override
    public void registerWatcher(String name, UiWatcher watcher) {
        this.uiDevice.registerWatcher(name, watcher);
    }

    @Override
    public void removeWatcher(String name) {
        this.uiDevice.removeWatcher(name);
    }

    @Override
    public void resetWatcherTriggers() {
        this.uiDevice.resetWatcherTriggers();
    }

    @Override
    public void runWatchers() {
        this.uiDevice.runWatchers();
    }

    @Override
    public void setOrientationLeft() throws LipeRMIException {
        this.uiDevice.setOrientationLeft();
    }

    @Override
    public void setOrientationNatural() throws LipeRMIException {
        this.uiDevice.setOrientationNatural();
    }

    @Override
    public void setOrientationRight() throws LipeRMIException {
        this.uiDevice.setOrientationRight();
    }

    @Override
    public void sleep() throws LipeRMIException {
        this.uiDevice.sleep();
    }

    @Override
    public boolean swipe(int startX, int startY, int endX, int endY, int steps) {
        return this.uiDevice.swipe(startX, startY, endX, endY, steps);
    }

    @Override
    public boolean swipe(Point[] segments, int segmentSteps) {
        return this.uiDevice.swipe(segments, segmentSteps);
    }

    @Override
    public boolean takeScreenshot(File storePath) {
        return this.uiDevice.takeScreenshot(storePath);
    }

    @Override
    public boolean takeScreenshot(File storePath, float scale, int quality) {
        return this.uiDevice.takeScreenshot(storePath, scale, quality);
    }

    @Override
    public void unfreezeRotation() throws LipeRMIException {
        this.uiDevice.unfreezeRotation();
    }

    @Override
    public void waitForIdle() {
        this.uiDevice.waitForIdle();
    }

    @Override
    public void waitForIdle(long time) {
        this.uiDevice.waitForIdle(time);
    }

    @Override
    public boolean waitForWindowUpdate(String packageName, long timeout) {
        return this.uiDevice.waitForWindowUpdate(packageName, timeout);
    }

    @Override
    public void wakeUp() throws LipeRMIException {
        this.uiDevice.wakeUp();
    }

    @Override
    public void setCompressedLayoutHeirarchy(boolean compressed) {
        uiDevice.setCompressedLayoutHeirarchy(compressed);
    }

    @Override
    public boolean openNotification() {
        return uiDevice.openNotification();
    }

    @Override
    public boolean openQuickSettings() {
        return uiDevice.openQuickSettings();
    }

    @Override
    public boolean drag(int startX, int startY, int endX, int endY, int steps) {
        return uiDevice.drag(startX, startY, endX, endY, steps);
    }

    private ExecuteWatchdog setupUiAutomatorRmiServer() throws IOException, InterruptedException {
        List cmdLine = new ArrayList<>();
        cmdLine.add("push");
        cmdLine.add(UIA_SERVER_PATH);
        cmdLine.add("/data/local/tmp/");
        this.getAdb().adb(cmdLine);

        cmdLine = new ArrayList<>();
        cmdLine.add("push");
        cmdLine.add(UIA_BUNDLE_PATH);
        cmdLine.add("/data/local/tmp/");
        this.getAdb().adb(cmdLine);

        cmdLine = new ArrayList<>();
        cmdLine.add("uiautomator");
        cmdLine.add("runtest");
        cmdLine.add(UIA_SERVER_JAR);
        cmdLine.add(UIA_BUNDLE_JAR);
        cmdLine.add("-c");
        cmdLine.add("com.android.uiautomator.stub.UiAutomatorRmiServer");
        ExecuteWatchdog dog = this.getAdb().shellAsync(cmdLine, Long.MAX_VALUE);

        Thread.sleep(5000);
        return dog;
    }

    public static void main(String[] args) throws Exception {
        Adb adb = new Adb();
        UiAutomatorDevice device = new UiAutomatorDevice();
        device.setAdb(adb);
        device.start();

        device.uninstall("com.mycompany.app");
        device.install("/opt/app-debug.apk");
        device.stop();
    }
}