All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.arquillian.droidium.container.configuration.AndroidSDK Maven / Gradle / Ivy

There is a newer version: 1.0.1.Final
Show newest version
/*
s * JBoss, Home of Professional Open Source
 * Copyright 2012, Red Hat Middleware LLC, and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * 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.
 *
 * Copyright (C) 2009, 2010 Jayway AB
 *
 * 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 org.arquillian.droidium.container.configuration;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Represents an Android SDK.
 *
 * @author Karel Piwko
 * @author Stefan Miklosovic
 * @author [email protected]
 * @author Manfred Moser 
 */
public class AndroidSDK {

    private AndroidContainerConfiguration configuration;

    private static final Logger logger = Logger.getLogger(AndroidSDK.class.getName());

    /**
     * property file in each platform folder with details about platform
     */
    private static final String SOURCE_PROPERTIES_FILENAME = "source.properties";

    /**
     * property name for platform version in sdk source.properties file
     */
    private static final String PLATFORM_VERSION_PROPERTY = "Platform.Version";

    /**
     * property name for API level version in SDK source.properties file
     */
    private static final String API_LEVEL_PROPERTY = "AndroidVersion.ApiLevel";

    /**
     * folder name for the SDK sub folder that contains the different platform versions
     */
    private static final String PLATFORMS_FOLDER_NAME = "platforms";

    /**
     * folder name for the SDK sub folder that contains the platform tools
     */
    private static final String PLATFORM_TOOLS_FOLDER_NAME = "platform-tools";

    /**
     * folder name of the SDK sub folder that contains build tools
     */
    private static final String BUILD_TOOLS_FOLDER_NAME = "build-tools";

    /**
     * folder name of system images in SDK
     */
    private static final String SYSTEM_IMAGES_FOLDER_NAME = "system-images";

    private static final class Platform implements Comparable {
        final String name;
        final String apiLevel;
        final String path;
        final List systemImages;

        public Platform(String name, String apiLevel, String path, List systemImages) {
            super();
            this.name = name;
            this.apiLevel = apiLevel;
            this.path = path;
            this.systemImages = systemImages;
        }

        public boolean hasSystemImage(String systemImage) {
            for (String tmp : systemImages) {
                if (tmp.equals(systemImage)) {
                    return true;
                }
            }

            return false;
        }

        @Override
        public int compareTo(Platform o) {

            // try to do a numeric comparison
            try {
                Integer current = Integer.parseInt(apiLevel);
                Integer other = Integer.parseInt(o.apiLevel);
                return current.compareTo(other);
            } catch (NumberFormatException e) {
                logger.log(Level.INFO, "Unable to compare platforms taking their api level as Integers, "
                        + "comparison as Strings follows");
            }

            // failed, try to compare as strings
            return apiLevel.compareTo(o.apiLevel);
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((apiLevel == null) ? 0 : apiLevel.hashCode());
            result = prime * result + ((name == null) ? 0 : name.hashCode());
            result = prime * result + ((path == null) ? 0 : path.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            Platform other = (Platform) obj;
            if (apiLevel == null) {
                if (other.apiLevel != null) {
                    return false;
                }
            } else if (!apiLevel.equals(other.apiLevel)) {
                return false;
            }
            if (name == null) {
                if (other.name != null) {
                    return false;
                }
            } else if (!name.equals(other.name)) {
                return false;
            }
            if (path == null) {
                if (other.path != null) {
                    return false;
                }
            } else if (!path.equals(other.path)) {
                return false;
            }
            return true;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder("Platform: ");
            sb.append(name).append("/API level ").append(apiLevel).append(" at ").append(path);
            return sb.toString();
        }
    }

    /**
     * Enumeration of all (up to November 2013) possible types of system images.
     *
     * @author Stefan Miklosovic
     *
     */
    private enum SystemImage {

        X86("x86"), ARMEABIV7A("armeabi-v7a"), MIPS("mips");

        private String name;

        private SystemImage(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return name;
        }

        /**
         * @return all system images concatenated to one string separated only by one space from each other
         */
        public static String getAll() {
            StringBuilder sb = new StringBuilder();

            for (SystemImage systemImage : SystemImage.values()) {
                sb.append(systemImage.toString());
                sb.append(" ");
            }

            return sb.toString().trim();
        }

    }

    private final File sdkPath;
    private final File javaPath;
    private final Platform platform;

    private List availablePlatforms;

    /**
     *
     * @param configuration
     * @throws AndroidContainerConfigurationException
     */
    public AndroidSDK(AndroidContainerConfiguration configuration) throws AndroidContainerConfigurationException {

        Validate.notNull(configuration, "AndroidSDK configuration must be provided");
        Validate.isReadableDirectory(configuration.getAndroidHome(), "Unable to read Android SDK from directory "
                + configuration.getAndroidHome());
        Validate.isReadableDirectory(configuration.getJavaHome(), "Unable to determine JAVA_HOME");

        this.sdkPath = new File(configuration.getAndroidHome());
        this.javaPath = new File(configuration.getJavaHome());
        availablePlatforms = findAvailablePlatforms();

        if (availablePlatforms.size() == 0) {
            throw new AndroidContainerConfigurationException("There are not any available platforms found on your system!");
        }

        Platform foundPlatform = null;

        if (configuration.getApiLevel() == null) {
            foundPlatform = availablePlatforms.get(availablePlatforms.size() - 1); // the latest one
            configuration.setApiLevel(foundPlatform.apiLevel);
        } else {
            foundPlatform = findPlatformByApiLevel(configuration.getApiLevel());
        }

        if (foundPlatform == null) {
            logger.log(Level.INFO, "API level {0} you specified in configuration via 'apiLevel' property "
                    + "is not present on your system. In such case, Droidium tries to find the highest API level "
                    + "available and sets it as the default one. When your emulator of some AVD name is not present "
                    + "in the system, Droidium will create it dynamically and this API level will be used when emulator "
                    + "will be created. All available platforms are: {1}", new Object[] { configuration.getApiLevel(),
                    getAllPlatforms() });
            foundPlatform = availablePlatforms.get(availablePlatforms.size() - 1);
            configuration.setApiLevel(foundPlatform.apiLevel);
        }

        if (foundPlatform.systemImages.size() == 0) {
            logger.log(Level.INFO, "There are not any system images found for your API level. You can use Droidium "
                    + "only with physical devices connected until you specify such API level which has system images "
                    + "available to use. Your current API level is: {0}", new Object[] { configuration.getApiLevel() });
        } else {
            if (configuration.getAbi() == null) {
                configuration.setAbi(foundPlatform.systemImages.get(0));
            } else {
                if (!foundPlatform.hasSystemImage(configuration.getAbi())) {
                    logger.log(Level.INFO, "ABI you want to use ({1}), is not present in the system for API level {0}. "
                            + "Droidium uses whatever comes first among {2} and it is available for your API level.",
                            new Object[] { configuration.getApiLevel(), configuration.getAbi(), SystemImage.getAll() });
                    configuration.setAbi(foundPlatform.systemImages.get(0));
                }
            }
        }

        platform = foundPlatform;
        this.configuration = configuration;
    }

    private String getAllPlatforms() {
        StringBuilder sb = new StringBuilder();
        for (Platform p : availablePlatforms) {
            sb.append("API level: ").append(p.apiLevel).append("(").append(p.name).append("), ");
        }
        if (sb.length() > 0) {
            sb.delete(sb.lastIndexOf(","), sb.length());
        }

        return sb.toString();
    }

    public AndroidContainerConfiguration getConfiguration() {
        return configuration;
    }

    public void setConfiguration(AndroidContainerConfiguration configuration) {
        this.configuration = configuration;
    }

    private Platform findPlatformByApiLevel(String apiLevel) {
        for (Platform p : availablePlatforms) {
            if (p.apiLevel.equals(apiLevel)) {
                return p;
            }
        }
        return null;
    }

    public enum Layout {
        LAYOUT_1_5, LAYOUT_2_3
    }

    public Layout getLayout() throws AndroidContainerConfigurationException {

        Validate.isReadableDirectory(sdkPath, "Unable to read Android SDK from directory " + sdkPath);

        final File platformTools = new File(sdkPath, PLATFORM_TOOLS_FOLDER_NAME);
        if (platformTools.exists() && platformTools.isDirectory()) {
            return Layout.LAYOUT_2_3;
        }

        final File platforms = new File(sdkPath, PLATFORMS_FOLDER_NAME);
        if (platforms.exists() && platforms.isDirectory()) {
            return Layout.LAYOUT_1_5;
        }

        throw new AndroidContainerConfigurationException("Android SDK could not be identified from path \"" + sdkPath + "\". ");
    }

    public String getPathForJavaTool(String tool) {

        File[] possiblePaths = { new File(javaPath, MessageFormat.format("bin/{0}", tool)),
                new File(javaPath, MessageFormat.format("bin/{0}.exe", tool)),
                new File(javaPath, MessageFormat.format("../bin/{0}", tool)),
                new File(javaPath, MessageFormat.format("../bin/{0}.exe", tool)), };

        for (File candidate : possiblePaths) {
            if (candidate.exists() && candidate.isFile() && candidate.canExecute()) {
                return candidate.getAbsolutePath();
            }
        }

        // construct error message
        StringBuilder exception = new StringBuilder("Could not find tool '")
                .append(tool)
                .append("'. Please ensure you've set JAVA_HOME environment property properly and that it points to your Java installation directory. ")
                .append("Searching at locations: ");
        String delimiter = "";
        for (File candidate : possiblePaths) {
            exception.append(delimiter).append(candidate.getAbsolutePath());
            delimiter = ", ";
        }

        throw new RuntimeException(exception.toString());
    }

    /**
     * Returns the complete path for a tool, based on this SDK.
     *
     * @param tool which tool, for example adb or dx.jar.
     * @return the complete path as a String, including the tool's filename.
     */
    public String getPathForTool(String tool) {

        File[] possiblePaths = { new File(sdkPath, MessageFormat.format("tools/{0}", tool)),
                new File(sdkPath, MessageFormat.format("tools/{0}.exe", tool)),
                new File(sdkPath, MessageFormat.format("tools/{0}.bat", tool)),
                new File(sdkPath, MessageFormat.format("{0}/{1}", PLATFORMS_FOLDER_NAME, tool)),
                new File(sdkPath, MessageFormat.format("{0}/{1}.exe", PLATFORMS_FOLDER_NAME, tool)),
                new File(sdkPath, MessageFormat.format("{0}/{1}.bat", PLATFORMS_FOLDER_NAME, tool)),
                new File(sdkPath, MessageFormat.format("{0}/lib/{1}", PLATFORMS_FOLDER_NAME, tool)),
                new File(sdkPath, MessageFormat.format("{0}/lib/{1}.exe", PLATFORMS_FOLDER_NAME, tool)),
                new File(sdkPath, MessageFormat.format("{0}/lib/{1}.bat", PLATFORMS_FOLDER_NAME, tool)),
                new File(sdkPath, MessageFormat.format("{0}/tools/{1}", PLATFORMS_FOLDER_NAME, tool)),
                new File(sdkPath, MessageFormat.format("{0}/tools/{1}.exe", PLATFORMS_FOLDER_NAME, tool)),
                new File(sdkPath, MessageFormat.format("{0}/tools/{1}.bat", PLATFORMS_FOLDER_NAME, tool)),
                new File(sdkPath, MessageFormat.format("{0}/tools/{1}", getPlatform().getName(), tool)),
                new File(sdkPath, MessageFormat.format("{0}/tools/{1}.exe", getPlatform().getName(), tool)),
                new File(sdkPath, MessageFormat.format("{0}/tools/{1}.bat", getPlatform().getName(), tool)),
                new File(sdkPath, MessageFormat.format("{0}/tools/lib/{1}", PLATFORMS_FOLDER_NAME, tool)),
                new File(sdkPath, MessageFormat.format("{0}/tools/lib/{1}.exe", PLATFORMS_FOLDER_NAME, tool)),
                new File(sdkPath, MessageFormat.format("{0}/tools/lib/{1}.bat", PLATFORMS_FOLDER_NAME, tool)),
                new File(sdkPath, MessageFormat.format("{0}/tools/lib/{1}", getPlatform().getName(), tool)),
                new File(sdkPath, MessageFormat.format("{0}/tools/lib/{1}.exe", getPlatform().getName(), tool)),
                new File(sdkPath, MessageFormat.format("{0}/tools/lib/{1}.bat", getPlatform().getName(), tool)),
                new File(sdkPath, MessageFormat.format("{0}/{1}", PLATFORM_TOOLS_FOLDER_NAME, tool)),
                new File(sdkPath, MessageFormat.format("{0}/{1}.exe", PLATFORM_TOOLS_FOLDER_NAME, tool)),
                new File(sdkPath, MessageFormat.format("{0}/{1}.bat", PLATFORM_TOOLS_FOLDER_NAME, tool)), };

        for (File candidate : possiblePaths) {
            if (candidate.exists() && candidate.isFile() && candidate.canExecute()) {
                return candidate.getAbsolutePath();
            }
        }

        // construct error message
        StringBuilder exception = new StringBuilder("Could not find tool '")
                .append(tool)
                .append("'. Please ensure you've set ANDROID_HOME environment property or androidHome property in arquillian.xml and this location contains all required packages")
                .append("Searching at locations: ");
        String delimiter = "";
        for (File candidate : possiblePaths) {
            exception.append(delimiter).append(candidate.getAbsolutePath());
            delimiter = ", ";
        }

        throw new RuntimeException(exception.toString());
    }

    private String getBuildTool(String tool) {

        // look only into android-sdks/platforms/android-{number}/tools/aapt
        File[] possiblePlatformPaths = {
                new File(sdkPath, MessageFormat.format("{0}/{1}/tools/{2}", PLATFORMS_FOLDER_NAME, getPlatform(), tool)),
                new File(sdkPath, MessageFormat.format("{0}/{1}/tools/{2}.exe", PLATFORMS_FOLDER_NAME, getPlatform(), tool)),
                new File(sdkPath, MessageFormat.format("{0}/{1}/tools/{2}.bat", PLATFORMS_FOLDER_NAME, getPlatform(), tool)) };

        for (File candidate : possiblePlatformPaths) {
            if (candidate.exists() && candidate.isFile() && candidate.canExecute()) {
                return candidate.getAbsolutePath();
            }
        }

        // go into android-sdks/build-tools/
        File possibleBuildPath = new File(sdkPath, BUILD_TOOLS_FOLDER_NAME);

        File[] dirs = possibleBuildPath.listFiles();
        Arrays.sort(dirs);

        for (File dir : dirs) {
            for (File candidate : new File[] { new File(dir, tool), new File(dir, tool + ".exe"), new File(dir, tool + ".bat") }) {
                if (candidate.exists() && candidate.isFile() && candidate.canExecute()) {
                    return candidate.getAbsolutePath();
                }
            }
        }

        throw new RuntimeException("Could not find tool '" + tool + ".");
    }

    /**
     * Get the emulator path.
     *
     * @return path to {@code emulator} command
     */
    public String getEmulatorPath() {
        return getPathForTool("emulator");
    }

    public String getMakeSdCardPath() {
        return getPathForTool("mksdcard");
    }

    /**
     * Get the android debug tool path (adb).
     *
     * @return path to {@code adb} command
     */
    public String getAdbPath() {
        return getPathForTool("adb");
    }

    public String getAaptPath() {
        return getBuildTool("aapt");
    }

    /**
     * Get the android tool path
     *
     * @return path to {@code android} command
     */
    public String getAndroidPath() {
        return getPathForTool("android");
    }

    /**
     * Returns the complete path for framework.aidl, based on this SDK.
     *
     * @return the complete path as a String, including the filename.
     * @throws AndroidConfigurationException
     */
    public String getPathForFrameworkAidl() throws AndroidContainerConfigurationException {
        final Layout layout = getLayout();
        switch (layout) {
            case LAYOUT_1_5: // intentional fall-through
            case LAYOUT_2_3:
                return getPlatform() + "/framework.aidl";
            default:
                throw new AndroidContainerConfigurationException("Unsupported layout \"" + layout + "\"!");
        }
    }

    public File getPlatform() {
        Validate.isReadableDirectory(sdkPath, "Unable to read Android SDK from directory " + sdkPath);

        final File platformsDirectory = new File(sdkPath, PLATFORMS_FOLDER_NAME);
        Validate.isReadableDirectory(platformsDirectory, "Unable to read Android SDK Platforms directory from directory "
                + platformsDirectory);

        if (platform == null) {
            final File[] platformDirectories = platformsDirectory.listFiles();
            Arrays.sort(platformDirectories);
            return platformDirectories[platformDirectories.length - 1];
        } else {
            final File platformDirectory = new File(platform.path);
            Validate.isReadableDirectory(platformsDirectory, "Unable to read Android SDK Platforms directory from directory "
                    + platformsDirectory);
            return platformDirectory;
        }
    }

    /**
     * Initialize the maps matching platform and api levels form the source properties files.
     *
     * @throws AndroidConfigurationException
     *
     */
    private List findAvailablePlatforms() throws AndroidContainerConfigurationException {
        List availablePlatforms = new ArrayList();

        List platformDirectories = getPlatformDirectories();
        for (File pDir : platformDirectories) {
            File propFile = new File(pDir, SOURCE_PROPERTIES_FILENAME);
            Properties properties = new Properties();
            try {
                properties.load(new FileInputStream(propFile));
            } catch (IOException e) {
                throw new AndroidContainerConfigurationException(
                        "Unable to read platform directory details from its configuration file " + propFile.getAbsoluteFile());
            }
            if (properties.containsKey(PLATFORM_VERSION_PROPERTY) && properties.containsKey(API_LEVEL_PROPERTY)) {
                String platform = properties.getProperty(PLATFORM_VERSION_PROPERTY);
                String apiLevel = properties.getProperty(API_LEVEL_PROPERTY);
                List systemImages = getSystemImages("android-" + apiLevel);

                Platform p = new Platform(platform, apiLevel, pDir.getAbsolutePath(), systemImages);
                availablePlatforms.add(p);
                if (logger.isLoggable(Level.FINE)) {
                    logger.fine("Found available platform: " + p.toString());
                }
            }
        }

        Collections.sort(availablePlatforms);
        return availablePlatforms;
    }

    /**
     * Gets the source properties files from all locally installed platforms.
     *
     * @return list of platform directories
     */
    private List getPlatformDirectories() {
        List sourcePropertyFiles = new ArrayList();

        final File platformsDirectory = new File(sdkPath, PLATFORMS_FOLDER_NAME);
        Validate.isReadableDirectory(platformsDirectory, "Unable to read Android SDK Platforms directory from directory "
                + platformsDirectory);

        final File[] platformDirectories = platformsDirectory.listFiles();
        for (File file : platformDirectories) {
            // only looking in android- folder so only works on reasonably new
            // sdk revisions..
            if (file.isDirectory() && file.getName().startsWith("android-")) {
                sourcePropertyFiles.add(file);
            }
        }
        return sourcePropertyFiles;
    }

    private List getSystemImageDirectories(String systemImageDirName) {

        List systemImages = new ArrayList();

        File dir = new File(new File(sdkPath, SYSTEM_IMAGES_FOLDER_NAME), systemImageDirName);

        if (!dir.exists()) {
            return systemImages;
        }

        for (File file : dir.listFiles()) {
            if (file.isDirectory()) {
                systemImages.add(file);
            }
        }

        return systemImages;
    }

    private List getSystemImages(String systemImageDirName) {

        List systemImageDirs = getSystemImageDirectories(systemImageDirName);

        List images = new ArrayList();

        for (File dir : systemImageDirs) {
            images.add(dir.getName());
        }

        Collections.sort(images);
        Collections.reverse(images); // to have x86 as the first one

        return images;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy