package hudson.plugins.android_emulator;
import hudson.Launcher.ProcStarter;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Computer;
import hudson.model.Hudson;
import hudson.model.Result;
import hudson.model.TaskListener;
import hudson.tasks.BuildWrapper;
import hudson.tasks.BuildWrapperDescriptor;
import hudson.util.ArgumentListBuilder;
import hudson.util.FormValidation;
import org.jvnet.hudson.plugins.port_allocator.PortAllocationManager;
Duration by which the emulator should start being available via adb.
Duration by which emulator booting should normally complete.
boolean showWindow, String commandLineOptions, int startupDelay) { public Environment setUp(AbstractBuild build, final Launcher launcher, BuildListener listener)
final EnvVars localVars = Computer.currentComputer().getEnvironment();
final EnvVars envVars = new EnvVars(localVars);
envVars.putAll(build.getEnvironment(listener));
deviceLocale, sdCardSize);
if (configError != null) { build.setResult(Result.NOT_BUILT);
if (androidHome == null) { build.setResult(Result.NOT_BUILT);
return doSetUp(build, launcher, listener, androidSdk, emuConfig);
private Environment doSetUp(final AbstractBuild<?, ?> build, final Launcher launcher,
final boolean isUnix = launcher.isUnix();
final Computer computer = Computer.currentComputer();
final boolean emulatorAlreadyExists;
emulatorAlreadyExists = launcher.getChannel().call(task);
build.setResult(Result.FAILURE);
build.setResult(Result.NOT_BUILT);
Thread.sleep(delaySecs * 1000);
final PortAllocationManager portAllocator = PortAllocationManager.getManager(computer);
final int userPort = portAllocator.allocateRandom(build, 0);
final int adbPort = portAllocator.allocateRandom(build, 0);
String emulatorArgs = String.format("-ports %s,%s %s", userPort, adbPort, avdArgs); final EnvVars buildEnvironment = build.getEnvironment(TaskListener.NULL);
final ProcStarter procStarter = launcher.launch().stdout(logger).stderr(logger);
final Proc emulatorProcess = procStarter.envs(buildEnvironment).cmds(emulatorCmd).start();
if (!socket || !emulatorProcess.isAlive()) { build.setResult(Result.NOT_BUILT);
cleanUp(logger, portAllocator, emulatorProcess, adbPort, userPort);
final String adbConnectArgs = "connect localhost:"+ adbPort;
int result = procStarter.cmds(adbConnectCmd).stdout(new NullOutputStream()).start().join();
build.setResult(Result.NOT_BUILT);
cleanUp(logger, portAllocator, emulatorProcess, adbPort, userPort);
final File artifactsDir = build.getArtifactsDir();
final FilePath logcatFile = build.getWorkspace().createTempFile("logcat_", ".log"); final String logcatArgs = "-s localhost:"+ adbPort +" logcat -v time";
final Proc logWriter = procStarter.cmds(logcatCmd).stdout(logcatStream).stderr(new NullOutputStream()).start();
build.setResult(Result.NOT_BUILT);
cleanUp(logger, launcher, androidSdk, portAllocator, emulatorProcess,
adbPort, userPort, logWriter, logcatFile, logcatStream, artifactsDir);
if (emulatorAlreadyExists && !wipeData) { Thread.sleep(bootDuration / 4);
final String keyEventArgs = String.format("-s localhost:%d shell input keyevent %%d", adbPort); procStarter.cmds(menuCmd).start().join();
procStarter.cmds(backCmd).start().join();
return new Environment() { env.put("ANDROID_AVD_DEVICE", "localhost:"+ adbPort); env.put("ANDROID_AVD_ADB_PORT", Integer.toString(adbPort)); env.put("ANDROID_AVD_USER_PORT", Integer.toString(userPort)); public boolean tearDown(AbstractBuild build, BuildListener listener)
cleanUp(logger, launcher, androidSdk, portAllocator, emulatorProcess,
adbPort, userPort, logWriter, logcatFile, logcatStream, artifactsDir);
Helper method for writing to the build log in a consistent manner.
log(logger, message, false);
Helper method for writing to the build log in a consistent manner.
message = '\t' + message.replace("\n", "\n\t"); logger.print("[android] "); Called when this wrapper needs to exit, so we need to clean up some processes etc.
- Parameters:
logger The build logger.portAllocator The port allocator used.emulatorProcess The Android emulator process.adbPort The ADB port used by the emulator.userPort The user port used by the emulator.
cleanUp(logger, null, null, portAllocator, emulatorProcess, adbPort, userPort, null, null, null, null);
Called when this wrapper needs to exit, so we need to clean up some processes etc.
- Parameters:
logger The build logger.launcher The launcher for the remote node.androidSdk The Android SDK being used.portAllocator The port allocator used.emulatorProcess The Android emulator process.adbPort The ADB port used by the emulator.userPort The user port used by the emulator.logcatProcess The adb logcat process.logcatFile The file the logcat output is being written to.logcatStream The stream the logcat output is being written to.artifactsDir The directory where build artifacts should go.
PortAllocationManager portAllocator, Proc emulatorProcess, int adbPort,
int userPort, Proc logcatProcess, FilePath logcatFile,
final String args = "disconnect localhost:"+ adbPort;
ArgumentListBuilder adbDisconnectCmd = Utils.getToolCommand(androidSdk, launcher.isUnix(), Tool.ADB, args);
final ProcStarter procStarter = launcher.launch().stderr(logger);
procStarter.cmds(adbDisconnectCmd).stdout(new NullOutputStream()).start().join();
if (logcatProcess != null) { if (logcatProcess.isAlive()) { if (logcatFile.length() != 0) { logcatFile.copyTo(new FilePath(artifactsDir).child("logcat.txt")); if (emulatorProcess.isAlive()) { portAllocator.free(adbPort);
portAllocator.free(userPort);
Expands the variable in the given string to its value in the environment variables available
to this build. The Hudson-specific build variables for this build are then substituted.
- Parameters:
envVars Map of the environment variables.buildVars Map of the build-specific variables.token The token which may or may not contain variables in the format ${foo}.- Returns:
- The given token, with applicable variable expansions done.
String result = Util.fixEmptyAndTrim(token);
result = Util.replaceMacro(Util.replaceMacro(result, envVars), buildVars);
Validates this instance's configuration.
- Returns:
- A human-readable error message, or
null if the config is valid.
Tries to validate the given Android SDK root directory; otherwise tries to
locate a copy of the SDK by checking for common environment variables.
- Parameters:
launcher The launcher for the remote node.envVars Environment variables for the build.androidHome The (variable-expanded) SDK root given in global config.- Returns:
- Either a discovered SDK path or, if all else fails, the given androidHome value.
String[] keys = { "ANDROID_SDK_ROOT", "ANDROID_SDK_HOME", "ANDROID_HOME", "ANDROID_SDK" };
String home = envVars.get(key);
if (Util.fixEmptyAndTrim(dir) == null) { private static final long serialVersionUID = 1L;
result = launcher.getChannel().call(task);
Validates whether the required SDK tools can be found on the PATH.
- Parameters:
launcher The launcher for the remote node.- Returns:
true if all the required tools are available.
final boolean isUnix = launcher.isUnix();
for (Tool tool : tools) { if (new File(toolsDirectory, executable).exists()) { if (toolCount == tools.length) { private static final long serialVersionUID = 1L;
return launcher.getChannel().call(task);
Determines where platform tools are stored for the given SDK instance.
- Parameters:
launcher The launcher for the remote node.androidHome The SDK we are going to use.
private static final long serialVersionUID = 1L;
return launcher.getChannel().call(task);
Waits for a socket on the remote machine's localhost to become available, or times out.
- Parameters:
launcher The launcher for the remote node.port The port to try and connect to.timeout How long to keep trying (in milliseconds) before giving up.- Returns:
true if the socket was available, false if we timed-out.
private boolean waitForSocket(Launcher launcher, int port, int timeout) { return launcher.getChannel().call(task);
Checks whether the emulator running on the given port has finished booting yet, or times out.
- Parameters:
logger The build logger.launcher The launcher for the remote node.androidHome The Android SDK root.port The emulator's ADB port.timeout How long to keep trying (in milliseconds) before giving up.- Returns:
true if the emulator has booted, false if we timed-out.
final AndroidSdk androidSdk, final int port, final int timeout) { int sleep = timeout / (int) Math.sqrt(timeout / 1000);
final String serialNo = "localhost:"+ port;
final String args = "-s "+ serialNo +" shell getprop dev.bootcomplete";
launcher.launch().cmds(cmd).stdout(stream).start().join();
Sends a user command to the running emulator via its telnet interface.
- Parameters:
logger The build logger.launcher The launcher for the remote node.port The emulator's telnet port.command The command to execute on the emulator's telnet interface.- Returns:
- Whether sending the command succeeded.
final int port, final String command) { socket = new Socket("127.0.0.1", port); private static final long serialVersionUID = 1L;
result = launcher.getChannel().call(task);
log(logger, String.format("Failed to execute emulator command '%s': %s", command, e)); The Android SDK home directory. Can include variables, e.g.
${ANDROID_HOME}.
If null, we will just assume the required commands are on the PATH.
String screenResolution = null;
boolean wipeData = false;
boolean showWindow = true;
String commandLineOptions = null;
avdName = Util.fixEmptyAndTrim(emulatorData.getString("avdName")); osVersion = Util.fixEmptyAndTrim(emulatorData.getString("osVersion")); screenDensity = Util.fixEmptyAndTrim(emulatorData.getString("screenDensity")); screenResolution = Util.fixEmptyAndTrim(emulatorData.getString("screenResolution")); deviceLocale = Util.fixEmptyAndTrim(emulatorData.getString("deviceLocale")); sdCardSize = Util.fixEmptyAndTrim(emulatorData.getString("sdCardSize")); if (sdCardSize != null) { commandLineOptions = formData.getString("commandLineOptions"); return new AndroidEmulator(avdName, osVersion, screenDensity, screenResolution,
deviceLocale, sdCardSize, wipeData, showWindow, commandLineOptions, startupDelay);
return "/plugin/android-emulator/help-buildConfig.html";
Used in config.jelly: Lists the OS versions available.
Used in config.jelly: Lists the screen densities available.
Used in config.jelly: Lists the screen resolutions available.
Used in config.jelly: Lists the locales available.
if (avdName == null || avdName.equals("")) { return ValidationResult.ok();
if (osVersion == null || osVersion.equals("")) { return ValidationResult.ok();
if (density == null || density.equals("")) { return ValidationResult.ok();
boolean allowVariables) { if (resolution == null || resolution.equals("")) { if (resolutionValue != null && densityValue != null
boolean densityFound = false;
if (okDensity.equals(densityValue)) { return ValidationResult.ok();
if (locale == null || locale.equals("")) { return ValidationResult.ok();
if (sdCardSize == null || sdCardSize.equals("")) { return ValidationResult.ok();
if (bytes < (9 * 1024 * 1024)) { return ValidationResult.ok();
if (fromWebConfig && !Hudson.getInstance().hasPermission(Hudson.ADMINISTER)) { return ValidationResult.ok();
if (fromWebConfig && (sdkRoot == null || sdkRoot.getPath().equals(""))) { return ValidationResult.ok();
return ValidationResult.ok();
final String[] sdkDirectories = { "tools", "platforms" }; for (String dirName : sdkDirectories) { final String[] toolDirectories = { "tools", "platform-tools" }; for (String dir : toolDirectories) { File toolPath = new File(toolsDir, executable);
if (toolsFound < Tool.values().length) { File platformsDir = new File(sdkRoot, "platforms");
if (platformsDir.list().length == 0) { return ValidationResult.ok();
Task that will block until it can either connect to a port on localhost, or it times-out.
- Parameters:
port The local TCP port to attempt to connect to.timeout How long to keep trying (in milliseconds) before giving up.
The Java equivalent of /dev/null.