package com.thoughtworks.calabash.android;
import net.lingala.zip4j.core.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import java.io.*;
import java.net.URL;
import java.util.Enumeration;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import static com.thoughtworks.calabash.android.CalabashLogger.info;
public class AndroidRunner {
public static final String TEST_SERVERS = "test_servers";
public static final String GEMS_ZIP = "gems_android.zip";
private final AndroidConfiguration configuration;
private final File apk;
private Environment environment;
private CalabashWrapper calabashWrapper;
/**
* @param apkPath path of the .apk file
* @param configuration android configuration
* @throws CalabashException
*/
public AndroidRunner(String apkPath, AndroidConfiguration configuration) throws CalabashException {
if (!apkPath.endsWith(".apk")) {
throw new CalabashException("invalid path to apk file");
}
apk = new File(apkPath);
if (!apk.exists()) {
throw new CalabashException("invalid path to apk file");
}
this.configuration = configuration;
this.environment = EnvironmentInitializer.initialize(configuration);
CalabashLogger.initialize(this.configuration);
File gemPath = extractGemsFromBundle();
calabashWrapper = new CalabashWrapper(gemPath, apk, configuration, environment);
}
/**
* @param apkPath path of the .apk file
* @throws CalabashException
*/
public AndroidRunner(String apkPath) throws CalabashException {
this(apkPath, new AndroidConfiguration());
}
/**
* Gets the underlying wrapper
*
* @return the calabashWrapper
*/
protected CalabashWrapper getCalabashWrapper() {
return calabashWrapper;
}
/**
* generate the instrumentation test server apk, resign the application with debug keystore
*
* @throws CalabashException
*/
public void setup() throws CalabashException {
try {
calabashWrapper.setup();
} catch (Exception e) {
String errorMessage = "calabash android setup failed: " + e.getMessage();
CalabashLogger.error(errorMessage, e);
throw new CalabashException(errorMessage, e);
}
}
/**
* install the signed apk and test server apk
*
* @return handle to the android application
* @throws CalabashException
*/
public AndroidApplication start() throws CalabashException {
if (!alreadySetup()) {
CalabashLogger.info("Application not setup. Performing setup...");
setup();
}
AndroidBridge androidBridge = new AndroidBridge(environment);
String serial = androidBridge.launchEmulator(configuration);
CalabashLogger.info("Using the serial: %s", serial);
calabashWrapper.start(serial);
return new AndroidApplication(calabashWrapper, serial);
}
public void setJrubyJarFile(File jrubyJarFile) {
environment.setJrubyHome(jrubyJarFile.getAbsolutePath());
}
private boolean alreadySetup() {
File test_servers = new File(getApkDir(), TEST_SERVERS);
if (!test_servers.exists()) {
return false;
}
File[] files = test_servers.listFiles(new FilenameFilter() {
public boolean accept(File file, String name) {
return name.endsWith(".apk");
}
});
return files.length > 0;
}
private File getApkDir() {
return apk.getParentFile();
}
private File extractGemsFromBundle() throws CalabashException {
File extractedDir = getExtractionDir();
File extracted = new File(extractedDir, "extracted");
if (extracted.exists()) {
CalabashLogger.info("Gems already present in temp dir");
return extractedDir;
}
CalabashLogger.info("Extracting gems to temp dir");
copyFileFromBundleTo("scripts", GEMS_ZIP, extractedDir);
try {
File gemsZip = new File(extractedDir, "gems_android.zip");
unzipWithPermission(extractedDir, gemsZip);
gemsZip.delete();
extracted.createNewFile();
} catch (Exception e) {
throw new CalabashException("Failed to unzip gems", e);
}
return extractedDir;
}
private void unzipWithPermission(File extractedDir, File gemszip) throws ZipException, CalabashException {
ZipFile zipFile = new ZipFile(gemszip);
zipFile.extractAll(extractedDir.getAbsolutePath());
if (!Utils.isWindows()) {
File jrubyExecutable = new File(extractedDir + File.separator + "jruby.home" + File.separator + "bin" + File.separator + "jruby");
String[] chmod = {"chmod", "+x", jrubyExecutable.getAbsolutePath()};
Utils.runCommand(chmod, "Could not change executable permission");
}
}
private File getExtractionDir() throws CalabashException {
try {
File tempFile = File.createTempFile("foo", "bar");
tempFile.delete();
File gemsDir = new File(tempFile.getParentFile(), "calabash-android-gems-" + getCurrentVersion());
if (!gemsDir.exists()) {
boolean created = gemsDir.mkdir();
if (!created)
throw new CalabashException("Can't create gems extraction directory. " + gemsDir.getAbsolutePath());
}
if (!gemsDir.isDirectory())
throw new CalabashException(String.format("Gems directory is invalid. %s is not a directory", gemsDir.getAbsolutePath()));
return gemsDir;
} catch (IOException e) {
throw new CalabashException("Can't create gems extraction directory.", e);
}
}
private String getCurrentVersion() {
try {
Enumeration<URL> resources = getClass().getClassLoader().getResources("META-INF/MANIFEST.MF");
while (resources.hasMoreElements()) {
try {
Manifest manifest = new Manifest(resources.nextElement().openStream());
Attributes mainAttributes = manifest.getMainAttributes();
if (mainAttributes != null && "calabash-android-java".equals(mainAttributes.getValue("Project-Name"))) {
String value = mainAttributes.getValue("Implementation-Version");
if (value != null)
return value;
}
} catch (Exception E) {
// ignore
}
}
} catch (Exception e) {
// ignore
}
return "";
}
private void copyFileFromBundleTo(String sourceDir, String fileName,
File outDir) throws CalabashException {
final ClassLoader classLoader = Thread.currentThread()
.getContextClassLoader();
InputStream stream = classLoader.getResourceAsStream(sourceDir + "/"
+ fileName);
if (stream == null) {
String message = String.format("Can't copy %s from the bundle. Make sure you are using the correct JAR file", fileName);
throw new CalabashException(message, null);
}
try {
File file = new File(outDir, fileName);
file.createNewFile();
FileOutputStream outFile = new FileOutputStream(file);
byte[] buffer = new byte[10240];
int len;
while ((len = stream.read(buffer)) != -1) {
outFile.write(buffer, 0, len);
}
outFile.close();
} catch (IOException e) {
String message = String.format("Can't copy %s from the bundle to %s. Failed to create destination file", fileName, outDir.getAbsolutePath());
throw new CalabashException(message, e);
}
}
/**
* Releases resources associated with this runner
*
* @throws CalabashException
*/
public void dispose() throws CalabashException {
calabashWrapper.dispose();
}
}