/*
* 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);
}
}
}