├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ ├── cd.yaml │ └── jenkins-security-scan.yml ├── .gitignore ├── .mvn ├── maven.config └── extensions.xml ├── src ├── main │ ├── webapp │ │ ├── help-screenDensity.html │ │ ├── help-screenResolution.html │ │ ├── icons │ │ │ ├── monkey-sad_48x48.png │ │ │ └── monkey-happy_48x48.png │ │ ├── help-failOnInstallFailure.html │ │ ├── help-emulatorNamed.html │ │ ├── help-deviceLocale.html │ │ ├── help-failOnUninstallFailure.html │ │ ├── help-uninstallFirst.html │ │ ├── help-deviceDefinition.html │ │ ├── help-snapshotLoad.html │ │ ├── help-snapshotSave.html │ │ ├── help-keepInWorkspace.html │ │ ├── help-installPackage.html │ │ ├── help-uninstallPackage.html │ │ ├── help-targetAbi.html │ │ ├── help-osVersion.html │ │ ├── help-buildConfig.html │ │ ├── help-createBuildFiles.html │ │ ├── help-publishMonkeyOutput.html │ │ ├── help-sdCard.html │ │ ├── help-runMonkey.html │ │ ├── help-installPrerequisites.html │ │ ├── help-installSdk.html │ │ ├── help-avdNameSuffix.html │ │ ├── help-hardware.html │ │ ├── help-sdkRoot.html │ │ └── help-emulatorCustom.html │ ├── resources │ │ ├── index.jelly │ │ ├── hudson │ │ │ └── plugins │ │ │ │ └── android_emulator │ │ │ │ ├── snapshot │ │ │ │ ├── SnapshotLoadBuilder │ │ │ │ │ └── config.jelly │ │ │ │ └── SnapshotSaveBuilder │ │ │ │ │ └── config.jelly │ │ │ │ ├── AndroidEmulator │ │ │ │ ├── help-executable.html │ │ │ │ ├── help-adbTimeout.html │ │ │ │ ├── help-startupTimeout.html │ │ │ │ ├── help-startupDelay.html │ │ │ │ ├── help-deleteAfterBuild.html │ │ │ │ ├── help-avdName.html │ │ │ │ ├── help-wipeData.html │ │ │ │ ├── help-showWindow.html │ │ │ │ ├── help-useSnapshots.html │ │ │ │ ├── help-commandLineOptions.html │ │ │ │ └── global.jelly │ │ │ │ ├── monkey │ │ │ │ ├── MonkeyAction │ │ │ │ │ └── summary.jelly │ │ │ │ ├── MonkeyRecorder │ │ │ │ │ └── config.jelly │ │ │ │ └── MonkeyBuilder │ │ │ │ │ └── config.jelly │ │ │ │ ├── UninstallBuilder │ │ │ │ └── config.jelly │ │ │ │ └── InstallBuilder │ │ │ │ └── config.jelly │ │ ├── jenkins.plugin.android.emulator.tools.AndroidSDKInstaller.json │ │ └── jenkins │ │ │ └── plugin │ │ │ └── android │ │ │ └── emulator │ │ │ ├── HardwareProperty │ │ │ ├── config.properties │ │ │ └── config.jelly │ │ │ ├── tools │ │ │ └── AndroidSDKInstaller │ │ │ │ ├── config.properties │ │ │ │ └── config.jelly │ │ │ ├── sdk │ │ │ └── pipeline │ │ │ │ ├── Messages.properties │ │ │ │ ├── ADBStep │ │ │ │ └── config.jelly │ │ │ │ ├── AVDManagerStep │ │ │ │ └── config.jelly │ │ │ │ ├── EmulatorStep │ │ │ │ └── config.jelly │ │ │ │ ├── SDKManagerStep │ │ │ │ └── config.jelly │ │ │ │ └── AbstractCLIStep │ │ │ │ ├── config.properties │ │ │ │ └── config.jelly │ │ │ ├── AndroidEmulatorBuild │ │ │ ├── config.properties │ │ │ └── config.jelly │ │ │ └── Messages.properties │ └── java │ │ ├── hudson │ │ └── plugins │ │ │ └── android_emulator │ │ │ ├── sdk │ │ │ ├── DefaultToolLocator.java │ │ │ ├── PlatformToolLocator.java │ │ │ ├── SdkToolLocator.java │ │ │ ├── EmulatorToolLocator.java │ │ │ ├── cli │ │ │ │ ├── AdbShellCommand04To22.java │ │ │ │ ├── AdbShellCommand00To03.java │ │ │ │ ├── SdkToolsCommands00To16.java │ │ │ │ ├── SdkCliCommand.java │ │ │ │ ├── AdbShellCommands.java │ │ │ │ ├── SdkToolsCommands.java │ │ │ │ ├── SdkCliCommandFactory.java │ │ │ │ ├── SdkToolsCommands17To25_2.java │ │ │ │ └── AdbShellCommandsCurrentBase.java │ │ │ ├── ToolLocator.java │ │ │ └── Tool.java │ │ │ ├── BuildNodeUnavailableException.java │ │ │ ├── SdkInstallationException.java │ │ │ ├── monkey │ │ │ ├── MonkeyResult.java │ │ │ ├── BuildOutcome.java │ │ │ └── MonkeyAction.java │ │ │ ├── AndroidEmulatorException.java │ │ │ ├── snapshot │ │ │ ├── SnapshotLoadBuilder.java │ │ │ ├── SnapshotSaveBuilder.java │ │ │ └── AbstractSnapshotBuilder.java │ │ │ ├── util │ │ │ └── ValidationResult.java │ │ │ ├── ReceiveEmulatorPortTask.java │ │ │ ├── ScreenDensity.java │ │ │ ├── ScreenResolution.java │ │ │ ├── UninstallBuilder.java │ │ │ ├── builder │ │ │ └── ProjectPrerequisitesInstaller.java │ │ │ └── TaskDispatcher.java │ │ └── jenkins │ │ └── plugin │ │ └── android │ │ └── emulator │ │ ├── sdk │ │ ├── home │ │ │ ├── HomeLocatorDescriptor.java │ │ │ ├── PerJobHomeLocator.java │ │ │ ├── DefaultHomeLocator.java │ │ │ ├── PerExecutorHomeLocator.java │ │ │ └── HomeLocator.java │ │ ├── pipeline │ │ │ ├── AbstractCLIStep.java │ │ │ ├── AbstractCLIStepExecution.java │ │ │ ├── ADBStep.java │ │ │ ├── AVDManagerStep.java │ │ │ ├── EmulatorStep.java │ │ │ └── SDKManagerStep.java │ │ └── cli │ │ │ ├── AVDevice.java │ │ │ ├── Targets.java │ │ │ ├── SDKPackages.java │ │ │ ├── ADBCLIBuilder.java │ │ │ └── CLICommand.java │ │ ├── tools │ │ ├── DetectionFailedException.java │ │ ├── Platform.java │ │ └── ToolLocator.java │ │ ├── AndroidSDKConstants.java │ │ ├── HardwareProperty.java │ │ └── AndroidSDKUtil.java └── test │ ├── resources │ └── jenkins │ │ └── plugin │ │ └── android │ │ └── emulator │ │ └── sdk │ │ └── cli │ │ └── avdmanager_list_target.out │ └── java │ ├── hudson │ └── plugins │ │ └── android_emulator │ │ ├── sdk │ │ ├── cli │ │ │ └── SdkCommandsTestData.java │ │ └── AndroidSdkTest.java │ │ └── EmulatorConfigTest.java │ └── jenkins │ └── plugin │ └── android │ └── emulator │ └── sdk │ └── cli │ ├── AVDManagerCLIBuilderTest.java │ └── SDKManagerCLIBuilderTest.java ├── docs └── images │ ├── android_artifacts.png │ ├── android_job-named.png │ ├── android_job-custom.png │ ├── android_matrix-axes.png │ ├── android_monkey-run.png │ ├── android_global-config.png │ ├── android_job-variables.png │ ├── android_matrix-result.png │ ├── android_monkey-publish.png │ ├── android_monkey-result.png │ └── android_install-package.png ├── .gitpod.yml ├── Jenkinsfile ├── NOTES └── pom.xml /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @jenkinsci/android-emulator-plugin-developers 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /work 3 | .DS_Store 4 | .settings/ 5 | .classpath 6 | .project 7 | .idea/ 8 | -------------------------------------------------------------------------------- /.mvn/maven.config: -------------------------------------------------------------------------------- 1 | -Pconsume-incrementals 2 | -Pmight-produce-incrementals 3 | -Dchangelist.format=%d.v%s 4 | -------------------------------------------------------------------------------- /src/main/webapp/help-screenDensity.html: -------------------------------------------------------------------------------- 1 | Density in dots-per-inch (dpi) or as an alias, e.g. "160" or "mdpi". 2 | -------------------------------------------------------------------------------- /docs/images/android_artifacts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/android-emulator-plugin/master/docs/images/android_artifacts.png -------------------------------------------------------------------------------- /docs/images/android_job-named.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/android-emulator-plugin/master/docs/images/android_job-named.png -------------------------------------------------------------------------------- /docs/images/android_job-custom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/android-emulator-plugin/master/docs/images/android_job-custom.png -------------------------------------------------------------------------------- /docs/images/android_matrix-axes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/android-emulator-plugin/master/docs/images/android_matrix-axes.png -------------------------------------------------------------------------------- /docs/images/android_monkey-run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/android-emulator-plugin/master/docs/images/android_monkey-run.png -------------------------------------------------------------------------------- /src/main/webapp/help-screenResolution.html: -------------------------------------------------------------------------------- 1 | Can be either a named resolution or explicit size, e.g. "WVGA" or "480x800". 2 | -------------------------------------------------------------------------------- /docs/images/android_global-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/android-emulator-plugin/master/docs/images/android_global-config.png -------------------------------------------------------------------------------- /docs/images/android_job-variables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/android-emulator-plugin/master/docs/images/android_job-variables.png -------------------------------------------------------------------------------- /docs/images/android_matrix-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/android-emulator-plugin/master/docs/images/android_matrix-result.png -------------------------------------------------------------------------------- /docs/images/android_monkey-publish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/android-emulator-plugin/master/docs/images/android_monkey-publish.png -------------------------------------------------------------------------------- /docs/images/android_monkey-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/android-emulator-plugin/master/docs/images/android_monkey-result.png -------------------------------------------------------------------------------- /docs/images/android_install-package.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/android-emulator-plugin/master/docs/images/android_install-package.png -------------------------------------------------------------------------------- /src/main/webapp/icons/monkey-sad_48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/android-emulator-plugin/master/src/main/webapp/icons/monkey-sad_48x48.png -------------------------------------------------------------------------------- /src/main/webapp/icons/monkey-happy_48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/android-emulator-plugin/master/src/main/webapp/icons/monkey-happy_48x48.png -------------------------------------------------------------------------------- /src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 | 2 |
3 | Starts an Android emulator with given properties before a build, then shuts it down after. 4 |
5 | -------------------------------------------------------------------------------- /src/main/webapp/help-failOnInstallFailure.html: -------------------------------------------------------------------------------- 1 | If the given APK fails to be installed (i.e. the Android package manager does not return "Success") 2 | and this option is enabled, then the build will be marked as failed. -------------------------------------------------------------------------------- /src/main/webapp/help-emulatorNamed.html: -------------------------------------------------------------------------------- 1 | Starts an existing emulator — typically created using Android's AVD Manager tool, either 2 | by launching it from Eclipse, or using the android command line. 3 | -------------------------------------------------------------------------------- /src/main/webapp/help-deviceLocale.html: -------------------------------------------------------------------------------- 1 | Must be a language and country pair that will exist on the emulator version being run.
2 | The values correspond to ISO 639-1 and ISO 3166 language and country codes, respectively. 3 | -------------------------------------------------------------------------------- /src/main/webapp/help-failOnUninstallFailure.html: -------------------------------------------------------------------------------- 1 | If the given package ID fails to be uninstalled (i.e. the Android package manager does not return 2 | "Success") and this option is enabled, then the build will be marked as failed. -------------------------------------------------------------------------------- /src/main/webapp/help-uninstallFirst.html: -------------------------------------------------------------------------------- 1 | If this option is checked, any existing instance of the given Android package 2 | will be removed from the device in question, before installation occurs. 3 |

4 | The device affected is chosen using the rules explained in the help section above. 5 |

6 | -------------------------------------------------------------------------------- /src/test/resources/jenkins/plugin/android/emulator/sdk/cli/avdmanager_list_target.out: -------------------------------------------------------------------------------- 1 | Available Android targets:==============] 100% Fetch remote repository... 2 | ---------- 3 | id: 1 or "android-21" 4 | Name: Android API 21 5 | Type: Platform 6 | API level: 21 7 | Revision: 2 8 | -------------------------------------------------------------------------------- /src/main/webapp/help-deviceDefinition.html: -------------------------------------------------------------------------------- 1 | SDK Tools ≥ 25.3 only
2 | Defines the optional device definition to use for the Android Virtual Device. The available list can be retrieved via 'avdmanager list device'.
3 | If the SDK Tools < 25.3 are used, the value is ignored. 4 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/sdk/DefaultToolLocator.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator.sdk; 2 | 3 | public class DefaultToolLocator implements ToolLocator { 4 | @Override 5 | public String findInSdk(final boolean useLegacySdkStructure) { 6 | return ToolLocator.TOOLS_DIR; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/sdk/PlatformToolLocator.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator.sdk; 2 | 3 | public class PlatformToolLocator implements ToolLocator { 4 | @Override 5 | public String findInSdk(final boolean useLegacySdkStructure) { 6 | return ToolLocator.PLATFORM_TOOLS_DIR; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | tasks: 2 | - init: mvn clean verify 3 | 4 | vscode: 5 | extensions: 6 | - bierner.markdown-preview-github-styles 7 | - vscjava.vscode-java-pack 8 | - redhat.java 9 | - vscjava.vscode-java-debug 10 | - vscjava.vscode-java-dependency 11 | - vscjava.vscode-java-test 12 | - vscjava.vscode-maven 13 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/android_emulator/snapshot/SnapshotLoadBuilder/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/android_emulator/snapshot/SnapshotSaveBuilder/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/webapp/help-snapshotLoad.html: -------------------------------------------------------------------------------- 1 | Loads the named snapshot into an Android emulator. 2 |

3 | If an Android emulator was started automatically via the "Run an Android emulator during build" 4 | option above, that device will be used. Otherwise, it is assumed that there is an Android emulator 5 | running on port 5554 (the default). 6 |

7 | -------------------------------------------------------------------------------- /src/main/webapp/help-snapshotSave.html: -------------------------------------------------------------------------------- 1 | Saves the current state of an Android emulator to the named snapshot. 2 |

3 | If an Android emulator was started automatically via the "Run an Android emulator during build" 4 | option above, that device will be used. Otherwise, it is assumed that there is an Android emulator 5 | running on port 5554 (the default). 6 |

7 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/android_emulator/AndroidEmulator/help-executable.html: -------------------------------------------------------------------------------- 1 |
2 | The emulator executable. Changing the default should not be necessary, but there are some bugs in the SDK 3 | which might prevent the default from working, see 4 | Issue 34233. 5 |
6 | 7 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/android_emulator/AndroidEmulator/help-adbTimeout.html: -------------------------------------------------------------------------------- 1 | This determines how long the Android Emulator plugin should wait for ADB to become available before starting the Emulator. 2 |

3 | This is useful, for example, if starting the ADB takes some considerable time - for instance on the slow systems. 4 | By default the plugin waits for 60 seconds.
5 |

-------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: maven 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | open-pull-requests-limit: 10 8 | target-branch: master 9 | reviewers: 10 | - orrc 11 | - nfalco79 12 | labels: 13 | - skip-changelog 14 | - package-ecosystem: github-actions 15 | directory: / 16 | schedule: 17 | interval: monthly 18 | 19 | -------------------------------------------------------------------------------- /src/main/webapp/help-keepInWorkspace.html: -------------------------------------------------------------------------------- 1 | With this option enabled, all the Android emulators will be kept in a workspace-specific directory. 2 | This has a few implications: 3 | 4 | 9 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/android_emulator/monkey/MonkeyAction/summary.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ${it.summary} 6 | 7 | 8 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | /* 2 | See the documentation for more options: 3 | https://github.com/jenkins-infra/pipeline-library/ 4 | */ 5 | buildPlugin( 6 | useContainerAgent: false, 7 | forkCount: '1C', // Set to `false` if you need to use Docker for containerized tests 8 | configurations: [ 9 | [platform: 'arm64linux', jdk: 21], 10 | [platform: 'windows', jdk: 17], 11 | [platform: 'linux', jdk: 25], 12 | ]) 13 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/BuildNodeUnavailableException.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator; 2 | 3 | import java.io.IOException; 4 | 5 | public class BuildNodeUnavailableException extends IOException { 6 | 7 | public BuildNodeUnavailableException() { 8 | super(Messages.NODE_UNAVAILABLE_EXCEPTION()); 9 | } 10 | 11 | private static final long serialVersionUID = 1L; 12 | 13 | } -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/sdk/SdkToolLocator.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator.sdk; 2 | 3 | public class SdkToolLocator implements ToolLocator { 4 | @Override 5 | public String findInSdk(final boolean useLegacySdkStructure) { 6 | if (!useLegacySdkStructure) { 7 | return ToolLocator.CMD_TOOLS_BIN_DIR; 8 | } 9 | return ToolLocator.TOOLS_BIN_DIR; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.mvn/extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | io.jenkins.tools.incrementals 4 | git-changelist-maven-extension 5 | 1.13 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/sdk/EmulatorToolLocator.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator.sdk; 2 | 3 | public class EmulatorToolLocator implements ToolLocator { 4 | @Override 5 | public String findInSdk(final boolean useLegacySdkStructure) { 6 | if (!useLegacySdkStructure) { 7 | return ToolLocator.EMULATOR_DIR; 8 | } else { 9 | return ToolLocator.TOOLS_DIR; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/SdkInstallationException.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator; 2 | 3 | public final class SdkInstallationException extends AndroidEmulatorException { 4 | 5 | public SdkInstallationException(String message) { 6 | super(message); 7 | } 8 | 9 | SdkInstallationException(String message, Throwable cause) { 10 | super(message, cause); 11 | } 12 | 13 | private static final long serialVersionUID = 1L; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/android_emulator/AndroidEmulator/help-startupTimeout.html: -------------------------------------------------------------------------------- 1 | This determines how long the Android Emulator plugin should wait for the emulator to start at the beginning of a build. 2 |

3 | This is useful, for example, if starting the emulator takes some considerable time - for instance if you are unable to use hardware acceleration. 4 | By default the plugin waits for 360 seconds. This value is doubled to 720 seconds for first time boots without an initial snapshot.
5 |

6 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/monkey/MonkeyResult.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator.monkey; 2 | 3 | enum MonkeyResult { 4 | /** Monkey test completed successfully */ 5 | Success, 6 | /** Application crashed while under test */ 7 | Crash, 8 | /** ANR occurred while under test */ 9 | AppNotResponding, 10 | /** No monkey output was found to parse */ 11 | NothingToParse, 12 | /** Monkey output was given, but outcome couldn't be determined */ 13 | UnrecognisedFormat 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/cd.yaml: -------------------------------------------------------------------------------- 1 | # Note: additional setup is required, see https://www.jenkins.io/redirect/continuous-delivery-of-plugins 2 | 3 | name: cd 4 | on: 5 | workflow_dispatch: 6 | check_run: 7 | types: 8 | - completed 9 | 10 | permissions: 11 | checks: read 12 | contents: write 13 | 14 | jobs: 15 | maven-cd: 16 | uses: jenkins-infra/github-reusable-workflows/.github/workflows/maven-cd.yml@v1 17 | secrets: 18 | MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} 19 | MAVEN_TOKEN: ${{ secrets.MAVEN_TOKEN }} 20 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/android_emulator/AndroidEmulator/help-startupDelay.html: -------------------------------------------------------------------------------- 1 | This determines how long the Android Emulator plugin should delay for, at the start of a build, before attempting to launch the emulator. 2 |

3 | This is useful, for example, if starting the emulator depends on other Build Environment plugins that need time to fully start up.
4 | e.g. If you find that starting a VNC server using the Xvnc plugin can take 30 seconds — due to slow hardware, or a large X startup script — you would enter 30 (or more) here. 5 |

6 | -------------------------------------------------------------------------------- /src/main/webapp/help-installPackage.html: -------------------------------------------------------------------------------- 1 | Installs the given Android package (APK) file onto an Android emulator or device. 2 |

3 | If an Android emulator was started automatically via the "Run an Android emulator 4 | during build" option above, the APK will be installed onto that device. 5 |

6 |

7 | Otherwise, the APK will be installed onto an emulator or USB-attached Android device.
8 | If more than one emulator or device is present, this step will currently hang until 9 | only one emulator or device is available — this is the default Android SDK behaviour. 10 |

11 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/android_emulator/AndroidEmulator/help-deleteAfterBuild.html: -------------------------------------------------------------------------------- 1 |

2 | If this option is selected, the Android emulator being run will be deleted when the build ends.
3 | This means permanantly erasing all of its files, snapshots and metadata from disk on the current 4 | build machine. 5 |

6 | This happens in all cases, regardless of: 7 | -------------------------------------------------------------------------------- /src/main/webapp/help-uninstallPackage.html: -------------------------------------------------------------------------------- 1 | Uninstalls the given Android package (APK) file from an Android emulator or device. 2 |

3 | If an Android emulator was started automatically via the "Run an Android emulator 4 | during build" option above, the APK will be uninstalled from that device. 5 |

6 |

7 | Otherwise, the APK will be uninstalled from an emulator or USB-attached Android device.
8 | If more than one emulator or device is present, this step will currently hang until 9 | only one emulator or device is available — this is the default Android SDK behaviour. 10 |

11 | -------------------------------------------------------------------------------- /src/main/webapp/help-targetAbi.html: -------------------------------------------------------------------------------- 1 | Should be the name of the ABI / system image to be used, e.g "armeabi" or "x86".
2 | Additionally a tag can be specified via <Tag>/<ABI>, e.g "google_apis/x86_64".
3 | SDK Tools ≤ 25.2
4 | If empty the default ABI for the select platform will be used. You only need to define this field if you have 5 | more than one system image per Android platform installed.
6 | SDK Tools ≥ 25.3
7 | If AVD is created via this plugin, the value is required to run 'avdmanager create avd'. 8 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/monkey/BuildOutcome.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator.monkey; 2 | 3 | import hudson.plugins.android_emulator.Messages; 4 | 5 | public enum BuildOutcome { 6 | 7 | UNSTABLE(Messages.BUILD_RESULT_UNSTABLE()), 8 | FAILURE(Messages.BUILD_RESULT_FAILURE()), 9 | IGNORE(Messages.BUILD_RESULT_IGNORE()); 10 | 11 | private final String displayName; 12 | 13 | private BuildOutcome(String displayName) { 14 | this.displayName = displayName; 15 | } 16 | 17 | public String getDisplayName() { 18 | return displayName; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/android_emulator/AndroidEmulator/help-avdName.html: -------------------------------------------------------------------------------- 1 |
2 | The names of existing Android emulator configurations (AVDs) can be seen by running the 3 | following command from the Android SDK: android list avd. 4 |

5 | The AVD must already exist at build time on the machine the build will be executed on — 6 | the emulator configuration will not be automatically created or copied from another machine. 7 |

8 | You can also do environment variable substitution for this setting, in the format: 9 | ${VARIABLE_NAME} or $VARIABLE_NAME. 10 |
11 | -------------------------------------------------------------------------------- /src/main/webapp/help-osVersion.html: -------------------------------------------------------------------------------- 1 | Can be an OS version, target name or SDK add-on, e.g. "2.1" or "android-7".
2 | For example, to use the Google APIs add-on to get an Android 2.3 emulator with Google Maps support, enter "Google Inc.:Google APIs:9".
3 |
4 | The available targets in your installation of the Android SDK can be listed with the command: android list target 5 | — the values shown within double quotes can be entered here. 6 |

7 | While common values will be auto-completed when you start typing here, you are not bound to using these values, as shown by the "Google APIs" example. 8 |

9 | -------------------------------------------------------------------------------- /src/main/webapp/help-buildConfig.html: -------------------------------------------------------------------------------- 1 |
2 | Select this option if you wish to automatically start an Android emulator of your choice before 3 | the build steps execute, with the emulator being stopped after building is complete. 4 |

5 | You can choose to start a pre-defined, existing Android emulator instance (also known as an 6 | Android Virtual Device, or AVD).
7 | Alternatively, the plugin can automatically create a new emulator on the build slave with 8 | properties you specify here. 9 |

10 | In any case, the "logcat" output will automatically be captured and archived. 11 |
12 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/sdk/cli/AdbShellCommand04To22.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator.sdk.cli; 2 | 3 | import hudson.plugins.android_emulator.constants.AndroidKeyEvent; 4 | 5 | /** 6 | * Extends {@code AdbShellCommandsCurrentBase} and simply overwrites the commands 7 | * which differ for devices running on API-level 4 to 22. 8 | */ 9 | public class AdbShellCommand04To22 extends AdbShellCommandsCurrentBase implements AdbShellCommands { 10 | 11 | @Override 12 | public SdkCliCommand getDismissKeyguardCommand(String deviceSerial) { 13 | return getSendKeyEventCommand(deviceSerial, AndroidKeyEvent.KEYCODE_MENU); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/android_emulator/AndroidEmulator/help-wipeData.html: -------------------------------------------------------------------------------- 1 |

2 | If this option is selected, the emulator will have its user data reset at start-up, as if the emulator was newly-created.
3 | Any modifications made to the system partition will remain untouched, as well as any SD cards and their contents that may exist.
4 | This is equivalent to running the Android emulator command with the -wipe-data option. 5 |

6 | Note: This option can add anywhere from 30 seconds to five minutes to the build startup time, depending on the build slave's CPU speed.
7 | Note: This option will be ignored if "Use emulator snapshots" is enabled. 8 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/android_emulator/AndroidEmulator/help-showWindow.html: -------------------------------------------------------------------------------- 1 | If this option is selected, the Android emulator user interface will be displayed on screen during the build.
2 | Untick this option if you do not want the emulator to be visible while it runs. 3 |

4 | Disabling this is useful, for example, on Linux machines that do not have a graphical user interface.
5 | If you do require the emulator to be displayed on such machines, you can install and enable the Xvnc plugin.
6 | With Xvnc enabled for the build, you can keep this option selected and the emulator will be launched in a VNC session. 7 | -------------------------------------------------------------------------------- /.github/workflows/jenkins-security-scan.yml: -------------------------------------------------------------------------------- 1 | name: Jenkins Security Scan 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | types: [ opened, synchronize, reopened ] 9 | workflow_dispatch: 10 | 11 | permissions: 12 | security-events: write 13 | contents: read 14 | actions: read 15 | 16 | jobs: 17 | security-scan: 18 | uses: jenkins-infra/jenkins-security-scan/.github/workflows/jenkins-security-scan.yaml@v2 19 | with: 20 | java-cache: 'maven' # Optionally enable use of a build dependency cache. Specify 'maven' or 'gradle' as appropriate. 21 | # java-version: 21 # Optionally specify what version of Java to set up for the build, or remove to use a recent default. 22 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/sdk/cli/AdbShellCommand00To03.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator.sdk.cli; 2 | 3 | /** 4 | * Extends {@code AdbShellCommands04To22} and simply overwrites the commands 5 | * which differ for devices running on API-level 3 and below. 6 | */ 7 | public class AdbShellCommand00To03 extends AdbShellCommand04To22 implements AdbShellCommands { 8 | 9 | @Override 10 | public SdkCliCommand getWaitForDeviceStartupCommand(final String deviceSerial) { 11 | return getAdbShellCommand(deviceSerial, true, "getprop dev.bootcomplete"); 12 | } 13 | 14 | @Override 15 | public String getWaitForDeviceStartupExpectedAnswer() { 16 | return "1"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/sdk/cli/SdkToolsCommands00To16.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator.sdk.cli; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Extends {@code SdkToolsCommandsCurrentBase} and simply overwrites the commands 7 | * which differ for SDK with major version prior 17. 8 | */ 9 | public class SdkToolsCommands00To16 extends SdkToolsCommands17To25_2 implements SdkToolsCommands { 10 | 11 | @Override 12 | public SdkCliCommand getSdkInstallAndUpdateCommand(final String proxySettings, final List components) { 13 | final SdkCliCommand cmd = super.getSdkInstallAndUpdateCommand(proxySettings, components); 14 | return new SdkCliCommand(cmd.getTool(), cmd.getArgs().replaceFirst("^update sdk -u -a", "update sdk -u -o")); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/webapp/help-createBuildFiles.html: -------------------------------------------------------------------------------- 1 | Note: This only works for projects using the deprecated Ant build system for Android. 2 |


3 | Creates or updates Ant build files for the Android apps, libraries and test projects in this job's 4 | workspace. 5 |

6 | This build step will search for Android projects in your workspace, ensure that the required 7 | platform(s) are installed, and then ensures that build files are in place by using the appropriate 8 | android update project command. 9 |

10 |

11 | Creating the build files for a test projects requires that the relative path to the respective app 12 | project is specified. This build step tries to do the right thing by selecting the app project which 13 | is nearest to the test project in the directory hierarchy. 14 |

15 | -------------------------------------------------------------------------------- /src/main/webapp/help-publishMonkeyOutput.html: -------------------------------------------------------------------------------- 1 | Selecting this option will examine the output of the Android 2 | Application Exerciser 3 | Monkey tool and publish a summary to the build page.
4 | If it is determined that monkey crashed the application or a not-responding situation 5 | occurred, the build will be marked as unstable, unless another "Set build result" option is chosen. 6 |

7 | Unless a filename is entered below, the output will be read from a file called "monkey.txt" 8 | in the root of the build workspace. 9 |

10 | To run monkey automatically on an Android emulator or device and write the results to a 11 | file, select "Add build step" above and choose "Run Android monkey tester". -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/android_emulator/monkey/MonkeyRecorder/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ${%Optional: Name of a file within the workspace to read monkey output from. Defaults to "monkey.txt" in the root of the workspace} 7 | 8 | 9 | 10 | ${it.displayName} 11 | ${%Sets the result of the build to this value if monkey caused a crash or ANR} 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/webapp/help-sdCard.html: -------------------------------------------------------------------------------- 1 | Should be a numeric value followed by the suffix 'K' or 'M', for kilobytes or megabytes, respectively.
2 | e.g. "32M" or "10240K" 3 |

4 | For emulators that do not yet exist:
5 | If this field is left blank, no SD card will be created when the emulator is.
6 | If a value is set, an SD card of the given size will be associated with the newly-created emulator. 7 |

8 |

9 | For emulators that have been created by past builds:
10 | If this field is left blank, any existing SD card will remain; it will not be deleted.
11 | If a value is set, and the emulator does not have an SD card associated, one will be created.
12 | If a value is set, and the emulator already has an SD card defined, the existing SD card's contents or size will not be changed. 13 |

14 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/sdk/cli/SdkCliCommand.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator.sdk.cli; 2 | 3 | import hudson.plugins.android_emulator.sdk.Tool; 4 | 5 | /** 6 | * Represents a SDK command-line-interface command by a given 7 | * {@code Tool} and by it's arguments. 8 | */ 9 | public class SdkCliCommand { 10 | private Tool tool; 11 | private String args; 12 | 13 | public SdkCliCommand(final Tool tool, final String args) { 14 | this.tool = tool; 15 | this.args = args; 16 | } 17 | 18 | public Tool getTool() { 19 | return tool; 20 | } 21 | 22 | public String getArgs() { 23 | return args; 24 | } 25 | 26 | public boolean isNoopCmd() { 27 | return (tool == null); 28 | } 29 | 30 | public static SdkCliCommand createNoopCommand() { 31 | return new SdkCliCommand(null, ""); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/android_emulator/UninstallBuilder/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | ${%ID of the Android package to be uninstalled} 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/test/java/hudson/plugins/android_emulator/sdk/cli/SdkCommandsTestData.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator.sdk.cli; 2 | 3 | public interface SdkCommandsTestData { 4 | String LIST_TARGETS_LEGACY_OUTPUT = """ 5 | Available Android targets: 6 | ---------- 7 | id: 1 or "android-23" 8 | Name: Android 6.0 9 | Type: Platform 10 | API level: 23 11 | Revision: 3 12 | Skins: HVGA, QVGA, WQVGA400, WQVGA432, WSVGA, WVGA800 (default), WVGA854, WXGA720, WXGA800, WXGA800-7in 13 | Tag/ABIs : no ABIs. 14 | ---------- 15 | id: 2 or "android-24" 16 | Name: Android 7.0 17 | Type: Platform 18 | API level: 24 19 | Revision: 2 20 | Skins: HVGA, QVGA, WQVGA400, WQVGA432, WSVGA, WVGA800 (default), WVGA854, WXGA720, WXGA800, WXGA800-7in 21 | Tag/ABIs : default/x86_64"""; 22 | } 23 | -------------------------------------------------------------------------------- /src/main/webapp/help-runMonkey.html: -------------------------------------------------------------------------------- 1 | Runs the Android Application 2 | Exerciser Monkey tool on an Android emulator or device. 3 |

4 | Unless a filename is entered under the "Advanced…" section, the monkey results will 5 | be written to a file named "monkey.txt" in the root of the workspace.
6 | Selecting the "Publish Android monkey tester results" option below can examine this file 7 | at the end of the build and publish a summary on the build page. 8 |

9 |

10 | If an Android emulator was started automatically via the "Run an Android emulator 11 | during build" option above, monkey will be run on that device. 12 |

13 |

14 | Otherwise, the tool will be run on an emulator or USB-attached Android device.
15 | If more than one emulator or device is present, this step will currently hang until 16 | only one emulator or device is available — this is the default Android SDK behaviour. 17 |

18 | -------------------------------------------------------------------------------- /src/main/webapp/help-installPrerequisites.html: -------------------------------------------------------------------------------- 1 | Note: This only works for projects using the deprecated Ant build system for Android.
2 | For Gradle projects, consider adding the Android SDK Manager plugin to your project. 4 |
5 | Installs any platforms required to build the Android apps in this job's workspace. 6 |

7 | In an Android app project, test project or library project built with Ant, the platform version to 8 | build against must be specified in either the project.properties or 9 | default.properties file, by setting the target parameter. 10 |

11 |

12 | This build step will search for these files in your workspace, determine which target platform(s) 13 | are required, and then ensure that they are installed. If the required platforms are already 14 | installed, no action is taken. 15 |
16 | Therefore it should always be safe to run this build step before any build steps which compile an 17 | Android project. 18 |

19 | -------------------------------------------------------------------------------- /src/main/webapp/help-installSdk.html: -------------------------------------------------------------------------------- 1 | With this option enabled, the Android SDK will be automatically downloaded, installed and configured 2 | on any machine where builds occur with the "Run an Android emulator during build" option enabled. 3 |

4 | The SDK will be downloaded on-demand, the first time a job is built which requires it.
5 | The SDK will be installed to $JENKINS_HOME/tools/android-sdk on the build machine.
6 | If a working Android SDK is found on such a machine, the SDK will not be downloaded or installed. 7 |

8 |

9 | Similarly, any Android prerequisites required to create a certain emulator will be installed when 10 | Jenkins detects that they are required. For example, if a job is configured to use an emulator 11 | with Android 3.2, but this is not yet installed, it will be automatically downloaded and installed 12 | when that job is built. This requires that you have Android SDK Tools r14 or later installed. 13 |

14 | For each build, the path to the Android SDK being used is exposed via the ANDROID_HOME 15 | environment variable. -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/sdk/ToolLocator.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator.sdk; 2 | 3 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 4 | 5 | public interface ToolLocator { 6 | public static final String BUILD_TOOLS_DIR = "build-tools"; 7 | public static final String EMULATOR_DIR = "emulator"; 8 | public static final String PLATFORM_TOOLS_DIR = "platform-tools"; 9 | public static final String PLATFORMS_DIR = "platforms"; 10 | public static final String TOOLS_DIR = "tools"; 11 | public static final String TOOLS_BIN_DIR = "tools/bin"; 12 | public static final String CMD_TOOLS_BIN_DIR = "cmdline-tools/bin"; 13 | 14 | @SuppressFBWarnings("MS_MUTABLE_ARRAY") 15 | public static final String[] SDK_DIRECTORIES_LEGACY = { 16 | TOOLS_DIR 17 | }; 18 | 19 | @SuppressFBWarnings("MS_MUTABLE_ARRAY") 20 | public static final String[] SDK_DIRECTORIES = { 21 | EMULATOR_DIR, PLATFORM_TOOLS_DIR, TOOLS_DIR, TOOLS_BIN_DIR 22 | }; 23 | 24 | String findInSdk(final boolean useLegacySdkStructure); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/AndroidEmulatorException.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator; 2 | 3 | abstract class AndroidEmulatorException extends Exception { 4 | 5 | protected AndroidEmulatorException(String message) { 6 | super(message); 7 | } 8 | 9 | protected AndroidEmulatorException(String message, Throwable cause) { 10 | super(message, cause); 11 | } 12 | 13 | private static final long serialVersionUID = 1L; 14 | 15 | } 16 | 17 | final class EmulatorDiscoveryException extends AndroidEmulatorException { 18 | 19 | EmulatorDiscoveryException(String message) { 20 | super(message); 21 | } 22 | 23 | private static final long serialVersionUID = 1L; 24 | 25 | } 26 | 27 | final class EmulatorCreationException extends AndroidEmulatorException { 28 | 29 | EmulatorCreationException(String message) { 30 | super(message); 31 | } 32 | 33 | EmulatorCreationException(String message, Throwable cause) { 34 | super(message, cause); 35 | } 36 | 37 | private static final long serialVersionUID = 1L; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/resources/jenkins.plugin.android.emulator.tools.AndroidSDKInstaller.json: -------------------------------------------------------------------------------- 1 | { 2 | "list": [ 3 | { 4 | "id": "cmdline-tools:12.0", 5 | "url": "https://dl.google.com/android/repository/commandlinetools-{os}-11076708_latest.zip", 6 | "name": "Command-line Tools 12.0" 7 | }, 8 | { 9 | "id": "cmdline-tools:4.0", 10 | "url": "https://dl.google.com/android/repository/commandlinetools-{os}-7302050_latest.zip", 11 | "name": "Command-line Tools 4.0" 12 | }, 13 | { 14 | "id": "cmdline-tools:3.0", 15 | "url": "https://dl.google.com/android/repository/commandlinetools-{os}-6858069_latest.zip", 16 | "name": "Command-line Tools 3.0" 17 | }, 18 | { 19 | "id": "cmdline-tools:2.1", 20 | "url": "https://dl.google.com/android/repository/commandlinetools-{os}-6609375_latest.zip", 21 | "name": "Command-line Tools 2.1" 22 | }, 23 | { 24 | "id": "cmdline-tools:1.0", 25 | "url": "https://dl.google.com/android/repository/commandlinetools-{os}-6200805_latest.zip", 26 | "name": "Command-line Tools 1.0" 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /src/main/webapp/help-avdNameSuffix.html: -------------------------------------------------------------------------------- 1 | Should be empty or a custom suffix for the AVD name.
2 | e.g. "mySuffix" or "withPdf" 3 |

4 | If this field is left blank, no custom suffix will be added to the AVD name that 5 | this plugin generates when automatically creating an emulator.
6 | If a value is set, a suffix will be added to the AVD name, with any invalid characters 7 | replaced by a hyphen (as AVD names may only contain [a-z A-Z 0-9 . _ -]). 8 |

9 |

10 | This allows you to run multiple emulators with the same configuration in parallel on the 11 | same build machine.
12 | Normally, if you create two jobs with the same emulator configuration, only one of those 13 | jobs will run at a time — as the other job is using the emulator. But by setting a 14 | custom suffix one on or both jobs, the emulator names will differ for the two jobs, and 15 | Jenkins will consider them as two completely different emulators, allowing them to run in 16 | parallel. 17 |

18 | Note that this will cause more disk space to be used, as a new emulator will be created 19 | on disk for each unique suffix used. 20 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/android_emulator/AndroidEmulator/help-useSnapshots.html: -------------------------------------------------------------------------------- 1 | Enabling snapshots allows the emulator to start up much faster, becoming ready for use in a matter of seconds. 2 |

3 | The first time an emulator is started with snapshots enabled, the plugin waits until the emulator has finished 4 | booting, unlocks the screen, and then saves the emulator state to disk.
5 | For subsequent builds, the emulator is started directly from this stored state. This means that Android has 6 | already finished booting, is sitting on the home screen, with the screen unlocked; i.e. ready for use. 7 |

8 | At the end of a build, the emulator state is not persisted to the snapshot file — subsequent jobs will 9 | always start from the same, clean state that was stored at the start of the first snapshot-enabled build. 10 |

11 | Should the emulator already have snapshots in place, these will be neither read nor overwritten — 12 | the plugin always writes its state to a separate snapshot file called "jenkins". 13 |

14 | Note: Using snapshots will consume around 150–200MB of disk space on the build slave, for each emulator. 15 | -------------------------------------------------------------------------------- /src/test/java/jenkins/plugin/android/emulator/sdk/cli/AVDManagerCLIBuilderTest.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugin.android.emulator.sdk.cli; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.io.InputStream; 6 | 7 | import org.junit.jupiter.api.Test; 8 | import java.util.List; 9 | 10 | import jenkins.plugin.android.emulator.sdk.cli.Targets.TargetType; 11 | 12 | class AVDManagerCLIBuilderTest { 13 | 14 | @Test 15 | void test_list_parse() throws Exception { 16 | try (InputStream is = this.getClass().getResourceAsStream("avdmanager_list_target.out")) { 17 | List targets = new AVDManagerCLIBuilder.ListTargetParser().parse(is); 18 | assertThat(targets).hasSize(1); 19 | Targets target = targets.get(0); 20 | assertThat(target.getId()).isEqualTo("android-21"); 21 | assertThat(target.getName()).isEqualTo("Android API 21"); 22 | assertThat(target.getType()).isEqualTo(TargetType.platform); 23 | assertThat(target.getApiLevel()).isEqualTo(21); 24 | assertThat(target.getRevision()).isEqualTo(2); 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /NOTES: -------------------------------------------------------------------------------- 1 | - SDK installation should be on by default; option name and help file need to be updated 2 | - ABI support appeared in r13, i.e. using --abi with "android create avd" 3 | - Need to fix getSdkRootFromPath/isSdkToolsDirectory as they're trying to do an impossible check: that both adb and emulator are in the same directory... to test this, install an SDK without platform-tools and watch the misdetection occur... 4 | - Need to combine discoverAndroidHome and getAndroidSdk 5 | - Grab Semaphore when calling adb so that, after we know we've auto-installed some components, we can manually restart adb after all Semaphores have been released -- though this depends on Android bug 21923 6 | - Tools r16 will probably make "android.bat" become "android.exe" which will cause issues in our Tool class 7 | - Add timeouts to all ADB actions, including "logcat -c" etc. 8 | - Rewrite/split-up wiki page into more parts 9 | - Update/add screenshots to wiki 10 | >> STDERR (or whatever we do) isn't caught on Windows, but is on Linux... 11 | [android] Creating Android AVD: /home/hudson/.android/avd/hudson_en-NZ_160_WXGA_android-14.avd 12 | Error: 'WXGA' is not a valid skin name or size (NNNxMMM) 13 | 14 | -------------------------------------------------------------------------------- /src/main/webapp/help-hardware.html: -------------------------------------------------------------------------------- 1 | Adding custom hardware properties allows you to override the default values for an AVD.
2 | For example, you can alter the amount of RAM available to each process (via the vm.heapSize 3 | property), or you can alter physical features of the emulated device, such as the presence of a keyboard. 4 |

5 | A list of common properties, along with their descriptions and default values can be found in the 6 | "Setting 7 | hardware emulation options" section of the Android Developers' site. 8 |

9 |

10 | To set a new hardware feature (or override a default value), enter the property name and the desired value.
11 | Generally, boolean properties should be either yes or no rather than true or false. 12 |

13 |

14 | Hardware properties configured here will be added to the appropriate AVD next time this job runs.
15 | However, removing hardware properties from the job configuration will not remove these properties from the AVD 16 | — the previously-added properties will remain. 17 |

18 | -------------------------------------------------------------------------------- /src/test/java/hudson/plugins/android_emulator/EmulatorConfigTest.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import hudson.plugins.android_emulator.sdk.Tool; 6 | import org.junit.jupiter.api.Test; 7 | 8 | class EmulatorConfigTest { 9 | 10 | // JENKINS-26338 11 | @Test 12 | void shouldSelectExecutor64WhenPassedAsExecutorAndAvdIsSelected() { 13 | EmulatorConfig emulatorConfigWithAvdName = 14 | EmulatorConfig.create("hudson_en-US_160_WVGA_android-21", "5.0", "160", "WVGA", "", "", false, false, 15 | false, "", "", "", "", "emulator64-arm", ""); 16 | assertEquals(Tool.EMULATOR64_ARM, emulatorConfigWithAvdName.getExecutable()); 17 | } 18 | 19 | @Test 20 | void shouldSelectExecutor64WhenPassedAsExecutorAndAvdIsEmpty() { 21 | EmulatorConfig emulatorConfigWithNoAvdName = 22 | EmulatorConfig.create("", "5.0", "160", "WVGA", "", "", false, false, false, "", "", "", "", 23 | "emulator64-arm", ""); 24 | assertEquals(Tool.EMULATOR64_ARM, emulatorConfigWithNoAvdName.getExecutable()); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/webapp/help-sdkRoot.html: -------------------------------------------------------------------------------- 1 |
2 | Enter the path — or an environment variable, in the format ${VARIABLE_NAME} 3 | — where an Android SDK 4 | installation can be found.
If you want to create emulators on-the-fly, this SDK installation 5 | should have one or more platforms installed (check the "platforms" directory, or 6 | the Android SDK 7 | and AVD Manager). 8 |

9 | If nothing is entered here, the Android SDK tools (under the "tools" 10 | directory) will be presumed to be available on your PATH.
Otherwise, this plugin 11 | will search for an SDK installation under the following environment variables on the build host: 12 |

13 | 19 | Alternatively, select the option below to have the Android SDK installed automatically when it is needed. 20 |
21 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/android_emulator/InstallBuilder/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ${%Path to an Android package file, within the current workspace, to be installed} 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/sdk/cli/AdbShellCommands.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator.sdk.cli; 2 | 3 | import hudson.plugins.android_emulator.constants.AndroidKeyEvent; 4 | 5 | /** 6 | * Commands which are run via 'adb shell' command on a specified device 7 | */ 8 | public interface AdbShellCommands { 9 | SdkCliCommand getListProcessesCommand(final String deviceSerial); 10 | 11 | SdkCliCommand getWaitForDeviceStartupCommand(final String deviceSerial); 12 | String getWaitForDeviceStartupExpectedAnswer(); 13 | 14 | SdkCliCommand getClearMainLogCommand(final String deviceSerial); 15 | 16 | SdkCliCommand getSetLogCatFormatToTimeCommand(final String deviceSerial); 17 | SdkCliCommand getLogMessageCommand(final String deviceSerial, final String logMessage); 18 | 19 | SdkCliCommand getSendKeyEventCommand(final String deviceSerial, final AndroidKeyEvent keyEvent); 20 | SdkCliCommand getSendBackKeyEventCommand(final String deviceSerial); 21 | 22 | SdkCliCommand getDismissKeyguardCommand(final String deviceSerial); 23 | 24 | SdkCliCommand getMonkeyInputCommand(final String deviceSerial, 25 | final long seedValue, final int throttleMs, 26 | final String extraArgs, final int eventCount); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/android_emulator/AndroidEmulator/help-commandLineOptions.html: -------------------------------------------------------------------------------- 1 |
2 | The text entered here will be passed to the Android emulator executable at runtime.
3 | For a full list of available options, please run emulator -help, or consult the 4 | Android Emulator 5 | page on the Android Developers' site. 6 |

7 | Environment variable substitution can be used for these command line options, in the format: 8 | ${VARIABLE_NAME} or $VARIABLE_NAME. 9 |

10 | For example, if you want your emulator to have a larger-than-default /data partition, 11 | allowing you to install large APKs, then you could enter:
-partition-size 128 12 |

13 | Options which cause the emulator to create files, for example the -tcpdump option, 14 | should be given an absolute filename, e.g.
-tcpdump ${WORKSPACE}/network.cap 15 |

16 | Similarly, because arbitrary options can be combined here, you must make sure to add 17 | double quotes around any parameters which may contain spaces, e.g.:
18 | -netdelay gprs -tcpdump "/tmp/emulator captures/${JOB_NAME}_${BUILD_ID}.cap" 19 |
20 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugin/android/emulator/HardwareProperty/config.properties: -------------------------------------------------------------------------------- 1 | # The MIT License 2 | # 3 | # Copyright (c) 2020, Nikolas Falco 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | 23 | key.title=Property 24 | value.title=Value -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugin/android/emulator/tools/AndroidSDKInstaller/config.properties: -------------------------------------------------------------------------------- 1 | # The MIT License 2 | # 3 | # Copyright (c) 2016, Nikolas Falco 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | 23 | channel.title=Channel 24 | channel.description=Use a specific channel for this build tools to install or update packages 25 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/android_emulator/AndroidEmulator/global.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/main/webapp/help-emulatorCustom.html: -------------------------------------------------------------------------------- 1 |
2 | Values specified here will be used to generate an Android emulator on-the-fly, if one doesn't 3 | already exist with the given properties. Note that each emulator may consume anywhere from 30 4 | to 80MB of disk space on the build host. 5 |

6 | Each property will autocomplete to the standard Android SDK values, or you can provide custom 7 | values, e.g. if you have additional platforms defined or want to use a custom resolution. 8 |

9 |

10 | You can also do environment variable substitution for any of these properties, in the format: 11 | ${VARIABLE_NAME} or $VARIABLE_NAME. 12 |

13 |

14 | For example, if you have configured a 15 | 16 | matrix build to include a "density" axis with values "120 160 240", you 17 | could enter ${density} for the "Screen density" property below and the 18 | appropriate substitution would be made when each individual build is executed. 19 |

20 | Emulators are stored in the usual $HOME/.android/avd directory, named in the format: 21 | hudson_[locale]_[density]_[resolution]_[target].
22 | For example: hudson_en-GB_160_HVGA_android-7 for a British English, medium density, HVGA 23 | device running Android 2.1. 24 |
25 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugin/android/emulator/sdk/pipeline/Messages.properties: -------------------------------------------------------------------------------- 1 | # 2 | # The MIT License (MIT) 3 | # 4 | # Copyright (c) 2020, Nikolas Falco 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | # THE SOFTWARE. 23 | AVDManagerStep.displayName=AVDManager Script 24 | SDKManagerStep.displayName=SDKManager Script 25 | EmulatorStep.displayName=QEMU Executable 26 | ADBStep.displayName=ADB Executable 27 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugin/android/emulator/sdk/home/HomeLocatorDescriptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2020, Nikolas Falco 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package jenkins.plugin.android.emulator.sdk.home; 25 | 26 | import hudson.model.Descriptor; 27 | 28 | public class HomeLocatorDescriptor extends Descriptor { 29 | } 30 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugin/android/emulator/sdk/pipeline/ADBStep/config.jelly: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugin/android/emulator/sdk/pipeline/AVDManagerStep/config.jelly: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugin/android/emulator/sdk/pipeline/EmulatorStep/config.jelly: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugin/android/emulator/sdk/pipeline/SDKManagerStep/config.jelly: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/snapshot/SnapshotLoadBuilder.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator.snapshot; 2 | 3 | import hudson.Functions; 4 | import hudson.model.Descriptor; 5 | import hudson.plugins.android_emulator.Messages; 6 | import hudson.tasks.Builder; 7 | 8 | import java.io.Serializable; 9 | 10 | import org.kohsuke.stapler.DataBoundConstructor; 11 | 12 | public class SnapshotLoadBuilder extends AbstractSnapshotBuilder { 13 | 14 | @DataBoundConstructor 15 | public SnapshotLoadBuilder(String name) { 16 | super(name); 17 | } 18 | 19 | @Override 20 | protected String getSnapshotAction() { 21 | return "load"; 22 | } 23 | 24 | @Override 25 | protected String getLogMessage(String name, int port) { 26 | return Messages.LOADING_SNAPSHOT(name, port); 27 | } 28 | 29 | //@Extension 30 | public static class DescriptorImpl extends Descriptor implements Serializable { 31 | 32 | private static final long serialVersionUID = 1L; 33 | 34 | public DescriptorImpl() { 35 | super(SnapshotLoadBuilder.class); 36 | load(); 37 | } 38 | 39 | @Override 40 | public String getHelpFile() { 41 | return Functions.getResourcePath() + "/plugin/android-emulator/help-snapshotLoad.html"; 42 | } 43 | 44 | @Override 45 | public String getDisplayName() { 46 | return Messages.LOAD_EMULATOR_SNAPSHOT(); 47 | } 48 | 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/snapshot/SnapshotSaveBuilder.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator.snapshot; 2 | 3 | import hudson.Functions; 4 | import hudson.model.Descriptor; 5 | import hudson.plugins.android_emulator.Messages; 6 | import hudson.tasks.Builder; 7 | 8 | import java.io.Serializable; 9 | 10 | import org.kohsuke.stapler.DataBoundConstructor; 11 | 12 | public class SnapshotSaveBuilder extends AbstractSnapshotBuilder { 13 | 14 | @DataBoundConstructor 15 | public SnapshotSaveBuilder(String name) { 16 | super(name); 17 | } 18 | 19 | @Override 20 | protected String getSnapshotAction() { 21 | return "save"; 22 | } 23 | 24 | @Override 25 | protected String getLogMessage(String name, int port) { 26 | return Messages.SAVING_SNAPSHOT(name, port); 27 | } 28 | 29 | //@Extension 30 | public static class DescriptorImpl extends Descriptor implements Serializable { 31 | 32 | private static final long serialVersionUID = 1L; 33 | 34 | public DescriptorImpl() { 35 | super(SnapshotSaveBuilder.class); 36 | load(); 37 | } 38 | 39 | @Override 40 | public String getHelpFile() { 41 | return Functions.getResourcePath() + "/plugin/android-emulator/help-snapshotSave.html"; 42 | } 43 | 44 | @Override 45 | public String getDisplayName() { 46 | return Messages.SAVE_EMULATOR_SNAPSHOT(); 47 | } 48 | 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugin/android/emulator/sdk/pipeline/AbstractCLIStep/config.properties: -------------------------------------------------------------------------------- 1 | # The MIT License 2 | # 3 | # Copyright (c) 2020, Nikolas Falco 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | 23 | buildTools.title=Build Tools 24 | buildTools.description=The build tools installation where installed packages will be provided to the this emulator 25 | sdk.homeLocation=SDK Home location 26 | arguments.title=Command arguments 27 | quiet.title=Show output to console log -------------------------------------------------------------------------------- /src/test/java/jenkins/plugin/android/emulator/sdk/cli/SDKManagerCLIBuilderTest.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugin.android.emulator.sdk.cli; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.io.InputStream; 6 | 7 | import org.junit.jupiter.api.Test; 8 | 9 | import jenkins.plugin.android.emulator.sdk.cli.SDKPackages.SDKPackage; 10 | 11 | class SDKManagerCLIBuilderTest { 12 | 13 | @Test 14 | void test_list_parse() throws Exception { 15 | try (InputStream is = this.getClass().getResourceAsStream("sdkmanager_list.out")) { 16 | SDKPackages packages = new SDKManagerCLIBuilder.ListPackagesParser().parse(is); 17 | assertThat(packages.getAvailable()).isNotEmpty().hasSize(236).allMatch(p -> p.getId() != null, "Name is null"); 18 | assertThat(packages.getInstalled()).isNotEmpty().hasSize(6).allMatch(p -> p.getId() != null, "Name is null"); 19 | assertThat(packages.getUpdates()).isNotEmpty().hasSize(1).allMatch(p -> p.getId() != null, "Name is null"); 20 | } 21 | } 22 | 23 | @Test 24 | void test_sort_packages() { 25 | SDKPackage p1 = new SDKPackage(); 26 | p1.setId("test"); 27 | p1.setVersion(new Version("1.0.0 rc1")); 28 | 29 | SDKPackage p2 = new SDKPackage(); 30 | p2.setId("test"); 31 | p2.setVersion(new Version("1.0.0")); 32 | 33 | SDKPackage p3 = new SDKPackage(); 34 | p3.setId("notest"); 35 | p3.setVersion(new Version("1.0.0 rc1")); 36 | 37 | assertThat(p1).isLessThan(p2).isGreaterThan(p3); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugin/android/emulator/sdk/pipeline/AbstractCLIStep.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugin.android.emulator.sdk.pipeline; 2 | 3 | import java.io.Serializable; 4 | 5 | import org.jenkinsci.plugins.workflow.steps.Step; 6 | import org.kohsuke.stapler.DataBoundSetter; 7 | 8 | import hudson.Util; 9 | 10 | import edu.umd.cs.findbugs.annotations.NonNull; 11 | import jenkins.plugin.android.emulator.sdk.home.HomeLocator; 12 | 13 | public abstract class AbstractCLIStep extends Step implements Serializable { 14 | 15 | private static final long serialVersionUID = 1L; 16 | 17 | protected final String emulatorTool; 18 | protected final HomeLocator homeLocationStrategy; 19 | protected final String arguments; 20 | protected boolean quiet = false; 21 | 22 | public AbstractCLIStep(@NonNull String emulatorTool, @NonNull HomeLocator homeLocationStrategy, @NonNull String arguments) { 23 | this.emulatorTool = Util.fixEmptyAndTrim(emulatorTool); 24 | this.homeLocationStrategy = homeLocationStrategy; 25 | this.arguments = Util.fixEmptyAndTrim(arguments); 26 | } 27 | 28 | public String getArguments() { 29 | return arguments; 30 | } 31 | 32 | public HomeLocator getHomeLocationStrategy() { 33 | return homeLocationStrategy; 34 | } 35 | 36 | public String getEmulatorTool() { 37 | return emulatorTool; 38 | } 39 | 40 | public boolean isQuiet() { 41 | return quiet; 42 | } 43 | 44 | @DataBoundSetter 45 | public void setQuiet(boolean quiet) { 46 | this.quiet = quiet; 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugin/android/emulator/tools/AndroidSDKInstaller/config.jelly: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugin/android/emulator/HardwareProperty/config.jelly: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugin/android/emulator/tools/DetectionFailedException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2020, Nikolas Falco 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package jenkins.plugin.android.emulator.tools; 25 | 26 | import java.io.IOException; 27 | 28 | /** 29 | * Indicates the failure to detect the OS. 30 | */ 31 | @SuppressWarnings("serial") 32 | public final class DetectionFailedException extends IOException { 33 | DetectionFailedException(String message) { 34 | super(message); 35 | } 36 | 37 | public DetectionFailedException(String message, Throwable cause) { 38 | super(message, cause); 39 | } 40 | } -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugin/android/emulator/AndroidEmulatorBuild/config.properties: -------------------------------------------------------------------------------- 1 | # The MIT License 2 | # 3 | # Copyright (c) 2020, Nikolas Falco 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | 23 | buildTools.title=Build Tools 24 | buildTools.description=The build tools installation where installed packages will be provided to the this emulator 25 | osVersion.title=Android OS version 26 | screenDensity.title=Density 27 | screenResolution.title=Resolution 28 | sdk.homeLocation=SDK Home location 29 | avdName.title=Emulator Name 30 | deviceLocale.title=Devide Locale 31 | deviceDefinition.title=Device Definition 32 | sdCardSize.title=SD Card Size 33 | sdCardSize.description=Value is expressed in MB 34 | targetABI.title=Target ABI 35 | hardwareProperties.title=Hardware 36 | hardwareProperties.description=Add custom hardware properties for this device 37 | options.title=Common emulator options 38 | advanced.adbTimeout.title=ADB timeout 39 | advanced.adbTimeout.description=Wait this many seconds for ADB to be available -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/snapshot/AbstractSnapshotBuilder.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator.snapshot; 2 | 3 | import static hudson.plugins.android_emulator.AndroidEmulator.log; 4 | import hudson.Launcher; 5 | import hudson.model.AbstractBuild; 6 | import hudson.model.BuildListener; 7 | import hudson.plugins.android_emulator.builder.AbstractBuilder; 8 | import hudson.plugins.android_emulator.util.Utils; 9 | 10 | import java.io.IOException; 11 | import java.io.PrintStream; 12 | 13 | import org.kohsuke.stapler.export.Exported; 14 | 15 | public abstract class AbstractSnapshotBuilder extends AbstractBuilder { 16 | 17 | private static final int DEFAULT_TIMEOUT_MS = 2 * 60 * 1000; 18 | 19 | /** Name of the snapshot involved. */ 20 | @Exported 21 | public final String name; 22 | 23 | protected AbstractSnapshotBuilder(String name) { 24 | this.name = name; 25 | } 26 | 27 | @Override 28 | public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) 29 | throws InterruptedException, IOException { 30 | final PrintStream logger = listener.getLogger(); 31 | 32 | // Expand snapshot name 33 | final String snapshotName = Utils.expandVariables(build, listener, name); 34 | 35 | // Get AVD port 36 | final int port = getDeviceTelnetPort(build, listener); 37 | 38 | // Send telnet command: "avd snapshot $action $name" 39 | log(logger, getLogMessage(snapshotName, port)); 40 | String command = String.format("avd snapshot %s %s", getSnapshotAction(), snapshotName); 41 | return Utils.sendEmulatorCommand(launcher, logger, port, command, getCommandTimeout()); 42 | } 43 | 44 | /* Retrieves the time in which the snapshot command should complete, in milliseconds. */ 45 | protected int getCommandTimeout() { 46 | return DEFAULT_TIMEOUT_MS; 47 | } 48 | 49 | /* Retrieves the snapshot action to execute (i.e. "load" or "save"). */ 50 | protected abstract String getSnapshotAction(); 51 | 52 | /* Retrieves the log message to print when performing the snapshot action. */ 53 | protected abstract String getLogMessage(String snapshotName, int avdPort); 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/sdk/cli/SdkToolsCommands.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator.sdk.cli; 2 | 3 | import hudson.plugins.android_emulator.sdk.Tool; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * CLI commands using the SDK tools. 9 | */ 10 | public interface SdkToolsCommands { 11 | 12 | SdkCliCommand getSdkInstallAndUpdateCommand(final String proxySettings, final List components); 13 | SdkCliCommand getListSdkComponentsCommand(); 14 | SdkCliCommand getListExistingTargetsCommand(); 15 | SdkCliCommand getListSystemImagesCommand(); 16 | boolean isImageForPlatformAndABIInstalled(final String listSystemImagesOutput, 17 | final String platform, final String abi); 18 | SdkCliCommand getCreatedAvdCommand(final String avdName, final boolean supportsSnapshots, 19 | final String sdCardSize, final String screenResolutionSkinName, final String deviceDefinition, 20 | final String androidTarget, final String systemImagePackagePath, final String tag); 21 | 22 | SdkCliCommand getAdbInstallPackageCommand(final String deviceIdentifier, final String packageFileName); 23 | SdkCliCommand getAdbUninstallPackageCommand(final String deviceIdentifier, final String packageId); 24 | 25 | /** 26 | * Creates the command ({@code Tool} and arguments to created a sdcard-images. 27 | * 28 | * @param absolutePathToSdCard The absolute path where the images should be created 29 | * @param requestedSdCardSize The requested size of the sdcard-image in bytes (may be suffixed with 'K', 'M', 'G') 30 | * @return a {@code SdkCommand} which holds the command to use and the arguments 31 | */ 32 | SdkCliCommand getCreateSdkCardCommand(final String absolutePathToSdCard, final String requestedSdCardSize); 33 | 34 | SdkCliCommand getEmulatorListSnapshotsCommand(final String avdName, final Tool executable); 35 | 36 | SdkCliCommand getAdbStartServerCommand(); 37 | SdkCliCommand getAdbKillServerCommand(); 38 | 39 | @Deprecated 40 | SdkCliCommand getUpdateProjectCommand(final String projectPath); 41 | @Deprecated 42 | SdkCliCommand getUpdateTestProjectCommand(final String projectPath, final String testMainClass); 43 | @Deprecated 44 | SdkCliCommand getUpdateLibProjectCommand(final String projectPath); 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/util/ValidationResult.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator.util; 2 | 3 | import hudson.util.FormValidation; 4 | 5 | import java.io.Serializable; 6 | 7 | public class ValidationResult implements Serializable { 8 | 9 | public static enum Type { 10 | OK, 11 | WARNING, 12 | ERROR 13 | } 14 | 15 | private final Type type; 16 | private final String message; 17 | private final boolean hasMarkup; 18 | 19 | public ValidationResult(Type type, String message) { 20 | this(type, message, false); 21 | } 22 | 23 | public ValidationResult(Type type, String message, boolean hasMarkup) { 24 | this.type = type; 25 | this.message = message; 26 | this.hasMarkup = hasMarkup; 27 | } 28 | 29 | public static ValidationResult ok() { 30 | return new ValidationResult(Type.OK, null); 31 | } 32 | 33 | public static ValidationResult warning(String message) { 34 | return new ValidationResult(Type.WARNING, message); 35 | } 36 | 37 | public static ValidationResult error(String message) { 38 | return new ValidationResult(Type.ERROR, message); 39 | } 40 | 41 | public static ValidationResult errorWithMarkup(String message) { 42 | return new ValidationResult(Type.ERROR, message, true); 43 | } 44 | 45 | public FormValidation getFormValidation() { 46 | switch (type) { 47 | case WARNING: 48 | return FormValidation.warning(message); 49 | case ERROR: 50 | if (hasMarkup) { 51 | return FormValidation.errorWithMarkup(message); 52 | } else { 53 | return FormValidation.error(message); 54 | } 55 | case OK: 56 | default: 57 | return FormValidation.ok(); 58 | } 59 | } 60 | 61 | public boolean isFatal() { 62 | return type == Type.ERROR; 63 | } 64 | 65 | public Type getType() { 66 | return type; 67 | } 68 | 69 | public String getMessage() { 70 | return message; 71 | } 72 | 73 | @Override 74 | public String toString() { 75 | return "ValidationResult[type="+ type +", message="+ message +"]"; 76 | } 77 | 78 | private static final long serialVersionUID = 1L; 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/ReceiveEmulatorPortTask.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.InputStreamReader; 7 | import java.net.ServerSocket; 8 | import java.net.Socket; 9 | 10 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 11 | import jenkins.security.MasterToSlaveCallable; 12 | 13 | /** 14 | * Task that will wait, up to a certain timeout, for an inbound connection from the emulator, 15 | * informing us on which port it is running. 16 | */ 17 | public final class ReceiveEmulatorPortTask 18 | extends MasterToSlaveCallable { 19 | 20 | private static final long serialVersionUID = 1L; 21 | 22 | private final int port; 23 | private final int timeout; 24 | 25 | /** 26 | * @param port The local TCP port to listen on. 27 | * @param timeout How many milliseconds to wait for an emulator connection before giving up. 28 | */ 29 | public ReceiveEmulatorPortTask(int port, int timeout) { 30 | this.port = port; 31 | this.timeout = timeout; 32 | } 33 | 34 | @SuppressFBWarnings("DM_DEFAULT_ENCODING") 35 | public Integer call() throws InterruptedException { 36 | // TODO: Find a better way to allow the build to be interrupted. 37 | // ServerSocket#accept() blocks and cannot be interrupted, which means that any 38 | // attempts to stop the build will fail. The best we can do here is to set the 39 | // SO_TIMEOUT, so at least if an emulator fails to start, we won't wait here forever 40 | try (ServerSocket socket = new ServerSocket(port)) { 41 | socket.setSoTimeout(timeout); 42 | 43 | // Wait for the emulator to connect to us 44 | Socket completed = socket.accept(); 45 | 46 | // Parse and return the port number the emulator sent us 47 | try (InputStream is = completed.getInputStream()) { 48 | BufferedReader reader = new BufferedReader(new InputStreamReader(is)); 49 | return Integer.parseInt(reader.readLine()); 50 | } catch (NumberFormatException ignore) { 51 | } 52 | } catch (IOException ignore) { 53 | } 54 | 55 | // Timed out 56 | return -1; 57 | } 58 | } -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugin/android/emulator/sdk/pipeline/AbstractCLIStep/config.jelly: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 27 | 28 | 29 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugin/android/emulator/sdk/home/PerJobHomeLocator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2020, Nikolas Falco 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package jenkins.plugin.android.emulator.sdk.home; 25 | 26 | import org.jenkinsci.Symbol; 27 | import org.kohsuke.stapler.DataBoundConstructor; 28 | 29 | import hudson.Extension; 30 | 31 | import edu.umd.cs.findbugs.annotations.NonNull; 32 | import hudson.FilePath; 33 | import jenkins.plugin.android.emulator.Messages; 34 | 35 | /** 36 | * Relocates the default SDK Home to the workspace folder. This allow clean 37 | * unused packages when the job is gone. 38 | */ 39 | public class PerJobHomeLocator extends HomeLocator { 40 | 41 | private static final long serialVersionUID = 1L; 42 | 43 | @DataBoundConstructor 44 | public PerJobHomeLocator() { 45 | // default constructor 46 | } 47 | 48 | @Override 49 | public FilePath locate(@NonNull FilePath workspace) { 50 | return workspace; 51 | } 52 | 53 | @Extension 54 | @Symbol("workspace") 55 | public static class DescriptorImpl extends HomeLocatorDescriptor { 56 | @Override 57 | public String getDisplayName() { 58 | return Messages.JobHomeLocationLocator_displayName(); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugin/android/emulator/Messages.properties: -------------------------------------------------------------------------------- 1 | # The MIT License 2 | # 3 | # Copyright (c) 2020, Nikolas Falco 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | 23 | required=Required 24 | nodeNotAvailable=Cannot get installation for node, since it count be not online 25 | noInstallationFound=No installation {0} found. Please define one in manager Jenkins. 26 | noExecutableFound=Couldn''t find executable "{0}" 27 | Platform.unknown=Unknown OS name: {0} 28 | SystemTools.failureOnProperties=Error getting system properties on remote Node 29 | SystemTools.unsupported32bitArchitecture=NodeJS does not have a 32bit package available for the current node 30 | AndroidSDKInstallation.displayName=Android Build Tool 31 | AndroidSDKInstaller.displayName=Install from dl.google.com/android/repository 32 | AndroidEmulatorBuild.displayName=Run an Android emulator during build 33 | AndroidEmulatorBuild.wrongDensity=Expected a pair integer greater than 0 34 | AndroidEmulatorBuild.defaultLocale=Locale will default to ''{0}'' if not specified 35 | AndroidEmulatorBuild.wrongLocale=Locale is incorrect 36 | AndroidEmulatorBuild.sdCardTooSmall=SD card size must be at least 9 MB 37 | JobHomeLocationLocator.displayName=Local to the workspace 38 | ExecutorHomeLocationLocator.displayName=Local to the executor 39 | DefaultHomeLocationLocator.displayName=Default (~/.android or %HOME%\\.android) 40 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugin/android/emulator/sdk/cli/AVDevice.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugin.android.emulator.sdk.cli; 2 | 3 | import java.util.Objects; 4 | 5 | public class AVDevice { 6 | 7 | private String name; 8 | private String path; 9 | private String sdCard; 10 | private String target; 11 | private String os; 12 | private String abi; 13 | private String error; 14 | 15 | public String getName() { 16 | return name; 17 | } 18 | 19 | public void setName(String name) { 20 | this.name = name; 21 | } 22 | 23 | public String getPath() { 24 | return path; 25 | } 26 | 27 | public void setPath(String path) { 28 | this.path = path; 29 | } 30 | 31 | public String getError() { 32 | return error; 33 | } 34 | 35 | public void setError(String error) { 36 | this.error = error; 37 | } 38 | 39 | public String getSDCard() { 40 | return sdCard; 41 | } 42 | 43 | public void setSDCard(String sdcard) { 44 | this.sdCard = sdcard; 45 | } 46 | 47 | public String getTarget() { 48 | return target; 49 | } 50 | 51 | public void setTarget(String target) { 52 | this.target = target; 53 | } 54 | 55 | public String getAndroidOS() { 56 | return os; 57 | } 58 | 59 | public void setAndroidOS(String os) { 60 | this.os = os; 61 | } 62 | 63 | public String getABI() { 64 | return abi; 65 | } 66 | 67 | public void setABI(String abi) { 68 | this.abi = abi; 69 | } 70 | 71 | @Override 72 | public int hashCode() { 73 | return Objects.hash(error, name, path, sdCard, target, abi, os); 74 | } 75 | 76 | @Override 77 | public boolean equals(Object obj) { 78 | if (this == obj) { 79 | return true; 80 | } 81 | if (obj == null) { 82 | return false; 83 | } 84 | if (getClass() != obj.getClass()) { 85 | return false; 86 | } 87 | AVDevice other = (AVDevice) obj; 88 | return Objects.equals(error, other.error) && Objects.equals(name, other.name) // 89 | && Objects.equals(path, other.path) && Objects.equals(sdCard, other.sdCard) // 90 | && Objects.equals(target, other.target) && Objects.equals(abi, other.abi) // 91 | && Objects.equals(os, other.os); 92 | } 93 | 94 | public boolean hasError() { 95 | return error == null; 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugin/android/emulator/sdk/pipeline/AbstractCLIStepExecution.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugin.android.emulator.sdk.pipeline; 2 | 3 | import org.jenkinsci.plugins.workflow.steps.StepContext; 4 | import org.jenkinsci.plugins.workflow.steps.SynchronousNonBlockingStepExecution; 5 | 6 | import hudson.AbortException; 7 | import hudson.EnvVars; 8 | import hudson.FilePath; 9 | import hudson.model.Computer; 10 | import hudson.model.Node; 11 | import hudson.model.TaskListener; 12 | import jenkins.plugin.android.emulator.AndroidSDKUtil; 13 | import jenkins.plugin.android.emulator.sdk.home.HomeLocator; 14 | import jenkins.plugin.android.emulator.tools.AndroidSDKInstallation; 15 | 16 | abstract class AbstractCLIStepExecution extends SynchronousNonBlockingStepExecution { 17 | private static final long serialVersionUID = 1L; 18 | 19 | private final String emulatorTool; 20 | private final HomeLocator homeLocationStrategy; 21 | 22 | protected AbstractCLIStepExecution(String emulatorTool, HomeLocator homeLocationStrategy, StepContext context) { 23 | super(context); 24 | this.emulatorTool = emulatorTool; 25 | this.homeLocationStrategy = homeLocationStrategy; 26 | } 27 | 28 | @Override 29 | protected Void run() throws Exception { 30 | FilePath workspace = getContext().get(FilePath.class); 31 | workspace.mkdirs(); 32 | 33 | AndroidSDKInstallation sdk = AndroidSDKUtil.getAndroidSDK(emulatorTool); 34 | if (sdk == null) { 35 | throw new AbortException(jenkins.plugin.android.emulator.Messages.noInstallationFound(emulatorTool)); 36 | } 37 | 38 | Computer computer = workspace.toComputer(); 39 | if (computer == null) { 40 | throw new AbortException(jenkins.plugin.android.emulator.Messages.nodeNotAvailable()); 41 | } 42 | Node node = computer.getNode(); 43 | if (node == null) { 44 | throw new AbortException(jenkins.plugin.android.emulator.Messages.nodeNotAvailable()); 45 | } 46 | 47 | TaskListener listener = getContext().get(TaskListener.class); 48 | EnvVars env = getContext().get(EnvVars.class); 49 | sdk = sdk.forNode(node, listener); 50 | sdk = sdk.forEnvironment(env); 51 | 52 | sdk.buildEnvVars(env); 53 | 54 | // configure home location 55 | FilePath homeLocation = homeLocationStrategy.locate(workspace); 56 | HomeLocator.buildEnvVars(homeLocation, env); 57 | 58 | return doRun(sdk, listener, env); 59 | } 60 | 61 | protected abstract Void doRun(AndroidSDKInstallation sdk, TaskListener listener, EnvVars env) throws Exception; 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugin/android/emulator/sdk/home/DefaultHomeLocator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2020, Nikolas Falco 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package jenkins.plugin.android.emulator.sdk.home; 25 | 26 | import java.io.IOException; 27 | 28 | import org.jenkinsci.Symbol; 29 | import org.kohsuke.stapler.DataBoundConstructor; 30 | 31 | import hudson.Extension; 32 | 33 | import edu.umd.cs.findbugs.annotations.NonNull; 34 | import hudson.FilePath; 35 | import jenkins.model.Jenkins; 36 | import jenkins.plugin.android.emulator.Messages; 37 | 38 | /** 39 | * Uses NPM's default global cache, which is actually {@code ~/.android} on Unix 40 | * system or {@code %HOME%\.android} on Windows system. 41 | */ 42 | public class DefaultHomeLocator extends HomeLocator { 43 | 44 | private static final long serialVersionUID = 3368523530762397938L; 45 | 46 | @DataBoundConstructor 47 | public DefaultHomeLocator() { 48 | // default constructor 49 | } 50 | 51 | @Override 52 | public FilePath locate(@NonNull FilePath workspace) { 53 | try { 54 | return FilePath.getHomeDirectory(workspace.getChannel()); 55 | } catch (InterruptedException | IOException e) { 56 | return Jenkins.get().getRootPath(); 57 | } 58 | } 59 | 60 | @Extension 61 | @Symbol("home") 62 | public static class DescriptorImpl extends HomeLocatorDescriptor { 63 | @Override 64 | public String getDisplayName() { 65 | return Messages.DefaultHomeLocationLocator_displayName(); 66 | } 67 | } 68 | 69 | } -------------------------------------------------------------------------------- /src/main/java/jenkins/plugin/android/emulator/AndroidSDKConstants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2020, Nikolas Falco 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package jenkins.plugin.android.emulator; 25 | 26 | public final class AndroidSDKConstants { 27 | 28 | private AndroidSDKConstants() { 29 | // default constructor 30 | } 31 | 32 | public static final String ANDROID_CACHE = ".android"; 33 | public static final String DDMS_CONFIG = "ddms.cfg"; 34 | public static final String LOCAL_REPO_CONFIG = "repositories.cfg"; 35 | 36 | public static final String ENV_ADB_TRACE = "ADB_TRACE"; 37 | public static final String ENV_ADB_LOCAL_TRANSPORT_MAX_PORT = "ADB_LOCAL_TRANSPORT_MAX_PORT"; 38 | /** 39 | * Sets the path to the directory that contains all AVD-specific files, 40 | * which mostly consist of very large disk images. 41 | *

42 | * The default location is $ANDROID_EMULATOR_HOME/avd/. You might want to 43 | * specify a new location if the default location is low on disk space. 44 | */ 45 | public static final String ENV_ANDROID_AVD_HOME = "ANDROID_AVD_HOME"; 46 | /** 47 | * Sets the path to the user-specific emulator configuration directory. 48 | *

49 | * The default location is $ANDROID_SDK_HOME/.android/. 50 | */ 51 | public static final String ENV_ANDROID_EMULATOR_HOME = "ANDROID_EMULATOR_HOME"; 52 | 53 | /** 54 | * The Android Debug Bridge (adb) server default TCP port. 55 | */ 56 | public static final int ADB_DEFAULT_SERVER_PORT = 5037; 57 | public static final int ADB_CONNECT_TIMEOUT = 60; 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugin/android/emulator/HardwareProperty.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2020, Nikolas Falco 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package jenkins.plugin.android.emulator; 25 | 26 | import org.apache.commons.lang3.StringUtils; 27 | import org.jenkinsci.Symbol; 28 | import org.kohsuke.stapler.DataBoundConstructor; 29 | import org.kohsuke.stapler.QueryParameter; 30 | 31 | import hudson.Extension; 32 | import hudson.Util; 33 | import hudson.model.AbstractDescribableImpl; 34 | import hudson.model.Descriptor; 35 | import hudson.util.FormValidation; 36 | 37 | public class HardwareProperty extends AbstractDescribableImpl { 38 | 39 | private final String key; 40 | private final String value; 41 | 42 | @DataBoundConstructor 43 | public HardwareProperty(String key, String value) { 44 | this.key = Util.fixEmptyAndTrim(key); 45 | this.value = Util.fixEmptyAndTrim(value); 46 | } 47 | 48 | public String getKey() { 49 | return key; 50 | } 51 | 52 | public String getValue() { 53 | return value; 54 | } 55 | 56 | @Symbol("hwProperty") 57 | @Extension 58 | public static final class DescriptorImpl extends Descriptor { 59 | @Override 60 | public String getDisplayName() { 61 | return "Property"; 62 | } 63 | 64 | public FormValidation doCheckKey(@QueryParameter String key) { 65 | if (StringUtils.isBlank(key)) { 66 | return FormValidation.error(Messages.required()); 67 | } 68 | return FormValidation.ok(); 69 | } 70 | } 71 | 72 | } -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/android_emulator/monkey/MonkeyBuilder/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ${%Zero or more Android package IDs to monkey around with. If not specified, all installed packages will be used. Multiple packages can be separated by a comma} 7 | 8 | 9 | 10 | 11 | ${%Number of events the monkey should perform. If not specified, no events will be generated} 12 | 13 | 14 | 15 | 16 | ${%In milliseconds. If 0 or not specified, events are generated as rapidly as possible} 17 | 18 | 19 | 20 | 21 | 22 | ${%Name of a file within the workspace to write monkey output to. Defaults to "monkey.txt" in the root of the workspace} 23 | 24 | 25 | 26 | 27 | ${%Seed value for the pseudo-random number generator. Enter a number, "timestamp", or "random". If 0 or not specified, a timestamp-based value will be used} 28 | 29 | 30 | 31 | 32 | ${%Zero or more intent categories for the monkey to visit. If not specified, activities with "android.intent.category.LAUNCHER" or "android.intent.category.MONKEY" will be used. Multiple categories can be separated by a comma} 33 | 34 | 35 | 36 | 37 | ${%A list of optional command line parameters to pass to monkey. e.g. "--ignore-native-crashes --pct-trackball 0"} 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/ScreenDensity.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator; 2 | 3 | import java.io.Serializable; 4 | 5 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 6 | import hudson.Util; 7 | 8 | @SuppressWarnings("serial") 9 | public class ScreenDensity implements Serializable { 10 | 11 | public static final ScreenDensity LOW = new ScreenDensity(120, "ldpi"); 12 | public static final ScreenDensity MEDIUM = new ScreenDensity(160, "mdpi"); 13 | public static final ScreenDensity TV_720P = new ScreenDensity(213, "tvdpi"); 14 | public static final ScreenDensity HIGH = new ScreenDensity(240, "hdpi"); 15 | public static final ScreenDensity EXTRA_HIGH = new ScreenDensity(320, "xhdpi"); 16 | public static final ScreenDensity EXTRA_HIGH_400 = new ScreenDensity(400); 17 | public static final ScreenDensity EXTRA_HIGH_420 = new ScreenDensity(420); 18 | public static final ScreenDensity EXTRA_EXTRA_HIGH = new ScreenDensity(480, "xxhdpi"); 19 | public static final ScreenDensity EXTRA_EXTRA_HIGH_560 = new ScreenDensity(560); 20 | public static final ScreenDensity EXTRA_EXTRA_EXTRA_HIGH = new ScreenDensity(640, "xxxhdpi"); 21 | private static final ScreenDensity[] PRESETS = new ScreenDensity[] { LOW, MEDIUM, TV_720P, HIGH, 22 | EXTRA_HIGH, EXTRA_HIGH_400, EXTRA_HIGH_420, EXTRA_EXTRA_HIGH, EXTRA_EXTRA_HIGH_560, 23 | EXTRA_EXTRA_EXTRA_HIGH }; 24 | 25 | public static ScreenDensity[] values() { 26 | return PRESETS; 27 | } 28 | 29 | public static ScreenDensity valueOf(String density) { 30 | if (Util.fixEmptyAndTrim(density) == null) { 31 | return null; 32 | } else { 33 | density = density.toLowerCase(); 34 | } 35 | 36 | for (ScreenDensity preset : PRESETS) { 37 | if (density.equals(preset.alias) || density.equals(preset.toString())) { 38 | return preset; 39 | } 40 | } 41 | 42 | // Return custom value, if things look valid 43 | try { 44 | int dpi = Integer.parseInt(density); 45 | return new ScreenDensity(dpi); 46 | } catch (NumberFormatException ex) { 47 | return null; 48 | } 49 | } 50 | 51 | private final int dpi; 52 | private final String alias; 53 | 54 | private ScreenDensity(int dpi, String alias) { 55 | this.dpi = dpi; 56 | this.alias = alias; 57 | } 58 | 59 | private ScreenDensity(int density) { 60 | this(density, null); 61 | } 62 | 63 | public boolean isCustomDensity() { 64 | return alias == null; 65 | } 66 | 67 | public int getDpi() { 68 | return dpi; 69 | } 70 | 71 | @Override 72 | public String toString() { 73 | return Integer.toString(dpi); 74 | } 75 | 76 | } -------------------------------------------------------------------------------- /src/main/java/jenkins/plugin/android/emulator/AndroidSDKUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2020, Nikolas Falco 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package jenkins.plugin.android.emulator; 25 | 26 | import edu.umd.cs.findbugs.annotations.Nullable; 27 | 28 | import jenkins.model.Jenkins; 29 | 30 | import edu.umd.cs.findbugs.annotations.NonNull; 31 | import jenkins.plugin.android.emulator.tools.AndroidSDKInstallation; 32 | import jenkins.plugin.android.emulator.tools.AndroidSDKInstallation.DescriptorImpl; 33 | 34 | public final class AndroidSDKUtil { 35 | private AndroidSDKUtil() { 36 | // default constructor 37 | } 38 | 39 | /** 40 | * Gets the AndroidSDK to use. 41 | * 42 | * @param name the name of AndroidSDK installation 43 | * @return a AndroidSDK installation for the given name if exists, 44 | * {@code null} otherwise. 45 | */ 46 | @Nullable 47 | public static AndroidSDKInstallation getAndroidSDK(@Nullable String name) { 48 | if (name != null) { 49 | for (AndroidSDKInstallation installation : getInstallations()) { 50 | if (name.equals(installation.getName())) 51 | return installation; 52 | } 53 | } 54 | return null; 55 | } 56 | 57 | /** 58 | * Get all AndroidSDK installation defined in Jenkins. 59 | * 60 | * @return an array of AndroidSDK tool installation 61 | */ 62 | @NonNull 63 | public static AndroidSDKInstallation[] getInstallations() { 64 | DescriptorImpl descriptor = (DescriptorImpl) Jenkins.get().getDescriptorOrDie(AndroidSDKInstallation.class); 65 | return descriptor.getInstallations(); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugin/android/emulator/sdk/pipeline/ADBStep.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugin.android.emulator.sdk.pipeline; 2 | 3 | import java.util.Set; 4 | 5 | import org.jenkinsci.plugins.workflow.steps.StepContext; 6 | import org.jenkinsci.plugins.workflow.steps.StepDescriptor; 7 | import org.jenkinsci.plugins.workflow.steps.StepExecution; 8 | import org.kohsuke.stapler.DataBoundConstructor; 9 | 10 | import com.google.common.collect.ImmutableSet; 11 | 12 | import edu.umd.cs.findbugs.annotations.NonNull; 13 | 14 | import hudson.EnvVars; 15 | import hudson.Extension; 16 | import hudson.FilePath; 17 | import hudson.Launcher; 18 | import hudson.model.TaskListener; 19 | import jenkins.plugin.android.emulator.sdk.cli.ADBCLIBuilder; 20 | import jenkins.plugin.android.emulator.sdk.cli.CLICommand; 21 | import jenkins.plugin.android.emulator.sdk.home.HomeLocator; 22 | import jenkins.plugin.android.emulator.tools.AndroidSDKInstallation; 23 | 24 | public class ADBStep extends AbstractCLIStep { 25 | private class ADBStepExecution extends AbstractCLIStepExecution { 26 | private static final long serialVersionUID = 1L; 27 | 28 | protected ADBStepExecution(StepContext context) { 29 | super(emulatorTool, homeLocationStrategy, context); 30 | } 31 | 32 | @Override 33 | protected Void doRun(AndroidSDKInstallation sdk, TaskListener listener, EnvVars env) throws Exception { 34 | FilePath adb = sdk.getToolLocator().getADB(getContext().get(Launcher.class)); 35 | 36 | String[] argumentsExp = env.expand(arguments.replaceAll("[\t\r\n]+", " ")).split("\\s+"); 37 | CLICommand cli = ADBCLIBuilder.with(adb) // 38 | .arguments(argumentsExp) // 39 | .withEnv(env); 40 | return quiet ? cli.execute() : cli.execute(listener); 41 | } 42 | 43 | } 44 | 45 | private static final long serialVersionUID = -1557453962312014910L; 46 | 47 | @DataBoundConstructor 48 | public ADBStep(@NonNull String emulatorTool, @NonNull HomeLocator homeLocationStrategy, @NonNull String arguments) { 49 | super(emulatorTool, homeLocationStrategy, arguments); 50 | } 51 | 52 | @Override 53 | public StepExecution start(StepContext context) throws Exception { 54 | return new ADBStepExecution(context); 55 | } 56 | 57 | @Extension 58 | public static class DescriptorImpl extends StepDescriptor { 59 | @Override 60 | public String getFunctionName() { 61 | return "adb"; 62 | } 63 | 64 | @Override 65 | public String getDisplayName() { 66 | return Messages.ADBStep_displayName(); 67 | } 68 | 69 | @Override 70 | public Set> getRequiredContext() { 71 | return ImmutableSet.of(FilePath.class, TaskListener.class, Launcher.class, EnvVars.class); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugin/android/emulator/sdk/pipeline/AVDManagerStep.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugin.android.emulator.sdk.pipeline; 2 | 3 | import java.util.Set; 4 | 5 | import org.jenkinsci.plugins.workflow.steps.StepContext; 6 | import org.jenkinsci.plugins.workflow.steps.StepDescriptor; 7 | import org.jenkinsci.plugins.workflow.steps.StepExecution; 8 | import org.kohsuke.stapler.DataBoundConstructor; 9 | 10 | import com.google.common.collect.ImmutableSet; 11 | 12 | import edu.umd.cs.findbugs.annotations.NonNull; 13 | 14 | import hudson.EnvVars; 15 | import hudson.Extension; 16 | import hudson.FilePath; 17 | import hudson.Launcher; 18 | import hudson.model.TaskListener; 19 | import jenkins.plugin.android.emulator.sdk.cli.AVDManagerCLIBuilder; 20 | import jenkins.plugin.android.emulator.sdk.cli.CLICommand; 21 | import jenkins.plugin.android.emulator.sdk.home.HomeLocator; 22 | import jenkins.plugin.android.emulator.tools.AndroidSDKInstallation; 23 | 24 | public class AVDManagerStep extends AbstractCLIStep { 25 | private class AVDManagerStepExecution extends AbstractCLIStepExecution { 26 | private static final long serialVersionUID = 1L; 27 | 28 | protected AVDManagerStepExecution(StepContext context) { 29 | super(emulatorTool, homeLocationStrategy, context); 30 | } 31 | 32 | @Override 33 | protected Void doRun(AndroidSDKInstallation sdk, TaskListener listener, EnvVars env) throws Exception { 34 | FilePath avdManager = sdk.getToolLocator().getAVDManager(getContext().get(Launcher.class)); 35 | 36 | String[] argumentsExp = env.expand(arguments.replaceAll("[\t\r\n]+", " ")).split("\\s+"); 37 | CLICommand cli = AVDManagerCLIBuilder.with(avdManager) // 38 | .arguments(argumentsExp) // 39 | .withEnv(env); 40 | return quiet ? cli.execute() : cli.execute(listener); 41 | } 42 | 43 | } 44 | 45 | private static final long serialVersionUID = -9142762434729619710L; 46 | 47 | @DataBoundConstructor 48 | public AVDManagerStep(@NonNull String emulatorTool, @NonNull HomeLocator homeLocationStrategy, @NonNull String arguments) { 49 | super(emulatorTool, homeLocationStrategy, arguments); 50 | } 51 | 52 | @Override 53 | public StepExecution start(StepContext context) throws Exception { 54 | return new AVDManagerStepExecution(context); 55 | } 56 | 57 | @Extension 58 | public static class DescriptorImpl extends StepDescriptor { 59 | @Override 60 | public String getFunctionName() { 61 | return "avdmanager"; 62 | } 63 | 64 | @Override 65 | public String getDisplayName() { 66 | return Messages.AVDManagerStep_displayName(); 67 | } 68 | 69 | @Override 70 | public Set> getRequiredContext() { 71 | return ImmutableSet.of(FilePath.class, TaskListener.class, Launcher.class, EnvVars.class); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/monkey/MonkeyAction.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator.monkey; 2 | 3 | import hudson.model.Action; 4 | import hudson.plugins.android_emulator.Messages; 5 | 6 | import org.apache.commons.lang3.builder.HashCodeBuilder; 7 | import org.kohsuke.stapler.export.Exported; 8 | 9 | public class MonkeyAction implements Action { 10 | 11 | private final MonkeyResult result; 12 | private final int eventCount; 13 | private final int totalEventCount; 14 | 15 | public MonkeyAction(MonkeyResult outcome) { 16 | this(outcome, 0, 0); 17 | } 18 | 19 | public MonkeyAction(MonkeyResult outcome, int eventsCompleted, int configuredEvents) { 20 | this.result = outcome; 21 | this.eventCount = eventsCompleted; 22 | this.totalEventCount = configuredEvents; 23 | } 24 | 25 | @Exported 26 | public String getResultIcon() { 27 | if (result == MonkeyResult.Success) { 28 | return "monkey-happy_48x48.png"; 29 | } 30 | return "monkey-sad_48x48.png"; 31 | } 32 | 33 | @Exported 34 | public String getSummary() { 35 | String description; 36 | switch (result) { 37 | case Success: 38 | description = Messages.MONKEY_RESULT_SUCCESS(eventCount); 39 | break; 40 | case Crash: 41 | description = Messages.MONKEY_RESULT_CRASH(eventCount, totalEventCount); 42 | break; 43 | case AppNotResponding: 44 | description = Messages.MONKEY_RESULT_ANR(eventCount, totalEventCount); 45 | break; 46 | case UnrecognisedFormat: 47 | description = Messages.MONKEY_RESULT_UNRECOGNISED(); 48 | break; 49 | case NothingToParse: 50 | default: 51 | description = Messages.MONKEY_RESULT_NONE(); 52 | break; 53 | } 54 | return Messages.MONKEY_RESULT(description); 55 | } 56 | 57 | public String getDisplayName() { 58 | return null; 59 | } 60 | 61 | public String getIconFileName() { 62 | return null; 63 | } 64 | 65 | public String getUrlName() { 66 | return null; 67 | } 68 | 69 | @Override 70 | public boolean equals(Object that) { 71 | if (this == that) { 72 | return true; 73 | } 74 | if (!(that instanceof MonkeyAction)) { 75 | return false; 76 | } 77 | 78 | MonkeyAction other = (MonkeyAction) that; 79 | return result == other.result 80 | && eventCount == other.eventCount 81 | && totalEventCount == other.totalEventCount; 82 | } 83 | 84 | @Override 85 | public int hashCode() { 86 | return new HashCodeBuilder() 87 | .append(result) 88 | .append(eventCount) 89 | .append(totalEventCount) 90 | .toHashCode(); 91 | } 92 | 93 | @Override 94 | public String toString() { 95 | return String.format("%s:%d,%d", result, eventCount, totalEventCount); 96 | } 97 | 98 | } -------------------------------------------------------------------------------- /src/main/java/jenkins/plugin/android/emulator/sdk/pipeline/EmulatorStep.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugin.android.emulator.sdk.pipeline; 2 | 3 | import java.util.Set; 4 | 5 | import org.jenkinsci.plugins.workflow.steps.StepContext; 6 | import org.jenkinsci.plugins.workflow.steps.StepDescriptor; 7 | import org.jenkinsci.plugins.workflow.steps.StepExecution; 8 | import org.kohsuke.stapler.DataBoundConstructor; 9 | 10 | import com.google.common.collect.ImmutableSet; 11 | 12 | import edu.umd.cs.findbugs.annotations.NonNull; 13 | 14 | import hudson.EnvVars; 15 | import hudson.Extension; 16 | import hudson.FilePath; 17 | import hudson.Launcher; 18 | import hudson.model.TaskListener; 19 | import jenkins.model.Jenkins; 20 | import jenkins.plugin.android.emulator.sdk.cli.CLICommand; 21 | import jenkins.plugin.android.emulator.sdk.cli.EmulatorCLIBuilder; 22 | import jenkins.plugin.android.emulator.sdk.home.HomeLocator; 23 | import jenkins.plugin.android.emulator.tools.AndroidSDKInstallation; 24 | 25 | public class EmulatorStep extends AbstractCLIStep { 26 | private class EmulatorStepExecution extends AbstractCLIStepExecution { 27 | private static final long serialVersionUID = 1L; 28 | 29 | protected EmulatorStepExecution(StepContext context) { 30 | super(emulatorTool, homeLocationStrategy, context); 31 | } 32 | 33 | @Override 34 | protected Void doRun(AndroidSDKInstallation sdk, TaskListener listener, EnvVars env) throws Exception { 35 | FilePath emulator = sdk.getToolLocator().getEmulator(getContext().get(Launcher.class)); 36 | 37 | String[] argumentsExp = env.expand(arguments.replaceAll("[\t\r\n]+", " ")).split("\\s+"); 38 | CLICommand cli = EmulatorCLIBuilder.with(emulator) // 39 | .proxy(Jenkins.get().proxy) // 40 | .arguments(argumentsExp) // 41 | .withEnv(env); 42 | return quiet ? cli.execute() : cli.execute(listener); 43 | } 44 | 45 | } 46 | 47 | private static final long serialVersionUID = -1557453962312014910L; 48 | 49 | @DataBoundConstructor 50 | public EmulatorStep(@NonNull String emulatorTool, @NonNull HomeLocator homeLocationStrategy, @NonNull String arguments) { 51 | super(emulatorTool, homeLocationStrategy, arguments); 52 | } 53 | 54 | @Override 55 | public StepExecution start(StepContext context) throws Exception { 56 | return new EmulatorStepExecution(context); 57 | } 58 | 59 | @Extension 60 | public static class DescriptorImpl extends StepDescriptor { 61 | @Override 62 | public String getFunctionName() { 63 | return "emulator"; 64 | } 65 | 66 | @Override 67 | public String getDisplayName() { 68 | return Messages.EmulatorStep_displayName(); 69 | } 70 | 71 | @Override 72 | public Set> getRequiredContext() { 73 | return ImmutableSet.of(FilePath.class, TaskListener.class, Launcher.class, EnvVars.class); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugin/android/emulator/sdk/cli/Targets.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2020, Nikolas Falco 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package jenkins.plugin.android.emulator.sdk.cli; 25 | 26 | import java.util.Objects; 27 | 28 | public class Targets { 29 | 30 | public enum TargetType { 31 | platform; 32 | } 33 | 34 | private String id; 35 | private String name; 36 | private TargetType type; 37 | private int apiLevel = -1; 38 | private int revision = -1; 39 | 40 | public String getId() { 41 | return id; 42 | } 43 | 44 | public void setId(String id) { 45 | this.id = id; 46 | } 47 | 48 | public String getName() { 49 | return name; 50 | } 51 | 52 | public void setName(String name) { 53 | this.name = name; 54 | } 55 | 56 | public TargetType getType() { 57 | return type; 58 | } 59 | 60 | public void setType(TargetType type) { 61 | this.type = type; 62 | } 63 | 64 | public int getApiLevel() { 65 | return apiLevel; 66 | } 67 | 68 | public void setAPILevel(int apiLevel) { 69 | this.apiLevel = apiLevel; 70 | } 71 | 72 | public int getRevision() { 73 | return revision; 74 | } 75 | 76 | public void setRevision(int revision) { 77 | this.revision = revision; 78 | } 79 | 80 | @Override 81 | public int hashCode() { 82 | return Objects.hash(id, revision); 83 | } 84 | 85 | @Override 86 | public boolean equals(Object obj) { 87 | if (this == obj) { 88 | return true; 89 | } 90 | if (obj == null) { 91 | return false; 92 | } 93 | if (getClass() != obj.getClass()) { 94 | return false; 95 | } 96 | Targets other = (Targets) obj; 97 | return Objects.equals(id, other.id) && revision == other.revision; 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/sdk/cli/SdkCliCommandFactory.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator.sdk.cli; 2 | 3 | import hudson.plugins.android_emulator.sdk.AndroidSdk; 4 | import hudson.plugins.android_emulator.util.Utils; 5 | 6 | /** 7 | * This helper class is used to retrieve the correct implementation 8 | * of the {@code adb shell} and the Android SDK tools commands for 9 | * the given device API level respective for the SDK version. 10 | */ 11 | public final class SdkCliCommandFactory { 12 | 13 | // empty private constructor to avoid instantiation and sub-classing 14 | private SdkCliCommandFactory() {} 15 | 16 | /** 17 | * Retrieve the correct {@code AdbShellCommands} for the given API-Level of the 18 | * device where the commands should run. 19 | * 20 | * @param deviceAPILevel Android API-Level for the target device 21 | * @return an object of an class implementing the {@code AdbShellCommands} interface to retrieve 22 | * the correct {@code adb shell} commands for the given API-level 23 | */ 24 | public static AdbShellCommands getAdbShellCommandForAPILevel(final int deviceAPILevel) { 25 | if (deviceAPILevel < 4) { 26 | return new AdbShellCommand00To03(); 27 | } else if (deviceAPILevel < 23) { 28 | return new AdbShellCommand04To22(); 29 | } else { 30 | return new AdbShellCommandsCurrentBase(); 31 | } 32 | } 33 | 34 | /** 35 | * Retrieve the correct {@code SdkCommands} for the given Android SDK. 36 | * 37 | * @param androidSdk SDK tools to extract the current version and retrieve the correct commands 38 | * @return an object of an class implementing the {@code SdkToolsCommands} interface to retrieve 39 | * the correct tools commands for the given SDK 40 | */ 41 | public static SdkToolsCommands getCommandsForSdk(final AndroidSdk androidSdk) { 42 | if (androidSdk != null && androidSdk.hasCommandLineTools()) { 43 | return new SdkToolsCommandsCurrentBase(); 44 | } 45 | 46 | // if no androidSdk is given, simply assume the latest commands 47 | final String sdkToolsVersion = (androidSdk != null) ? androidSdk.getSdkToolsVersion() : String.valueOf(Integer.MAX_VALUE); 48 | return SdkCliCommandFactory.getCommandsForSdk(sdkToolsVersion); 49 | } 50 | 51 | /** 52 | * Retrieve the correct {@code SdkCommands} for the given SDK Tools major version. 53 | * 54 | * @param sdkToolsVersion SDK Tools version, to retrieve the correct commands 55 | * @return an object of an class implementing the {@code SdkToolsCommands} interface to retrieve 56 | * the correct tools commands for the given SDK version 57 | */ 58 | public static SdkToolsCommands getCommandsForSdk(final String sdkToolsVersion) { 59 | if (Utils.isVersionOlderThan(sdkToolsVersion, "17")) { 60 | return new SdkToolsCommands00To16(); 61 | } else if (Utils.isVersionOlderThan(sdkToolsVersion, "25.3")) { 62 | return new SdkToolsCommands17To25_2(); 63 | } else { 64 | return new SdkToolsCommandsCurrentBase(); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugin/android/emulator/sdk/home/PerExecutorHomeLocator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2020, Nikolas Falco 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package jenkins.plugin.android.emulator.sdk.home; 25 | 26 | import org.jenkinsci.Symbol; 27 | import org.kohsuke.stapler.DataBoundConstructor; 28 | 29 | import hudson.Extension; 30 | 31 | import edu.umd.cs.findbugs.annotations.NonNull; 32 | import hudson.FilePath; 33 | import hudson.model.Computer; 34 | import hudson.model.Executor; 35 | import hudson.model.Node; 36 | import jenkins.plugin.android.emulator.Messages; 37 | 38 | /** 39 | * Relocates the default SDk home to a folder specific for the executor in the 40 | * node home folder {@code ~/android_$executorNumber/.android}. 41 | */ 42 | public class PerExecutorHomeLocator extends HomeLocator { 43 | 44 | private static final long serialVersionUID = 5353670448852887996L; 45 | 46 | @DataBoundConstructor 47 | public PerExecutorHomeLocator() { 48 | // default constructor 49 | } 50 | 51 | @Override 52 | public FilePath locate(@NonNull FilePath workspace) { 53 | final Computer computer = workspace.toComputer(); 54 | if (computer == null) { 55 | throw new IllegalStateException(Messages.nodeNotAvailable()); 56 | } 57 | final Node node = computer.getNode(); 58 | if (node == null) { 59 | throw new IllegalStateException(Messages.nodeNotAvailable()); 60 | } 61 | final FilePath rootPath = node.getRootPath(); 62 | final Executor executor = Executor.currentExecutor(); 63 | if (rootPath == null || executor == null) { 64 | return null; 65 | } 66 | return rootPath.child("android_" + executor.getNumber()); 67 | } 68 | 69 | @Extension 70 | @Symbol("executor") 71 | public static class DescriptorImpl extends HomeLocatorDescriptor { 72 | @Override 73 | public String getDisplayName() { 74 | return Messages.ExecutorHomeLocationLocator_displayName(); 75 | } 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugin/android/emulator/sdk/pipeline/SDKManagerStep.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugin.android.emulator.sdk.pipeline; 2 | 3 | import java.util.Set; 4 | 5 | import org.jenkinsci.plugins.workflow.steps.StepContext; 6 | import org.jenkinsci.plugins.workflow.steps.StepDescriptor; 7 | import org.jenkinsci.plugins.workflow.steps.StepExecution; 8 | import org.kohsuke.stapler.DataBoundConstructor; 9 | 10 | import com.google.common.collect.ImmutableSet; 11 | 12 | import edu.umd.cs.findbugs.annotations.NonNull; 13 | 14 | import hudson.EnvVars; 15 | import hudson.Extension; 16 | import hudson.FilePath; 17 | import hudson.Launcher; 18 | import hudson.model.TaskListener; 19 | import hudson.plugins.android_emulator.Constants; 20 | import jenkins.model.Jenkins; 21 | import jenkins.plugin.android.emulator.sdk.cli.CLICommand; 22 | import jenkins.plugin.android.emulator.sdk.cli.SDKManagerCLIBuilder; 23 | import jenkins.plugin.android.emulator.sdk.home.HomeLocator; 24 | import jenkins.plugin.android.emulator.tools.AndroidSDKInstallation; 25 | 26 | public class SDKManagerStep extends AbstractCLIStep { 27 | private class SDKManagerStepExecution extends AbstractCLIStepExecution { 28 | private static final long serialVersionUID = 1L; 29 | 30 | protected SDKManagerStepExecution(StepContext context) { 31 | super(emulatorTool, homeLocationStrategy, context); 32 | } 33 | 34 | @Override 35 | protected Void doRun(AndroidSDKInstallation sdk, TaskListener listener, EnvVars env) throws Exception { 36 | FilePath sdkManager = sdk.getToolLocator().getSDKManager(getContext().get(Launcher.class)); 37 | 38 | String[] argumentsExp = env.expand(arguments.replaceAll("[\t\r\n]+", " ")).split("\\s+"); 39 | CLICommand cli = SDKManagerCLIBuilder.with(sdkManager) // 40 | .proxy(Jenkins.get().proxy) // 41 | .sdkRoot(env.get(Constants.ENV_VAR_ANDROID_SDK_ROOT)) // 42 | .arguments(argumentsExp) // 43 | .withEnv(env); 44 | return quiet ? cli.execute() : cli.execute(listener); 45 | } 46 | 47 | } 48 | 49 | private static final long serialVersionUID = -1557453962312014910L; 50 | 51 | @DataBoundConstructor 52 | public SDKManagerStep(@NonNull String emulatorTool, @NonNull HomeLocator homeLocationStrategy, @NonNull String arguments) { 53 | super(emulatorTool, homeLocationStrategy, arguments); 54 | } 55 | 56 | @Override 57 | public StepExecution start(StepContext context) throws Exception { 58 | return new SDKManagerStepExecution(context); 59 | } 60 | 61 | @Extension 62 | public static class DescriptorImpl extends StepDescriptor { 63 | @Override 64 | public String getFunctionName() { 65 | return "sdkmanager"; 66 | } 67 | 68 | @Override 69 | public String getDisplayName() { 70 | return Messages.AVDManagerStep_displayName(); 71 | } 72 | 73 | @Override 74 | public Set> getRequiredContext() { 75 | return ImmutableSet.of(FilePath.class, TaskListener.class, Launcher.class, EnvVars.class); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/test/java/hudson/plugins/android_emulator/sdk/AndroidSdkTest.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator.sdk; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.IOException; 6 | 7 | import static org.junit.jupiter.api.Assertions.assertEquals; 8 | import static org.junit.jupiter.api.Assertions.assertFalse; 9 | import static org.junit.jupiter.api.Assertions.assertTrue; 10 | 11 | class AndroidSdkTest { 12 | 13 | @Test 14 | void testGetSdkToolsMajorVersion() { 15 | assertEquals(0, createSdkWithTools(null).getSdkToolsMajorVersion()); 16 | assertEquals(20, createSdkWithTools("20").getSdkToolsMajorVersion()); 17 | assertEquals(20, createSdkWithTools("20.0").getSdkToolsMajorVersion()); 18 | assertEquals(20, createSdkWithTools("20.0.1").getSdkToolsMajorVersion()); 19 | assertEquals(21, createSdkWithTools("21 rc3").getSdkToolsMajorVersion()); 20 | assertEquals(22, createSdkWithTools("22.6").getSdkToolsMajorVersion()); 21 | } 22 | 23 | @Test 24 | void testEmulatorEngineV2Support() { 25 | assertEqualsHelperEmulatorEngineV2Support(null, false, false); 26 | assertEqualsHelperEmulatorEngineV2Support("24", false, false); 27 | assertEqualsHelperEmulatorEngineV2Support("24.1.1", false, false); 28 | assertEqualsHelperEmulatorEngineV2Support("24.4", false, false); 29 | assertEqualsHelperEmulatorEngineV2Support("25", true, false); 30 | assertEqualsHelperEmulatorEngineV2Support("25.2.1", true, false); 31 | assertEqualsHelperEmulatorEngineV2Support("25.3", true, false); 32 | assertEqualsHelperEmulatorEngineV2Support("26", true, true); 33 | assertEqualsHelperEmulatorEngineV2Support("26.1.1", true, true); 34 | assertEqualsHelperEmulatorEngineV2Support("27", true, true); 35 | } 36 | 37 | @Test 38 | void testUseLegacySdkStructure() { 39 | assertFalse(createSdkWithTools(null).useLegacySdkStructure()); 40 | assertTrue(createSdkWithTools("24").useLegacySdkStructure()); 41 | assertTrue(createSdkWithTools("24.1.1").useLegacySdkStructure()); 42 | assertTrue(createSdkWithTools("24.4").useLegacySdkStructure()); 43 | assertTrue(createSdkWithTools("25").useLegacySdkStructure()); 44 | assertTrue(createSdkWithTools("25.2.1").useLegacySdkStructure()); 45 | assertFalse(createSdkWithTools("25.3").useLegacySdkStructure()); 46 | assertFalse(createSdkWithTools("26").useLegacySdkStructure()); 47 | assertFalse(createSdkWithTools("26.1.1").useLegacySdkStructure()); 48 | assertFalse(createSdkWithTools("27").useLegacySdkStructure()); 49 | } 50 | 51 | private void assertEqualsHelperEmulatorEngineV2Support(final String sdkToolsVersion, 52 | final boolean expectedSupportV2Result, final boolean expectedSupportV2FullResult) { 53 | 54 | final AndroidSdk androidSdk = createSdkWithTools(sdkToolsVersion); 55 | assertEquals(expectedSupportV2Result, androidSdk.supportsEmulatorV2()); 56 | assertEquals(expectedSupportV2FullResult, androidSdk.supportsEmulatorV2Full()); 57 | } 58 | 59 | private static AndroidSdk createSdkWithTools(String version) { 60 | AndroidSdk sdk = null; 61 | try { 62 | sdk = new AndroidSdk(null, null); 63 | sdk.setSdkToolsVersion(version); 64 | } catch (IOException e) { 65 | e.printStackTrace(); 66 | } 67 | return sdk; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugin/android/emulator/sdk/home/HomeLocator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2020, Nikolas Falco 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package jenkins.plugin.android.emulator.sdk.home; 25 | 26 | import java.io.IOException; 27 | import java.io.Serializable; 28 | 29 | import edu.umd.cs.findbugs.annotations.CheckForNull; 30 | 31 | import hudson.AbortException; 32 | 33 | import edu.umd.cs.findbugs.annotations.NonNull; 34 | import hudson.EnvVars; 35 | import hudson.ExtensionPoint; 36 | import hudson.FilePath; 37 | import hudson.model.AbstractDescribableImpl; 38 | import hudson.model.Computer; 39 | import hudson.plugins.android_emulator.Constants; 40 | import jenkins.plugin.android.emulator.AndroidSDKConstants; 41 | 42 | /** 43 | * Strategy pattern that decides the location of the SDK home location for a 44 | * build. 45 | */ 46 | public abstract class HomeLocator extends AbstractDescribableImpl implements ExtensionPoint, Serializable { 47 | 48 | private static final long serialVersionUID = 1L; 49 | 50 | /** 51 | * Called during the build on the master to determine the location of the 52 | * local SDK home location. 53 | * 54 | * @param workspace the workspace file path locator 55 | * @return null to let SDK build tool uses its default location. Otherwise 56 | * this must be located on the same node as described by this path. 57 | */ 58 | public abstract FilePath locate(@NonNull FilePath workspace); 59 | 60 | @Override 61 | public HomeLocatorDescriptor getDescriptor() { 62 | return (HomeLocatorDescriptor) super.getDescriptor(); 63 | } 64 | 65 | public static void buildEnvVars(@NonNull FilePath homeLocation, @CheckForNull EnvVars env) throws IOException, InterruptedException { 66 | if (env == null) { 67 | env = new EnvVars(); 68 | } 69 | env.put(Constants.ENV_VAR_ANDROID_SDK_HOME, homeLocation.getRemote()); 70 | 71 | FilePath emulatorLocation = homeLocation.child(AndroidSDKConstants.ANDROID_CACHE); 72 | env.put(AndroidSDKConstants.ENV_ANDROID_EMULATOR_HOME, emulatorLocation.getRemote()); 73 | 74 | FilePath avdPath = emulatorLocation.child("avd"); 75 | avdPath.mkdirs(); // ensure that this folder exists 76 | env.put(AndroidSDKConstants.ENV_ANDROID_AVD_HOME, avdPath.getRemote()); 77 | } 78 | } -------------------------------------------------------------------------------- /src/main/java/jenkins/plugin/android/emulator/sdk/cli/SDKPackages.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugin.android.emulator.sdk.cli; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | import java.util.Objects; 6 | 7 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 8 | 9 | public class SDKPackages { 10 | public static class SDKPackage implements Comparable { 11 | private String id; 12 | private Version version; 13 | private Version available; 14 | private String description; 15 | private String location; 16 | 17 | public String getId() { 18 | return id; 19 | } 20 | 21 | public void setId(String id) { 22 | this.id = id; 23 | } 24 | 25 | public Version getVersion() { 26 | return version; 27 | } 28 | 29 | public void setVersion(Version version) { 30 | this.version = version; 31 | } 32 | 33 | public String getDescription() { 34 | return description; 35 | } 36 | 37 | public void setDescription(String description) { 38 | this.description = description; 39 | } 40 | 41 | public String getLocation() { 42 | return location; 43 | } 44 | 45 | public void setLocation(String location) { 46 | this.location = location; 47 | } 48 | 49 | public Version getAvailable() { 50 | return available; 51 | } 52 | 53 | public void setAvailable(Version available) { 54 | this.available = available; 55 | } 56 | 57 | @Override 58 | public int compareTo(SDKPackage o) { 59 | if (o == null) { 60 | return 1; 61 | } 62 | int result = id.compareTo(o.getId()); 63 | if (result == 0) { 64 | result = version.compareTo(o.getVersion()); 65 | } 66 | return result; 67 | } 68 | 69 | @Override 70 | public int hashCode() { 71 | return Objects.hash(id, version); 72 | } 73 | 74 | @Override 75 | public boolean equals(Object obj) { 76 | if (this == obj) { 77 | return true; 78 | } 79 | if (obj == null) { 80 | return false; 81 | } 82 | if (getClass() != obj.getClass()) 83 | return false; 84 | SDKPackage other = (SDKPackage) obj; 85 | return Objects.equals(id, other.id) && Objects.equals(version, other.version); 86 | } 87 | 88 | @Override 89 | public String toString() { 90 | return id; 91 | } 92 | } 93 | 94 | private List available = new LinkedList<>(); 95 | private List updates = new LinkedList<>(); 96 | private List installed = new LinkedList<>(); 97 | 98 | public List getAvailable() { 99 | return available; 100 | } 101 | 102 | public void setAvailable(List available) { 103 | this.available = available; 104 | } 105 | 106 | public List getUpdates() { 107 | return updates; 108 | } 109 | 110 | public void setUpdates(List updates) { 111 | this.updates = updates; 112 | } 113 | 114 | public List getInstalled() { 115 | return installed; 116 | } 117 | 118 | public void setInstalled(List installed) { 119 | this.installed = installed; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugin/android/emulator/AndroidEmulatorBuild/config.jelly: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 27 | 28 | 29 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 |

73 | 74 |
75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugin/android/emulator/tools/Platform.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2020, Nikolas Falco 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package jenkins.plugin.android.emulator.tools; 25 | 26 | import java.io.IOException; 27 | import java.util.Locale; 28 | import java.util.Map; 29 | 30 | import hudson.model.Computer; 31 | import hudson.model.Node; 32 | import jenkins.plugin.android.emulator.Messages; 33 | 34 | /** 35 | * Supported platform. 36 | */ 37 | public enum Platform { 38 | LINUX("", "bin"), WINDOWS(".bat", "bin"), OSX(".sh", "bin"); 39 | 40 | /** 41 | * Choose the extension file name suitable to run cli commands. 42 | */ 43 | public final String extension; 44 | /** 45 | * Choose the folder path suitable bin folder of the bundle. 46 | */ 47 | public final String binFolder; 48 | 49 | Platform(String extension, String binFolder) { 50 | this.extension = extension; 51 | this.binFolder = binFolder; 52 | } 53 | 54 | public boolean is(String line) { 55 | return line.contains(name()); 56 | } 57 | 58 | /** 59 | * Determines the platform of the given node. 60 | * 61 | * @param node 62 | * the computer node 63 | * @return a platform value that represent the given node 64 | * @throws DetectionFailedException 65 | * when the current platform node is not supported. 66 | */ 67 | public static Platform of(Node node) throws DetectionFailedException { 68 | try { 69 | Computer computer = node.toComputer(); 70 | if (computer == null) { 71 | throw new DetectionFailedException(Messages.nodeNotAvailable()); 72 | } 73 | return detect(computer.getSystemProperties()); 74 | } catch (IOException | InterruptedException e) { 75 | throw new DetectionFailedException(Messages.SystemTools_failureOnProperties(), e); 76 | } 77 | } 78 | 79 | public static Platform current() throws DetectionFailedException { 80 | return detect(System.getProperties()); 81 | } 82 | 83 | private static Platform detect(Map systemProperties) throws DetectionFailedException { 84 | String arch = ((String) systemProperties.get("os.name")).toLowerCase(Locale.ENGLISH); 85 | if (arch.contains("linux")) { 86 | return LINUX; 87 | } 88 | if (arch.contains("windows")) { 89 | return WINDOWS; 90 | } 91 | if (arch.contains("mac")) { 92 | return OSX; 93 | } 94 | throw new DetectionFailedException(Messages.Platform_unknown(arch)); 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/ScreenResolution.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator; 2 | 3 | import java.io.Serializable; 4 | 5 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 6 | import hudson.Util; 7 | 8 | @SuppressWarnings("serial") 9 | public class ScreenResolution implements Serializable { 10 | public static final ScreenResolution QVGA = new ScreenResolution(240, 320, "QVGA", "QVGA"); 11 | public static final ScreenResolution WQVGA = new ScreenResolution(240, 400, "WQVGA", "WQVGA400"); 12 | public static final ScreenResolution FWQVGA = new ScreenResolution(240, 432, "FWQVGA", "WQVGA432"); 13 | public static final ScreenResolution HVGA = new ScreenResolution(320, 480, "HVGA", "HVGA"); 14 | public static final ScreenResolution WVGA = new ScreenResolution(480, 800, "WVGA", "WVGA800"); 15 | public static final ScreenResolution FWVGA = new ScreenResolution(480, 854, "FWVGA", "WVGA854"); 16 | public static final ScreenResolution WSVGA = new ScreenResolution(1024, 654, "WSVGA", "WSVGA"); 17 | public static final ScreenResolution WXGA_720 = new ScreenResolution(1280, 720, "WXGA720", "WXGA720"); 18 | public static final ScreenResolution WXGA_800 = new ScreenResolution(1280, 800, "WXGA800", "WXGA800"); 19 | public static final ScreenResolution WXGA = new ScreenResolution(1280, 800, "WXGA", "WXGA"); 20 | private static final ScreenResolution[] PRESETS = new ScreenResolution[] { QVGA, WQVGA, FWQVGA, HVGA, 21 | WVGA, FWVGA, WSVGA, 22 | WXGA_720, WXGA_800, WXGA }; 23 | 24 | public static ScreenResolution[] values() { 25 | return PRESETS; 26 | } 27 | 28 | public static ScreenResolution valueOf(String resolution) { 29 | if (Util.fixEmptyAndTrim(resolution) == null) { 30 | return null; 31 | } 32 | 33 | // Try matching against aliases 34 | for (ScreenResolution preset : PRESETS) { 35 | if (resolution.equalsIgnoreCase(preset.alias)) { 36 | return preset; 37 | } 38 | } 39 | 40 | // Check for pixel values 41 | resolution = resolution.toLowerCase(); 42 | if (!resolution.matches(Constants.REGEX_SCREEN_RESOLUTION)) { 43 | return null; 44 | } 45 | 46 | // Try matching against pixel values 47 | int index = resolution.indexOf('x'); 48 | int width = 0; 49 | int height = 0; 50 | try { 51 | width = Integer.parseInt(resolution.substring(0, index)); 52 | height = Integer.parseInt(resolution.substring(index+1)); 53 | } catch (NumberFormatException ex) { 54 | return null; 55 | } 56 | for (ScreenResolution preset : PRESETS) { 57 | if (width == preset.width && height == preset.height) { 58 | return preset; 59 | } 60 | } 61 | 62 | // Return custom value 63 | return new ScreenResolution(width, height); 64 | } 65 | 66 | private final int width; 67 | private final int height; 68 | private final String alias; 69 | private final String skinName; 70 | 71 | private ScreenResolution(int width, int height, String alias, String skinName) { 72 | this.width = width; 73 | this.height = height; 74 | this.alias = alias; 75 | this.skinName = skinName; 76 | } 77 | 78 | private ScreenResolution(int width, int height) { 79 | this(width, height, null, null); 80 | } 81 | 82 | public boolean isCustomResolution() { 83 | return alias == null; 84 | } 85 | 86 | public String getSkinName() { 87 | if (isCustomResolution()) { 88 | return getDimensionString(); 89 | } 90 | 91 | return skinName; 92 | } 93 | 94 | public String getDimensionString() { 95 | return width +"x"+ height; 96 | } 97 | 98 | @Override 99 | public String toString() { 100 | if (isCustomResolution()) { 101 | return getDimensionString(); 102 | } 103 | 104 | return alias; 105 | } 106 | 107 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.jenkins-ci.plugins 7 | plugin 8 | 5.28 9 | 10 | 11 | 12 | android-emulator 13 | hpi 14 | ${changelist} 15 | Android Emulator Plugin 16 | https://github.com/jenkinsci/android-emulator-plugin 17 | 18 | 19 | 999999-SNAPSHOT 20 | 2.479 21 | ${jenkins.baseline}.3 22 | jenkinsci/${project.artifactId}-plugin 23 | false 24 | 25 | 26 | 27 | 28 | orrc 29 | Christopher Orr 30 | chris@orr.me.uk 31 | 1 32 | 33 | 34 | nfalco79 35 | Nikolas Falco 36 | nfalco79@hotmail.com 37 | +1 38 | 39 | 40 | 41 | 42 | 43 | The MIT License (MIT) 44 | https://opensource.org/licenses/MIT 45 | repo 46 | 47 | 48 | 49 | 50 | 51 | 52 | io.jenkins.tools.bom 53 | bom-${jenkins.baseline}.x 54 | 5054.v620b_5d2b_d5e6 55 | pom 56 | import 57 | 58 | 59 | 60 | 61 | 62 | 63 | io.jenkins.plugins 64 | commons-lang3-api 65 | 66 | 67 | org.jenkins-ci.plugins 68 | port-allocator 69 | 351.v7a_94cf6e4677 70 | compile 71 | 72 | 73 | org.jenkins-ci.plugins 74 | matrix-project 75 | 76 | 77 | 78 | org.apache.commons 79 | commons-lang3 80 | 81 | 82 | 83 | 84 | net.dongliu 85 | apk-parser 86 | 2.6.10 87 | 88 | 89 | org.mockito 90 | mockito-core 91 | test 92 | 93 | 94 | org.assertj 95 | assertj-core 96 | 3.27.6 97 | test 98 | 99 | 100 | 101 | 102 | scm:git:https://github.com/${gitHubRepo}.git 103 | scm:git:git@github.com:${gitHubRepo}.git 104 | https://github.com/${gitHubRepo} 105 | ${scmTag} 106 | 107 | 108 | 109 | 110 | repo.jenkins-ci.org 111 | https://repo.jenkins-ci.org/public/ 112 | 113 | 114 | 115 | 116 | 117 | repo.jenkins-ci.org 118 | https://repo.jenkins-ci.org/public/ 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugin/android/emulator/sdk/cli/ADBCLIBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2020, Nikolas Falco 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package jenkins.plugin.android.emulator.sdk.cli; 25 | 26 | import edu.umd.cs.findbugs.annotations.CheckForNull; 27 | import edu.umd.cs.findbugs.annotations.Nullable; 28 | 29 | import hudson.EnvVars; 30 | import hudson.FilePath; 31 | import hudson.util.ArgumentListBuilder; 32 | import jenkins.plugin.android.emulator.AndroidSDKConstants; 33 | 34 | /** 35 | * Build a command line argument for emulator command. 36 | * 37 | * @author Nikolas Falco 38 | */ 39 | public class ADBCLIBuilder { 40 | 41 | private static final String ARG_START_SERVER = "start-server"; 42 | private static final String ARG_KILL_SERVER = "kill-server"; 43 | 44 | public static ADBCLIBuilder with(@Nullable FilePath executable) { 45 | return new ADBCLIBuilder(executable); 46 | } 47 | 48 | private FilePath executable; 49 | private String serial; 50 | private boolean trace = false; 51 | private int port = 5037; 52 | private int maxEmulator = 16; 53 | 54 | private ADBCLIBuilder(@CheckForNull FilePath executable) { 55 | if (executable == null) { 56 | throw new IllegalArgumentException("Invalid empty or null executable"); 57 | } 58 | this.executable = executable; 59 | } 60 | 61 | public ADBCLIBuilder serial(String serial) { 62 | this.serial = serial; 63 | return this; 64 | } 65 | 66 | public ADBCLIBuilder port(int port) { 67 | if (port <= 1023) { // system ports 68 | throw new IllegalArgumentException("Invalid port " + port); 69 | } 70 | this.port = port; 71 | return this; 72 | } 73 | 74 | public ADBCLIBuilder maxEmulators(int maxEmulator) { 75 | this.maxEmulator = maxEmulator; 76 | return this; 77 | } 78 | 79 | public ADBCLIBuilder trace() { 80 | this.trace = true; 81 | return this; 82 | } 83 | 84 | public CLICommand start() { 85 | ArgumentListBuilder arguments = buildGlobalOptions(); 86 | arguments.add(ARG_START_SERVER); 87 | 88 | return new CLICommand<>(executable, arguments, buildEnvVars()); 89 | } 90 | 91 | private EnvVars buildEnvVars() { 92 | EnvVars env = new EnvVars(); 93 | if (trace) { 94 | env.put(AndroidSDKConstants.ENV_ADB_TRACE, "all,adb,sockets,packets,rwx,usb,sync,sysdeps,transport,jdwp"); 95 | } 96 | env.put(AndroidSDKConstants.ENV_ADB_LOCAL_TRANSPORT_MAX_PORT, String.valueOf(5553 + (maxEmulator * 2))); 97 | return env; 98 | } 99 | 100 | public CLICommand stop() { 101 | ArgumentListBuilder arguments = buildGlobalOptions(); 102 | arguments.add(ARG_KILL_SERVER); 103 | 104 | return new CLICommand<>(executable, arguments, buildEnvVars()); 105 | } 106 | 107 | private ArgumentListBuilder buildGlobalOptions() { 108 | ArgumentListBuilder arguments = new ArgumentListBuilder(); 109 | 110 | if (serial != null) { 111 | arguments.add("-s", serial); 112 | } 113 | 114 | arguments.add("-P", String.valueOf(port)); 115 | return arguments; 116 | } 117 | 118 | public CLICommand arguments(String[] args) { 119 | ArgumentListBuilder arguments = buildGlobalOptions(); 120 | arguments.add(args); 121 | 122 | return new CLICommand(executable, arguments, buildEnvVars()) // 123 | .withInput("\r\n"); 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/UninstallBuilder.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator; 2 | 3 | import hudson.Extension; 4 | import hudson.Functions; 5 | import hudson.Launcher; 6 | import hudson.Util; 7 | import hudson.model.AbstractBuild; 8 | import hudson.model.BuildListener; 9 | import hudson.model.Descriptor; 10 | import hudson.plugins.android_emulator.builder.AbstractBuilder; 11 | import hudson.plugins.android_emulator.sdk.AndroidSdk; 12 | import hudson.plugins.android_emulator.util.Utils; 13 | import hudson.plugins.android_emulator.util.ValidationResult; 14 | import hudson.tasks.Builder; 15 | import hudson.util.FormValidation; 16 | import net.sf.json.JSONObject; 17 | import org.kohsuke.stapler.DataBoundConstructor; 18 | import org.kohsuke.stapler.QueryParameter; 19 | import org.kohsuke.stapler.StaplerRequest; 20 | 21 | import java.io.IOException; 22 | import java.io.PrintStream; 23 | import java.io.Serializable; 24 | 25 | public class UninstallBuilder extends AbstractBuilder { 26 | 27 | /** Package ID of the APK to be uninstalled. */ 28 | private final String packageId; 29 | 30 | /** Whether to fail the build if uninstallation isn't successful. */ 31 | private final boolean failOnUninstallFailure; 32 | 33 | @DataBoundConstructor 34 | @SuppressWarnings("hiding") 35 | public UninstallBuilder(String packageId, boolean failOnUninstallFailure) { 36 | this.packageId = Util.fixEmptyAndTrim(packageId); 37 | this.failOnUninstallFailure = failOnUninstallFailure; 38 | } 39 | 40 | public String getPackageId() { 41 | return packageId; 42 | } 43 | 44 | public boolean shouldFailBuildOnFailure() { 45 | return failOnUninstallFailure; 46 | } 47 | 48 | @Override 49 | public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) 50 | throws InterruptedException, IOException { 51 | final PrintStream logger = listener.getLogger(); 52 | 53 | // Discover Android SDK 54 | AndroidSdk androidSdk = getAndroidSdk(build, launcher, listener); 55 | if (androidSdk == null) { 56 | return false; 57 | } 58 | 59 | // Check whether a value was provided 60 | final String packageId = getPackageId(); 61 | if (Util.fixEmptyAndTrim(packageId) == null) { 62 | AndroidEmulator.log(logger, Messages.PACKAGE_ID_NOT_SPECIFIED()); 63 | return false; 64 | } 65 | 66 | // Expand package ID value 67 | String expandedPackageId = Utils.expandVariables(build, listener, packageId); 68 | String deviceIdentifier = getDeviceIdentifier(build, listener); 69 | 70 | // Wait for package manager to become ready 71 | AndroidEmulator.log(logger, Messages.WAITING_FOR_CORE_PROCESS()); 72 | boolean ready = waitForCoreProcess(build, launcher, androidSdk, deviceIdentifier); 73 | if (!ready) { 74 | AndroidEmulator.log(logger, Messages.CORE_PROCESS_DID_NOT_START()); 75 | } 76 | 77 | // Execute uninstallation 78 | boolean success = uninstallApk(build, launcher, logger, androidSdk, deviceIdentifier, expandedPackageId); 79 | if (!success && failOnUninstallFailure) { 80 | return false; 81 | } 82 | return true; 83 | } 84 | 85 | @Extension 86 | public static final class DescriptorImpl extends Descriptor implements Serializable { 87 | 88 | private static final long serialVersionUID = 1L; 89 | 90 | public DescriptorImpl() { 91 | super(UninstallBuilder.class); 92 | load(); 93 | } 94 | 95 | @Override 96 | public boolean configure(final StaplerRequest req, final JSONObject formData) { 97 | save(); 98 | return true; 99 | } 100 | 101 | @Override 102 | public String getHelpFile() { 103 | return Functions.getResourcePath() + "/plugin/android-emulator/help-uninstallPackage.html"; 104 | } 105 | 106 | @Override 107 | public String getDisplayName() { 108 | return Messages.UNINSTALL_ANDROID_PACKAGE(); 109 | } 110 | 111 | /** 112 | * Check the package id is set in the uninstall configure 113 | * @param value specified by UninstallBuilder/config.jelly 114 | * @return FormValidation 115 | */ 116 | public FormValidation doCheckPackageId(@QueryParameter String value) { 117 | // trim first 118 | String packageId = Util.fixEmptyAndTrim(value); 119 | 120 | if (packageId == null || packageId.length() == 0) { 121 | return ValidationResult.error(Messages.PACKAGE_ID_NOT_SPECIFIED()).getFormValidation(); 122 | } 123 | 124 | return ValidationResult.ok().getFormValidation(); 125 | } 126 | 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/sdk/cli/SdkToolsCommands17To25_2.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator.sdk.cli; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.regex.Matcher; 6 | import java.util.regex.Pattern; 7 | 8 | import org.apache.commons.lang3.StringUtils; 9 | 10 | import hudson.plugins.android_emulator.sdk.Tool; 11 | 12 | /** 13 | * Extends {@code SdkToolsCommandsCurrentBase} and simply overwrites the commands 14 | * which differ for SDK Tools version 17 to 25.2. 15 | */ 16 | public class SdkToolsCommands17To25_2 extends SdkToolsCommandsCurrentBase implements SdkToolsCommands { 17 | 18 | @Override 19 | public SdkCliCommand getSdkInstallAndUpdateCommand(final String proxySettings, final List components) { 20 | final List complist = new ArrayList(components); 21 | complist.remove("emulator"); 22 | 23 | final String upgradeArgs = String.format("update sdk -u -a %s -t %s", proxySettings, StringUtils.join(complist, ',')); 24 | return new SdkCliCommand(Tool.ANDROID_LEGACY, upgradeArgs); 25 | } 26 | 27 | @Override 28 | public SdkCliCommand getListSdkComponentsCommand() { 29 | return new SdkCliCommand(Tool.ANDROID_LEGACY, "list sdk --extended"); 30 | } 31 | 32 | @Override 33 | public SdkCliCommand getListExistingTargetsCommand() { 34 | return new SdkCliCommand(Tool.ANDROID_LEGACY, "list target"); 35 | } 36 | 37 | @Override 38 | public SdkCliCommand getListSystemImagesCommand() { 39 | return getListExistingTargetsCommand(); 40 | } 41 | 42 | @Override 43 | public boolean isImageForPlatformAndABIInstalled(final String listSystemImagesOutput, 44 | final String platform, final String abi) { 45 | // Check whether the desired ABI is included in the output 46 | Pattern regex = Pattern.compile(String.format("\"%s\".+?%s", platform, abi), Pattern.DOTALL); 47 | Matcher matcher = regex.matcher(listSystemImagesOutput); 48 | if (!matcher.find() || matcher.group(0).contains("---")) { 49 | // We did not find the desired ABI within the section for the given platform 50 | return false; 51 | } else { 52 | return true; 53 | } 54 | } 55 | 56 | @Override 57 | public SdkCliCommand getCreatedAvdCommand(final String avdName, final boolean supportsSnapshots, 58 | final String sdCardSize, final String screenResolutionSkinName, final String deviceDefinition, 59 | final String androidTarget, final String systemImagePackagePath, final String tag) { 60 | 61 | // Build up basic arguments to `android` command 62 | final StringBuilder args = new StringBuilder(100); 63 | args.append("create avd "); 64 | 65 | // Overwrite any existing files 66 | args.append("-f"); 67 | 68 | // Initialise snapshot support, regardless of whether we will actually use it 69 | if (supportsSnapshots) { 70 | args.append(" -a"); 71 | } 72 | 73 | if (sdCardSize != null) { 74 | args.append(" -c "); 75 | args.append(sdCardSize); 76 | } 77 | 78 | // screen resolution not supported at creation time in Android Emulator 2.0 79 | // will be added as skin on emulator start 80 | args.append(" -s "); 81 | args.append(screenResolutionSkinName); 82 | 83 | args.append(" -n "); 84 | args.append(avdName); 85 | 86 | args.append(" -t "); 87 | args.append(androidTarget); 88 | 89 | if (tag != null && !tag.isEmpty()) { 90 | args.append(" --tag "); 91 | args.append(tag); 92 | } 93 | 94 | return new SdkCliCommand(Tool.ANDROID_LEGACY, args.toString()); 95 | } 96 | 97 | @Override 98 | public SdkCliCommand getUpdateProjectCommand(final String projectPath) { 99 | return getGenericUpdateProjectCommand("project", projectPath, null); 100 | } 101 | 102 | @Override 103 | public SdkCliCommand getUpdateTestProjectCommand(final String projectPath, final String testMainClass) { 104 | return getGenericUpdateProjectCommand("test-project", projectPath, testMainClass); 105 | } 106 | 107 | @Override 108 | public SdkCliCommand getUpdateLibProjectCommand(final String projectPath) { 109 | return getGenericUpdateProjectCommand("lib-project", projectPath, null); 110 | } 111 | 112 | private SdkCliCommand getGenericUpdateProjectCommand(final String projectType, 113 | final String projectPath, final String testMainClass) { 114 | String mainClassArg = ""; 115 | if (testMainClass != null) { 116 | mainClassArg = String.format(" -m %s", testMainClass); 117 | } 118 | final String updateProjectArgs = 119 | String.format("update %s -p %s%s", projectType, projectPath, mainClassArg); 120 | return new SdkCliCommand(Tool.ANDROID_LEGACY, updateProjectArgs); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/sdk/cli/AdbShellCommandsCurrentBase.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator.sdk.cli; 2 | 3 | import hudson.plugins.android_emulator.constants.AndroidKeyEvent; 4 | import hudson.plugins.android_emulator.sdk.Tool; 5 | 6 | /** 7 | * This class holds the implementations for all used commands via {@code adb shell} 8 | * on devices running the latest API. As some calls have never changed in history, 9 | * it is most likely that this class is used as base for the other version implementations. 10 | */ 11 | public class AdbShellCommandsCurrentBase implements AdbShellCommands { 12 | 13 | @Override 14 | public SdkCliCommand getListProcessesCommand(final String deviceSerial) { 15 | return getAdbShellCommand(deviceSerial, "ps"); 16 | } 17 | 18 | // Other tools use the "bootanim" variant, which supposedly signifies the system has booted a bit further; 19 | // though this doesn't appear to be available on Android 1.5, while it should work fine on Android 1.6+ 20 | @Override 21 | public SdkCliCommand getWaitForDeviceStartupCommand(final String deviceSerial) { 22 | return getAdbShellCommand(deviceSerial, true, "getprop init.svc.bootanim"); 23 | } 24 | 25 | @Override 26 | public String getWaitForDeviceStartupExpectedAnswer() { 27 | return "stopped"; 28 | } 29 | 30 | @Override 31 | public SdkCliCommand getClearMainLogCommand(final String deviceSerial) { 32 | return getAdbShellCommand(deviceSerial, "logcat -c"); 33 | } 34 | 35 | @Override 36 | public SdkCliCommand getSetLogCatFormatToTimeCommand(String deviceSerial) { 37 | return getAdbShellCommand(deviceSerial, "logcat -v time"); 38 | } 39 | 40 | @Override 41 | public SdkCliCommand getLogMessageCommand(final String deviceSerial, final String logMessage) { 42 | final String logCommand = String.format("log -p v -t Jenkins '%s'", logMessage); 43 | return getAdbShellCommand(deviceSerial, logCommand); 44 | } 45 | 46 | @Override 47 | public SdkCliCommand getSendKeyEventCommand(final String deviceSerial, final AndroidKeyEvent keyEvent) { 48 | final String inputKeyEventCommand = String.format("input keyevent %d", keyEvent.getKeyCode()); 49 | return getAdbShellCommand(deviceSerial, inputKeyEventCommand); 50 | } 51 | 52 | @Override 53 | public SdkCliCommand getSendBackKeyEventCommand(final String deviceSerial) { 54 | return getSendKeyEventCommand(deviceSerial, AndroidKeyEvent.KEYCODE_BACK); 55 | } 56 | 57 | @Override 58 | public SdkCliCommand getDismissKeyguardCommand(final String deviceSerial) { 59 | // Android 6.0 introduced a command to dismiss the keyguard on unsecured devices 60 | return getAdbShellCommand(deviceSerial, "wm dismiss-keyguard"); 61 | } 62 | 63 | @Override 64 | public SdkCliCommand getMonkeyInputCommand(final String deviceSerial, 65 | final long seedValue, final int throttleMs, 66 | final String extraArgs, final int eventCount) { 67 | final String command = String.format("monkey -v -v -s %d --throttle %d %s %d", seedValue, 68 | throttleMs, extraArgs, eventCount); 69 | return getAdbShellCommand(deviceSerial, command); 70 | } 71 | 72 | /** 73 | * Generic method to generate and 'adb shell' command to run on the given device. 74 | * 75 | * @param deviceSerial device to run adb command on (add via '-s' option) 76 | * @param waitForDevice if true the 'wait-for-device' directive is added as adb parameter 77 | * @param command the command to run on the device 78 | * @return {@code SdkCliCommand} object which holds the ADB-Tool and the generated command 79 | */ 80 | protected SdkCliCommand getAdbShellCommand(final String deviceSerial, final boolean waitForDevice, final String command) { 81 | final String deviceSerialArgs; 82 | if (deviceSerial != null && !deviceSerial.isEmpty()) { 83 | deviceSerialArgs = "-s " + deviceSerial + " "; 84 | } else { 85 | deviceSerialArgs = ""; 86 | } 87 | final String waitForDeviceStr = (waitForDevice) ? "wait-for-device " : ""; 88 | 89 | final String shellCommand = String.format("%s%sshell %s", deviceSerialArgs, waitForDeviceStr, command); 90 | return new SdkCliCommand(Tool.ADB, shellCommand); 91 | } 92 | 93 | /** 94 | * Generic method to generate and 'adb shell' command to run on the given device. 95 | * Wrapper for {@code getAdbShellCommand} with assuming waitForDevice is false. 96 | * 97 | * @param deviceSerial device to run adb command on (add via '-s' option) 98 | * @param command the command to run on the device 99 | * @return {@code SdkCliCommand} object which holds the ADB-Tool and the generated command 100 | */ 101 | protected SdkCliCommand getAdbShellCommand(final String deviceSerial, final String command) { 102 | return getAdbShellCommand(deviceSerial, false, command); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/sdk/Tool.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator.sdk; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 7 | 8 | public enum Tool { 9 | ADB("adb", ".exe", new PlatformToolLocator()), 10 | ANDROID_LEGACY("android", ".bat"), 11 | EMULATOR("emulator", ".exe", new EmulatorToolLocator()), 12 | EMULATOR_ARM("emulator-arm", ".exe", new EmulatorToolLocator()), 13 | EMULATOR_MIPS("emulator-mips", ".exe", new EmulatorToolLocator()), 14 | EMULATOR_X86("emulator-x86", ".exe", new EmulatorToolLocator()), 15 | EMULATOR64_ARM("emulator64-arm", ".exe", new EmulatorToolLocator()), 16 | EMULATOR64_MIPS("emulator64-mips", ".exe", new EmulatorToolLocator()), 17 | EMULATOR64_X86("emulator64-x86", ".exe", new EmulatorToolLocator()), 18 | AVDMANAGER("avdmanager", ".bat", new SdkToolLocator()), 19 | SDKMANAGER("sdkmanager", ".bat", new SdkToolLocator()), 20 | MKSDCARD("mksdcard", ".exe"); 21 | 22 | @SuppressFBWarnings("MS_MUTABLE_ARRAY") 23 | public static Tool[] EMULATORS = new Tool[] { EMULATOR, 24 | EMULATOR_ARM, EMULATOR_MIPS, EMULATOR_X86, 25 | EMULATOR64_ARM, EMULATOR64_MIPS, EMULATOR64_X86 26 | }; 27 | 28 | private static Tool[] CMD_LINE_TOOLS = new Tool[] { 29 | AVDMANAGER, SDKMANAGER 30 | }; 31 | 32 | private static Tool[] REQUIRED = new Tool[] { 33 | ADB, EMULATOR, AVDMANAGER, SDKMANAGER 34 | }; 35 | 36 | private static Tool[] REQUIRED_LEGACY = new Tool[] { 37 | ADB, ANDROID_LEGACY, EMULATOR 38 | }; 39 | 40 | public final String executable; 41 | public final String windowsExtension; 42 | public final ToolLocator toolLocator; 43 | 44 | Tool(String executable, String windowsExtension) { 45 | this(executable, windowsExtension, new DefaultToolLocator()); 46 | } 47 | 48 | Tool(String executable, String windowsExtension, ToolLocator toolLocator) { 49 | this.executable = executable; 50 | this.windowsExtension = windowsExtension; 51 | this.toolLocator = toolLocator; 52 | } 53 | 54 | public String getExecutable(boolean isUnix) { 55 | if (isUnix) { 56 | return executable; 57 | } 58 | return executable + windowsExtension; 59 | } 60 | 61 | private String getPathInSdk(final boolean legacySdkStructure, final boolean isUnix) { 62 | return toolLocator.findInSdk(legacySdkStructure) + "/" + getExecutable(isUnix); 63 | } 64 | 65 | public String getPathInSdk(final AndroidSdk androidSdk, final boolean isUnix) { 66 | return getPathInSdk(androidSdk.useLegacySdkStructure(), isUnix); 67 | } 68 | 69 | /** 70 | * Retrieve a list of relative paths to the SDK root directory 71 | * for the given tools, either for the legacy or the new structure. 72 | * 73 | * @param tools the Tools to get the relative path for 74 | * @param useLegacy return the paths for the new or the legacy structure 75 | * @param isUnix if false the windows suffix is appended 76 | * @return a list of relative paths (including the executable name) expected to exist 77 | */ 78 | private static String[] getRequiredToolsRelativePaths(final Tool[] tools, final boolean useLegacy, final boolean isUnix) { 79 | final List paths = new ArrayList<>(); 80 | for (final Tool tool : tools) { 81 | paths.add(tool.getPathInSdk(useLegacy, isUnix)); 82 | } 83 | return paths.toArray(new String[0]); 84 | } 85 | 86 | /** 87 | * Retrieve a list of relative paths to the SDK root directory 88 | * for all necessary tools needed for a SDK installations using 89 | * the new structure (tools/bin, emulator dir). 90 | * 91 | * @param isUnix if false the windows suffix is appended 92 | * @return a list of relative paths (including the executable name) expected to exist 93 | */ 94 | public static String[] getRequiredToolsRelativePaths(final boolean isUnix) { 95 | return getRequiredToolsRelativePaths(Tool.REQUIRED, false, isUnix); 96 | } 97 | 98 | public static String[] getRequiredCmdLineToolsPaths(final boolean isUnix) { 99 | return getRequiredToolsRelativePaths(Tool.CMD_LINE_TOOLS, false, isUnix); 100 | } 101 | 102 | /** 103 | * Retrieve a list of relative paths to the SDK root directory 104 | * for all necessary tools needed for a SDK installations using 105 | * the legacy structure (tools/android, ...). 106 | * 107 | * @param isUnix if false the windows suffix is appended 108 | * @return a list of relative paths (including the executable name) expected to exist 109 | */ 110 | public static String[] getRequiredToolsLegacyRelativePaths(final boolean isUnix) { 111 | return getRequiredToolsRelativePaths(Tool.REQUIRED_LEGACY, true, isUnix); 112 | } 113 | 114 | @Override 115 | public String toString() { 116 | return executable; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugin/android/emulator/tools/ToolLocator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2020, Nikolas Falco 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package jenkins.plugin.android.emulator.tools; 25 | 26 | import java.io.File; 27 | import java.io.IOException; 28 | 29 | import edu.umd.cs.findbugs.annotations.CheckForNull; 30 | 31 | import hudson.FilePath; 32 | 33 | import edu.umd.cs.findbugs.annotations.NonNull; 34 | import hudson.Launcher; 35 | import hudson.plugins.android_emulator.sdk.Tool; 36 | import hudson.remoting.VirtualChannel; 37 | import jenkins.security.MasterToSlaveCallable; 38 | 39 | public class ToolLocator { 40 | private final class LookupExecuteCallable extends MasterToSlaveCallable { 41 | 42 | private static final long serialVersionUID = -6703610106678288597L; 43 | 44 | private final Tool tool; 45 | 46 | public LookupExecuteCallable(Tool tool) { 47 | this.tool = tool; 48 | } 49 | 50 | @Override 51 | public String call() throws IOException { 52 | Platform currentPlatform = platform; 53 | File toolHome = new File(home, tool.toolLocator.findInSdk(false)); 54 | if (!toolHome.exists()) { 55 | toolHome = new File(home, tool.toolLocator.findInSdk(true)); 56 | } 57 | File cmd = new File(toolHome, tool.getExecutable(currentPlatform != Platform.WINDOWS)); 58 | if (cmd.exists()) { 59 | return cmd.getPath(); 60 | } 61 | return null; 62 | } 63 | } 64 | 65 | private final Platform platform; 66 | private final String home; 67 | 68 | public ToolLocator(@NonNull Platform platform, @CheckForNull String home) { 69 | this.platform = platform; 70 | this.home = home; 71 | } 72 | 73 | /** 74 | * Gets the executable path of SDKManager on the given target system. 75 | * 76 | * @param launcher a way to start processes 77 | * @return the sdkmanager executable in the system is exists, {@code null} 78 | * otherwise. 79 | * @throws InterruptedException if the step is interrupted 80 | * @throws IOException if something goes wrong 81 | */ 82 | public FilePath getSDKManager(final Launcher launcher) throws InterruptedException, IOException { 83 | return getToolLocation(launcher, Tool.SDKMANAGER); 84 | } 85 | 86 | /** 87 | * Gets the executable path of AVDManager on the given target system. 88 | * 89 | * @param launcher a way to start processes 90 | * @return the avdmanager executable in the system is exists, {@code null} 91 | * otherwise. 92 | * @throws InterruptedException if the step is interrupted 93 | * @throws IOException if something goes wrong 94 | */ 95 | public FilePath getAVDManager(final Launcher launcher) throws InterruptedException, IOException { 96 | return getToolLocation(launcher, Tool.AVDMANAGER); 97 | } 98 | 99 | /** 100 | * Gets the executable path of ADB on the given target system. 101 | * 102 | * @param launcher a way to start processes 103 | * @return the adb executable in the system is exists, {@code null} 104 | * otherwise. 105 | * @throws InterruptedException if the step is interrupted 106 | * @throws IOException if something goes wrong 107 | */ 108 | public FilePath getADB(final Launcher launcher) throws InterruptedException, IOException { 109 | return getToolLocation(launcher, Tool.ADB); 110 | } 111 | 112 | /** 113 | * Gets the executable path of emulator on the given target system. 114 | * 115 | * @param launcher a way to start processes 116 | * @return the emulator executable in the system is exists, {@code null} 117 | * otherwise. 118 | * @throws InterruptedException if the step is interrupted 119 | * @throws IOException if something goes wrong 120 | */ 121 | public FilePath getEmulator(Launcher launcher) throws InterruptedException, IOException { 122 | return getToolLocation(launcher, Tool.EMULATOR); 123 | } 124 | 125 | private FilePath getToolLocation(final Launcher launcher, Tool tool) throws IOException, InterruptedException { 126 | // DO NOT REMOVE this callable otherwise paths constructed by File 127 | // and similar API will be based on the master node O.S. 128 | final VirtualChannel channel = launcher.getChannel(); 129 | if (channel == null) { 130 | throw new IOException("Unable to get a channel for the launcher"); 131 | } 132 | return new FilePath(channel, channel.call(new LookupExecuteCallable(tool))); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/builder/ProjectPrerequisitesInstaller.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator.builder; 2 | 3 | import static hudson.plugins.android_emulator.AndroidEmulator.log; 4 | import hudson.Extension; 5 | import hudson.FilePath; 6 | import hudson.Functions; 7 | import hudson.Launcher; 8 | import hudson.model.BuildListener; 9 | import hudson.model.Descriptor; 10 | import hudson.model.AbstractBuild; 11 | import hudson.plugins.android_emulator.BuildNodeUnavailableException; 12 | import hudson.plugins.android_emulator.Messages; 13 | import hudson.plugins.android_emulator.SdkInstaller; 14 | import hudson.plugins.android_emulator.sdk.AndroidSdk; 15 | import hudson.plugins.android_emulator.util.ConfigFileUtils; 16 | import hudson.remoting.VirtualChannel; 17 | import hudson.tasks.Builder; 18 | import jenkins.MasterToSlaveFileCallable; 19 | 20 | import java.io.File; 21 | import java.io.IOException; 22 | import java.io.PrintStream; 23 | import java.io.Serializable; 24 | import java.util.Collection; 25 | import java.util.HashSet; 26 | 27 | import org.apache.tools.ant.DirectoryScanner; 28 | import org.kohsuke.stapler.DataBoundConstructor; 29 | 30 | public class ProjectPrerequisitesInstaller extends AbstractBuilder { 31 | 32 | @DataBoundConstructor 33 | public ProjectPrerequisitesInstaller() { 34 | // Nowt to do 35 | } 36 | 37 | @Override 38 | public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) 39 | throws InterruptedException, IOException { 40 | final PrintStream logger = listener.getLogger(); 41 | 42 | // Gather list of platforms specified by Android project files in the workspace 43 | log(logger, Messages.FINDING_PROJECTS()); 44 | final FilePath workspace = build.getWorkspace(); 45 | if (workspace == null) { 46 | throw new BuildNodeUnavailableException(); 47 | } 48 | final Collection platforms = workspace.act(new ProjectPlatformFinder(listener)); 49 | if (platforms == null || platforms.isEmpty()) { 50 | // Nothing to install, but that's ok 51 | log(logger, Messages.NO_PROJECTS_FOUND_FOR_PREREQUISITES()); 52 | return true; 53 | } 54 | 55 | // Ensure we have an SDK 56 | AndroidSdk androidSdk = getAndroidSdk(build, launcher, listener); 57 | if (androidSdk == null) { 58 | return false; 59 | } 60 | 61 | // Install platform(s) 62 | log(logger, Messages.ENSURING_PLATFORMS_INSTALLED(platforms)); 63 | for (String platform : platforms) { 64 | SdkInstaller.installPlatform(logger, launcher, androidSdk, platform, null, true); 65 | } 66 | 67 | // Done! 68 | return true; 69 | } 70 | 71 | /** FileCallable to determine Android target projects specified in a given directory. */ 72 | private static final class ProjectPlatformFinder extends MasterToSlaveFileCallable> { 73 | 74 | private final BuildListener listener; 75 | private transient PrintStream logger; 76 | 77 | ProjectPlatformFinder(BuildListener listener) { 78 | this.listener = listener; 79 | } 80 | 81 | public Collection invoke(File workspace, VirtualChannel channel) 82 | throws IOException, InterruptedException { 83 | if (logger == null) { 84 | logger = listener.getLogger(); 85 | } 86 | 87 | // Find the appropriate file: project.properties or default.properties 88 | final String[] filePatterns = { "**/default.properties", "**/project.properties" }; 89 | DirectoryScanner scanner = new DirectoryScanner(); 90 | scanner.setBasedir(workspace); 91 | scanner.setIncludes(filePatterns); 92 | scanner.scan(); 93 | 94 | // Extract platform from each config file 95 | Collection platforms = new HashSet(); 96 | String[] files = scanner.getIncludedFiles(); 97 | if (files != null) { 98 | for (String filename : files) { 99 | String platform = getPlatformFromProjectFile(logger, new File(workspace, filename)); 100 | if (platform != null) { 101 | log(logger, Messages.PROJECT_HAS_PLATFORM(filename, platform)); 102 | platforms.add(platform); 103 | } 104 | } 105 | } 106 | 107 | return platforms; 108 | } 109 | 110 | private static String getPlatformFromProjectFile(PrintStream logger, File f) { 111 | String platform = null; 112 | try { 113 | // Read configured target platform from file 114 | platform = ConfigFileUtils.parseConfigFile(f).get("target"); 115 | if (platform != null) { 116 | platform = platform.trim(); 117 | } 118 | } catch (IOException e) { 119 | log(logger, Messages.READING_PROJECT_FILE_FAILED(), e); 120 | e.printStackTrace(); 121 | } 122 | return platform; 123 | } 124 | 125 | private static final long serialVersionUID = 1L; 126 | } 127 | 128 | @Extension 129 | public static final class DescriptorImpl extends Descriptor implements Serializable { 130 | 131 | private static final long serialVersionUID = 1L; 132 | 133 | public DescriptorImpl() { 134 | super(ProjectPrerequisitesInstaller.class); 135 | } 136 | 137 | @Override 138 | public String getHelpFile() { 139 | return Functions.getResourcePath() + "/plugin/android-emulator/help-installPrerequisites.html"; 140 | } 141 | 142 | @Override 143 | public String getDisplayName() { 144 | return Messages.INSTALL_PREREQUISITES(); 145 | } 146 | 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/android_emulator/TaskDispatcher.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.android_emulator; 2 | 3 | import hudson.Extension; 4 | import hudson.matrix.MatrixConfiguration; 5 | import hudson.model.BuildableItemWithBuildWrappers; 6 | import hudson.model.Computer; 7 | import hudson.model.Executor; 8 | import hudson.model.Node; 9 | import hudson.model.Queue; 10 | import hudson.model.Queue.BuildableItem; 11 | import hudson.model.Queue.Executable; 12 | import hudson.model.Queue.Task; 13 | import hudson.model.queue.CauseOfBlockage; 14 | import hudson.model.queue.QueueTaskDispatcher; 15 | import hudson.model.queue.SubTask; 16 | 17 | import hudson.plugins.android_emulator.AndroidEmulator.DescriptorImpl; 18 | import jenkins.model.Jenkins; 19 | 20 | /** 21 | * This QueueTaskDispatcher prevents any one Android emulator instance from being executed more than 22 | * once concurrently on any one build machine. 23 | *

24 | * From the given {@link hudson.model.Queue.Task Task}, we form a hash of the emulator configuration 25 | * and check whether any other build currently running on the given {@link hudson.model.Node Node} 26 | * is already using this configuration. If so, we veto execution of the given {@code Task}. 27 | *

28 | * As Android emulator attributes will quite often be parameterised (especially for matrix builds), 29 | * we attempt to expand as many variables as possible, i.e. from the environment of the {@code Node} 30 | * and the axis combination for matrix builds. Because we are evaluating these parameters before the 31 | * build has actually started, it's possible that the variable expansions made aren't 100% accurate, 32 | * for example if there are earlier {@code BuildWrapper} instances contributing to the environment. 33 | */ 34 | @Extension 35 | public class TaskDispatcher extends QueueTaskDispatcher { 36 | 37 | @Override 38 | public CauseOfBlockage canTake(Node node, Task task) { 39 | // If the given task doesn't use the AndroidEmulator BuildWrapper, we don't care. 40 | // Or, if there is an emulator hash, but with unresolved environment variables, we shouldn't block the build 41 | String desiredHash = getEmulatorConfigHashForTask(node, task); 42 | if (desiredHash == null || desiredHash.contains("$")) { 43 | return null; 44 | } 45 | 46 | // If the AndroidEmulator uses workspace-local emulators, we don't care. 47 | DescriptorImpl descriptor = Jenkins.get().getDescriptorByType(DescriptorImpl.class); 48 | if (descriptor != null && descriptor.shouldKeepInWorkspace) { 49 | return null; 50 | } 51 | 52 | // Check for builds in the queue which have the same emulator config as this task 53 | Queue queue = Jenkins.get().getQueue(); 54 | for (BuildableItem item : queue.getBuildableItems()) { 55 | Task queuedTask = item.task; 56 | if (task == queuedTask) { 57 | continue; 58 | } 59 | 60 | // If build with matching config is about to start (is "pending"), hold off for a moment 61 | if (queue.isPending(queuedTask)) { 62 | String queuedTaskHash = getEmulatorConfigHashForTask(node, queuedTask); 63 | if (desiredHash.equals(queuedTaskHash)) { 64 | return CauseOfBlockage.fromMessage(Messages._WAITING_FOR_EMULATOR()); 65 | } 66 | } 67 | } 68 | 69 | // Check whether a build with this emulator config is already running on this machine 70 | final Computer computer = node.toComputer(); 71 | if (computer == null) { 72 | return CauseOfBlockage.fromMessage(Messages._NO_EXECUTORS_ON_NODE()); 73 | } 74 | 75 | for (Executor e : computer.getExecutors()) { 76 | Executable executable = e.getCurrentExecutable(); 77 | if (executable == null) { 78 | continue; 79 | } 80 | 81 | String hash = getEmulatorConfigHashForTask(node, executable.getParent()); 82 | if (desiredHash.equals(hash)) { 83 | return CauseOfBlockage.fromMessage(Messages._WAITING_FOR_EMULATOR()); 84 | } 85 | } 86 | 87 | // Nope, no conflicting builds on this node 88 | return null; 89 | } 90 | 91 | /** 92 | * Determines the Android emulator configuration for the given task, if any. 93 | * 94 | * @param node The node on which the task should be executed, so we can retrieve its environment. 95 | * @param task The task whose Android emulator configuration should be determined. 96 | * @return A hash representing the Android emulator configuration for the task, or {@code null} 97 | * if the given task is not configured to start an Android emulator. 98 | */ 99 | private static String getEmulatorConfigHashForTask(Node node, SubTask task) { 100 | // If the job doesn't use any BuildWrappers, we don't care 101 | if (!(task instanceof BuildableItemWithBuildWrappers)) { 102 | return null; 103 | } 104 | 105 | // Fetch the item that actually contains the BuildWrapper config and downcast it 106 | BuildableItemWithBuildWrappers job; 107 | MatrixConfiguration matrixBuild = null; 108 | if (task instanceof MatrixConfiguration) { 109 | matrixBuild = (MatrixConfiguration) task; 110 | job = matrixBuild.getParent(); 111 | } else { 112 | job = (BuildableItemWithBuildWrappers) task; 113 | } 114 | 115 | // If we aren't one of the wrappers for this build, we don't care 116 | AndroidEmulator androidWrapper = job.getBuildWrappersList().get(AndroidEmulator.class); 117 | if (androidWrapper == null) { 118 | return null; 119 | } 120 | 121 | if (matrixBuild != null) { 122 | // If this is a matrix sub-build, substitute in the build variables 123 | return androidWrapper.getConfigHash(node, matrixBuild.getCombination()); 124 | } 125 | return androidWrapper.getConfigHash(node); 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugin/android/emulator/sdk/cli/CLICommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2020, Nikolas Falco 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package jenkins.plugin.android.emulator.sdk.cli; 25 | 26 | import java.io.ByteArrayInputStream; 27 | import java.io.ByteArrayOutputStream; 28 | import java.io.IOException; 29 | import java.io.InputStream; 30 | import java.io.OutputStream; 31 | import java.nio.charset.StandardCharsets; 32 | import java.util.ArrayList; 33 | import java.util.List; 34 | 35 | import edu.umd.cs.findbugs.annotations.Nullable; 36 | 37 | import org.apache.commons.io.input.NullInputStream; 38 | import org.apache.tools.ant.filters.StringInputStream; 39 | 40 | import edu.umd.cs.findbugs.annotations.NonNull; 41 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 42 | import hudson.EnvVars; 43 | import hudson.FilePath; 44 | import hudson.Launcher.ProcStarter; 45 | import hudson.Proc; 46 | import hudson.model.TaskListener; 47 | import hudson.util.ArgumentListBuilder; 48 | import hudson.util.ForkOutputStream; 49 | import hudson.util.StreamTaskListener; 50 | 51 | public class CLICommand { 52 | 53 | public interface OutputParser { 54 | R parse(InputStream input) throws IOException; 55 | } 56 | 57 | private final FilePath command; 58 | private final ArgumentListBuilder arguments; 59 | private final EnvVars env; 60 | private InputStream stdin = new NullInputStream(0); 61 | private FilePath root; 62 | private OutputParser parser; 63 | 64 | CLICommand(FilePath command, ArgumentListBuilder arguments, EnvVars env) { 65 | this.command = command; 66 | this.arguments = arguments; 67 | this.env = env; 68 | } 69 | 70 | public ArgumentListBuilder arguments() { 71 | return arguments; 72 | } 73 | 74 | public CLICommand withEnv(String key, String value) { 75 | env.put(key, value); 76 | return this; 77 | } 78 | 79 | public CLICommand withEnv(EnvVars env) { 80 | this.env.putAll(env); 81 | return this; 82 | } 83 | 84 | public R execute() throws IOException, InterruptedException { 85 | return execute(new StreamTaskListener(OutputStream.nullOutputStream(), StandardCharsets.UTF_8)); 86 | } 87 | 88 | public R execute(@NonNull TaskListener output) throws IOException, InterruptedException { 89 | List args = getArguments(); 90 | 91 | // command.createLauncher(output) 92 | ProcStarter starter = command.createLauncher(output).launch() // 93 | .envs(env) // 94 | .stdin(stdin) // 95 | .pwd(root == null ? command.getParent() : root) // 96 | .cmds(args) // 97 | .masks(getMasks(args.size())); 98 | 99 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 100 | if (parser != null) { 101 | // clone output to make content available to the parser 102 | starter.stdout(new ForkOutputStream(output.getLogger(), baos)); 103 | } else { 104 | starter.stdout(output); 105 | } 106 | 107 | int exitCode = starter.join(); 108 | if (exitCode != 0) { 109 | throw new IOException(command.getBaseName() + " " + arguments.toString() + " failed. exit code: " + exitCode + "."); 110 | } 111 | 112 | if (parser != null) { 113 | return parser.parse(new ByteArrayInputStream(baos.toByteArray())); 114 | } 115 | return null; 116 | } 117 | 118 | public Proc executeAsync(@Nullable TaskListener output) throws IOException, InterruptedException { 119 | List args = getArguments(); 120 | 121 | // command.createLauncher(output) 122 | ProcStarter starter = command.createLauncher(output).launch() // 123 | .envs(env) // 124 | .stdin(stdin) // 125 | .pwd(root == null ? command.getParent() : root) // 126 | .cmds(args) // 127 | .masks(getMasks(args.size())); 128 | 129 | if (output != null) { 130 | starter.stdout(output); 131 | } 132 | 133 | return starter.start(); 134 | } 135 | 136 | private boolean[] getMasks(final int size) { 137 | boolean[] masks = new boolean[size]; 138 | masks[0] = false; 139 | System.arraycopy(arguments.toMaskArray(), 0, masks, 1, size - 1); 140 | return masks; 141 | } 142 | 143 | private List getArguments() { 144 | List args = new ArrayList<>(arguments.toList()); 145 | args.add(0, command.getRemote()); 146 | return args; 147 | } 148 | 149 | CLICommand withRoot(FilePath root) { 150 | this.root = root; 151 | return this; 152 | } 153 | 154 | CLICommand withParser(OutputParser parser) { 155 | this.parser = parser; 156 | return this; 157 | } 158 | 159 | CLICommand withInput(String input) { 160 | this.stdin = new StringInputStream(input); 161 | return this; 162 | } 163 | 164 | } --------------------------------------------------------------------------------