public interface AndroidApp {

	String getBasePackage();

	String getMainActivity();

	void setMainActivity(String mainActivity);

	String getVersionName();

	void deleteFileFromWithinApk(String file);

	String getAppId();

	/**
	 * For testing only
	 */
	String getAbsolutePath();
} public interface AndroidDevice {

  /**
   * test only
   */
  IDevice getDevice();

  String getSerialNumber();

  Locale getLocale();

  String getName();

  AndroidDeviceBrand getBrand();

  Dimension getScreenSize();

  void tap(int x, int y);

  void swipe(int x1, int y1, int x2, int y2);

  /**
   * @see KeyEvent
   */
  void inputKeyevent(int value);

  BufferedImage takeScreenshot();

  void takeScreenshot(String fileUrl);

  boolean isDeviceReady();

  boolean isScreenOn();

  DeviceTargetPlatform getTargetPlatform();

  String currentActivity();

  void invokeActivity(String activity);

  boolean start(AndroidApp app);

  /**
   * dump current activity view xml
   */
  String getDump();

  boolean handlePopBox(String deviceBrand);

  /**
   * io.appium.unlock/.Unlock
   */
  void unlock();

  void install(AndroidApp app);

  boolean isInstalled(String appBasePackage);

  boolean isInstalled(AndroidApp app);

  void uninstall(AndroidApp app);

  void uninstall(String appBasePackage);

  void forwardPort(int local, int remote);

  void removeForwardPort(int local);

  void clearUserData(AndroidApp app);

  void clearUserData(String appBasePackage);

  void kill(AndroidApp aut);

  void kill(String appBasePackage);

  String runAdbCommand(String parameter);

  String getExternalStoragePath();

  String getCrashLog();

  boolean isWifiOff();

  DeviceInfo getDeviceInfo();

  void restartADB();

  /**
   *
   * @param logCatListener
   */
  void addLogCatListener(LogCatListener logCatListener);

  void removeLogCatListener(LogCatListener logCatListener);

  List<ClientDataInfo> getClientDatasInfo();

  /**
   * return all Dalvik/ART VM processes
   */
  Client[] getAllClient();

  /**
   *
   * @param appName
   * @return
   */
  Client getClientByAppName(String appName);
} public final class AndroidDeviceBrand {

  private static final List<AndroidDeviceBrand> brands = new ArrayList<AndroidDeviceBrand>();

  public static final AndroidDeviceBrand XIAOMI_MI_3W = createInstance(
      "xiaomi", "mi_3w");
  public static final AndroidDeviceBrand XIAOMI_MI_2 = createInstance(
      "xiaomi", "mi_2");
  public static final AndroidDeviceBrand XIAOMI_MI_3 = createInstance(
      "xiaomi", "mi_3");
  public static final AndroidDeviceBrand XIAOMI_MI_4W = createInstance(
      "xiaomi", "mi_4w");
  public static final AndroidDeviceBrand OPPO_X9007 = createInstance("oppo",
      "x9007");
  public static final AndroidDeviceBrand MEIZU_M355 = createInstance("meizu",
      "m355");
  public static final AndroidDeviceBrand HTC_M8ST = createInstance("htc",
      "htc_m8st");

  public static final AndroidDeviceBrand EMPTY = createInstance("", "");

  private String manufacture;
  private String model;

  public String getModel() {
    return model;
  }

  public void setModel(String model) {
    this.model = model;
  }

  public String getManufacture() {
    return manufacture;
  }

  public void setManufacture(String manufacture) {
    this.manufacture = manufacture;
  }

  @Override
  public String toString() {
    return "AndroidDeviceBrand [manufacture=" + manufacture + ", model="
        + model + "]";
  }

  private AndroidDeviceBrand(String manufacture, String model) {
    this.manufacture = manufacture;
    this.model = model;
  }

  public boolean isXiaoMi() {
    return this.equals(AndroidDeviceBrand.XIAOMI_MI_2)
        || this.equals(AndroidDeviceBrand.XIAOMI_MI_3)
        || this.equals(AndroidDeviceBrand.XIAOMI_MI_3W) || this.equals(
        AndroidDeviceBrand.XIAOMI_MI_4W);
  }

  private static AndroidDeviceBrand createInstance(String manufacture,
      String model) {
    AndroidDeviceBrand brand = new AndroidDeviceBrand(manufacture, model);
    brands.add(brand);
    return brand;
  }

  public static AndroidDeviceBrand from(String manufacture, String model) {
    for (AndroidDeviceBrand brand : brands) {
      if (brand.getManufacture().equals(manufacture)
          && brand.getModel().equals(model)) {
        return brand;
      }
    }
    return EMPTY;
  }
} public enum DeviceTargetPlatform {
  ANDROID10("2.3.3"), ANDROID11("3.0"), ANDROID12("3.1"), ANDROID13("3.2"), ANDROID14(
      "4.0"), ANDROID15("4.0.3"), ANDROID16("4.1.2"), ANDROID17("4.2.2"), ANDROID18(
      "4.3"), ANDROID19("4.4"), ANDROID20("4.4W"), ANDROID21("5.0"), ANDROID22("5.1"),
  ANDROID23("6.0");
  public static final String ANDROID = "ANDROID";

  private String versionNumber;
  private String api;

  DeviceTargetPlatform(String version) {
    this.versionNumber = version;
    this.api = this.name().replace(ANDROID, "");
  }

  public String getSdkFolderName() {
    return name().replace(ANDROID, "android-");
  }

  public static DeviceTargetPlatform fromPlatformVersion(String text) {
    if (text != null) {
      for (DeviceTargetPlatform b : DeviceTargetPlatform.values()) {
        if (b.name().equals(ANDROID + text) || b.name().equals(text)) {
          return b;
        }
      }
    }
    return null;
  }

  public static DeviceTargetPlatform fromInt(String text) {
    if (text != null) {
      for (DeviceTargetPlatform b : DeviceTargetPlatform.values()) {
        if (b.name().equals(ANDROID + text)) {
          return b;
        }
      }
    }
    return null;
  }

  /**
   * @return version number of OS displayed on the device.
   */
  public String getVersionNumber() {
    return versionNumber;
  }

  /**
   * @return api number, used in #{SelendroidCapabilities#PLATFORM_VERSION}
   */
  public String getApi() {
    return api;
  }

  public String formatedName() {
    return this.name() + "(" + this.versionNumber + ")";
  }
} public class AndroidDeviceException extends NestedException {
	private static final long serialVersionUID = 5431510243540521938L;

	public AndroidDeviceException(String message) {
		super(message);
	}

	public AndroidDeviceException(Throwable t) {
		super(t);
	}

	public AndroidDeviceException(String message, Throwable t) {
		super(message, t);
	}
} public class DeviceUnlockException extends NestedException {

	public DeviceUnlockException(String msg) {
		super(msg);
	}

	/**
	 * 
	 */
	private static final long serialVersionUID = -5487201673781343155L;

} public class NestedException extends RuntimeException {

	private static final long serialVersionUID = 5439915454935047936L;

	static {
		// Eagerly load the NestedExceptionUtils class to avoid classloader
		// deadlock
		// issues on OSGi when calling getMessage(). Reported by Don Brown;
		// SPR-5607.
		NestedExceptionUtils.class.getName();
	}

	/**
	 * Construct a {@code NestedRuntimeException} with the specified detail
	 * message.
	 * 
	 * @param msg
	 *            the detail message
	 */
	public NestedException(String msg) {
		super(msg);
	}

	/**
	 * Construct a {@code NestedRuntimeException} with the specified detail
	 * message and nested exception.
	 * 
	 * @param msg
	 *            the detail message
	 * @param cause
	 *            the nested exception
	 */
	public NestedException(String msg, Throwable cause) {
		super(msg, cause);
	}

	public NestedException(Throwable cause) {
		super(cause);
	}

	/**
	 * Return the detail message, including the message from the nested
	 * exception if there is one.
	 */
	@Override
	public String getMessage() {
		return NestedExceptionUtils
				.buildMessage(super.getMessage(), getCause());
	}

	/**
	 * Retrieve the innermost cause of this exception, if any.
	 * 
	 * @return the innermost exception, or {@code null} if none
	 * @since 2.0
	 */
	public Throwable getRootCause() {
		Throwable rootCause = null;
		Throwable cause = getCause();
		while (cause != null && cause != rootCause) {
			rootCause = cause;
			cause = cause.getCause();
		}
		return rootCause;
	}

	/**
	 * Retrieve the most specific cause of this exception, that is, either the
	 * innermost cause (root cause) or this exception itself.
	 * <p>
	 * Differs from {@link #getRootCause()} in that it falls back to the present
	 * exception if there is no root cause.
	 * 
	 * @return the most specific cause (never {@code null})
	 * @since 2.0.3
	 */
	public Throwable getMostSpecificCause() {
		Throwable rootCause = getRootCause();
		return (rootCause != null ? rootCause : this);
	}

	/**
	 * Check whether this exception contains an exception of the given type:
	 * either it is of the given class itself or it contains a nested cause of
	 * the given type.
	 * 
	 * @param exType
	 *            the exception type to look for
	 * @return whether there is a nested exception of the specified type
	 */
	@SuppressWarnings("rawtypes")
	public boolean contains(Class exType) {
		if (exType == null) {
			return false;
		}
		if (exType.isInstance(this)) {
			return true;
		}
		Throwable cause = getCause();
		if (cause == this) {
			return false;
		}
		if (cause instanceof NestedException) {
			return ((NestedException) cause).contains(exType);
		} else {
			while (cause != null) {
				if (exType.isInstance(cause)) {
					return true;
				}
				if (cause.getCause() == cause) {
					break;
				}
				cause = cause.getCause();
			}
			return false;
		}
	}

} public class ImageUtils {

    static final Logger logger = LoggerFactory.getLogger(ImageUtils.class);
    private static Hashtable<?, ?> EMPTY_HASH = new Hashtable<>();
    private static int[] BAND_OFFSETS_32 = {0, 1, 2, 3};
    private static int[] BAND_OFFSETS_16 = {0, 1};

    public static BufferedImage convertImage(RawImage rawImage) {
        switch (rawImage.bpp) {
            case 16:
                return rawImage16toARGB(rawImage);
            case 32:
                return rawImage32toARGB(rawImage);
        }
        return null;
    }

    static int getMask(int length) {
        int res = 0;
        for (int i = 0; i < length; i++) {
            res = (res << 1) + 1;
        }
        return res;
    }

    private static BufferedImage rawImage32toARGB(RawImage rawImage) {
        DataBufferByte dataBuffer = new DataBufferByte(rawImage.data,
                rawImage.size);

        PixelInterleavedSampleModel sampleModel = new PixelInterleavedSampleModel(
                0, rawImage.width, rawImage.height, 4, rawImage.width * 4,
                BAND_OFFSETS_32);

        WritableRaster raster = Raster.createWritableRaster(sampleModel,
                dataBuffer, new Point(0, 0));

        return new BufferedImage(new ThirtyTwoBitColorModel(rawImage), raster,
                false, EMPTY_HASH);
    }

    private static BufferedImage rawImage16toARGB(RawImage rawImage) {
        DataBufferByte dataBuffer = new DataBufferByte(rawImage.data,
                rawImage.size);

        PixelInterleavedSampleModel sampleModel = new PixelInterleavedSampleModel(
                0, rawImage.width, rawImage.height, 2, rawImage.width * 2,
                BAND_OFFSETS_16);

        WritableRaster raster = Raster.createWritableRaster(sampleModel,
                dataBuffer, new Point(0, 0));

        return new BufferedImage(new SixteenBitColorModel(rawImage), raster,
                false, EMPTY_HASH);
    }

    public static byte[] toByteArray(BufferedImage image) {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try {
            ImageIO.write(image, "gif", out);
            return out.toByteArray();
        } catch (IOException e) {
            throw new RuntimeException("", e);
        }
    }

    public static void writeToFile(BufferedImage image, String filePath) {

        try {
            ImageIO.write(image, "png", new File(filePath));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static boolean sameAs(BufferedImage myImage,
                                 BufferedImage otherImage, double percent) {
        long start = System.currentTimeMillis();

        int width = myImage.getWidth();
        int height = myImage.getHeight();

        int numDiffPixels = 0;
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                if (myImage.getRGB(x, y) != otherImage.getRGB(x, y)) {
                    numDiffPixels++;
                }
            }
        }
        double numberPixels = height * width;
        double rs = 1.0 - (int) ((numDiffPixels / numberPixels) * 100) / 100.0;

        long now = System.currentTimeMillis();
        logger.debug("picture compare spend {} millseconds and result is", now
                - start, rs);
        return rs >= percent;
    }
} @Deprecated
public class ClientDataInfo {

  private final transient ClientData clientData;
  private final transient Client client;

  private String name;
  private Integer pid;
  private Integer port;

  public ClientDataInfo(Client client) {
    this.clientData = client.getClientData();
    this.client = client;
    this.name = name();
    this.pid = clientData.getPid();
    this.port = port();
  }

  public String getName() {
    return name;
  }

  public Integer getPid() {
    return pid;
  }

  public Integer getPort() {
    return port;
  }

  @Override
  public String toString() {
    return "ClientDataInfo [name=" + name + ", pid=" + pid + ", port="
        + port + "]";
  }

  protected String name() {
    String name = clientData.getClientDescription();
    if (name != null) {
      if ((clientData.isValidUserId()) && (clientData.getUserId() != 0)) {
        return String.format(Locale.CHINA, "%s (%d)", new Object[] {
            name, Integer.valueOf(clientData.getUserId())});
      }
      return name;
    }
    return "?";
  }

  protected Integer port() {
    int port = client.getDebuggerListenPort();
    return port;
  }
} public class DeviceInfo {

	private String name;
	private String serial;
	private String osName;
	private String kernel;
	private String ram;
	private String rom;
	private String cpu;

	private Integer battery;
	private Integer density;


	public String getSerial() {
		return serial;
	}

	public void setSerial(String serial) {
		this.serial = serial;
	}

	public Integer getBattery() {
		return battery;
	}

	public void setBattery(Integer battery) {
		this.battery = battery;
	}

	public Integer getDensity() {
		return density;
	}

	public void setDensity(Integer density) {
		this.density = density;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getOsName() {
		return osName;
	}

	public void setOsName(String osName) {
		this.osName = osName;
	}

	public String getKernel() {
		return kernel;
	}

	public void setKernel(String kernel) {
		this.kernel = kernel;
	}

	public String getRam() {
		return ram;
	}

	public void setRam(String ram) {
		this.ram = ram;
	}

	public String getRom() {
		return rom;
	}

	public void setRom(String rom) {
		this.rom = rom;
	}

	public String getCpu() {
		return cpu;
	}

	public void setCpu(String cpu) {
		this.cpu = cpu;
	}

	@Override
	public boolean equals(Object o) {
		if (this == o) return true;
		if (o == null || getClass() != o.getClass()) return false;

		DeviceInfo that = (DeviceInfo) o;

		return serial.equals(that.serial);

	}

	@Override
	public int hashCode() {
		return serial.hashCode();
	}
} public class AndroidSdk {
	
	public static final String ANDROID_FOLDER_PREFIX = "android-";
	public static final String ANDROID_HOME = "ANDROID_HOME";

	public static File adb() {

		return new File(platformToolsHome(), "adb"
				+ platformExecutableSuffixExe());
	}

	public static File aapt() throws AndroidSdkException {
		StringBuffer command = new StringBuffer();
		command.append("aapt");
		command.append(platformExecutableSuffixExe());
		File platformToolsAapt = new File(platformToolsHome(),
				command.toString());

		if (platformToolsAapt.isFile()) {
			return platformToolsAapt;
		}

		File buildToolsFolder = buildToolsHome();

		return new File(
				findLatestAndroidPlatformFolder(
						buildToolsFolder,
						"Command 'aapt' was not found inside the Android SDK. Please update to the latest development tools and try again."),
				command.toString());
	}

	public static File android() {
		StringBuffer command = new StringBuffer();
		command.append(toolsHome());

		return new File(toolsHome(), "android" + platformExecutableSuffixBat());
	}

	public static File emulator() {
		return new File(toolsHome(), "emulator" + platformExecutableSuffixExe());
	}

	private static File toolsHome() {
		StringBuffer command = new StringBuffer();
		command.append(androidHome());
		command.append(File.separator);
		command.append("tools");
		command.append(File.separator);
		return new File(command.toString());
	}

	private static File buildToolsHome() {
		StringBuffer command = new StringBuffer();
		command.append(androidHome());
		command.append(File.separator);
		command.append("build-tools");
		command.append(File.separator);

		return new File(command.toString());
	}

	private static File platformToolsHome() {
		StringBuffer command = new StringBuffer();
		command.append(androidHome());
		command.append(File.separator);
		command.append("platform-tools");
		command.append(File.separator);
		return new File(command.toString());
	}

	public static String androidHome() {
		String androidHome = System.getenv(ANDROID_HOME);

		if (androidHome == null) {
			throw new RuntimeException("Environment variable '" + ANDROID_HOME
					+ "' was not found!");
		}
		return androidHome;
	}

	/**
	 * @return path to android.jar of latest android api.
	 */
	public static String androidJar() {
		String platformsRootFolder = androidHome() + File.separator
				+ "platforms";
		File platformsFolder = new File(platformsRootFolder);

		return new File(findLatestAndroidPlatformFolder(platformsFolder,
				"No installed Android APIs have been found."), "android.jar")
				.getAbsolutePath();
	}

	protected static File findLatestAndroidPlatformFolder(File rootFolder,
			String errorMessage) {
		File[] androidApis = rootFolder.listFiles(new AndroidFileFilter());
		if (androidApis == null || androidApis.length == 0) {
			throw new RuntimeException(errorMessage);
		}
		Arrays.sort(androidApis, Collections.reverseOrder());
		return androidApis[0].getAbsoluteFile();
	}

	public static class AndroidFileFilter implements FileFilter {

		@Override
		public boolean accept(File pathname) {
			String fileName = pathname.getName();

			String regex = "\\d{2}\\.\\d{1}\\.\\d{1}";
			if (fileName.matches(regex)
					|| fileName.startsWith(ANDROID_FOLDER_PREFIX)) {
				return true;
			}
			return false;
		}
	}
} public class AndroidSdkException extends NestedException {
  private static final long serialVersionUID = 5431510243540521938L;

  public AndroidSdkException(String message) {
    super(message);
  }

  public AndroidSdkException(Throwable t) {
    super(t);
  }

  public AndroidSdkException(String message, Throwable t) {
    super(message, t);
  }
} public class ShellCommand {

	private static final Logger log = LoggerFactory.getLogger(ShellCommand.class
			.getName());

	public static String exec(CommandLine commandLine)
			throws ShellCommandException {
		return exec(commandLine, 20000);
	}

	public static String exec(CommandLine commandline, long timeoutInMillies)
			throws ShellCommandException {
		log.debug("executing command: " + commandline);
		PritingLogOutputStream outputStream = new PritingLogOutputStream();
		DefaultExecutor exec = new DefaultExecutor();
		exec.setWatchdog(new ExecuteWatchdog(timeoutInMillies));
		PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream);
		exec.setStreamHandler(streamHandler);
		try {
			exec.execute(commandline);
		} catch (Exception e) {
			throw new ShellCommandException(
					"An error occured while executing shell command: "
							+ commandline, new ShellCommandException(
							outputStream.getOutput()));
		}
		return (outputStream.getOutput());
	}

	public static void execAsync(CommandLine commandline)
			throws ShellCommandException {
		execAsync(null, commandline);
	}

	@SuppressWarnings("rawtypes")
	public static void execAsync(String display, CommandLine commandline)
			throws ShellCommandException {
		log.debug("executing async command: " + commandline);
		DefaultExecutor exec = new DefaultExecutor();

		ExecuteResultHandler handler = new DefaultExecuteResultHandler();
		PumpStreamHandler streamHandler = new PumpStreamHandler(
				new PritingLogOutputStream());
		exec.setStreamHandler(streamHandler);
		try {
			if (display == null || display.isEmpty()) {
				exec.execute(commandline, handler);
			} else {
				Map env = EnvironmentUtils.getProcEnvironment();
				EnvironmentUtils.addVariableToEnvironment(env, "DISPLAY=:"
						+ display);

				exec.execute(commandline, env, handler);
			}
		} catch (Exception e) {
			throw new ShellCommandException(
					"An error occured while executing shell command: "
							+ commandline, e);
		}
	}

	private static class PritingLogOutputStream extends LogOutputStream {

		private StringBuilder output = new StringBuilder();

		@Override
		protected void processLine(String line, int level) {
			log.debug("OUTPUT FROM PROCESS: " + line);

			output.append(line).append("\n");
		}

		public String getOutput() {
			return output.toString();
		}
	}
} public class ShellCommandException extends NestedException {
	
	private static final long serialVersionUID = 268831360479853360L;

	public ShellCommandException(String message) {
		super(message);
	}

	public ShellCommandException(Throwable t) {
		super(t);
	}

	public ShellCommandException(String message, Throwable t) {
		super(message, t);
	}
}
import com.android.ddmlib.AdbCommandRejectedException;
import com.android.ddmlib.Client;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.RawImage;
import com.android.ddmlib.TimeoutException;
import com.android.ddmlib.logcat.LogCatListener;
import com.android.ddmlib.logcat.LogCatReceiverTask;
import com.github.cosysoft.device.android.AndroidApp;
import com.github.cosysoft.device.android.AndroidDevice;
import com.github.cosysoft.device.android.AndroidDeviceBrand;
import com.github.cosysoft.device.android.DeviceTargetPlatform;
import com.github.cosysoft.device.exception.AndroidDeviceException;
import com.github.cosysoft.device.exception.DeviceUnlockException;
import com.github.cosysoft.device.exception.NestedException;
import com.github.cosysoft.device.image.ImageUtils;
import com.github.cosysoft.device.model.ClientDataInfo;
import com.github.cosysoft.device.model.DeviceInfo;
import com.github.cosysoft.device.shell.AndroidSdk;
import com.github.cosysoft.device.shell.AndroidSdkException;
import com.github.cosysoft.device.shell.ShellCommand;
import com.github.cosysoft.device.shell.ShellCommandException;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.profiler.Profiler;
    }

    AbstractDevice that = (AbstractDevice) o;

    return device.equals(that.device);
  }

  @Override
  public void tap(int x, int y) {
    CommandLine command = adbCommand("shell", "input", "tap",
        String.valueOf(x), String.valueOf(y));

    executeCommandQuietly(command, COMMAND_TIMEOUT * 6);
    try {
      Thread.sleep(1000);
    } catch (InterruptedException ie) {
      throw new RuntimeException(ie);
    }
  }

  ;

  @Override
  public void swipe(int x1, int y1, int x2, int y2) {
    CommandLine command = adbCommand("shell", "input", "swipe",
        String.valueOf(x1), String.valueOf(y1), String.valueOf(x2),
        String.valueOf(y2));

    executeCommandQuietly(command, COMMAND_TIMEOUT * 6);
    try {
      Thread.sleep(1000);
    } catch (InterruptedException ie) {
      throw new RuntimeException(ie);
    }
  }

  @Override
  public boolean isWifiOff() {
    CommandLine command = adbCommand("shell", "settings", "get", "global",
        "wifi_on");
    String commandOutput = executeCommandQuietly(command);
    String result = commandOutput.trim();

    return "1".equals(result) ? false : true;
  }

  @Override
  public String getName() {
    return device.getName();
  }

  @Override
  public AndroidDeviceBrand getBrand() {
    if (brand != null) {
      return brand;
    }
    String name = getName();
    String manufacture = StringUtils.substringBefore(name, "-");
    String model = StringUtils.substringBetween(name, "-", "-");
    brand = AndroidDeviceBrand.from(manufacture, model);
    return brand;
  }

  ;

  @Override
  public DeviceInfo getDeviceInfo() {

    DeviceInfo deviceInfo = new DeviceInfo();
    deviceInfo.setName(this.getName());
    deviceInfo.setSerial(this.getSerialNumber());
    deviceInfo.setDensity(device.getDensity());
    DeviceTargetPlatform tf = this.getTargetPlatform();
    deviceInfo.setOsName(tf.formatedName());

    try {
      deviceInfo.setKernel(device.getSystemProperty("ro.build.kernel.id")
          .get());
      deviceInfo.setBattery(device.getBattery().get());
    } catch (InterruptedException | ExecutionException e) {
      log.warn("getDeviceInfo", e);
    }

    return deviceInfo;
  }

  ;

  private LogCatReceiverTask logCatReceiverTask;
  private final Set<LogCatListener> logCatListeners = new HashSet<>();
  ;
  private boolean logCatRunning = false;

  @Override
  public synchronized void addLogCatListener(LogCatListener logCatListener) {
    if (logCatReceiverTask == null) {
      logCatReceiverTask = new LogCatReceiverTask(device);
    }
    logCatReceiverTask.addLogCatListener(logCatListener);
    logCatListeners.add(logCatListener);
    if (!logCatRunning) {
      logCatReceiverTask.run();
      logCatRunning = true;
    }
  }

  @Override
  public synchronized void removeLogCatListener(LogCatListener logCatListener) {
    if (logCatReceiverTask == null) {
      logCatReceiverTask = new LogCatReceiverTask(device);
    }
    logCatReceiverTask.removeLogCatListener(logCatListener);
    logCatListeners.remove(logCatListener);
    if (logCatListeners.size() < 1) {
      logCatReceiverTask.stop();
      logCatRunning = false;
    }
  }

  @Override
  public List<ClientDataInfo> getClientDatasInfo() {