org.sikuli.android.ADBDevice.java Source code

Java tutorial

Introduction

Here is the source code for org.sikuli.android.ADBDevice.java

Source

/*
 * Copyright (c) 2010-2016, Sikuli.org, sikulix.com
 * Released under the MIT License.
 *
 */

package org.sikuli.android;

import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.imgproc.Imgproc;
import org.sikuli.basics.Debug;
import org.sikuli.basics.FileManager;
import org.sikuli.script.RunTime;
import org.sikuli.script.ScreenImage;
import se.vidstige.jadb.JadbDevice;
import se.vidstige.jadb.JadbException;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ADBDevice {

    private static int lvl = 3;

    private static void log(int level, String message, Object... args) {
        Debug.logx(level, "ADBDevice: " + message, args);
    }

    private JadbDevice device = null;
    private int devW = -1;
    private int devH = -1;
    private ADBRobot robot = null;
    private ADBScreen screen = null;

    private List<String> deviceProps = new ArrayList<>();
    private int deviceVersion = -1;
    private String sDeviceVersion = "???";

    private static ADBDevice adbDevice = null;

    public static int KEY_HOME = 3;
    public static int KEY_BACK = 4;
    public static int KEY_MENU = 82;
    public static int KEY_POWER = 26;

    private ADBDevice() {
    }

    public static ADBDevice init() {
        if (adbDevice == null) {
            adbDevice = new ADBDevice();
            adbDevice.device = ADBClient.getDevice();
            if (adbDevice.device == null) {
                adbDevice = null;
            } else {
                adbDevice.deviceProps = Arrays.asList(adbDevice.exec("getprop").split("\n"));
                //[ro.build.version.release]: [6.0.1]
                //[ro.product.brand]: [google]
                //[ro.product.manufacturer]: [asus]
                //[ro.product.model]: [Nexus 7]
                //[ro.product.name]: [razor]
                //[ro.serialno]: [094da986]
                Pattern pProp = Pattern.compile("\\[(.*?)\\]:.*?\\[(.*)\\]");
                Matcher mProp = null;
                String val = "";
                String key = "";
                for (String prop : adbDevice.deviceProps) {
                    if (!prop.startsWith("[ro."))
                        continue;
                    mProp = pProp.matcher(prop);
                    if (mProp.find()) {
                        key = mProp.group(1);
                        if (key.contains("build.version.release")) {
                            val = mProp.group(2);
                            try {
                                adbDevice.deviceVersion = Integer.parseInt(val.split("\\.")[0]);
                                adbDevice.sDeviceVersion = val;
                            } catch (Exception e) {
                            }
                        }
                    }
                }
                log(lvl, "init: %s", adbDevice.toString());
            }
        }
        return adbDevice;
    }

    public static void reset() {
        adbDevice = null;
        ADBClient.reset();
    }

    public String toString() {
        return String.format("attached device: serial(%s) display(%dx%d) version(%s)", getDeviceSerial(),
                getBounds().width, getBounds().height, sDeviceVersion);
    }

    public ADBRobot getRobot(ADBScreen screen) {
        if (robot == null) {
            this.screen = screen;
            robot = new ADBRobot(screen, this);
        }
        return robot;
    }

    public String getDeviceSerial() {
        return device.getSerial();
    }

    public Rectangle getBounds() {
        if (devW < 0) {
            Dimension dim = getDisplayDimension();
            devW = (int) dim.getWidth();
            devH = (int) dim.getHeight();
        }
        return new Rectangle(0, 0, devW, devH);
    }

    public ScreenImage captureScreen() {
        BufferedImage bimg = captureDeviceScreen();
        return new ScreenImage(getBounds(), bimg);
    }

    public ScreenImage captureScreen(Rectangle rect) {
        BufferedImage bimg = captureDeviceScreen(rect.x, rect.y, rect.width, rect.height);
        return new ScreenImage(rect, bimg);
    }

    public BufferedImage captureDeviceScreen() {
        return captureDeviceScreen(0, 0, devW, devH);
    }

    public BufferedImage captureDeviceScreen(int y, int _h) {
        return captureDeviceScreen(0, y, devW, _h);
    }

    public BufferedImage captureDeviceScreen(int x, int y, int w, int h) {
        Mat matImage = captureDeviceScreenMat(x, y, w, h);
        BufferedImage bImage = null;
        if (matImage != null) {
            bImage = new BufferedImage(matImage.width(), matImage.height(), BufferedImage.TYPE_3BYTE_BGR);
            byte[] bImageData = ((DataBufferByte) bImage.getRaster().getDataBuffer()).getData();
            matImage.get(0, 0, bImageData);
        }
        return bImage;
    }

    public Mat captureDeviceScreenMat(int x, int y, int w, int h) {
        byte[] imagePrefix = new byte[12];
        byte[] image = new byte[0];
        int actW = w;
        if (x + w > devW) {
            actW = devW - x;
        }
        int actH = h;
        if (y + h > devH) {
            actH = devH - y;
        }
        Debug timer = Debug.startTimer();
        try {
            InputStream stdout = device.executeShell("screencap");
            stdout.read(imagePrefix);
            if (imagePrefix[8] != 0x01) {
                log(-1, "captureDeviceScreenMat: image type not RGBA");
                return null;
            }
            if (byte2int(imagePrefix, 0, 4) != devW || byte2int(imagePrefix, 4, 4) != devH) {
                log(-1, "captureDeviceScreenMat: width or height differ from device values");
                return null;
            }
            image = new byte[actW * actH * 4];
            int lenRow = devW * 4;
            byte[] row = new byte[lenRow];
            for (int count = 0; count < y; count++) {
                stdout.read(row);
            }
            boolean shortRow = x + actW < devW;
            for (int count = 0; count < actH; count++) {
                if (shortRow) {
                    stdout.read(row);
                    System.arraycopy(row, x * 4, image, count * actW * 4, actW * 4);
                } else {
                    stdout.read(image, count * actW * 4, actW * 4);
                }
            }
            long duration = timer.end();
            log(lvl, "captureDeviceScreenMat:[%d,%d %dx%d] %d", x, y, actW, actH, duration);
        } catch (IOException | JadbException e) {
            log(-1, "captureDeviceScreenMat: [%d,%d %dx%d] %s", x, y, actW, actH, e);
        }
        Mat matOrg = new Mat(actH, actW, CvType.CV_8UC4);
        matOrg.put(0, 0, image);
        Mat matImage = new Mat();
        Imgproc.cvtColor(matOrg, matImage, Imgproc.COLOR_RGBA2BGR, 3);
        return matImage;
    }

    private int byte2int(byte[] bytes, int start, int len) {
        int val = 0;
        int fact = 1;
        for (int i = start; i < start + len; i++) {
            int b = bytes[i] & 0xff;
            val += b * fact;
            fact *= 256;
        }
        return val;
    }

    private Dimension getDisplayDimension() {
        String dump = dumpsys("display");
        String token = "mDefaultViewport= ... deviceWidth=1200, deviceHeight=1920}";
        Dimension dim = null;
        Pattern displayDimension = Pattern
                .compile("mDefaultViewport.*?=.*?deviceWidth=(\\d*).*?deviceHeight=(\\d*)");
        Matcher match = displayDimension.matcher(dump);
        if (match.find()) {
            int w = Integer.parseInt(match.group(1));
            int h = Integer.parseInt(match.group(2));
            dim = new Dimension(w, h);
        } else {
            log(-1, "getDisplayDimension: dumpsys display: token not found: %s", token);
        }
        return dim;
    }

    public String exec(String command, String... args) {
        InputStream stdout = null;
        String out = "";
        try {
            stdout = device.executeShell(command, args);
            out = inputStreamToString(stdout, "UTF-8");
        } catch (IOException | JadbException e) {
            log(-1, "exec: %s: %s", command, e);
        }
        return out;
    }

    public String dumpsys(String component) {
        InputStream stdout = null;
        String out = "";
        try {
            if (component == null || component.isEmpty()) {
                component = "power";
            }
            if (component.toLowerCase().contains("all")) {
                stdout = device.executeShell("dumpsys");
            } else {
                stdout = device.executeShell("dumpsys", component);
            }
            out = inputStreamToString(stdout, "UTF-8");
        } catch (IOException | JadbException e) {
            log(-1, "dumpsys: %s: %s", component, e);
        }
        return out;
    }

    public String printDump(String component) {
        String dump = dumpsys(component);
        if (!dump.isEmpty()) {
            System.out.println("***** Android device dump: " + component);
            System.out.println(dump);
        }
        return dump;
    }

    public String printDump() {
        String dump = dumpsys("all");
        if (!dump.isEmpty()) {
            File out = new File(RunTime.get().fSikulixStore, "android_dump_" + getDeviceSerial() + ".txt");
            System.out.println("***** Android device dump all services");
            System.out.println("written to file: " + out.getAbsolutePath());
            FileManager.writeStringToFile(dump, out);
        }
        return dump;
    }

    private static final int BUFFER_SIZE = 4 * 1024;

    private static String inputStreamToString(InputStream inputStream, String charsetName) {
        StringBuilder builder = new StringBuilder();
        InputStreamReader reader = null;
        try {
            reader = new InputStreamReader(inputStream, charsetName);
            char[] buffer = new char[BUFFER_SIZE];
            int length;
            while ((length = reader.read(buffer)) != -1) {
                builder.append(buffer, 0, length);
            }
            return builder.toString();
        } catch (Exception e) {
            return "";
        }
    }

    public void wakeUp(int seconds) {
        int times = seconds * 4;
        try {
            if (null == isDisplayOn()) {
                log(-1, "wakeUp: not possible - see log");
                return;
            }
            device.executeShell("input", "keyevent", "26");
            while (0 < times--) {
                if (isDisplayOn()) {
                    return;
                } else {
                    RunTime.pause(0.25f);
                }
            }
        } catch (Exception e) {
            log(-1, "wakeUp: did not work: %s", e);
        }
        log(-1, "wakeUp: timeout: %d seconds", seconds);
    }

    public Boolean isDisplayOn() {
        // deviceidle | grep mScreenOn=true|false
        // v < 5: power | grep mScreenOn=true|false
        // v > 4: power | grep Display Power: state=ON|OFF
        String dump = dumpsys("power");
        Pattern displayOn = Pattern.compile("mScreenOn=(..)");
        String isOn = "tr";
        if (deviceVersion > 4) {
            displayOn = Pattern.compile("Display Power: state=(..)");
            isOn = "ON";
        }
        Matcher match = displayOn.matcher(dump);
        if (match.find()) {
            if (match.group(1).contains(isOn)) {
                return true;
            }
            return false;
        } else {
            log(-1, "isDisplayOn: (Android version %d) dumpsys power: pattern not found: %s", deviceVersion,
                    displayOn);
        }
        return null;
    }

    public void inputKeyEvent(int key) {
        try {
            device.executeShell("input", "keyevent", Integer.toString(key));
        } catch (Exception e) {
            log(-1, "inputKeyEvent: %d did not work: %s", e.getMessage());
        }
    }

    public void tap(int x, int y) {
        try {
            device.executeShell("input tap", Integer.toString(x), Integer.toString(y));
        } catch (IOException | JadbException e) {
            log(-1, "tap: %s", e);
        }
    }

    public void swipe(int x1, int y1, int x2, int y2) {
        try {
            device.executeShell("input swipe", Integer.toString(x1), Integer.toString(y1), Integer.toString(x2),
                    Integer.toString(y2));
        } catch (IOException | JadbException e) {
            log(-1, "swipe: %s", e);
        }
    }

    private String textBuffer = "";
    private boolean typing = false;

    public synchronized boolean typeStarts() {
        if (!typing) {
            textBuffer = "";
            typing = true;
            return true;
        }
        return false;
    }

    public synchronized void typeEnds() {
        if (typing) {
            input(textBuffer);
            typing = false;
        }
    }

    public void typeChar(char character) {
        if (typing) {
            textBuffer += character;
        }
    }

    public static float inputDelay = 0.05f;

    public void input(String text) {
        try {
            device.executeShell("input text ", text);
            RunTime.pause(text.length() * inputDelay);
        } catch (Exception e) {
            log(-1, "input: %s", e);
        }
    }
}