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.
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));
String configError = isConfigValid(avdName, osVersion, screenDensity, screenResolution, deviceLocale);
if (configError != null) { build.setResult(Result.NOT_BUILT);
build.setResult(Result.NOT_BUILT);
screenResolution, deviceLocale);
return doSetUp(build, launcher, logger, androidHome, emuConfig);
private Environment doSetUp(final AbstractBuild<?, ?> build, final Launcher launcher,
final Computer computer = Computer.currentComputer();
final EnvVars environment = computer.getEnvironment();
final boolean emulatorAlreadyExists;
emulatorAlreadyExists = launcher.getChannel().call(task);
build.setResult(Result.NOT_BUILT);
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); ArgumentListBuilder emulatorCmd = getToolCommand(launcher, androidHome, "emulator", "emulator.exe", emulatorArgs);
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;
ArgumentListBuilder adbConnectCmd = getToolCommand(launcher, androidHome, "adb", "adb.exe", adbConnectArgs);
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";
ArgumentListBuilder logcatCmd = getToolCommand(launcher, androidHome, "adb", "adb.exe", logcatArgs);
final Proc logWriter = procStarter.cmds(logcatCmd).stdout(logcatStream).stderr(new NullOutputStream()).start();
if (!emulatorAlreadyExists) { build.setResult(Result.NOT_BUILT);
cleanUp(logger, portAllocator, emulatorProcess, adbPort, userPort,
logWriter, logcatFile, logcatStream, artifactsDir);
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, portAllocator, emulatorProcess, adbPort, userPort,
logWriter, logcatFile, logcatStream, artifactsDir);
Helper method for writing to the build log in a consistent manner.
logger.print("[android] "); cleanUp(logger, 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.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.
Proc emulatorProcess, int adbPort, int userPort, Proc logcatProcess,
portAllocator.free(adbPort);
portAllocator.free(userPort);
if (logcatProcess != null) { if (logcatFile.length() != 0) { logcatFile.copyTo(new FilePath(artifactsDir).child("logcat.txt")); 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 reached, either from the given root or PATH.
- Parameters:
launcher The launcher for the remote node.androidHome The (variable-expanded) SDK root given in global config.- Returns:
true if all the required tools are available.
final String executable = "tools/" + (launcher.isUnix() ? "adb" : "adb.exe");
paths.add(0, androidHome);
private static final long serialVersionUID = 1L;
return launcher.getChannel().call(task);
Retrieves the path to the Android SDK tools directory, based on the given SDK root path.
- Parameters:
androidHome The path to the Android SDK root, may be empty or null.- Returns:
- The path to the general Android SDK tools directory.
if (androidHome == null) { androidToolsDir = androidHome +"/tools/";
Generates a ready-to-use ArgumentListBuilder for one of the Android SDK tools.
- Parameters:
launcher The launcher for the remote node.androidHome The Android SDK root.unixCmd The executable to run on normal systems.windowsCmd The executable for elsewhere.args Any extra arguments for the command.- Returns:
- Arguments including the full path to the SDK and any extra Windows stuff required.
final String executable = launcher.isUnix() ? unixCmd : windowsCmd;
ArgumentListBuilder builder = new ArgumentListBuilder(androidToolsDir + executable);
builder.add(Util.tokenize(args));
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 String androidHome, 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";
ArgumentListBuilder cmd = getToolCommand(launcher, androidHome, "adb", "adb.exe", args);
launcher.launch().cmds(cmd).stdout(stream).start().join();
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;
screenDensity = config.getString("screenDensity"); screenResolution = config.getString("screenResolution"); deviceLocale = config.getString("deviceLocale"); return new AndroidEmulator(avdName, osVersion, screenDensity, screenResolution, deviceLocale);
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 (fromWebConfig && !Hudson.getInstance().hasPermission(Hudson.ADMINISTER)) { return ValidationResult.ok();
return ValidationResult.ok();
return ValidationResult.ok();
for (String dirName : new String[] { "tools", "platforms" }) { final String[] requiredTools = { "adb", "android", "emulator" }; for (String toolName : requiredTools) { for (String extension : new String[] { "", ".bat", ".exe" }) { File tool = new File(sdkRoot, "tools/"+ toolName + extension);
if (toolsFound != requiredTools.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.