Friday, October 4, 2019

Understanding How Anbox (Native Android App Runner on Linux Host) Works

2019-10-24

One of the things I really like about computer science is the creativity part. I often think about possibilities of things. And a couple weeks ago, I was thinking that it'd be sleek to be able to run Android application natively on Linux OS. There are already simulator/emulator-based solution, but they typically have performance impact and not-so-nice user experience. For example, the Android device emulator in Windows 10, the official one that comes with the latest version of Android Studio often causes 99% of CPU usage even when it's not running anything. From googling around, I learned that it was because of the audio subsystem. Disabling audio seems to fix that issue. But this got me thinking if there's a more native way to run Android application on Linux, ideally by using the host machine's Linux kernel. It should be totally doable because Google's Chrome OS already does that. I'm pretty sure Chrome OS doesn't use emulation-based technique because a lot of the Chromebooks out there don't have fancy specifications.

And just like so many other things, somebody already did it! I found Anbox (https://anbox.io/) through a quick Google search! This thing is kinda cool. So it uses DKMS, which is a way to compile kernel module dynamically to whatever host Kernel is running on. DKMS is used to compile binder and ashmem, which
are IPC mechanisms used in Android. These two are specifically created by Android author for Android, so they're not available on the mainstream Kernel. Anbox put these DKMS modules are on separate github: https://github.com/anbox/anbox-modules, which is essentially the kernel drivers code forked out of Android kernel, and then modified to work on host device. Installation can be done by looking at the README file over there.


Installing Anbox is quite painful in Arch Linux. According to its manual, it should work flawlessly on Ubuntu with snap package, unfortunately it doesn't work with Arch.  I tried doing yaourt anbox but it didn't work.  So I had to follow the guide over here: https://wiki.archlinux.org/index.php/Anbox. I cloned each of the AUR repositiories mentioned there, and then installed them manually using makepkg command.

After minor modifications, I was able to finally install all the modules mentioned on the Arch Linux's wiki page. Unfortunately, after that things still just don't run out of the box. I had to peek PKGBUILD and anbox source code (https://github.com/anbox/anbox-modules) to figure out how they can be run. So after everything is installed, we need to run 2 things, container manager, and session manager:
1. sudo systemctl start anbox-container-manager.service
2. systemctl --user status anbox-session-manager.service

After the two are run, if we do `adb devices`, we should see one new device shows up. This is our Android device, with which we can install APK and stuffs.

Throughout this process, I learned about DBus, which is a commonly used IPC mechanism in Linux, this is being used by Anbox to communicate with each other. anbox session-manager creates a DBUS server, which is used by anbox wait-ready to communicate with container manager about the status such as whether the Android device is created

2019-10-07

Today I tried to understand how source code (https://github.com/anbox/anbox) work internally from high-level perspective. So I started off the main.cpp file under src/ directory, which is the entry point of the program. While browsing the code, it appears to be using a lot of newer C++ features, such as:
I was actively using C++ when working at NVIDIA 2013-2015 doing Android OS-related work, but I never know such features exist. I guess they're newer features or amongst the set of features that are "banned" by Google in C++ -- throughout Google, there are only subsets of C++ features allowed to be used -- Looking through this Anbox codebase, I kinda understand now why Google bans some of C++ features. They're just so confusing and adding quite significant amount of time to get up to speed with a codebase. I can see those features being useful, but they're just not necessary IMO. Anyway, let's get back on topic.

So container manager, session manager, and the client side to launch a new Android application are run through this same entry point: main.cpp. Each of the possible "commands" is defined by a Command class, where there's an action callback which gets called when that command is run through terminal. For example, "anbox session-manager" ultimately invokes the action callback inside of session_manager.cpp (line 119, by the time of this writing). Different commands are separated into different files under src/anbox/cmds, which makes it easy to figure out how they work.

From my experience working with large codebase (AOSP), the best way to understand how it works is to go backward from running the application, find portion of the program that is interesting, then backtrack to figure out which code path does that interesting thing. In anbox's case, for me personally,  one thing that piques my interest is when the Android operating system starts to "kicks in". I want to understand how the host OS starts running the LXC container that contains the "Android operating system". From my experience with AOSP, it's much alike Linux OS in general. Bootloader boots up, then it bootstraps the kernel, then the kernel runs the very first program, init process, which then runs zygote which handles all the JVM-related things.

In order to understand how Android "kicks in", I took on how "anbox launch" command works (i.e. "anbox launch --package=org.anbox.appmgr --component=org.anbox.appmgr.AppViewActivity"), which when called, opens up a new window with the specified Android application running there (i.e. Whatsapp). So what happens is that main.cpp creates an instance of Daemon call (daemon.cpp), then invokes its run function with the program arguments. Pretty straightforwardly, daemon.cpp invokes Launch class, which is the subclass of Command class. After that, the  action callback inside of Launch's constructor is called. First thing it does is to creates and displays a splash screen. It then uses D-BUS to talk to the other daemon, which I'm not sure yet whether it's container manager or session manager. Through RPC, it tells the daemon that it wants to run an Android acitivity whose package and component names described on the program arguments.

TO BE CONTINUED....