Skip to content

Commit a173d65

Browse files
authored
Support API levels for SDK extensions and add missing targets for automotive and desktop. (#428)
1 parent 50d5b10 commit a173d65

14 files changed

+127
-90
lines changed

.github/workflows/main.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ jobs:
3939
api-level: 35
4040
target: google_apis
4141
arch: x86_64
42+
- os: ubuntu-latest
43+
api-level: 34-ext10
44+
target: android-automotive
45+
arch: x86_64
46+
system-image-api-level: 34-ext9
4247

4348
steps:
4449
- name: checkout
@@ -85,6 +90,7 @@ jobs:
8590
api-level: ${{ matrix.api-level }}
8691
target: ${{ matrix.target }}
8792
arch: ${{ matrix.arch }}
93+
system-image-api-level: ${{ matrix.system-image-api-level }}
8894
profile: Galaxy Nexus
8995
cores: 2
9096
sdcard-path-or-size: 100M
@@ -102,6 +108,7 @@ jobs:
102108
api-level: ${{ matrix.api-level }}
103109
target: ${{ matrix.target }}
104110
arch: ${{ matrix.arch }}
111+
system-image-api-level: ${{ matrix.system-image-api-level }}
105112
profile: Galaxy Nexus
106113
cores: 2
107114
ram-size: 2048M

.github/workflows/manually.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ on:
77
required: true
88
default: 'ubuntu-latest'
99
api-level:
10-
description: 'API level of the platform and system image'
10+
description: 'API level of the platform and system image (if not overridden with system-image-api-level input) - e.g. 33, 35-ext15, Baklava'
1111
required: true
1212
default: '34'
13+
system-image-api-level:
14+
description: 'API level of the system image - e.g. 34-ext10, 35-ext15'
1315
target:
14-
description: 'target of the system image - default, google_apis, google_apis_playstore, aosp_atd, google_atd, android-wear, android-wear-cn, android-tv or google-tv'
16+
description: 'target of the system image - default, google_apis, google_apis_playstore, aosp_atd, google_atd, android-wear, android-wear-cn, android-tv, google-tv, android-automotive, android-automotive-playstore or android-desktop'
1517
required: true
1618
default: 'default'
1719
arch:
@@ -68,6 +70,7 @@ jobs:
6870
api-level: ${{ github.event.inputs.api-level }}
6971
target: ${{ github.event.inputs.target }}
7072
arch: ${{ github.event.inputs.arch }}
73+
system-image-api-level: ${{ github.event.inputs.system-image-api-level }}
7174
profile: Galaxy Nexus
7275
emulator-options: ${{ github.event.inputs.emulator-options }}
7376
emulator-build: ${{ github.event.inputs.emulator-build }}

README.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,31 @@ jobs:
119119
script: ./gradlew connectedCheck
120120
```
121121
122+
If you need a specific [SDKExtension](https://developer.android.com/guide/sdk-extensions) for the system image but not the platform
123+
124+
```yml
125+
jobs:
126+
test:
127+
runs-on: ubuntu-latest
128+
steps:
129+
- name: checkout
130+
uses: actions/checkout@v4
131+
132+
- name: Enable KVM
133+
run: |
134+
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
135+
sudo udevadm control --reload-rules
136+
sudo udevadm trigger --name-match=kvm
137+
138+
- name: run tests
139+
uses: reactivecircus/android-emulator-runner@v2
140+
with:
141+
api-level: 34
142+
system-image-api-level: 34-ext9
143+
target: android-automotive
144+
script: ./gradlew connectedCheck
145+
```
146+
122147
We can significantly reduce emulator startup time by setting up AVD snapshot caching:
123148
124149
1. add a `gradle/actions/setup-gradle@v4` step for caching Gradle, more details see [#229](https://github.com/ReactiveCircus/android-emulator-runner/issues/229)
@@ -180,7 +205,8 @@ jobs:
180205
| **Input** | **Required** | **Default** | **Description** |
181206
|-|-|-|-|
182207
| `api-level` | Required | N/A | API level of the platform system image - e.g. 23 for Android Marshmallow, 29 for Android 10. **Minimum API level supported is 15**. |
183-
| `target` | Optional | `default` | Target of the system image - `default`, `google_apis`, `playstore`, `android-wear`, `android-wear-cn`, `android-tv`, `google-tv`, `aosp_atd` or `google_atd`. Note that `aosp_atd` and `google_atd` currently require the following: `api-level: 30`, `arch: x86` or `arch: arm64-v8` and `channel: canary`. |
208+
| `system-image-api-level` | Optional | `ap-level` | API level of the system image - e.g. 23 for Android Marshmallow, 29 for Android 10. |
209+
| `target` | Optional | `default` | Target of the system image - `default`, `google_apis`, `playstore`, `android-wear`, `android-wear-cn`, `android-tv`, `google-tv`, `aosp_atd`, `google_atd`, `android-automotive`, `android-automotive-playstore` or `android-desktop`. Note that `aosp_atd` and `google_atd` currently require the following: `api-level: 30`, `arch: x86` or `arch: arm64-v8` and `channel: canary`. |
184210
| `arch` | Optional | `x86` | CPU architecture of the system image - `x86`, `x86_64` or `arm64-v8a`. Note that `x86_64` image is only available for API 21+. `arm64-v8a` images require Android 4.2+ and are limited to fewer API levels (e.g. 30). |
185211
| `profile` | Optional | N/A | Hardware profile used for creating the AVD - e.g. `Nexus 6`. For a list of all profiles available, run `avdmanager list device`. |
186212
| `cores` | Optional | 2 | Number of cores to use for the emulator (`hw.cpu.ncore` in config.ini). |
@@ -243,5 +269,6 @@ These are some of the open-source projects using (or used) **Android Emulator Ru
243269
- [ACRA/acra](https://github.com/ACRA/acra/blob/master/.github/workflows/test.yml)
244270
- [bitfireAT/davx5-ose](https://github.com/bitfireAT/davx5-ose/blob/dev-ose/.github/workflows/test-dev.yml)
245271
- [robolectric/robolectric](https://github.com/robolectric/robolectric/blob/master/.github/workflows/tests.yml)
272+
- [home-assistant/android](https://github.com/home-assistant/android/blob/master/.github/workflows/pr.yml)
246273

247274
If you are using **Android Emulator Runner** and want your project included in the list, please feel free to open a pull request.

__tests__/input-validator.test.ts

Lines changed: 15 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,6 @@
11
import * as validator from '../src/input-validator';
22
import { MAX_PORT, MIN_PORT } from '../src/input-validator';
33

4-
describe('api-level validator tests', () => {
5-
it('Throws if api-level is not a number', () => {
6-
const func = () => {
7-
validator.checkApiLevel('api');
8-
};
9-
expect(func).toThrowError(`Unexpected API level: 'api'.`);
10-
});
11-
12-
it('Throws if api-level is not an integer', () => {
13-
const func = () => {
14-
validator.checkApiLevel('29.1');
15-
};
16-
expect(func).toThrowError(`Unexpected API level: '29.1'.`);
17-
});
18-
19-
it('Throws if api-level is lower than min API supported', () => {
20-
const func = () => {
21-
validator.checkApiLevel('14');
22-
};
23-
expect(func).toThrowError(`Minimum API level supported is ${validator.MIN_API_LEVEL}.`);
24-
});
25-
26-
it('Validates successfully with valid api-level', () => {
27-
const func1 = () => {
28-
validator.checkApiLevel('15');
29-
};
30-
expect(func1).not.toThrow();
31-
32-
const func2 = () => {
33-
validator.checkApiLevel('29');
34-
};
35-
expect(func2).not.toThrow();
36-
const func3 = () => {
37-
validator.checkApiLevel('UpsideDownCake-ext5');
38-
};
39-
expect(func3).not.toThrow();
40-
const func4 = () => {
41-
validator.checkApiLevel('TiramisuPrivacySandbox');
42-
};
43-
expect(func4).not.toThrow();
44-
});
45-
});
46-
474
describe('target validator tests', () => {
485
it('Throws if target is unknown', () => {
496
const func = () => {
@@ -97,6 +54,21 @@ describe('target validator tests', () => {
9754
validator.checkTarget('google-tv');
9855
};
9956
expect(func9).not.toThrow();
57+
58+
const func10 = () => {
59+
validator.checkTarget('android-automotive');
60+
};
61+
expect(func10).not.toThrow();
62+
63+
const func11 = () => {
64+
validator.checkTarget('android-automotive-playstore');
65+
};
66+
expect(func11).not.toThrow();
67+
68+
const func12 = () => {
69+
validator.checkTarget('android-desktop');
70+
};
71+
expect(func12).not.toThrow();
10072
});
10173
});
10274

action-types.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
inputs:
22
api-level:
3-
type: integer
3+
type: string
4+
system-image-api-level:
5+
type: string
46
target:
57
type: enum
68
allowed-values:
@@ -13,6 +15,9 @@ inputs:
1315
- android-wear-cn
1416
- android-tv
1517
- google-tv
18+
- android-automotive
19+
- android-automotive-playstore
20+
- android-desktop
1621
arch:
1722
type: enum
1823
allowed-values:

action.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ branding:
66
color: 'green'
77
inputs:
88
api-level:
9-
description: 'API level of the platform and system image - e.g. 23 for Android Marshmallow, 29 for Android 10'
9+
description: 'API level of the platform and system image - e.g. 23, 33, 35-ext15, Baklava'
1010
required: true
11+
system-image-api-level:
12+
description: 'API level of the system image - e.g. 34-ext10, 35-ext15. If not set the `api-level` input will be used.'
13+
required: false
1114
target:
12-
description: 'target of the system image - default, google_apis, google_apis_playstore, aosp_atd, google_atd, android-wear, android-wear-cn, android-tv or google-tv'
15+
description: 'target of the system image - default, google_apis, google_apis_playstore, aosp_atd, google_atd, android-wear, android-wear-cn, android-tv, google-tv, android-automotive, android-automotive-playstore or android-desktop'
1316
default: 'default'
1417
arch:
1518
description: 'CPU architecture of the system image - x86, x86_64 or arm64-v8a'

lib/emulator-manager.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const fs = __importStar(require("fs"));
3838
/**
3939
* Creates and launches a new AVD instance with the specified configurations.
4040
*/
41-
function launchEmulator(apiLevel, target, arch, profile, cores, ramSize, heapSize, sdcardPathOrSize, diskSize, avdName, forceAvdCreation, emulatorBootTimeout, port, emulatorOptions, disableAnimations, disableSpellChecker, disableLinuxHardwareAcceleration, enableHardwareKeyboard) {
41+
function launchEmulator(systemImageApiLevel, target, arch, profile, cores, ramSize, heapSize, sdcardPathOrSize, diskSize, avdName, forceAvdCreation, emulatorBootTimeout, port, emulatorOptions, disableAnimations, disableSpellChecker, disableLinuxHardwareAcceleration, enableHardwareKeyboard) {
4242
return __awaiter(this, void 0, void 0, function* () {
4343
try {
4444
console.log(`::group::Launch Emulator`);
@@ -48,7 +48,7 @@ function launchEmulator(apiLevel, target, arch, profile, cores, ramSize, heapSiz
4848
const profileOption = profile.trim() !== '' ? `--device '${profile}'` : '';
4949
const sdcardPathOrSizeOption = sdcardPathOrSize.trim() !== '' ? `--sdcard '${sdcardPathOrSize}'` : '';
5050
console.log(`Creating AVD.`);
51-
yield exec.exec(`sh -c \\"echo no | avdmanager create avd --force -n "${avdName}" --abi '${target}/${arch}' --package 'system-images;android-${apiLevel};${target};${arch}' ${profileOption} ${sdcardPathOrSizeOption}"`);
51+
yield exec.exec(`sh -c \\"echo no | avdmanager create avd --force -n "${avdName}" --abi '${target}/${arch}' --package 'system-images;android-${systemImageApiLevel};${target};${arch}' ${profileOption} ${sdcardPathOrSizeOption}"`);
5252
}
5353
if (cores) {
5454
yield exec.exec(`sh -c \\"printf 'hw.cpu.ncore=${cores}\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`);

lib/input-validator.js

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
11
"use strict";
22
Object.defineProperty(exports, "__esModule", { value: true });
3-
exports.checkDiskSize = exports.checkEmulatorBuild = exports.checkEnableHardwareKeyboard = exports.checkDisableLinuxHardwareAcceleration = exports.checkDisableSpellchecker = exports.checkDisableAnimations = exports.checkPort = exports.checkForceAvdCreation = exports.checkChannel = exports.checkArch = exports.checkTarget = exports.checkApiLevel = exports.PREVIEW_API_LEVELS = exports.MAX_PORT = exports.MIN_PORT = exports.VALID_CHANNELS = exports.VALID_ARCHS = exports.VALID_TARGETS = exports.MIN_API_LEVEL = void 0;
3+
exports.checkDiskSize = exports.checkEmulatorBuild = exports.checkEnableHardwareKeyboard = exports.checkDisableLinuxHardwareAcceleration = exports.checkDisableSpellchecker = exports.checkDisableAnimations = exports.checkPort = exports.checkForceAvdCreation = exports.checkChannel = exports.checkArch = exports.checkTarget = exports.MAX_PORT = exports.MIN_PORT = exports.VALID_CHANNELS = exports.VALID_ARCHS = exports.VALID_TARGETS = exports.MIN_API_LEVEL = void 0;
44
exports.MIN_API_LEVEL = 15;
5-
exports.VALID_TARGETS = ['default', 'google_apis', 'aosp_atd', 'google_atd', 'google_apis_playstore', 'android-wear', 'android-wear-cn', 'android-tv', 'google-tv'];
5+
exports.VALID_TARGETS = [
6+
'default',
7+
'google_apis',
8+
'aosp_atd',
9+
'google_atd',
10+
'google_apis_playstore',
11+
'android-wear',
12+
'android-wear-cn',
13+
'android-tv',
14+
'google-tv',
15+
'android-automotive',
16+
'android-automotive-playstore',
17+
'android-desktop',
18+
];
619
exports.VALID_ARCHS = ['x86', 'x86_64', 'arm64-v8a'];
720
exports.VALID_CHANNELS = ['stable', 'beta', 'dev', 'canary'];
821
exports.MIN_PORT = 5554;
922
exports.MAX_PORT = 5584;
10-
exports.PREVIEW_API_LEVELS = ['Tiramisu', 'UpsideDownCake', 'VanillaIceCream', 'Baklava'];
11-
function checkApiLevel(apiLevel) {
12-
if (exports.PREVIEW_API_LEVELS.some((previewLevel) => apiLevel.startsWith(previewLevel)))
13-
return;
14-
if (isNaN(Number(apiLevel)) || !Number.isInteger(Number(apiLevel))) {
15-
throw new Error(`Unexpected API level: '${apiLevel}'.`);
16-
}
17-
if (Number(apiLevel) < exports.MIN_API_LEVEL) {
18-
throw new Error(`Minimum API level supported is ${exports.MIN_API_LEVEL}.`);
19-
}
20-
}
21-
exports.checkApiLevel = checkApiLevel;
2223
function checkTarget(target) {
2324
if (!exports.VALID_TARGETS.includes(target)) {
2425
throw new Error(`Value for input.target '${target}' is unknown. Supported options: ${exports.VALID_TARGETS}.`);

lib/main.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,12 @@ function run() {
6363
}
6464
// API level of the platform and system image
6565
const apiLevel = core.getInput('api-level', { required: true });
66-
(0, input_validator_1.checkApiLevel)(apiLevel);
6766
console.log(`API level: ${apiLevel}`);
67+
let systemImageApiLevel = core.getInput('system-image-api-level');
68+
if (!systemImageApiLevel) {
69+
systemImageApiLevel = apiLevel;
70+
}
71+
console.log(`System image API level: ${systemImageApiLevel}`);
6872
// target of the system image
6973
const targetInput = core.getInput('target');
7074
const target = targetInput == 'playstore' ? 'google_apis_playstore' : targetInput;
@@ -179,7 +183,7 @@ function run() {
179183
}));
180184
console.log(`::endgroup::`);
181185
// install SDK
182-
yield (0, sdk_installer_1.installAndroidSdk)(apiLevel, target, arch, channelId, emulatorBuild, ndkVersion, cmakeVersion);
186+
yield (0, sdk_installer_1.installAndroidSdk)(apiLevel, systemImageApiLevel, target, arch, channelId, emulatorBuild, ndkVersion, cmakeVersion);
183187
// execute pre emulator launch script if set
184188
if (preEmulatorLaunchScripts !== undefined) {
185189
console.log(`::group::Run pre emulator launch script`);
@@ -198,7 +202,7 @@ function run() {
198202
console.log(`::endgroup::`);
199203
}
200204
// launch an emulator
201-
yield (0, emulator_manager_1.launchEmulator)(apiLevel, target, arch, profile, cores, ramSize, heapSize, sdcardPathOrSize, diskSize, avdName, forceAvdCreation, emulatorBootTimeout, port, emulatorOptions, disableAnimations, disableSpellchecker, disableLinuxHardwareAcceleration, enableHardwareKeyboard);
205+
yield (0, emulator_manager_1.launchEmulator)(systemImageApiLevel, target, arch, profile, cores, ramSize, heapSize, sdcardPathOrSize, diskSize, avdName, forceAvdCreation, emulatorBootTimeout, port, emulatorOptions, disableAnimations, disableSpellchecker, disableLinuxHardwareAcceleration, enableHardwareKeyboard);
202206
// execute the custom script
203207
try {
204208
// move to custom working directory if set

lib/sdk-installer.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ const CMDLINE_TOOLS_URL_LINUX = 'https://dl.google.com/android/repository/comman
4646
* Installs & updates the Android SDK for the macOS platform, including SDK platform for the chosen API level, latest build tools, platform tools, Android Emulator,
4747
* and the system image for the chosen API level, CPU arch, and target.
4848
*/
49-
function installAndroidSdk(apiLevel, target, arch, channelId, emulatorBuild, ndkVersion, cmakeVersion) {
49+
function installAndroidSdk(apiLevel, systemImageApiLevel, target, arch, channelId, emulatorBuild, ndkVersion, cmakeVersion) {
5050
return __awaiter(this, void 0, void 0, function* () {
5151
try {
5252
console.log(`::group::Install Android SDK`);
@@ -95,7 +95,7 @@ function installAndroidSdk(apiLevel, target, arch, channelId, emulatorBuild, ndk
9595
yield io.rmRF('emulator.zip');
9696
}
9797
console.log('Installing system images.');
98-
yield exec.exec(`sh -c \\"sdkmanager --install 'system-images;android-${apiLevel};${target};${arch}' --channel=${channelId} > /dev/null"`);
98+
yield exec.exec(`sh -c \\"sdkmanager --install 'system-images;android-${systemImageApiLevel};${target};${arch}' --channel=${channelId} > /dev/null"`);
9999
if (ndkVersion) {
100100
console.log(`Installing NDK ${ndkVersion}.`);
101101
yield exec.exec(`sh -c \\"sdkmanager --install 'ndk;${ndkVersion}' --channel=${channelId} > /dev/null"`);

src/emulator-manager.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as fs from 'fs';
55
* Creates and launches a new AVD instance with the specified configurations.
66
*/
77
export async function launchEmulator(
8-
apiLevel: string,
8+
systemImageApiLevel: string,
99
target: string,
1010
arch: string,
1111
profile: string,
@@ -33,7 +33,7 @@ export async function launchEmulator(
3333
const sdcardPathOrSizeOption = sdcardPathOrSize.trim() !== '' ? `--sdcard '${sdcardPathOrSize}'` : '';
3434
console.log(`Creating AVD.`);
3535
await exec.exec(
36-
`sh -c \\"echo no | avdmanager create avd --force -n "${avdName}" --abi '${target}/${arch}' --package 'system-images;android-${apiLevel};${target};${arch}' ${profileOption} ${sdcardPathOrSizeOption}"`
36+
`sh -c \\"echo no | avdmanager create avd --force -n "${avdName}" --abi '${target}/${arch}' --package 'system-images;android-${systemImageApiLevel};${target};${arch}' ${profileOption} ${sdcardPathOrSizeOption}"`
3737
);
3838
}
3939

src/input-validator.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
export const MIN_API_LEVEL = 15;
2-
export const VALID_TARGETS: Array<string> = ['default', 'google_apis', 'aosp_atd', 'google_atd', 'google_apis_playstore', 'android-wear', 'android-wear-cn', 'android-tv', 'google-tv'];
2+
export const VALID_TARGETS: Array<string> = [
3+
'default',
4+
'google_apis',
5+
'aosp_atd',
6+
'google_atd',
7+
'google_apis_playstore',
8+
'android-wear',
9+
'android-wear-cn',
10+
'android-tv',
11+
'google-tv',
12+
'android-automotive',
13+
'android-automotive-playstore',
14+
'android-desktop',
15+
];
316
export const VALID_ARCHS: Array<string> = ['x86', 'x86_64', 'arm64-v8a'];
417
export const VALID_CHANNELS: Array<string> = ['stable', 'beta', 'dev', 'canary'];
518
export const MIN_PORT = 5554;
619
export const MAX_PORT = 5584;
7-
export const PREVIEW_API_LEVELS: Array<string> = ['Tiramisu', 'UpsideDownCake', 'VanillaIceCream', 'Baklava'];
8-
9-
export function checkApiLevel(apiLevel: string): void {
10-
if (PREVIEW_API_LEVELS.some((previewLevel) => apiLevel.startsWith(previewLevel))) return;
11-
if (isNaN(Number(apiLevel)) || !Number.isInteger(Number(apiLevel))) {
12-
throw new Error(`Unexpected API level: '${apiLevel}'.`);
13-
}
14-
if (Number(apiLevel) < MIN_API_LEVEL) {
15-
throw new Error(`Minimum API level supported is ${MIN_API_LEVEL}.`);
16-
}
17-
}
1820

1921
export function checkTarget(target: string): void {
2022
if (!VALID_TARGETS.includes(target)) {

0 commit comments

Comments
 (0)