Skip to main content

Setting Up An Emulator

warning

Work In Progress - Initial Rough Draft

QEMU - Generic Emulator

QEMU fancies itself "A generic and open source machine emulator and virtualizer". I've been using QEMU for over 25 years. It is an amazing tool that can emulate completely different CPU architectures on x86ish (and other types of) host systems. Nowadays, since the time of Linux Kernel-based Virtual Machines (KVM), it is also a virtualization frontend.

  • QEMU can do whole system emulation, for example: Run a virtual hardware platform with a firmware bootloader that boots a kernel and supports a full user environment. This is very flexible and powerful, but also very slow due to the host CPU having to not only convert all of the applications instructions, but it also must handle all of the hardware emulation (e.g. MMU & IO).

  • QEMU can do userspace emulation, for example: Run a ARM64 binary on a x86_64 based system. The idea here is that the executable is the same ELF format that an x86_64 would be. It has .data, .text, and .bss sections like any other. The primary difference is that the executable code (found in .text) is ARM64 instructions instead of x86_64 instructions. QEMU can do on-the-fly translation of those instructions to allow the executable to make standard ABI calls that all Linux systems support (i.e. the syscall interface).

  • QEMU can do whole system virtualization. Emulation is the act of converting instruction sets from one to another or mocking up hardware that doesn't actually exist. Virtualization is different in that the instructions are not translated and the virtual hardware is generally backed by real hardware that serves the same function as the API. The real hardware backed aspect plus supported Virtualization technology (e.g. VMX) allows virtualization to provide developers the desired system encapsulation without overly sacrificing on speed or performance. Hypervisors and virtual machines are virtualizations, not emulations.

Android Emulator is a fork of qemu. When I originally started messing around with Android emulator years ago, it was doing full emulation of the system. I could run the ARM version of Android 7 on my x86_64 machine. This always felt really good to me because I was running the exact same code on my developer host as I was on the device.

For performance reasons and due to virtualization extension availability, Android now is developed using virtualization. In general, we run the same APK on x86_64 as we do on ARM64, but Android invokes the CPU specific libaries for the platform it is running on. So while we're not running from the same binary code, its running from the same source code (ideally).

The point that I am driving at here is that we call the Android tool an emulator, but its really a virtual machine. As such, it runs faster and you need to run it with the system image that matches your developer host (in contrast to the target Android device). Another key piece of knowledge is that you can't have any other hypervisor running other than KVM. If you are using VMWare or VirtualBox for other VMs on the developer host, you'll need to compeletely shutdown those services to get the emulator to run happily.

Note: I've gone down a great many paths to circumvent the multi-hypervisor issue. Running qemu inside another VM is technically possible, but the overhead is unbearable (IMHO). Please do not consider running qemu inside a container, containers don't work like that. One strategy that has worked for me is to run QEMU on a completely different machine and connect my ADB server to the ADB server on that machine. It makes for a more complicated environment, but it does allow me to continue using VirtualBox on my developer host while using KVM on a second host. More on this later.

Installing Emulator and System Image

Enough about QEMU, lets install the Android "emulator" (i.e. Virtual Machine):

yes | sdkmanager emulator

Verify install with: emulator -version:

(adb-venv) $ emulator -version
INFO | Android emulator version 36.1.9.0 (build_id 13823996) (CL:N/A)
INFO | Graphics backend: gfxstream
Android emulator version 36.1.9.0 (build_id 13823996) (CL:N/A)
Copyright (C) 2006-2024 The Android Open Source Project and many others.
This program is a derivative of the QEMU CPU emulator (www.qemu.org).

This software is licensed under the terms of the GNU General Public
License version 2, as published by the Free Software Foundation, and
may be copied, distributed, and modified under those terms.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

Assuming everything has gone well, we now need to select a base system image to run with the emulator. If you simply run sdkmanager --list | grep system-images, you get a large list of available system images to install. I want to install an x86_64 based image for Android 13. (We want Android 13 in this case because its the last version that doesn't have a bunch of new security features making emulator rooting and debugging a big hastle.)

The tools in sdkmanager are referenced with the SDK version, not the Android version. Therefore we need to determine what the associated SDK is for Android 13. Two different sites list the mapping:

I also asked ChatGPT to make me a chart:

Android VersionAPI / SDK LevelCodename / Notes
1636
1535
1434
1333
12L32
1231
1130
1029
9.028Pie
8.127Oreo MR1
8.026Oreo
7.125Nougat MR1
7.024Nougat
6.023Marshmallow
5.122Lollipop MR1
5.021Lollipop
4.4W (Wear)20KitKat Watch
4.419KitKat
4.3.x18Jelly Bean MR2
4.2.x17Jelly Bean MR1
4.1.x16Jelly Bean
4.0.3 – 4.0.415Ice Cream Sandwich MR1
4.0.1 – 4.0.214Ice Cream Sandwich
3.213Honeycomb MR2
3.112Honeycomb MR1
3.011Honeycomb
2.3.3 – 2.3.710Gingerbread MR1
2.3 – 2.3.29Gingerbread
2.28Froyo
2.17Eclair MR1
2.0 – 2.0.15 – 6Eclair
1.64Donut
1.53Cupcake
1.12
1.01

: Android Version to SDK Mapping

Based on the above information, I'm looking for a system image with the name system-images;android-33 for architecture x86_64:

(adb-venv) $ sdkmanager --list | grep 'system-images;android-33' | grep 'x86_64'
system-images;android-33;default;x86_64 | 2 | Intel x86_64 Atom System Image | syst
em-images/android-33/default/x86_64
system-images;android-33-ext4;google_apis_playstore;x86_64 | 1 |
Google Play Intel x86_64 Atom System Image
system-images;android-33-ext5;google_apis_playstore;x86_64 | 2 |
Google Play Intel x86_64 Atom System Image
system-images;android-33;android-automotive-distant-display-playstore;x86_64 | 2 |
Automotive Distant Display with Google Play x86_64 System Image
system-images;android-33;android-automotive;x86_64 | 5 |
Android Automotive with Google APIs x86_64 System Image
system-images;android-33;android-desktop;x86_64 | 4 |
Desktop Intel x86_64 Atom System Image
system-images;android-33;android-wear;x86_64 | 4 |
Wear OS 4 Intel x86_64 Atom System Image
system-images;android-33;aosp_atd;x86_64 | 2 |
AOSP ATD Intel x86_64 Atom System Image
system-images;android-33;default;x86_64 | 2 |
Intel x86_64 Atom System Image
system-images;android-33;google_apis;x86_64 | 17 |
Google APIs Intel x86_64 Atom System Image
system-images;android-33;google_apis_playstore;x86_64 | 9 |
Google Play Intel x86_64 Atom System Image
system-images;android-33;google_atd;x86_64 | 2 |
Google APIs ATD Intel x86_64 Atom System Image

To keep things as simple as possible, I'll go for the default image. Install the image with sdkmanager:

sdkmanager "system-images;android-33;default;x86_64"

After the install completes, you can see the install located in $ANDROID_HOME/system-images/android-33/default/x86_64/. Hopefully you can see how the name of the system image directly maps to where its stored in the system. Within that folder, you'll find all of the base files used to run the Android system.

Create an Android Virtual Machine (AVD)

As mentioned before, the avdmanager from cmdline-tools is used to generate a "virtual machine" based on a given system image and some other possible input about the hardware configuration (i.e. the qemu configuration). For our purposes, we'll keep things simple for now. But you should know that its here that you can select whether you'd like to mock up a specific device type (e.g. Pixel 6) for your specific needs. Be aware, its not identical to the real thing, but usually enough to trick the application and enough to provide an appropriate visual representation (e.g. same screen pixel dimensions as the real thing).

To create a default AVD based on the default Android SDK-33 system image for an x86_64:

(adb-venv) $ avdmanager create avd -n android13 -k "system-images;android-33;default;x86_64"
[=======================================] 100% Fetch remote repository...
Auto-selecting single ABI x86_64
Do you wish to create a custom hardware profile? [no]

You can hit Enter on Do you wish to create a custom hardware profile? [no] to skip it. Once you've created the AVD, you can see the files its created at $ANDROID_HOME/avd/android13.avd/ and $ANDROID_HOME/avd/android13.ini.

If you want to remove an AVD from the system, you can use the delete subcommand:

avdmanager delete avd -n android13

I often use this when I've made a mess of the environment and want to start over. You will lose any stateful data in the system state when you do this. All the system state is stored in the relavant $ANDROID_HOME/avd/<NAME>.avd/ folder.

Start The Emulator

Now that we have created a emulator, lets fire it up. You can list available AVDs in the system from the emulator with something like: emulator -list-avds. We're using our android13 AVD, so we can start the emulator with:

emulator -avd android13

Presuming you are in a desktop environment in Linux, the above command (if successful) will launch a new window that shows the Android operating system booting and eventually running. There is a toolbox off to the side where you can poke and prod the AVD as if it were a real device. There are also a number of options that allow you to configure various aspects of the AVD (or hardware) state as seen by the system.

Personally, I never use the emulator GUI interface unless I have to. It doesn't perform well in constrained environments and has a lot of limitations with regards to remote access (that I often require). In the next section we'll discuss using an alternative third party tool called scrcpy to get a more flexible visualization of the system's GUI.

Another thing that I don't like about running emulator -avd android13 is that it doesn't provide the console output I want to see. When I'm working in a device, I want to know where the kernel is struggling, I want to see if I'm in a boot loop, I want to monitor device state changes, and I want to have a general understanding the the system is up and responsive (even if my application is locked up or halted). To do this, I use a specific set of options with emulator:

emulator -avd android13 -no-snapshot -writable-system -show-kernel -verbose -no-audio -no-window
  • no-snapshot - Don't save the system state on reboot. I want the system to be volatile to maximize repeatability. This also makes rebooting faster since it doesn't need to waste time creating a snapshot.
  • writable-system - A requirement for rooting and making system level changes on a running AVD.
  • show-kernel - Dumps the kernel log to STDOUT (similar to dmesg).
  • verbose - Give us all the details about the initialization and bootup procedure.
  • no-audio - Don't use developer host audio resources. (Useful if you aren't testing or using AVD audio.)
  • no-window - Don't use developer host video resources. (Useful if you are using scrcpy.)

Note: When you run the above command, you no longer will see a window with the Android screen output. Instead, it'll only be console-ish information in the terminal.

ADB To The Emulator

Great! We should now have an emulator running! I recommend that you disconnect any other Android devices from the developer host unless you want to type -s <device-name> endlessly.

By default if you run adb devices, you'll likely now see the following (assuming the AVD/emulator is running):

(adb-venv) $ adb devices
List of devices attached
emulator-5554 device

ADB is automatically able to connect to the emulator (via USB protocol). There is no need to worry about Developer Tools and enabling USB debugging. Remember, all of that is for security purposes. In an emulator, we're are significantly less secure by design to enable and facilitate development, troubleshooting, and debugging.

You can connect to the emulator now by simply running adb shell. Note: This is a non-root shell access so it'll be pretty challenging to break anything at this point.

You should also be able to adb push and adb pull to locations you have access to as a user.

Install and Run scrcpy (i.e. "Screen Copy")

You can locate and download a version of scrcpy from the Github release page. For example:

cd ~/Downloads
curl -LO https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-linux-x86_64-v3.3.3.tar.gz
tar -xf scrcpy-linux-x86_64-v3.3.3.tar.gz
# Move the scrcpy to the folder in our PATH from env.sh
mv scrcpy-linux-x86_64-v3.3.3 ~/.android/scrcpy

Note: Once installed, one big thing I'd like everyone to note is that the $ANDROID_HOME/scrcpy folder has a adb binary in the folder. If you run which adb you'll also see that this adb has precendence over platform-tools. This has never presented an issue for me, but not having the environment configured this way has caused issues. scrcpy really wants adb to exist in its own folder, so I recommend leaving all of that as it is until you have a stronger grasp on the complete environment.

Now that scrcpy has been installed, you can simply run it to get a window with the Android GUI. In the event you have multiple devices on your system, you may need to specifc the -s parameter just like the adb client. One thing I always do when running scrcpy is to disassociate it from my shell. That way I can reuse my shell or close my terminal and not interrupt scrcpy window. This is done with:

setsid scrcpy &>/dev/null &

Of course this also hides any errors, so only do this after you have things working. Its also worth noting that anytime the Android Server or AVD reboots, scrcpy will close. Some adb commands will cause this to occur and you'll need to restart your scrcpy. I personally work around this by having a tmux pane dedicated to some of the GUI applications and then rerun commands from that shell's history. You could also write a easy script and drop it into the $ANDROID_HOME/misc-tools folder (which is already in the $PATH).

Since scrcpy is more slim in its interface, it has alot of command line options and keyboard shortcuts to perform similar tasks to the side toolbox from pure emulator GUI. See shortcuts below (copied from scrcpy Github).

scrcpy Keyboard Shortcuts (Click Here To Expand)

Shortcuts

Actions can be performed on the scrcpy window using keyboard and mouse shortcuts.

In the following list, MOD is the shortcut modifier. By default, it's (left) Alt or (left) Super.

It can be changed using --shortcut-mod. Possible keys are lctrl, rctrl, lalt, ralt, lsuper and rsuper. For example:

# use RCtrl for shortcuts
scrcpy --shortcut-mod=rctrl

# use either LCtrl or LSuper for shortcuts
scrcpy --shortcut-mod=lctrl,lsuper

Super is typically the Windows or Cmd key.

ActionShortcut
Switch fullscreen modeMOD+f
Rotate display leftMOD+ (left)
Rotate display rightMOD+ (right)
Flip display horizontallyMOD+Shift+ (left) | MOD+Shift+ (right)
Flip display verticallyMOD+Shift+ (up) | MOD+Shift+ (down)
Pause or re-pause displayMOD+z
Unpause displayMOD+Shift+z
Reset video capture/encodingMOD+Shift+r
Resize window to 1:1 (pixel-perfect)MOD+g
Resize window to remove black bordersMOD+w | Double-left-click¹
Click on HOMEMOD+h | Middle-click
Click on BACKMOD+b | MOD+Backspace | Right-click²
Click on APP_SWITCHMOD+s | 4th-click³
Click on MENU (unlock screen)⁴MOD+m
Click on VOLUME_UPMOD+ (up)
Click on VOLUME_DOWNMOD+ (down)
Click on POWERMOD+p
Power onRight-click²
Turn device screen off (keep mirroring)MOD+o
Turn device screen onMOD+Shift+o
Rotate device screenMOD+r
Expand notification panelMOD+n | 5th-click³
Expand settings panelMOD+n+n | Double-5th-click³
Collapse panelsMOD+Shift+n
Copy to clipboard⁵MOD+c
Cut to clipboard⁵MOD+x
Synchronize clipboards and paste⁵MOD+v
Inject computer clipboard textMOD+Shift+v
Open keyboard settings (HID keyboard only)MOD+k
Enable/disable FPS counter (on stdout)MOD+i
Pinch-to-zoom/rotateCtrl+click-and-move
Tilt vertically (slide with 2 fingers)Shift+click-and-move
Tilt horizontally (slide with 2 fingers)Ctrl+Shift+click-and-move
Drag & drop APK fileInstall APK from computer
Drag & drop non-APK filePush file to device

¹Double-click on black borders to remove them.
²Right-click turns the screen on if it was off, presses BACK otherwise.
³4th and 5th mouse buttons, if your mouse has them.
⁴For react-native apps in development, MENU triggers development menu.
⁵Only on Android >= 7.

Shortcuts with repeated keys are executed by releasing and pressing the key a second time. For example, to execute "Expand settings panel":

  1. Press and keep pressing MOD.
  2. Then double-press n.
  3. Finally, release MOD.

All Ctrl+key shortcuts are forwarded to the device, so they are handled by the active application.

Rooting The Emulator

Unlike a real device where you have to authorize unlocking, unlock bootloader, patch booter, and then flash a rooted image; In the (Android 13 based) emulator, all you have to do is run the following commands:

adb root
adb remount

Note: If you were running scrcpy, you'll see it close. Simply rerun to get it up and running in the root setup.

One of the easiest ways to know if adb root worked is by running adb shell. When you opened ADB before, you were presented with a prompt that ended with a $. A rooted prompted (by convention) is shown with a #.

Peek and poke around in the system without worry. If you do break something, you can always reboot the system. If you feel you've done something that has persisted a reboot, you can delete and recreate the AVD from scratch. This is the beauty about rooting and experimenting in an emulator: No bricking a device!

Recap

We did the following:

  • Installed the Android emulator
  • Installed a base system image (Android 13 / default / x86_64)
  • Created an Android Virtual Device (AVD)
  • Started the AVD/emulator
  • Installed scrcpy
  • Started the AVD/emulator in background only
  • Started scrcpy to see background AVD/emulator.
  • Rooted the AVD/emulator