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
35 changes: 35 additions & 0 deletions .github/workflows/build-release-tags.yml
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,39 @@ jobs:
- name: Add Android toolchain
if: ${{ matrix.target == 'Android' }}
run: |
# Setup Android SDK
ANDROID_SDK_ROOT="$HOME/android-sdk"

# Download command-line tools if not present
if [ ! -d "$ANDROID_SDK_ROOT/cmdline-tools/latest" ]; then
echo "Downloading Android command-line tools..."
mkdir -p "$ANDROID_SDK_ROOT/cmdline-tools"
wget https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -O cmdline-tools.zip
unzip -q cmdline-tools.zip -d "$ANDROID_SDK_ROOT/cmdline-tools"
mv "$ANDROID_SDK_ROOT/cmdline-tools/cmdline-tools" "$ANDROID_SDK_ROOT/cmdline-tools/latest"
rm cmdline-tools.zip
else
echo "Android command-line tools already exist"
fi

# Always accept licenses and install/update required SDK components
echo "Installing/updating Android SDK components..."
yes | "$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" --licenses || true
"$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" "platform-tools" "platforms;android-34" "build-tools;34.0.0"

# Set environment variables
echo "ANDROID_HOME=$ANDROID_SDK_ROOT" >> $GITHUB_ENV
echo "ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT" >> $GITHUB_ENV
echo "$ANDROID_SDK_ROOT/platform-tools" >> $GITHUB_PATH
echo "$ANDROID_SDK_ROOT/cmdline-tools/latest/bin" >> $GITHUB_PATH

# Verify SDK installation
echo "=== Android SDK Setup ==="
echo "ANDROID_HOME: $ANDROID_SDK_ROOT"
echo "SDK contents:"
ls -la "$ANDROID_SDK_ROOT"

# Download and setup Android NDK
NDK_VERSION="r27b"
NDK_DIR="$HOME/android-ndk-$NDK_VERSION"

Expand All @@ -308,6 +341,8 @@ jobs:
echo "Android NDK already exists at $NDK_DIR, skipping download"
fi

echo "ANDROID_HOME=$ANDROID_SDK_ROOT"
echo "ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT"
echo "ANDROID_NDK_HOME=$NDK_DIR" >> $GITHUB_ENV

rustup target add aarch64-linux-android
Expand Down
35 changes: 35 additions & 0 deletions .github/workflows/cmake.yml
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,39 @@ jobs:
- name: Add Android toolchain
if: ${{ matrix.target == 'Android' }}
run: |
# Setup Android SDK
ANDROID_SDK_ROOT="$HOME/android-sdk"

# Download command-line tools if not present
if [ ! -d "$ANDROID_SDK_ROOT/cmdline-tools/latest" ]; then
echo "Downloading Android command-line tools..."
mkdir -p "$ANDROID_SDK_ROOT/cmdline-tools"
wget https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -O cmdline-tools.zip
unzip -q cmdline-tools.zip -d "$ANDROID_SDK_ROOT/cmdline-tools"
mv "$ANDROID_SDK_ROOT/cmdline-tools/cmdline-tools" "$ANDROID_SDK_ROOT/cmdline-tools/latest"
rm cmdline-tools.zip
else
echo "Android command-line tools already exist"
fi

# Always accept licenses and install/update required SDK components
echo "Installing/updating Android SDK components..."
yes | "$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" --licenses || true
"$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" "platform-tools" "platforms;android-34" "build-tools;34.0.0"

# Set environment variables
echo "ANDROID_HOME=$ANDROID_SDK_ROOT" >> $GITHUB_ENV
echo "ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT" >> $GITHUB_ENV
echo "$ANDROID_SDK_ROOT/platform-tools" >> $GITHUB_PATH
echo "$ANDROID_SDK_ROOT/cmdline-tools/latest/bin" >> $GITHUB_PATH

# Verify SDK installation
echo "=== Android SDK Setup ==="
echo "ANDROID_HOME: $ANDROID_SDK_ROOT"
echo "SDK contents:"
ls -la "$ANDROID_SDK_ROOT"

# Download and setup Android NDK
NDK_VERSION="r27b"
NDK_DIR="$HOME/android-ndk-$NDK_VERSION"

Expand All @@ -459,6 +492,8 @@ jobs:
rm ndk.zip
fi

echo "ANDROID_HOME=$ANDROID_SDK_ROOT" >> $GITHUB_ENV
echo "ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT" >> $GITHUB_ENV
echo "ANDROID_NDK_HOME=$NDK_DIR" >> $GITHUB_ENV

rustup target add aarch64-linux-android
Expand Down
10 changes: 8 additions & 2 deletions build/Android/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,14 @@ set(PROTOC_EXECUTABLE "${_THIRDPARTY_BUILD_DIR}/protobuf_host/bin/protoc${CMAKE_

set(TESTING OFF)

find_library(log-lib log)
find_library(android-lib android)
add_library(android::log INTERFACE IMPORTED GLOBAL)
set_target_properties(android::log PROPERTIES IMPORTED_LIBNAME "log")

add_library(android::android INTERFACE IMPORTED GLOBAL)
set_target_properties(android::android PROPERTIES IMPORTED_LIBNAME "android")

# Include common build parameters
include(../CommonBuildParameters.cmake)

# Add Android AAR build for secure storage
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/securestorage ${CMAKE_BINARY_DIR}/android_aar)
18 changes: 18 additions & 0 deletions build/Android/securestorage/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Gradle build outputs
.gradle/
build/
library/build/

# Gradle wrapper (download on demand)
.gradle-wrapper.jar
gradlew
gradlew.bat
gradle/

# Local configuration
local.properties

# IDE files
.idea/
*.iml
.DS_Store
33 changes: 33 additions & 0 deletions build/Android/securestorage/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Android AAR build for KeyStoreHelper
cmake_minimum_required(VERSION 3.22)

# Check if Gradle wrapper exists, if not, set it up
set(GRADLEW_PATH "${CMAKE_CURRENT_SOURCE_DIR}/gradlew")
if(NOT EXISTS "${GRADLEW_PATH}")
message(STATUS "Gradle wrapper not found, running setup script...")
execute_process(
COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/setup-gradle-wrapper.sh
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
RESULT_VARIABLE SETUP_RESULT
)
if(NOT SETUP_RESULT EQUAL 0)
message(FATAL_ERROR "Failed to set up Gradle wrapper. Please check your internet connection.")
endif()
message(STATUS "Gradle wrapper setup complete")
endif()

# Custom target to build the AAR using Gradle
add_custom_target(build_android_aar ALL
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/gradlew :library:assembleRelease
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Building securestorage AAR with Gradle"
BYPRODUCTS ${CMAKE_CURRENT_SOURCE_DIR}/library/build/outputs/aar/library-release.aar
)

# Install the AAR to the installation directory
install(FILES
${CMAKE_CURRENT_SOURCE_DIR}/library/build/outputs/aar/library-release.aar
DESTINATION lib/android
RENAME securestorage-release.aar
OPTIONAL
)
78 changes: 78 additions & 0 deletions build/Android/securestorage/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Android Secure Storage AAR

This directory contains the Android Gradle project for building the SuperGenius secure storage AAR.

## Overview

The AAR contains:
- `ai.gnus.sdk.KeyStoreHelper` - Java class providing secure key storage using Android KeyStore

**Source Location**: The Java source is maintained in `src/local_secure_storage/impl/KeyStoreHelper.java` and referenced by the Gradle build. This ensures a single source of truth alongside the C++ implementation.

## Building

### Via CMake (Recommended)
When building the Android version of SuperGenius, the AAR will be built automatically during `make`:

```bash
cd build/OSX
mkdir -p Android && cd Android
cmake ../.. \
-DCMAKE_ANDROID_NDK=/path/to/ndk \
-DANDROID_ABI=arm64-v8a \
-DCMAKE_BUILD_TYPE=Release
make
make install
```

The Gradle wrapper will be set up automatically if needed during the CMake configure step.
The AAR will be built during `make` and installed to `${CMAKE_INSTALL_PREFIX}/lib/android/securestorage-release.aar` during `make install`.

### Via Gradle Directly
You can also build the AAR directly using Gradle:

```bash
cd build/Android/securestorage
./gradlew :library:assembleRelease
```

Output: `build/Android/securestorage/library/build/outputs/aar/library-release.aar`

## Usage in Unity/GeniusSDK

1. Include the AAR in your Unity project's `Assets/Plugins/Android/` directory
2. Include your SDK `.so` file in `Assets/Plugins/Android/libs/[ABI]/`
3. Initialize KeyStoreHelper before using native secure storage:

```java
import ai.gnus.sdk.KeyStoreHelper;

public class YourUnityActivity extends UnityPlayerActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// This must be called AFTER the native SDK .so is loaded
// It caches the class reference using the app's ClassLoader
KeyStoreHelper.initialize(this);
}
}
```

**Important**: The AAR's `nativeInit` method is implemented in your SDK's `.so` file. Make sure Unity loads the native library before calling `KeyStoreHelper.initialize()`.

## JNI Contract

The C++ code in `src/local_secure_storage/impl/Android.cpp`:
- Implements: `Java_ai_gnus_sdk_KeyStoreHelper_nativeInit` - Caches class reference using app ClassLoader
- Calls these static Java methods:
- `ai.gnus.sdk.KeyStoreHelper.load()Ljava/lang/String;`
- `ai.gnus.sdk.KeyStoreHelper.save(Ljava/lang/String;)Z`
- `ai.gnus.sdk.KeyStoreHelper.delete(Ljava/lang/String;)Z`

**ClassLoader Fix**: The native code now uses the app's ClassLoader (via the Context passed to `initialize()`) instead of the system ClassLoader, which fixes crashes when secure storage is accessed from worker threads.

## Requirements

- Android SDK 33
- Min SDK 28 (Android 9.0)
- Gradle 8.1+
21 changes: 21 additions & 0 deletions build/Android/securestorage/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Top-level build file
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.1.0'
}
}

allprojects {
repositories {
google()
mavenCentral()
}
}

task clean(type: Delete) {
delete rootProject.buildDir
}
4 changes: 4 additions & 0 deletions build/Android/securestorage/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Project-wide Gradle settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
android.useAndroidX=true
android.enableJetifier=false
40 changes: 40 additions & 0 deletions build/Android/securestorage/library/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
plugins {
id 'com.android.library'
}

android {
namespace 'ai.gnus.sdk'
compileSdk 33

defaultConfig {
minSdk 28
targetSdk 33

consumerProguardFiles "consumer-rules.pro"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

sourceSets {
main {
java {
srcDirs = ['../../../../src/local_secure_storage/impl']
}
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}

dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
}
4 changes: 4 additions & 0 deletions build/Android/securestorage/library/consumer-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Consumer ProGuard rules for securestorage library
-keep class ai.gnus.sdk.KeyStoreHelper {
public static *;
}
4 changes: 4 additions & 0 deletions build/Android/securestorage/library/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Keep KeyStoreHelper class and all its methods for JNI access
-keep class ai.gnus.sdk.KeyStoreHelper {
public static *;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- No permissions required for KeyStore access -->
</manifest>
2 changes: 2 additions & 0 deletions build/Android/securestorage/settings.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
rootProject.name = 'SuperGenius'
include ':library'
61 changes: 61 additions & 0 deletions build/Android/securestorage/setup-gradle-wrapper.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/bin/bash
# Script to set up Gradle wrapper for the Android AAR build

set -e

cd "$(dirname "$0")"

# Check if gradlew already exists
if [ -f gradlew ]; then
echo "Gradle wrapper already exists"
exit 0
fi

GRADLE_VERSION="8.2"

# Check if gradle is installed and what version
if command -v gradle &> /dev/null; then
GRADLE_VER=$(gradle --version | grep "^Gradle" | awk '{print $2}')
echo "Found Gradle $GRADLE_VER"

# Check if version is at least 6.0
if [ "$(printf '%s\n' "6.0" "$GRADLE_VER" | sort -V | head -n1)" = "6.0" ]; then
echo "Using installed Gradle to create wrapper..."
gradle wrapper --gradle-version $GRADLE_VERSION
exit 0
else
echo "Warning: Gradle $GRADLE_VER is too old (need 6.0+)"
fi
fi

echo "Downloading Gradle wrapper files directly..."

# Create gradle/wrapper directory
mkdir -p gradle/wrapper

# Download gradle-wrapper.jar
curl -L "https://raw.githubusercontent.com/gradle/gradle/v${GRADLE_VERSION}.0/gradle/wrapper/gradle-wrapper.jar" \
-o gradle/wrapper/gradle-wrapper.jar

# Download gradlew
curl -L "https://raw.githubusercontent.com/gradle/gradle/v${GRADLE_VERSION}.0/gradlew" \
-o gradlew

# Download gradlew.bat
curl -L "https://raw.githubusercontent.com/gradle/gradle/v${GRADLE_VERSION}.0/gradlew.bat" \
-o gradlew.bat

# Create gradle-wrapper.properties
cat > gradle/wrapper/gradle-wrapper.properties << EOF
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
EOF

chmod +x gradlew

echo "Gradle wrapper setup complete"
Loading
Loading