Repository: ReactiveCircus/android-emulator-runner
Pull request #159 (2021-06-17 18:16:41)

Support AVD snapshot caching
Add `force-avd-creation` which when set to `false` will skip avd creation if avd with same name exists.

Setting `force-avd-creation` to `false` allows cached AVD to be used when starting emulator which significantly reduces emulator startup time from **1-3 minutes** down to **~15 seconds**.

Snapshot caching can be done by running the action with snapshot (no `-no-snapshot` in `emulator-options`) if no snapshot cache is found, before running the action again (with `-no-snapshot-save` to make sure the cache won't be updated after running tests) to run to actual tests:
```
- uses: actions/cache@v2
  id: avd-cache
  with:
    path: |
      ~/.android/avd/*
      ~/.android/adb*
    key: avd-${{ matrix.api-level }}

- name: generate snapshot for caching
  if: steps.avd-cache.outputs.cache-hit != 'true'
  uses: reactivecircus/android-emulator-runner@v2
  with:
    api-level: ${{ matrix.api-level }}
    force-avd-creation: false
    emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
    disable-animations: false
    script: echo "Generated AVD snapshot for caching."

- name: run tests
  uses: reactivecircus/android-emulator-runner@v2
  with:
    api-level: ${{ matrix.api-level }}
    force-avd-creation: false
    emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
    disable-animations: true
    script: ./gradlew connectedDebugAndroidTest
```

Related to #76.


Patch:
diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml
index fb9535c..aa1ce08 100644
--- a/.github/workflows/workflow.yml
+++ b/.github/workflows/workflow.yml
@@ -64,21 +64,52 @@ jobs:
           ~/.gradle/caches
           ~/.gradle/wrapper
         key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}
+    - uses: actions/cache@v2
+      id: avd-cache
+      with:
+        path: |
+          ~/.android/avd/*
+          ~/.android/adb*
+          ~/.android/debug.keystore
+        key: avd-${{ matrix.api-level }}-${{ matrix.os }}-${{ matrix.target }}
+
+    - name: assemble tests
+      run: |
+        cd ./test-fixture/
+        ./gradlew assembleAndroidTest
+
+    - name: run emulator to generate snapshot for caching
+      if: steps.avd-cache.outputs.cache-hit != 'true'
+      uses: ./
+      with:
+        api-level: ${{ matrix.api-level }}
+        target: ${{ matrix.target }}
+        arch: x86
+        profile: Galaxy Nexus
+        cores: 2
+        sdcard-path-or-size: 100M
+        avd-name: test
+        force-avd-creation: false
+        emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
+        disable-animations: false
+        working-directory: ./test-fixture/
+        script: echo "Generated AVD snapshot for caching."
+
     - name: run action
       uses: ./
       with:
         api-level: ${{ matrix.api-level }}
         target: ${{ matrix.target }}
         arch: x86
-        profile: Nexus 6
+        profile: Galaxy Nexus
         cores: 2
         sdcard-path-or-size: 100M
         avd-name: test
-        emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -camera-back none
+        force-avd-creation: false
+        emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
         disable-animations: true
         working-directory: ./test-fixture/
         script: |
           echo $GITHUB_REPOSITORY
           adb devices
-          ./gradlew help
           ./gradlew connectedDebugAndroidTest
diff --git a/README.md b/README.md
index f5021cb..7287d43 100644
--- a/README.md
+++ b/README.md
@@ -31,14 +31,14 @@ jobs:
   test:
     runs-on: macos-latest
     steps:
-    - name: checkout
-      uses: actions/checkout@v2
-
-    - name: run tests
-      uses: reactivecircus/android-emulator-runner@v2
-      with:
-        api-level: 29
-        script: ./gradlew connectedCheck
+      - name: checkout
+        uses: actions/checkout@v2
+
+      - name: run tests
+        uses: reactivecircus/android-emulator-runner@v2
+        with:
+          api-level: 29
+          script: ./gradlew connectedCheck
 ```
 
 We can also leverage GitHub Actions's build matrix to test across multiple configurations:
@@ -52,17 +52,17 @@ jobs:
         api-level: [21, 23, 29]
         target: [default, google_apis]
     steps:
-    - name: checkout
-      uses: actions/checkout@v2
-
-    - name: run tests
-      uses: reactivecircus/android-emulator-runner@v2
-      with:
-        api-level: ${{ matrix.api-level }}
-        target: ${{ matrix.target }}
-        arch: x86_64
-        profile: Nexus 6
-        script: ./gradlew connectedCheck
+      - name: checkout
+        uses: actions/checkout@v2
+
+      - name: run tests
+        uses: reactivecircus/android-emulator-runner@v2
+        with:
+          api-level: ${{ matrix.api-level }}
+          target: ${{ matrix.target }}
+          arch: x86_64
+          profile: Nexus 6
+          script: ./gradlew connectedCheck
 ```
 
 If you need specific versions of **NDK** and **CMake** installed:
@@ -72,16 +72,70 @@ jobs:
   test:
     runs-on: macos-latest
     steps:
-    - name: checkout
-      uses: actions/checkout@v2
-
-    - name: run tests
-      uses: reactivecircus/android-emulator-runner@v2
-      with:
-        api-level: 29
-        ndk: 21.0.6113669
-        cmake: 3.10.2.4988404
-        script: ./gradlew connectedCheck
+      - name: checkout
+        uses: actions/checkout@v2
+
+      - name: run tests
+        uses: reactivecircus/android-emulator-runner@v2
+        with:
+          api-level: 29
+          ndk: 21.0.6113669
+          cmake: 3.10.2.4988404
+          script: ./gradlew connectedCheck
+```
+
+We can significantly reduce emulator startup time by setting up AVD snapshot caching:
+
+1. add an `actions/cache@v2` step for caching the `avd`
+2. add a `reactivecircus/android-emulator-runner@v2` step to generate a clean snapshot - specify `emulator-options` without `no-snapshot`
+3. add another `reactivecircus/android-emulator-runner@v2` step to run your tests using existing AVD / snapshot - specify `emulator-options` with `no-snapshot-save`
+
+```
+jobs:
+  test:
+    runs-on: macos-latest
+    strategy:
+      matrix:
+        api-level: [21, 23, 29]
+    steps:
+      - name: checkout
+        uses: actions/checkout@v2
+
+      - name: Gradle cache
+        uses: actions/cache@v2
+        with:
+          path: |
+            ~/.gradle/caches
+            ~/.gradle/wrapper
+          key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}-${{ hashFiles('**/buildSrc/**/*.kt') }}
+
+      - name: AVD cache
+        uses: actions/cache@v2
+        id: avd-cache
+        with:
+          path: |
+            ~/.android/avd/*
+            ~/.android/adb*
+          key: avd-${{ matrix.api-level }}
+
+      - name: create AVD and generate snapshot for caching
+        if: steps.avd-cache.outputs.cache-hit != 'true'
+        uses: reactivecircus/android-emulator-runner@v2
+        with:
+          api-level: ${{ matrix.api-level }}
+          force-avd-creation: false
+          emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
+          disable-animations: false
+          script: echo "Generated AVD snapshot for caching."
+
+      - name: run tests
+        uses: reactivecircus/android-emulator-runner@v2
+        with:
+          api-level: ${{ matrix.api-level }}
+          force-avd-creation: false
+          emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
+          disable-animations: true
+          script: ./gradlew connectedCheck
 ```
 
 ## Configurations
@@ -95,6 +149,7 @@ jobs:
 | `cores` | Optional | 2 | Number of cores to use for the emulator (`hw.cpu.ncore` in config.ini). |
 | `sdcard-path-or-size` | Optional | N/A | Path to the SD card image for this AVD or the size of a new SD card image to create for this AVD, in KB or MB, denoted with K or M. - e.g. `path/to/sdcard`, or `1000M`. |
 | `avd-name` | Optional | `test` | Custom AVD name used for creating the Android Virtual Device. |
+| `force-avd-creation` | Optional | `true` | Whether to force create the AVD by overwriting an existing AVD with the same name as `avd-name` - `true` or `false`. |
 | `emulator-options` | Optional | See below | Command-line options used when launching the emulator (replacing all default options) - e.g. `-no-window -no-snapshot -camera-back emulated`. |
 | `disable-animations` | Optional | `true` | Whether to disable animations - `true` or `false`. |
 | `disable-spellchecker` | Optional | `false` | Whether to disable spellchecker - `true` or `false`. |
diff --git a/action.yml b/action.yml
index 5753070..4f56c23 100644
--- a/action.yml
+++ b/action.yml
@@ -24,6 +24,9 @@ inputs:
   avd-name:
     description: 'custom AVD name used for creating the Android Virtual Device'
     default: 'test'
+  force-avd-creation:
+    description: 'whether to force create the AVD by overwriting an existing AVD with the same name as `avd-name` - `true` or `false`'
+    default: 'true'
   emulator-options:
     description: 'command-line options used when launching the emulator - e.g. `-no-window -no-snapshot -camera-back emulated`'
     default: '-no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim'
@@ -31,10 +34,10 @@ inputs:
     description: 'whether to disable animations - true or false'
     default: 'true'
   disable-spellchecker:
-    description: Whether to disable spellchecker - `true` or `false`.
+    description: 'whether to disable spellchecker - `true` or `false`'
     default: 'false'
   disable-linux-hw-accel:
-    description: Whether to disable hardware acceleration on Linux machines - `true` or `false`.
+    description: 'whether to disable hardware acceleration on Linux machines - `true` or `false`'
     default: 'true'
   emulator-build:
     description: 'build number of a specific version of the emulator binary to use - e.g. `6061023` for emulator v29.3.0.0'
diff --git a/lib/emulator-manager.js b/lib/emulator-manager.js
index 6ce380e..d5c2ec9 100644
--- a/lib/emulator-manager.js
+++ b/lib/emulator-manager.js
@@ -30,27 +30,31 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
 Object.defineProperty(exports, "__esModule", { value: true });
 exports.killEmulator = exports.launchEmulator = void 0;
 const exec = __importStar(require("@actions/exec"));
+const fs = __importStar(require("fs"));
 const EMULATOR_BOOT_TIMEOUT_SECONDS = 600;
 /**
  * Creates and launches a new AVD instance with the specified configurations.
  */
-function launchEmulator(apiLevel, target, arch, profile, cores, sdcardPathOrSize, avdName, emulatorOptions, disableAnimations, disableSpellChecker, disableLinuxHardwareAcceleration) {
+function launchEmulator(apiLevel, target, arch, profile, cores, sdcardPathOrSize, avdName, forceAvdCreation, emulatorOptions, disableAnimations, disableSpellChecker, disableLinuxHardwareAcceleration) {
     return __awaiter(this, void 0, void 0, function* () {
-        // create a new AVD
-        const profileOption = profile.trim() !== '' ? `--device '${profile}'` : '';
-        const sdcardPathOrSizeOption = sdcardPathOrSize.trim() !== '' ? `--sdcard '${sdcardPathOrSize}'` : '';
-        console.log(`Creating AVD.`);
-        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}"`);
+        // create a new AVD if AVD directory does not already exist or forceAvdCreation is true
+        const avdPath = `${process.env.ANDROID_AVD_HOME}/${avdName}.avd`;
+        if (!fs.existsSync(avdPath) || forceAvdCreation) {
+            const profileOption = profile.trim() !== '' ? `--device '${profile}'` : '';
+            const sdcardPathOrSizeOption = sdcardPathOrSize.trim() !== '' ? `--sdcard '${sdcardPathOrSize}'` : '';
+            console.log(`Creating AVD.`);
+            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}"`);
+        }
         if (cores) {
-            yield exec.exec(`sh -c \\"printf 'hw.cpu.ncore=${cores}\n' >> ~/.android/avd/"${avdName}".avd"/config.ini`);
+            yield exec.exec(`sh -c \\"printf 'hw.cpu.ncore=${cores}\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`);
         }
-        // start emulator
-        console.log('Starting emulator.');
         //turn off hardware acceleration on Linux
         if (process.platform === 'linux' && disableLinuxHardwareAcceleration) {
             console.log('Disabling Linux hardware acceleration.');
             emulatorOptions += ' -accel off';
         }
+        // start emulator
+        console.log('Starting emulator.');
         yield exec.exec(`sh -c \\"${process.env.ANDROID_SDK_ROOT}/emulator/emulator -avd "${avdName}" ${emulatorOptions} &"`, [], {
             listeners: {
                 stderr: (data) => {
diff --git a/lib/input-validator.js b/lib/input-validator.js
index 1d2a37f..ce5f8f6 100644
--- a/lib/input-validator.js
+++ b/lib/input-validator.js
@@ -1,6 +1,6 @@
 "use strict";
 Object.defineProperty(exports, "__esModule", { value: true });
-exports.checkEmulatorBuild = exports.checkDisableLinuxHardwareAcceleration = exports.checkDisableSpellchecker = exports.checkDisableAnimations = exports.checkArch = exports.checkTarget = exports.checkApiLevel = exports.VALID_ARCHS = exports.VALID_TARGETS = exports.MIN_API_LEVEL = void 0;
+exports.checkEmulatorBuild = exports.checkDisableLinuxHardwareAcceleration = exports.checkDisableSpellchecker = exports.checkDisableAnimations = exports.checkForceAvdCreation = exports.checkArch = exports.checkTarget = exports.checkApiLevel = exports.VALID_ARCHS = exports.VALID_TARGETS = exports.MIN_API_LEVEL = void 0;
 exports.MIN_API_LEVEL = 15;
 exports.VALID_TARGETS = ['default', 'google_apis', 'google_apis_playstore'];
 exports.VALID_ARCHS = ['x86', 'x86_64', 'arm64-v8a'];
@@ -25,6 +25,12 @@ function checkArch(arch) {
     }
 }
 exports.checkArch = checkArch;
+function checkForceAvdCreation(forceAvdCreation) {
+    if (!isValidBoolean(forceAvdCreation)) {
+        throw new Error(`Input for input.force-avd-creation should be either 'true' or 'false'.`);
+    }
+}
+exports.checkForceAvdCreation = checkForceAvdCreation;
 function checkDisableAnimations(disableAnimations) {
     if (!isValidBoolean(disableAnimations)) {
         throw new Error(`Input for input.disable-animations should be either 'true' or 'false'.`);
diff --git a/lib/main.js b/lib/main.js
index f3a4ef8..95dcfef 100644
--- a/lib/main.js
+++ b/lib/main.js
@@ -72,6 +72,11 @@ function run() {
             // custom name used for creating the AVD
             const avdName = core.getInput('avd-name');
             console.log(`AVD name: ${avdName}`);
+            // force AVD creation
+            const forceAvdCreationInput = core.getInput('force-avd-creation');
+            input_validator_1.checkForceAvdCreation(forceAvdCreationInput);
+            const forceAvdCreation = forceAvdCreationInput === 'true';
+            console.log(`force avd creation: ${forceAvdCreation}`);
             // emulator options
             const emulatorOptions = core.getInput('emulator-options').trim();
             console.log(`emulator options: ${emulatorOptions}`);
@@ -125,7 +130,7 @@ function run() {
             // install SDK
             yield sdk_installer_1.installAndroidSdk(apiLevel, target, arch, emulatorBuild, ndkVersion, cmakeVersion);
             // launch an emulator
-            yield emulator_manager_1.launchEmulator(apiLevel, target, arch, profile, cores, sdcardPathOrSize, avdName, emulatorOptions, disableAnimations, disableSpellchecker, disableLinuxHardwareAcceleration);
+            yield emulator_manager_1.launchEmulator(apiLevel, target, arch, profile, cores, sdcardPathOrSize, avdName, forceAvdCreation, emulatorOptions, disableAnimations, disableSpellchecker, disableLinuxHardwareAcceleration);
             // execute the custom script
             try {
                 // move to custom working directory if set
diff --git a/lib/sdk-installer.js b/lib/sdk-installer.js
index 746438f..099b266 100644
--- a/lib/sdk-installer.js
+++ b/lib/sdk-installer.js
@@ -57,6 +57,8 @@ function installAndroidSdk(apiLevel, target, arch, emulatorBuild, ndkVersion, cm
         }
         // add paths for commandline-tools and platform-tools
         core.addPath(`${cmdlineToolsPath}/latest:${cmdlineToolsPath}/latest/bin:${process.env.ANDROID_SDK_ROOT}/platform-tools`);
+        // set standard AVD path
+        core.exportVariable('ANDROID_AVD_HOME', `${process.env.HOME}/.android/avd`);
         // additional permission and license requirements for Linux
         const sdkPreviewLicensePath = `${process.env.ANDROID_SDK_ROOT}/licenses/android-sdk-preview-license`;
         if (!isOnMac && !fs.existsSync(sdkPreviewLicensePath)) {
diff --git a/src/emulator-manager.ts b/src/emulator-manager.ts
index 026d255..5a79293 100644
--- a/src/emulator-manager.ts
+++ b/src/emulator-manager.ts
@@ -1,4 +1,5 @@
 import * as exec from '@actions/exec';
+import * as fs from 'fs';
 
 const EMULATOR_BOOT_TIMEOUT_SECONDS = 600;
 
@@ -13,32 +14,36 @@ export async function launchEmulator(
   cores: string,
   sdcardPathOrSize: string,
   avdName: string,
+  forceAvdCreation: boolean,
   emulatorOptions: string,
   disableAnimations: boolean,
   disableSpellChecker: boolean,
   disableLinuxHardwareAcceleration: boolean
 ): Promise<void> {
-  // create a new AVD
-  const profileOption = profile.trim() !== '' ? `--device '${profile}'` : '';
-  const sdcardPathOrSizeOption = sdcardPathOrSize.trim() !== '' ? `--sdcard '${sdcardPathOrSize}'` : '';
-  console.log(`Creating AVD.`);
-  await exec.exec(
-    `sh -c \\"echo no | avdmanager create avd --force -n "${avdName}" --abi '${target}/${arch}' --package 'system-images;android-${apiLevel};${target};${arch}' ${profileOption} ${sdcardPathOrSizeOption}"`
-  );
+  // create a new AVD if AVD directory does not already exist or forceAvdCreation is true
+  const avdPath = `${process.env.ANDROID_AVD_HOME}/${avdName}.avd`;
+  if (!fs.existsSync(avdPath) || forceAvdCreation) {
+    const profileOption = profile.trim() !== '' ? `--device '${profile}'` : '';
+    const sdcardPathOrSizeOption = sdcardPathOrSize.trim() !== '' ? `--sdcard '${sdcardPathOrSize}'` : '';
+    console.log(`Creating AVD.`);
+    await exec.exec(
+      `sh -c \\"echo no | avdmanager create avd --force -n "${avdName}" --abi '${target}/${arch}' --package 'system-images;android-${apiLevel};${target};${arch}' ${profileOption} ${sdcardPathOrSizeOption}"`
+    );
+  }
 
   if (cores) {
-    await exec.exec(`sh -c \\"printf 'hw.cpu.ncore=${cores}\n' >> ~/.android/avd/"${avdName}".avd"/config.ini`);
+    await exec.exec(`sh -c \\"printf 'hw.cpu.ncore=${cores}\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`);
   }
 
-  // start emulator
-  console.log('Starting emulator.');
-
   //turn off hardware acceleration on Linux
   if (process.platform === 'linux' && disableLinuxHardwareAcceleration) {
     console.log('Disabling Linux hardware acceleration.');
     emulatorOptions += ' -accel off';
   }
 
+  // start emulator
+  console.log('Starting emulator.');
+
   await exec.exec(`sh -c \\"${process.env.ANDROID_SDK_ROOT}/emulator/emulator -avd "${avdName}" ${emulatorOptions} &"`, [], {
     listeners: {
       stderr: (data: Buffer) => {
diff --git a/src/input-validator.ts b/src/input-validator.ts
index b5875d1..35a8085 100644
--- a/src/input-validator.ts
+++ b/src/input-validator.ts
@@ -23,6 +23,12 @@ export function checkArch(arch: string): void {
   }
 }
 
+export function checkForceAvdCreation(forceAvdCreation: string): void {
+  if (!isValidBoolean(forceAvdCreation)) {
+    throw new Error(`Input for input.force-avd-creation should be either 'true' or 'false'.`);
+  }
+}
+
 export function checkDisableAnimations(disableAnimations: string): void {
   if (!isValidBoolean(disableAnimations)) {
     throw new Error(`Input for input.disable-animations should be either 'true' or 'false'.`);
diff --git a/src/main.ts b/src/main.ts
index f73aec2..8f96bf6 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,6 +1,15 @@
 import * as core from '@actions/core';
 import { installAndroidSdk } from './sdk-installer';
-import { checkApiLevel, checkTarget, checkArch, checkDisableAnimations, checkEmulatorBuild, checkDisableSpellchecker, checkDisableLinuxHardwareAcceleration } from './input-validator';
+import {
+  checkApiLevel,
+  checkTarget,
+  checkArch,
+  checkDisableAnimations,
+  checkEmulatorBuild,
+  checkDisableSpellchecker,
+  checkDisableLinuxHardwareAcceleration,
+  checkForceAvdCreation
+} from './input-validator';
 import { launchEmulator, killEmulator } from './emulator-manager';
 import * as exec from '@actions/exec';
 import { parseScript } from './script-parser';
@@ -51,6 +60,12 @@ async function run() {
     const avdName = core.getInput('avd-name');
     console.log(`AVD name: ${avdName}`);
 
+    // force AVD creation
+    const forceAvdCreationInput = core.getInput('force-avd-creation');
+    checkForceAvdCreation(forceAvdCreationInput);
+    const forceAvdCreation = forceAvdCreationInput === 'true';
+    console.log(`force avd creation: ${forceAvdCreation}`);
+
     // emulator options
     const emulatorOptions = core.getInput('emulator-options').trim();
     console.log(`emulator options: ${emulatorOptions}`);
@@ -114,7 +129,20 @@ async function run() {
     await installAndroidSdk(apiLevel, target, arch, emulatorBuild, ndkVersion, cmakeVersion);
 
     // launch an emulator
-    await launchEmulator(apiLevel, target, arch, profile, cores, sdcardPathOrSize, avdName, emulatorOptions, disableAnimations, disableSpellchecker, disableLinuxHardwareAcceleration);
+    await launchEmulator(
+      apiLevel,
+      target,
+      arch,
+      profile,
+      cores,
+      sdcardPathOrSize,
+      avdName,
+      forceAvdCreation,
+      emulatorOptions,
+      disableAnimations,
+      disableSpellchecker,
+      disableLinuxHardwareAcceleration
+    );
 
     // execute the custom script
     try {
diff --git a/src/sdk-installer.ts b/src/sdk-installer.ts
index d00c229..40738ba 100644
--- a/src/sdk-installer.ts
+++ b/src/sdk-installer.ts
@@ -31,6 +31,9 @@ export async function installAndroidSdk(apiLevel: number, target: string, arch:
   // add paths for commandline-tools and platform-tools
   core.addPath(`${cmdlineToolsPath}/latest:${cmdlineToolsPath}/latest/bin:${process.env.ANDROID_SDK_ROOT}/platform-tools`);
 
+  // set standard AVD path
+  core.exportVariable('ANDROID_AVD_HOME', `${process.env.HOME}/.android/avd`);
+
   // additional permission and license requirements for Linux
   const sdkPreviewLicensePath = `${process.env.ANDROID_SDK_ROOT}/licenses/android-sdk-preview-license`;
   if (!isOnMac && !fs.existsSync(sdkPreviewLicensePath)) {


Test patch:
diff --git a/__tests__/input-validator.test.ts b/__tests__/input-validator.test.ts
index 4a6553f..f02bb84 100644
--- a/__tests__/input-validator.test.ts
+++ b/__tests__/input-validator.test.ts
@@ -82,6 +82,27 @@ describe('arch validator tests', () => {
   });
 });
 
+describe('force-avd-creation validator tests', () => {
+  it('Throws if force-avd-creation is not a boolean', () => {
+    const func = () => {
+      validator.checkForceAvdCreation('yes');
+    };
+    expect(func).toThrowError(`Input for input.force-avd-creation should be either 'true' or 'false'.`);
+  });
+
+  it('Validates successfully if force-avd-creation is either true or false', () => {
+    const func1 = () => {
+      validator.checkForceAvdCreation('true');
+    };
+    expect(func1).not.toThrow();
+
+    const func2 = () => {
+      validator.checkForceAvdCreation('false');
+    };
+    expect(func2).not.toThrow();
+  });
+});
+
 describe('disable-animations validator tests', () => {
   it('Throws if disable-animations is not a boolean', () => {
     const func = () => {