Initial commit

This commit is contained in:
Halim Qarroum
2023-02-15 03:01:51 +00:00
commit b021fd4d0d
16 changed files with 1612 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
version = 1
[[analyzers]]
name = "secrets"
[[analyzers]]
name = "docker"
+92
View File
@@ -0,0 +1,92 @@
name: Docker Android CI
on:
push:
branches: [ "main" ]
paths:
- 'Dockerfile'
- '.github/workflows/*'
- 'deps/*'
- 'keys/*'
- 'scripts/*'
jobs:
build-api-33:
runs-on: ubuntu-latest
steps:
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: Build Docker image with Android 33
uses: docker/build-push-action@v4
with:
push: true
tags: halimqarroum/docker-android:api-33
build-args: API_LEVEL=33
build-api-32:
runs-on: ubuntu-latest
steps:
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: Build Docker image with Android 32
uses: docker/build-push-action@v4
with:
push: true
tags: halimqarroum/docker-android:api-32
build-args: API_LEVEL=32
build-api-28:
runs-on: ubuntu-latest
steps:
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: Build Docker image with Android 28
uses: docker/build-push-action@v4
with:
push: true
tags: halimqarroum/docker-android:api-28
build-args: |
"API_LEVEL=28"
"ARCHITECTURE=x86"
build-api-28-playstore:
runs-on: ubuntu-latest
steps:
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: Build Docker image with Android 28 and the PlayStore
uses: docker/build-push-action@v4
with:
push: true
tags: halimqarroum/docker-android:api-28-playstore
build-args: |
"API_LEVEL=28"
"ARCHITECTURE=x86"
"IMG_TYPE=google_apis_playstore"
build-minimal:
runs-on: ubuntu-latest
steps:
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: Build minimal Docker image
uses: docker/build-push-action@v4
with:
push: true
tags: halimqarroum/docker-android:minimal
build-args: INSTALL_ANDROID_SDK=0
+7
View File
@@ -0,0 +1,7 @@
language: ruby
services:
- docker
script:
- docker build -t android-emulator .
+81
View File
@@ -0,0 +1,81 @@
FROM adoptopenjdk/openjdk11:alpine-jre
# Docker labels.
LABEL maintainer "Halim Qarroum <hqm.post@gmail.com>"
LABEL description "A Docker image allowing to run an Android emulator"
LABEL version "1.0.0"
# `redir.c` will be used to redirect
# localhost ADB ports to the container interface.
COPY deps/redir/redir.c /usr/src/redir.c
# Installing required packages.
RUN apk update && \
apk upgrade && \
apk add --no-cache \
alpine-sdk \
bash \
unzip \
wget \
libvirt-daemon \
dbus \
polkit \
virt-manager && \
# Compile `redir`.
gcc /usr/src/redir.c -o /usr/bin/redir && \
strip /usr/bin/redir && \
# Cleanup APK.
apk del alpine-sdk && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /var/cache/apk/*
# Arguments that can be overriden at build-time.
ARG INSTALL_ANDROID_SDK=1
ARG API_LEVEL=33
ARG IMG_TYPE=google_apis
ARG ARCHITECTURE=x86_64
ARG CMD_LINE_VERSION=9477386_latest
# Environment variables.
ENV ANDROID_SDK_ROOT=/opt/android \
ANDROID_PLATFORM_VERSION="platforms;android-$API_LEVEL" \
PACKAGE_PATH="system-images;android-${API_LEVEL};${IMG_TYPE};${ARCHITECTURE}" \
API_LEVEL=$API_LEVEL \
ARCHITECTURE=$ARCHITECTURE \
ABI=${IMG_TYPE}/${ARCHITECTURE} \
QTWEBENGINE_DISABLE_SANDBOX=1 \
ANDROID_EMULATOR_WAIT_TIME_BEFORE_KILL=10
# Exporting environment variables for keeping in the path
# Android SDK binaries and shared libraries.
ENV PATH "${PATH}:${ANDROID_SDK_ROOT}/platform-tools"
ENV PATH "${PATH}:${ANDROID_SDK_ROOT}/emulator"
ENV PATH "${PATH}:${ANDROID_SDK_ROOT}/cmdline-tools/tools/bin"
ENV LD_LIBRARY_PATH "$ANDROID_SDK_ROOT/emulator/lib64:$ANDROID_SDK_ROOT/emulator/lib64/qt/lib"
# Set the working directory to /opt
WORKDIR /opt
# Exposing the Android emulator console port
# and the ADB port.
EXPOSE 5554 5555
# Initializing the required directories.
RUN mkdir /root/.android/ && \
touch /root/.android/repositories.cfg
# Exporting ADB keys.
COPY keys/* /root/.android/
# Copy the startup scripts.
COPY scripts/* /opt/
# Make the scripts executable.
RUN chmod +x /opt/*.sh
# This layer will download the Android command-line tools
# to install the Android SDK, emulator and system images.
# It will then install the Android SDK and emulator.
RUN /opt/install-sdk.sh
# Set the entrypoint
ENTRYPOINT ["/opt/start-emulator.sh"]
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Halim Qarroum
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+141
View File
@@ -0,0 +1,141 @@
<br /><br /><br />
<p align="center">
<img width="400" src="assets/icon.png" />
</p><br /><br />
# docker-android
> A minimal and customizable Docker image running the Android emulator as a service.
[![Docker Image CI](https://github.com/HQarroum/docker-android/actions/workflows/docker-image.yml/badge.svg)](https://github.com/HQarroum/docker-android/actions/workflows/docker-image.yml)
[![DeepSource](https://deepsource.io/gh/HQarroum/docker-android.svg/?label=active+issues&show_trend=true&token=JTfGSHolIiMj0WNfv2ES0I6X)](https://deepsource.io/gh/HQarroum/docker-android/?ref=repository-badge)
Current version: **1.0.0**
## 📋 Table of contents
- [Features](#-features)
- [Description](#-description)
- [Usage](#-usage)
- [See also](#-see-also)
## 🔖 Features
- Minimal Alpine based image bundled with the Android emulator and KVM support.
- Bundles the Java Runtime Environment 11 in the image.
- Customizable Android version, device type and image types.
- Port-forwarding of emulator and ADB on the container network interface built-in.
- Emulator images are wiped each time the emulator re-starts.
- Runs headless, suitable for CI farms. Compatible with [`scrcpy`](https://github.com/Genymobile/scrcpy) to remotely control the Android screen.
## 🔰 Description
The focus of this project is to provide a size-optimized Docker image bundled with the minimal amount of software required to expose a fully functionning Android emulator that's remotely controllable over the network. This image only contains the Android emulator itself, an ADB server used to remotely connect into the emulator from outside the container, and QEMU with `libvirt` support.
You can build this image without the Android SDK and without the Android emulator to make the image smaller. Below is a size comparison between some of the possible build variants.
Variant | Uncompressed | Compressed |
------------------------- | ---------------- | ------------ |
SDK 33 + Emulator | 5.84 GB | 1.97 GB |
SDK 28 + Emulator | 4.29 GB | 1.46 GB |
Without SDK and emulator | 414 MB | 138 MB |
## 📘 Usage
By default, a build will bundle the Android SDK, platform tools and emulator with the image.
```bash
docker build -t android-emulator .
```
### Running the container
Once the image is built, you can mount your KVM driver on the container and expose its ADB port.
> Ensure 4GB of memory and at least 8GB of disk space for API 33.
```bash
docker run -it --rm --device /dev/kvm -p 5555:5555 android-emulator
```
### Connect ADB to the container
The ADB server in the container will be spawned automatically and listen on all interfaces in the container. After a few seconds, once the kernel has booted, you will be able to connect ADB to the container.
```bash
adb connect 127.0.0.1:5555
```
Additionally, you can use [`scrcpy`](https://github.com/Genymobile/scrcpy) to control the screen of the emulator remotely. To do so, you simply have to connect ADB and run it locally.
> By default, the emulator runs with a Pixel preset (1080x1920).
```bash
scrcpy
```
<br />
<table>
<tr>
<td>
<img width="260" src="assets/screenshot.png" />
</td>
<td>
<img width="260" src="assets/screenshot-2.png" />
</td>
<td>
<img width="260" src="assets/screenshot-3.png" />
</td>
</tr>
</table>
<br />
### Customize the image
It is possible to customize the API level (Android version) and the image type (Google APIs vs PlayStore) when building the image.
> By default, the image will build with API 33 with support for Google APIs for an x86_64 architecture.
This can come in handy when integrating multiple images as part of a CI pipeline where an application or a set of applications need to be tested against different Android versions. There are 2 variables that can be specified at build time to change the Android image.
- `API_LEVEL` - Specifies the [API level](https://apilevels.com/) associated with the image. Use this parameter to change the Android version.
- `IMG_TYPE` - Specifies the type of image to install.
- `ARCHITECTURE` Specifies the CPU architecture of the Android image. Note that only `x86_64` and `x86` are actively supported by this image.
The below example will install Android Pie with support for the Google Play Store.
```bash
docker build \
--build-arg API_LEVEL=28 \
--build-arg IMG_TYPE=google_apis_playstore \
--build-arg ARCHITECTURE=x86 \
--tag android-emulator .
```
### Mount an external drive in the container
It might be sometimes useful to have the entire Android SDK folder outside of the container (stored on a shared distributed filesystem such as NFS for example), to significantly reduce the size and the build time of the image.
To do so, you can specify a specific argument at build time to disable the download and installation of the SDK in the image.
```bash
docker build -t android-emulator --build-arg INSTALL_ANDROID_SDK=0 .
```
> You will need mount the SDK in the container at `/opt/android`.
```bash
docker run -it --rm --device /dev/kvm -p 5555:5555 -v /shared/android/sdk:/opt/android/ android-emulator
```
### Pull from Docker Hub
Different pre-built images of `docker-android` exist on [Docker Hub](https://hub.docker.com/repository/docker/halimqarroum/docker-android/general). Each image variant is tagged using its the api level and image type. For example, to pull an API 33 image, you can run the following.
```bash
docker pull halimqarroum/docker-android:api-33
```
## 👀 See also
- The [alpine-android](https://github.com/alvr/alpine-android) project which is based on a different Alpine image.
- The [docker-android](https://github.com/budtmo/docker-android) project which offers a WebRTC interface to an Android emulator.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

+1140
View File
File diff suppressed because it is too large Load Diff
+28
View File
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCjLJ91cKt62g2W
DksA7UjUHOeuveDHKzuwgswpIr4d75OHSBOYsIs5HBu5jPJ0UOZ0pMNJVZMHx7+T
J4vrSLmcjg02FWkngkr2DaZyxaDnGYhra452ARoTF3fc2pOflrXjiLPRUvxwTeNt
B0kJT7PrmTm82ZsEahsoRqdwvuyfoJzy5H9KzBr1OSdTdbRzvm48up9qOy0mdNbG
0994FyMWMeFWj07Y7MT39wUZhDEKOyLx/JE57s7QWde8OM8UA+et2FNud0eMP6kF
nG7RJdcmnw7e6BVzkNNKojyaGo8xSchHGAxCPykCtZBnl9b0mfjkWAKOPbu0YQEz
jppHmqx5AgMBAAECggEAAQ81m9QW8O3Af9Ro7UEdI29qm6pU7sYblZpLzAqTNGuN
SCgSJIAk70J89ZfyLQonbONy9yNsmHTWDBUKJgVrWxZbBJpf++cvKGjPR1h8InP6
fEQPATohmrUgzJjIs5UNakRFcpr405vV2avd8AcJ8oIkq1K4XG957KmExckfjSvB
oaWR5ZCZ4TO60C0k8za1VhnnFUpwcXgE5qno8yneEgu3EiH1zxRsUkmp/P0KWJbt
RJMZEAi+xPD85v0/IhAIiNgu15SvQzEFA/OzF2Vmrg8gCyXp0OEu2rTat8PNOBtR
mIeX9XxBK9CJFawFtrmDwPWn5rHhclmrsL8Fh6+zeQKBgQDOz/r6Hu3IoAzGO/X5
aH8oheiejBNsiqGH3EtSwUxR28ZBZxQ1+U5khsuCRSNzG4QKedbMyL1Qc0X3gLOv
qG0GVxU2V/2Ru9yZ4FErYZWIZwY0PPJtLvCv2HbEANtd8LXAvInzmzXVSQlPVjvy
MPklVt5gSZCmIlIP5Tx6j3XBvQKBgQDJ+68ia6Tl2qw4PIaS0bHbCWcZuDz4UPF4
BNGUysGm3Utd2JmD8TpDIqn9xjjVBILWlpyCe4lzuPl/YaKHnqEMuxskcxzZr7p5
WmkEzqDOnzm7e8f0My2vZRvftYMwGqbcA18swpVqi9LR8IFDrO3ZlabXTYf/eLf6
+3ePgA1bbQKBgGQ5RleQRYvEhGxNApcrnPP2cRTkrUUcqcs9ZwNjyaih6SIo4hKG
DweIi3dGKjfOTd8FzKLRZW1nb7DSHkIyTSOVcCI14hyN/gpUOwHa+KxxwP/vyUxA
Q0P0kxnJkIWl8W7F79SukxgZnPllwQdN1G/T+VCQVhks6LfrT7PVcivBAoGAdEOn
wAxuUtDYqVZNsF18HzT+X9OpZNZS6F79tMQxTDe9ljuDwmssulBNwYJEXHnIRnVT
idhKeo5TL3un7qDk20soiIaqaLO6hvf/04CNF+KXfM/3fR2ognmU3eTBqwLsYWCf
fEhVcYsiwy3JeockMkbuTqiWS/vVC2eMntF63tECgYBQfH7JtZ+b+ckH05fuckbY
tGFKx959xV7a6fJJQUHGHdbUxPdJvk76TdHT8ouBmjNU21iQq7kL0kHTnDcHBRjS
QwAPvOIoXKNNRpfgTQz532vSknCG/AMTxHhZlOFb6Lgsn261oYlNi2OPrG8DrknM
UWezE5O8/B5V4t69i66/rw==
-----END PRIVATE KEY-----
+1
View File
@@ -0,0 +1 @@
QAAAADcCzLh5rJpHmo4zAWG0uz2OAljk+Jn01pdnkLUCKT9CDBhHyEkxjxqaPKJK05BzFejeDp8m1yXRbpwFqT+MR3duU9it5wMUzzi811nQzu45kfzxIjsKMYQZBff3xOzYTo9W4TEWIxd439PG1nQmLTtqn7o8br5ztHVTJzn1GsxKf+TynKCf7L5wp0YoG2oEm9m8OZnrs08JSQdt401w/FLRs4jjtZafk9rcdxcTGgF2jmtriBnnoMVypg32SoInaRU2DY6cuUjriyeTv8cHk1VJw6R05lB08oy5Gxw5i7CYE0iHk+8dviIpzIKwOyvH4L2u5xzUSO0ASw6WDdp6q3B1nyyjFVLaEEvn7qsNCOAjrJUcH/y8LJO8JQLqOdZwsvZFayy8B3egrbOc3StUOpPv9mwrbpe0dt9E5/Yc9iG4QpDsYKHFKTsEJrsZDR7Qu0XAaLXnurGsI7CIwo6FSpCyIwDQ33/P5gNeMuN4sPYKqoc20QZ7WxIRvG9Ro2NiMFAJnz0EUBMeU5ZInqh7gSr2ss5EUSHdOgLtuiI3kedozzV9/EDpiwe+Kld1O7WRN5Fz3Zv2YgW3lau3nbLvJOkFsSP4WXaNfYuwLaA62adHSyme829X+zO+x+X0UTZHKDRe2hQQgHJcRMWHWfmBjuudKgbl8P5ukVxj/IIqxbeyxs4YnwEAAQA= @unknown
+34
View File
@@ -0,0 +1,34 @@
#!/bin/bash
set -e
# Writes a log with a key and a value
# as a JSON object on the standard output.
function write_log() {
echo "{ \"type\": \"$1\", \"value\": \"$2\" }"
}
# Writes a state update on the standard output.
function update_state() {
write_log "state-update" "$1"
}
# Waits for the emulator to boot and writes
# state updates on the standard output.
function wait_for_boot() {
update_state "booting"
# Waiting for the ADB server to start.
while [ -n "$(adb wait-for-device > /dev/null)" ]; do
adb wait-for-device
sleep 1
done
# Waiting for the boot sequence to be completed.
COMPLETED=$(adb shell getprop sys.boot_completed | tr -d '\r')
while [ "$COMPLETED" != "1" ]; do
COMPLETED=$(adb shell getprop sys.boot_completed | tr -d '\r')
sleep 5
done
update_state "booted"
}
+14
View File
@@ -0,0 +1,14 @@
# If the installation flag of the Android SDK is set
# we download the Android command-line tools,
# install the SDK, platform tools and the emulator.
if [ "$INSTALL_ANDROID_SDK" == "1" ]; then
echo "Installing the Android SDK, platform tools and emulator ..."
wget https://dl.google.com/android/repository/commandlinetools-linux-${CMD_LINE_VERSION}.zip -P /tmp && \
mkdir -p $ANDROID_SDK_ROOT/cmdline-tools/ && \
unzip -d $ANDROID_SDK_ROOT/cmdline-tools/ /tmp/commandlinetools-linux-${CMD_LINE_VERSION}.zip && \
mv $ANDROID_SDK_ROOT/cmdline-tools/cmdline-tools/ $ANDROID_SDK_ROOT/cmdline-tools/tools/ && \
rm /tmp/commandlinetools-linux-${CMD_LINE_VERSION}.zip && \
yes | sdkmanager --licenses && \
sdkmanager --install "$PACKAGE_PATH" && \
yes | sdkmanager --verbose $PACKAGE_PATH $ANDROID_PLATFORM_VERSION platform-tools emulator
fi
+46
View File
@@ -0,0 +1,46 @@
#!/bin/bash
set -e
source ./emulator-monitoring.sh
# The emulator console port.
EMULATOR_CONSOLE_PORT=5554
# The ADB port used to connect to ADB.
ADB_PORT=5555
# Start ADB server by listening on all interfaces.
echo "Starting the ADB server ..."
adb -a -P 5037 server nodaemon &
# Detect ip and forward ADB ports outside to outside interface
ip=$(ip addr list eth0 | grep "inet " | cut -d' ' -f6 | cut -d/ -f1)
redir --laddr=$ip --lport=$EMULATOR_CONSOLE_PORT --caddr=127.0.0.1 --cport=$EMULATOR_CONSOLE_PORT &
redir --laddr=$ip --lport=$ADB_PORT --caddr=127.0.0.1 --cport=$ADB_PORT &
export USER=root
# Creating the Android Virtual Emulator.
echo "Creating the Android Virtual Emulator ..."
echo no | avdmanager create avd -n android --abi ${ABI} -k "$PACKAGE_PATH" --device "pixel"
# Asynchronously write updates on the standard output
# about the state of the boot sequence.
#cat ~/.android/avd/android.avd/config.ini
wait_for_boot &
# Start the emulator with no audio, no GUI, and no snapshots.
echo "Starting the emulator ..."
if emulator \
-avd android \
-noaudio \
-no-boot-anim \
-no-window \
-no-snapshot-save \
-qemu \
-enable-kvm;
then
update_state "stopped"
else
update_state "stopped"
fi