Skip to Content

Android emulators

Run the app on several emulators at once, including one per supported Android version.

bun run emulators runs the whole stack and opens OpenBeacon on several Android emulators that move around Derby. You can run it in two ways:

  • Route mode (the default) starts four copies of one Android Virtual Device (AVD), each following a different route. It's a quick way to see several group members moving on the map.
  • Version mode starts one emulator per Android version. Use it to check a change on old and new Android at the same time, or to reproduce a bug on one specific version.

Before you start

You need Bun, Python 3, Java 17, Docker Compose and the Android SDK with the emulator and platform tools. In Android Studio's SDK Manager, install the Android NDK and accept the SDK licences. adb and emulator must be on your PATH.

  • Route mode needs at least one AVD. Create one in Android Studio's Device Manager.
  • Version mode creates its own AVDs, so it also needs Android SDK Command-line Tools (latest) and ANDROID_HOME set to your SDK.

Stop anything already using Metro's port 8081 or the emulator ports the run needs. Each emulator takes two ports from 5554 upwards, so four emulators use 5554–5560 and all ten versions use 5554–5572.

What it does

  1. Installs dependencies.
  2. Starts PostgreSQL and Redis in Docker, applies database migrations and starts the backend with hot reload. It skips this step if you point it at another backend.
  3. Builds the Android development APK, unless you pass --apk.
  4. Starts Metro and the emulators, installs the app on each and opens it. If the app closes during startup, it tries again, up to three times per emulator.
  5. Moves each emulator's GPS once a second until you press Ctrl+C.

On the first run it creates .env from .env.example with a generated auth secret. It then stops and lists the storage settings you still need to fill in: S3_ACCESS_KEY_ID, S3_ACCESS_KEY, S3_BUCKET_NAME, R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET and R2_PM_TILES_KEY. Pictures and maps need real storage credentials and a PMTiles object. It never overwrites an existing .env.

The four routes are:

RouteSpeedWhere
Stationary0 mphCentral Derby
Walking3 mphA loop through the centre
Jogging6 mphA loop in Darley Park
Car70 mphThe A6 south-east of Derby, reversing at each end

Press Ctrl+C to stop the backend, Metro and the emulators. The database and cache keep running; stop them with docker compose stop db redis. Each emulator's log is saved to a temporary folder, which is printed at startup and again if something fails.

Run specific Android versions

bun run emulators:versions

This starts one emulator for every supported Android version. To run only some, list their API levels:

bun run emulators:versions 24 36
bun run emulators:versions 31

bun run emulators:versions is a shortcut for bun run emulators --android-versions, so the two lines above are the same as bun run emulators --android-versions 24 36 and bun run emulators --android-versions 31. Running a single version is the easiest way to reproduce a bug someone reported on their Android version.

The supported API levels are 24, 26, 28, 29, 30, 31, 33, 34, 35 and 36. The compatibility matrix maps them to Android versions. Any other number is rejected. Duplicates are ignored, and the emulators always start in order from oldest to newest.

For each version, the script:

  • installs the Google APIs system image with sdkmanager if it's missing, using arm64-v8a on ARM machines and x86_64 on others, and reinstalls any image left half-installed;
  • creates an AVD called OpenBeacon_API_<level> on a Pixel 6 profile with a 6 GB data partition and a hardware keyboard, and recreates it if its settings don't match;
  • gives the emulators routes in turn: the first version is stationary, the second walking, the third jogging, the fourth driving, and then the routes repeat.

AVDs go in ANDROID_AVD_HOME, or ~/.android/avd if that isn't set.

Options

OptionDefaultWhat it does
--avd NAMEFirst AVD that emulator -list-avds printsRoute mode: the AVD to run four copies of. Can't be used with --android-versions.
--android-versions [API ...]Off. With no levels, all ten.Version mode: one emulator per listed API level, creating any AVDs and system images that are missing.
--memory-mb MIB6144 in route mode. In version mode, a default for each version (see the matrix).RAM for each emulator, in MiB. Must be a positive whole number.
--skip-memory-checkOffStarts the emulators even if the machine doesn't seem to have enough free RAM.
--api-url URLhttp://10.0.2.2:3000The backend the app talks to. Any other value uses that backend and doesn't start a local one. It must be an http or https origin with no path, query or fragment.
--external-backendOffUses a backend you're already running at the default address, instead of starting one.
--apk PATHBuilds a debug APKInstalls an existing Expo development APK and skips the Android build.
-h, --helpPrints the options.

You can combine version mode with the other options, for example:

bun run emulators:versions 24 30 36 --memory-mb 4096
bun run emulators:versions 34 --apk apps/mobile/android/app/build/outputs/apk/debug/app-debug.apk
bun run emulators --avd Medium_Phone --api-url https://api.example.com

10.0.2.2 is how an Android emulator reaches your computer. The API URL is built into the development bundle, so restart the command when you switch backends. That backend's auth and network settings must allow the URL.

Memory

Before building anything, the script adds up the RAM each emulator needs plus 2 GiB of headroom. It then compares the total with the free memory reported by /proc/meminfo on Linux or vm_stat on macOS. If there isn't enough, it stops and suggests fewer emulators, a lower --memory-mb or --skip-memory-check. If it can't read free memory, it skips the check.

Route mode needs about 26 GiB by default: four emulators at 6 GiB plus headroom. Version mode with all ten versions needs about 56 GiB. Most machines need a shorter list of versions or a lower --memory-mb.

Android SDK and Java

The script uses ANDROID_HOME, or ANDROID_SDK_ROOT if that isn't set. If that SDK has no NDK or no accepted licence and ~/Android/Sdk has both, it uses ~/Android/Sdk instead and says so. On Linux, if /usr/lib/jvm/java-17-openjdk exists and your current Java isn't 17, it switches to Java 17 for the run.

If the Android build fails, check that the NDK is installed, the SDK licences are accepted, and ANDROID_HOME points to that SDK.

Android compatibility matrix

The app's Android build settings come from the Expo 55 and React Native 0.83 defaults. The app doesn't override them.

SettingValueMeaning
minSdk24 (Android 7.0)The oldest version that can install the app
targetSdk36 (Android 16)The version whose behaviour rules the app follows. Google Play needs 36 for new apps and updates.
compileSdk36 (Android 16)The SDK the app is built against

The minimum is a technical floor, not a promise that every phone at that version works. Raising targetSdk brings in newer Android rules even on older phones, so check the version changes below when you update Expo or React Native.

AndroidAPIEmulator RAMWhat changes for OpenBeaconEmulatorReal phone
7.0243 GiBThe minimum. java.util.Base64 doesn't exist below API 26, so native code uses Base64Codec, which falls back to android.util.Base64.CheckedNot yet tested
8.0263 GiBFirst version with java.util.Base64.CheckedNot yet tested
9284 GiBCheckedNot yet tested
10294 GiBBackground location becomes its own permission. The tracking service declares the location foreground service type.CheckedNot yet tested
11304 GiBBackground location must be granted in Settings and requested after foreground location.CheckedNot yet tested
12316 GiBPeople can grant approximate location only, including in the background. Foreground services generally can't start from the background.CheckedNot yet tested
13336 GiBNotification permission. A foreground service still shows its notification in Task Manager if it's denied, and people can stop the app from there.CheckedNot yet tested
14348 GiBStricter while-in-use checks when starting a location service from the background, and a foreground service permission is required.CheckedNot yet tested
15358 GiBThe Robolectric SDK the offline queue tests run on, the newest that works with Java 17.CheckedNot yet tested
16368 GiBThe target and compile SDK.CheckedNot yet tested

"Checked" means the app ran on that version's emulator with bun run emulators:versions. That's how the Android 7 location upload bug was found and fixed.

Versions not in the list:

  • Android 7.1 (25), 8.1 (27) and 12L (32) are above the minimum, so the app installs on them, but they have no emulator in the script.
  • Android 17 isn't in the script yet and hasn't been checked.
  • Versions below 7.0 can't install the app.

Emulators don't replace real phones for battery, GPS, background behaviour or manufacturer power management.

Real-device test plan

No real phone has been signed off yet. This is the coverage being worked towards:

CoverageDeviceWhat it checksStatus
FloorAndroid 7.0 emulator, and a physical phone if one turns upInstall, launch, sign-in, map, capture and the encrypted queue at the minimum versionNot yet tested
Older phoneA phone launched around 2021, on Android 11 or 12Background permission, battery, overnight and offline behaviourNot yet tested
Manufacturer differencesSamsung and one other manufacturer, including a budget phone, ideally Android 12–14Power management, permission recovery, performanceNot yet tested
CurrentAndroid 15 and 16 phonesThe most common versions, foreground service and notification behaviourNot yet tested
NewestAndroid 17 emulator or phoneForward compatibility with target 36Not yet tested
Version changesAndroid 10–14 emulators as neededBackground permission, approximate location, service starts and notificationsNot yet tested

One phone can cover more than one row. For each run, record:

  • the phone: manufacturer, model, launch year, Android version and API level, security patch and Google Play services version;
  • the conditions: app build, network and battery mode;
  • the run itself: tester, date, results and any bugs.

On every device, try these cases:

  • allowing, denying and later granting location, background location and notifications;
  • approximate instead of precise location;
  • turning location services off;
  • screen off, rebooting and force-stopping the app;
  • manufacturer battery restrictions;
  • long periods offline, then reconnecting.

Check that locations still arrive encrypted and up to date, and measure battery use and delay rather than assuming tracking never stops.

On this page