Overview
The Android app uses Kotlin with Jetpack Compose for the UI and integrates the emulator core via JNI (Java Native Interface). The core can be built with either the Rust implementation or the CEmu reference backend.Architecture
dlopen(), allowing the app to switch between Rust and CEmu without recompilation.
Prerequisites
Install Android Studio
Download and install Android Studio with Android SDK API 24+
Building
Using the Unified Build Script
The project includes a unified build script that handles both Rust compilation and APK packaging:The build script automatically:
- Compiles the Rust core for the target architecture(s)
- Runs Gradle to build the APK
- Optionally installs the APK via
adb
Using Make Shortcuts
Manual Gradle Build
If you prefer to use Gradle directly (after building the Rust core manually):Integration Guide
Step 1: Initialize EmulatorBridge
TheEmulatorBridge class provides a Kotlin-friendly interface to the native emulator:
MainActivity.kt
Step 2: Load ROM and Start Emulation
Loading ROM
Step 3: Run the Emulation Loop
The emulator runs cycle-by-cycle. For real-time emulation at 48MHz and 60 FPS:Emulation Loop
Step 4: Render the Display
The emulator provides a framebuffer as ARGB8888 pixels:Rendering
Step 5: Handle Keyboard Input
The TI-84 Plus CE uses an 8×7 key matrix:Key Input
Key Matrix Reference
Key Matrix Reference
Common key positions:
- ON key:
(2, 0) - ENTER key:
(6, 0) - Arrow keys: Down
(7, 0), Left(7, 1), Right(7, 2), Up(7, 3) - Number keys:
0is(3, 0),1is(3, 1), …,9is(5, 3) - Math:
+is(6, 1),-is(6, 2),×is(6, 3),÷is(6, 4)
source/android/app/src/main/java/com/calc/emulator/MainActivity.kt for the complete key map.Step 6: State Persistence
Save and restore emulator state for seamless app backgrounding:State Management
Step 7: Loading Program Files
Inject.8xp (programs) or .8xv (app variables) into the calculator’s flash:
Program Loading
Backend Switching
The Android app supports runtime backend switching when multiple backends are available:Backend Switching
Native Integration (JNI)
For advanced use cases, you can interact with the JNI layer directly.JNI Method Signatures
FromEmulatorBridge.kt:346:
JNI Methods
CMake Configuration
The native build is configured inandroid/app/src/main/cpp/CMakeLists.txt:1:
CMakeLists.txt
Performance Tuning
Cycle Budget
At 48MHz and 60 FPS, each frame should execute ~800,000 cycles. Adjust based on your performance needs:Performance Tuning
Display Updates
Only copy the framebuffer when the LCD is on:Display Check
Background Execution
Pause emulation when the app goes to background:Lifecycle
Example App
The reference implementation is available atsource/android/:
- MainActivity.kt - Main activity with emulation loop and UI
- EmulatorBridge.kt - Kotlin wrapper around JNI
- EmulatorPreferences.kt - Shared preferences for settings
- StateManager.kt - State persistence to app storage
API Reference
EmulatorBridge Methods
Create the emulator instance. Must be called before any other operations.Returns:
true on success, false on failureDestroy the emulator instance and free resources.
Load ROM data into the emulator.Parameters:
romBytes: ROM file contents (typically 4MB for TI-84 Plus CE)
0 on success, negative error code on failureInject a
.8xp or .8xv file into flash. Must be called after loadRom() and before powerOn().Parameters:fileBytes: Program or AppVar file contents
Simulate ON key press+release to start execution. Call after
loadRom().Reset the emulator to initial state (cold boot).
Run emulation for the specified number of CPU cycles.Parameters:
cycles: Number of cycles to execute (typically 800,000 per frame)
Copy the current framebuffer to an Android Bitmap.Parameters:
bitmap: Target bitmap (must be 320×240, ARGB_8888)
true on successSet key state in the 8×7 key matrix.Parameters:
row: Key row (0-7)col: Key column (0-7)down:trueif pressed,falseif released
Check if the LCD is on (should display content).Returns:
true if LCD is active, false if off or sleepingGet the backlight brightness level.Returns: 0-255 (0 = off/black)
Save the current emulator state.Returns: State data as ByteArray, or null on failure
Load a saved emulator state.Parameters:
stateData: Previously saved state data
0 on success, negative error code on failureTroubleshooting
Build fails: ANDROID_NDK_HOME not set
Build fails: ANDROID_NDK_HOME not set
Set the environment variable to your NDK installation:Or let the build script auto-detect it from
ANDROID_HOME.App crashes on startup with UnsatisfiedLinkError
App crashes on startup with UnsatisfiedLinkError
Ensure the Rust core was built for the correct architecture:This builds for all ABIs (ARM64, ARM32, x86_64, x86).
Emulator runs slowly or stutters
Emulator runs slowly or stutters
- Reduce the cycle count: try 400,000 cycles per frame (0.5x speed)
- Build in Release mode:
--releaseflag - Profile with Android Studio’s CPU Profiler
Screen stays black after loading ROM
Screen stays black after loading ROM
Call
powerOn() after loading the ROM to simulate pressing the ON key:Next Steps
iOS Integration
Learn how to integrate the emulator into iOS apps with Swift
Web Integration
Build a web version using WebAssembly