Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Android

on:
push:
branches: [main]
paths:
- 'android/**'
- '.github/workflows/android.yml'
pull_request:
branches: [main]
paths:
- 'android/**'
- '.github/workflows/android.yml'
workflow_dispatch:

jobs:
build:
name: Debug build
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
with:
lfs: true

- name: Set up Java 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'

- name: Install NDK and CMake
run: |
yes | $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager \
"ndk;30.0.14904198" \
"cmake;3.22.1"

- name: Cache Gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ hashFiles('android/**/*.gradle*', 'android/**/gradle-wrapper.properties') }}
restore-keys: gradle-

- name: Cache NDK build artifacts
uses: actions/cache@v4
with:
path: android/app/.cxx
key: ndk-${{ runner.os }}-${{ hashFiles('android/app/src/main/cpp/CMakeLists.txt') }}
restore-keys: ndk-${{ runner.os }}-

- name: Build
working-directory: android
run: ./gradlew assembleDebug

- name: Upload APK
uses: actions/upload-artifact@v4
with:
name: hawkeye-android-debug
path: android/app/build/outputs/apk/debug/app-debug.apk
retention-days: 7
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,13 @@ build-debug/
build-release/
.dmux/
.dmux-hooks/

# Android
android/local.properties
android/.gradle/
android/app/build/
android/app/.cxx/
android/build/
android/.idea/workspace.xml
android/.claude/
android/.idea/*
15 changes: 15 additions & 0 deletions android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
3 changes: 3 additions & 0 deletions android/.idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

86 changes: 86 additions & 0 deletions android/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Hawkeye Android

Android port of the Hawkeye 3D renderer, running the desktop C/Raylib scene natively on Android via NativeActivity and OpenGL ES 3.0.

## Architecture

The app has no Java/Kotlin logic beyond the manifest declaration. `NativeActivity` loads `libhawkeye.so`, which runs a standard C `main()` entry point via Raylib's Android platform backend.

```
android/
├── app/src/main/
│ ├── AndroidManifest.xml — declares NativeActivity, requires OpenGL ES 3.0
│ ├── assets/ — all symlinks into the parent Hawkeye project
│ │ ├── fonts -> ../../../../fonts
│ │ ├── models -> ../../../../models
│ │ ├── shaders -> ../../../../shaders
│ │ └── themes -> ../../../../themes
│ └── cpp/
│ ├── CMakeLists.txt — fetches Raylib 5.5, compiles rendering subset
│ └── android_main.c — entry point: scene_init + vehicle_init + render loop
```

The Hawkeye source files compiled in are: `scene.c`, `vehicle.c`, `asset_path.c`, `theme.c`, `ortho_panel.c`. MAVLink, ULog, HUD, and replay code are excluded.

## Shader Compatibility

The original shaders use `#version 330` (desktop OpenGL). On Android they are patched at load time via Raylib's `SetLoadFileTextCallback`:

- `#version 330` → `#version 300 es`
- `precision mediump float;` is injected into fragment shaders

No shader copies are kept in the Android project — the `shaders/` asset dir is a symlink to the originals.

## Environment Setup

If you haven't done Android development before, here's what you need:

### 1. Install Android Studio

Download and install [Android Studio](https://developer.android.com/studio) for your platform (macOS, Linux, or Windows). The installer includes the Android SDK and the SDK Manager.

### 2. Install the NDK and CMake

Open Android Studio, go to **Settings → Languages & Frameworks → Android SDK → SDK Tools**, check:

- **NDK (Side by side)** — install version `30.0.14904198`
- **CMake** — install version `3.22.1`

Click **Apply**.

Alternatively, from the command line (replace `$ANDROID_SDK_ROOT` with your SDK path — typically `~/Library/Android/sdk` on macOS or `~/Android/Sdk` on Linux):

```bash
$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager "ndk;30.0.14904198" "cmake;3.22.1"
```

### 3. Open the project

Open the `android/` directory in Android Studio. Gradle will sync automatically and download any remaining dependencies.

### Note on symlinks

The `assets/` directory uses symlinks into the parent repo (fonts, models, shaders, themes). These work on macOS and Linux out of the box. On Windows, either enable [Developer Mode](https://learn.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development) before cloning or use WSL.

## Requirements

- Android SDK (API 29+)
- NDK 30.0.14904198
- CMake 3.22+
- A device or emulator with OpenGL ES 3.0 support

## Building

```bash
./gradlew assembleDebug
```

Raylib 5.5 is fetched automatically by CMake on the first build. Built ABIs: `arm64-v8a`, `x86_64`.

## Deploying

```bash
adb install -r app/build/outputs/apk/debug/app-debug.apk
adb shell am start -n com.px4.hawkeye.android/android.app.NativeActivity
```

1 change: 1 addition & 0 deletions android/app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
60 changes: 60 additions & 0 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
plugins {
alias(libs.plugins.android.application)
}

android {
namespace = "com.px4.hawkeye.android"
compileSdk {
version = release(36) {
minorApiLevel = 1
}
}

ndkVersion = "30.0.14904198"

externalNativeBuild {
cmake {
path = file("src/main/cpp/CMakeLists.txt")
version = "3.22.1"
}
}

defaultConfig {
applicationId = "com.px4.hawkeye.android"
minSdk = 29
targetSdk = 36
versionCode = 1
versionName = "1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

externalNativeBuild {
cmake {
abiFilters += listOf("arm64-v8a", "x86_64")
}
}
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}

dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
}
21 changes: 21 additions & 0 deletions android/app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
35 changes: 35 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-feature android:glEsVersion="0x00030000" android:required="true" />

<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.HawkeyeAndroid">

<activity
android:name="android.app.NativeActivity"
android:exported="true"
android:screenOrientation="landscape"
android:configChanges="orientation|screenSize|keyboardHidden">

<meta-data
android:name="android.app.lib_name"
android:value="hawkeye" />

<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

</application>

</manifest>
1 change: 1 addition & 0 deletions android/app/src/main/assets/fonts
1 change: 1 addition & 0 deletions android/app/src/main/assets/models
1 change: 1 addition & 0 deletions android/app/src/main/assets/shaders
1 change: 1 addition & 0 deletions android/app/src/main/assets/themes
46 changes: 46 additions & 0 deletions android/app/src/main/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
cmake_minimum_required(VERSION 3.22)
project(hawkeye C)

# Tell Raylib to target Android with OpenGL ES 3.0
set(PLATFORM "Android" CACHE STRING "" FORCE)
set(GRAPHICS "GRAPHICS_API_OPENGL_ES3" CACHE STRING "" FORCE)

# Fetch Raylib 5.5 — same version as the desktop project
include(FetchContent)
FetchContent_Declare(
raylib
GIT_REPOSITORY https://github.com/raysan5/raylib.git
GIT_TAG 5.5
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(raylib)

# Hawkeye rendering source subset — no MAVLink/ULog/HUD
# CMakeLists.txt lives at android/app/src/main/cpp/ — five levels up is the repo root
set(HAWKEYE_SRC "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../src")

add_library(hawkeye SHARED
android_main.c
${HAWKEYE_SRC}/scene.c
${HAWKEYE_SRC}/vehicle.c
${HAWKEYE_SRC}/asset_path.c
${HAWKEYE_SRC}/theme.c
${HAWKEYE_SRC}/ortho_panel.c
)

target_include_directories(hawkeye PRIVATE
${HAWKEYE_SRC}
${ANDROID_NDK}/sources/android/native_app_glue
)

target_link_libraries(hawkeye
raylib
android
log
EGL
GLESv3
)

# ANativeActivity_onCreate is called by the Android Java runtime via JNI, not by any
# C code — so the linker would silently strip it from the shared library without this.
target_link_options(hawkeye PRIVATE "-Wl,-u,ANativeActivity_onCreate")
Loading
Loading