io.selendroid.android.impl.DefaultAndroidEmulator.java Source code

Java tutorial

Introduction

Here is the source code for io.selendroid.android.impl.DefaultAndroidEmulator.java

Source

/*
 * Copyright 2012-2013 eBay Software Foundation and selendroid committers.
 * 
 * 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 io.selendroid.android.impl;

import io.selendroid.android.AndroidEmulator;
import io.selendroid.android.AndroidSdk;
import io.selendroid.android.TelnetClient;
import io.selendroid.device.DeviceTargetPlatform;
import io.selendroid.exceptions.AndroidDeviceException;
import io.selendroid.exceptions.SelendroidException;
import io.selendroid.exceptions.ShellCommandException;
import io.selendroid.io.ShellCommand;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Scanner;
import java.util.logging.Logger;
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.lang3.StringUtils;

import com.android.ddmlib.IDevice;
import com.beust.jcommander.internal.Lists;

public class DefaultAndroidEmulator extends AbstractDevice implements AndroidEmulator {
    private static final String EMULATOR_SERIAL_PREFIX = "emulator-";
    private static final Logger log = Logger.getLogger(DefaultAndroidEmulator.class.getName());
    public static final String ANDROID_EMULATOR_HARDWARE_CONFIG = "hardware-qemu.ini";
    public static final String FILE_LOCKING_SUFIX = ".lock";

    private String screenSize;
    private DeviceTargetPlatform targetPlatform;
    private String avdName;
    private File avdRootFolder;
    private Locale locale = null;
    private boolean wasStartedBySelendroid;

    protected DefaultAndroidEmulator() {
        this.wasStartedBySelendroid = Boolean.FALSE;
    }

    public DefaultAndroidEmulator(String avdName, String abi, String screenSize, String target, File avdFilePath) {
        this.avdName = avdName;
        this.screenSize = screenSize;
        this.avdRootFolder = avdFilePath;
        this.targetPlatform = DeviceTargetPlatform.fromInt(target);
        this.wasStartedBySelendroid = !isEmulatorStarted();
    }

    public File getAvdRootFolder() {
        return avdRootFolder;
    }

    public String getScreenSize() {
        return screenSize;
    }

    public DeviceTargetPlatform getTargetPlatform() {
        return targetPlatform;
    }

    /*
     * (non-Javadoc)
     * 
     * @see io.selendroid.android.impl.AndroidEmulator#isEmulatorAlreadyExistent()
     */
    @Override
    public boolean isEmulatorAlreadyExistent() {
        File emulatorFolder = new File(FileUtils.getUserDirectory(),
                File.separator + ".android" + File.separator + "avd" + File.separator + getAvdName() + ".avd");
        return emulatorFolder.exists();
    }

    public String getAvdName() {
        return avdName;
    }

    public static List<AndroidEmulator> listAvailableAvds() throws AndroidDeviceException {
        List<AndroidEmulator> avds = Lists.newArrayList();

        CommandLine cmd = new CommandLine(AndroidSdk.android());
        cmd.addArgument("list", false);
        cmd.addArgument("avds", false);

        String output = null;
        try {
            output = ShellCommand.exec(cmd, 20000);
        } catch (ShellCommandException e) {
            throw new AndroidDeviceException(e);
        }
        Map<String, Integer> startedDevices = mapDeviceNamesToSerial();

        String[] avdsOutput = StringUtils.splitByWholeSeparator(output, "---------");
        if (avdsOutput != null && avdsOutput.length > 0) {
            for (int i = 0; i < avdsOutput.length; i++) {
                if (avdsOutput[i].contains("Name:") == false) {
                    continue;
                }
                String element = avdsOutput[i];
                String avdName = extractValue("Name: (.*?)$", element);
                String abi = extractValue("ABI: (.*?)$", element);
                String screenSize = extractValue("Skin: (.*?)$", element);
                String target = extractValue("\\(API level (.*?)\\)", element);
                File avdFilePath = new File(extractValue("Path: (.*?)$", element));
                DefaultAndroidEmulator emulator = new DefaultAndroidEmulator(avdName, abi, screenSize, target,
                        avdFilePath);
                if (startedDevices.containsKey(avdName)) {
                    emulator.setSerial(startedDevices.get(avdName));
                }
                avds.add(emulator);
            }
        }
        return avds;
    }

    private static Map<String, Integer> mapDeviceNamesToSerial() {
        Map<String, Integer> mapping = new HashMap<String, Integer>();
        CommandLine command = new CommandLine(AndroidSdk.adb());
        command.addArgument("devices");
        Scanner scanner;
        try {
            scanner = new Scanner(ShellCommand.exec(command));
        } catch (ShellCommandException e) {
            return mapping;
        }
        while (scanner.hasNextLine()) {
            String line = scanner.nextLine();
            Pattern pattern = Pattern.compile("emulator-\\d\\d\\d\\d");
            Matcher matcher = pattern.matcher(line);
            if (matcher.find()) {
                String serial = matcher.group(0);

                Integer port = Integer.valueOf(serial.replaceAll("emulator-", ""));
                TelnetClient client = null;
                try {
                    client = new TelnetClient(port);
                    String avdName = client.sendCommand("avd name");
                    mapping.put(avdName, port);
                } catch (AndroidDeviceException e) {
                    // ignore
                } finally {
                    if (client != null) {
                        client.close();
                    }
                }
                Socket socket = null;
                PrintWriter out = null;
                BufferedReader in = null;
                try {
                    socket = new Socket("127.0.0.1", port);
                    out = new PrintWriter(socket.getOutputStream(), true);
                    in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    if (in.readLine() == null) {
                        throw new AndroidDeviceException("error");
                    }

                    out.write("avd name\r\n");
                    out.flush();
                    in.readLine();// OK
                    String avdName = in.readLine();
                    mapping.put(avdName, port);
                } catch (Exception e) {
                    // ignore
                } finally {
                    try {
                        out.close();
                        in.close();
                        socket.close();
                    } catch (Exception e) {
                        // do nothing
                    }
                }
            }
        }
        scanner.close();

        return mapping;
    }

    @Override
    public boolean isEmulatorStarted() {
        File lockedEmulatorHardwareConfig = new File(avdRootFolder,
                ANDROID_EMULATOR_HARDWARE_CONFIG + FILE_LOCKING_SUFIX);
        return lockedEmulatorHardwareConfig.exists();
    }

    @Override
    public String toString() {
        return "AndroidEmulator [screenSize=" + screenSize + ", targetPlatform=" + targetPlatform + ", serial="
                + serial + ", avdName=" + avdName + "]";
    }

    public void setSerial(int port) {
        this.port = port;
        serial = EMULATOR_SERIAL_PREFIX + port;
    }

    public Integer getPort() {
        if (isSerialConfigured()) {
            return Integer.parseInt(serial.replace(EMULATOR_SERIAL_PREFIX, ""));
        }
        return null;
    }

    @Override
    public void start(Locale locale, int emulatorPort, Map<String, Object> options) throws AndroidDeviceException {
        if (isEmulatorStarted()) {
            throw new SelendroidException("Error - Android emulator is already started " + this);
        }
        Long timeout = null;
        String emulatorOptions = null;
        String display = null;
        if (options != null) {
            if (options.containsKey(TIMEOUT_OPTION)) {
                timeout = (Long) options.get(TIMEOUT_OPTION);
            }
            if (options.containsKey(DISPLAY_OPTION)) {
                display = (String) options.get(DISPLAY_OPTION);
            }
            if (options.containsKey(EMULATOR_OPTIONS)) {
                emulatorOptions = (String) options.get(EMULATOR_OPTIONS);
            }
        }

        if (display != null) {
            log.info("Using display " + display + " for running the emulator");
        }
        if (timeout == null) {
            timeout = 120000L;
        }
        log.info("Using timeout of '" + timeout / 1000 + "' seconds to start the emulator.");
        this.locale = locale;

        CommandLine cmd = new CommandLine(AndroidSdk.emulator());

        cmd.addArgument("-no-snapshot-save", false);
        cmd.addArgument("-avd", false);
        cmd.addArgument(avdName, false);
        cmd.addArgument("-port", false);
        cmd.addArgument(String.valueOf(emulatorPort), false);
        if (locale != null) {
            cmd.addArgument("-prop", false);
            cmd.addArgument("persist.sys.language=" + locale.getLanguage(), false);
            cmd.addArgument("-prop", false);
            cmd.addArgument("persist.sys.country=" + locale.getCountry(), false);
        }
        if (emulatorOptions != null && emulatorOptions.isEmpty() == false) {
            cmd.addArgument(emulatorOptions, false);
        }

        long start = System.currentTimeMillis();
        long timemoutEnd = start + timeout;
        try {
            ShellCommand.execAsync(display, cmd);
        } catch (ShellCommandException e) {
            throw new SelendroidException("unable to start the emulator: " + this);
        }
        setSerial(emulatorPort);
        Boolean adbKillServerAttempted = false;
        while (isDeviceReady() == false) {

            if (!adbKillServerAttempted && System.currentTimeMillis() - start > 10000) {
                CommandLine adbDevicesCmd = new CommandLine(AndroidSdk.adb());
                adbDevicesCmd.addArgument("devices", false);

                String devices = "";
                try {
                    devices = ShellCommand.exec(adbDevicesCmd, 20000);
                } catch (ShellCommandException e) {
                    // pass
                }
                if (!devices.contains(String.valueOf(emulatorPort))) {
                    CommandLine resetAdb = new CommandLine(AndroidSdk.adb());
                    resetAdb.addArgument("kill-server", false);

                    try {
                        ShellCommand.exec(resetAdb, 20000);
                    } catch (ShellCommandException e) {
                        throw new SelendroidException("unable to kill the adb server");
                    }
                }
                adbKillServerAttempted = true;
            }
            if (timemoutEnd >= System.currentTimeMillis()) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                }
            } else {
                throw new AndroidDeviceException("The emulator with avd '" + getAvdName()
                        + "' was not started after " + (System.currentTimeMillis() - start) / 1000 + " seconds.");
            }
        }

        log.info("Emulator start took: " + (System.currentTimeMillis() - start) / 1000 + " seconds");
        log.info("Please have in mind, starting an emulator takes usually about 45 seconds.");
        unlockEmulatorScreen();

        waitForLauncherToComplete();

        // we observed that emulators can sometimes not be 'fully loaded'
        // if we click on the All Apps button and wait for it to load it is more likely to be in a
        // usable state.
        allAppsGridView();

        waitForLauncherToComplete();
        setWasStartedBySelendroid(true);
    }

    public void unlockEmulatorScreen() throws AndroidDeviceException {
        CommandLine event82 = new CommandLine(AndroidSdk.adb());

        if (isSerialConfigured()) {
            event82.addArgument("-s", false);
            event82.addArgument(serial, false);
        }
        event82.addArgument("shell", false);
        event82.addArgument("input", false);
        event82.addArgument("keyevent", false);
        event82.addArgument("82", false);

        try {
            ShellCommand.exec(event82, 20000);
        } catch (ShellCommandException e) {
            throw new AndroidDeviceException(e);
        }

        CommandLine event4 = new CommandLine(AndroidSdk.adb());

        if (isSerialConfigured()) {
            event4.addArgument("-s", false);
            event4.addArgument(serial, false);
        }
        event4.addArgument("shell", false);
        event4.addArgument("input", false);
        event4.addArgument("keyevent", false);
        event4.addArgument("4", false);
        try {
            ShellCommand.exec(event4, 20000);
        } catch (ShellCommandException e) {
            throw new AndroidDeviceException(e);
        }
    }

    private void waitForLauncherToComplete() throws AndroidDeviceException {
        waitForLauncherToComplete(true);
    }

    private void waitForLauncherToComplete(Boolean delay) throws AndroidDeviceException {
        CommandLine event = new CommandLine(AndroidSdk.adb());

        if (isSerialConfigured()) {
            event.addArgument("-s", false);
            event.addArgument(serial, false);
        }
        event.addArgument("shell", false);
        event.addArgument("ps", false);
        String homeScreenLaunched = null;
        try {
            homeScreenLaunched = ShellCommand.exec(event, 20000);
        } catch (ShellCommandException e) {
            throw new AndroidDeviceException(e);
        }
        if (homeScreenLaunched != null && homeScreenLaunched.contains("S com.android.launcher")) {
            if (!delay)
                return;
        } else {
            // it's still running, sleep for a bit
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            waitForLauncherToComplete(true);
        }

        // it's done right? ... well, maybe... check again after waiting a second
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        waitForLauncherToComplete(false);

    }

    private void allAppsGridView() throws AndroidDeviceException {
        String[] dimensions = screenSize.split("x");
        int x = Integer.parseInt(dimensions[0]);
        int y = Integer.parseInt(dimensions[1]);
        if (x > y) {
            y = y / 2;
            x = x - 30;
        } else {
            x = x / 2;
            y = y - 30;
        }

        List<String> coordinates = new ArrayList<String>();
        coordinates.add("3 0 " + x);
        coordinates.add("3 1 " + y);
        coordinates.add("1 330 1");
        coordinates.add("0 0 0");
        coordinates.add("1 330 0");
        coordinates.add("0 0 0");

        for (String coordinate : coordinates) {
            CommandLine event1 = new CommandLine(AndroidSdk.adb());
            if (isSerialConfigured()) {
                event1.addArgument("-s", false);
                event1.addArgument(serial, false);
            }
            event1.addArgument("shell", false);
            event1.addArgument("sendevent", false);
            event1.addArgument("dev/input/event0", false);
            event1.addArgument(coordinate, false);
            try {
                ShellCommand.exec(event1);
            } catch (ShellCommandException e) {
                throw new AndroidDeviceException(e);
            }
        }

        try {
            Thread.sleep(750);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    private void stopEmulator() throws AndroidDeviceException {
        TelnetClient client = null;
        try {
            client = new TelnetClient(getPort());
            client.sendQuietly("kill");
        } catch (AndroidDeviceException e) {
            // ignore
        } finally {
            if (client != null) {
                client.close();
            }
        }
    }

    @Override
    public void stop() throws AndroidDeviceException {
        if (wasStartedBySelendroid) {
            stopEmulator();
            Boolean killed = false;
            while (isEmulatorStarted()) {
                log.info("emulator still running, sleeping 0.5, waiting for it to release the lock");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException ie) {
                    throw new RuntimeException(ie);
                }
                if (!killed) {
                    try {
                        stopEmulator();
                    } catch (AndroidDeviceException sce) {
                        killed = true;
                    }
                }
            }
        }
    }

    @Override
    public Locale getLocale() {
        return locale;
    }

    @Override
    public void setIDevice(IDevice iDevice) {
        super.device = iDevice;
    }

    public String getSerial() {
        return serial;
    }

    public void setWasStartedBySelendroid(boolean wasStartedBySelendroid) {
        this.wasStartedBySelendroid = wasStartedBySelendroid;
    }
}