├── .gitignore ├── AndroidPackaging ├── .gitignore ├── AndroidPackaging.androidproj ├── AndroidPackaging.androidproj.user ├── app │ ├── build.gradle.template │ ├── release-key.keystore │ └── src │ │ └── main │ │ ├── AndroidManifest.xml.template │ │ └── res │ │ └── values │ │ └── strings.xml ├── build.gradle.template ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties.template ├── gradlew.bat └── settings.gradle.template ├── CMakeLists.txt ├── GenerateVisualStudioProject.bat ├── README.md └── Source └── main.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .google 3 | .idea 4 | **/*.iml 5 | local.properties 6 | build 7 | *~ 8 | .externalNativeBuild 9 | libwebp 10 | .DS_Store 11 | **/ndkHelperBin 12 | **/.cxx 13 | display-p3/third_party 14 | **/GeneratedProjectFiles/* 15 | **/.vs/* 16 | **/ARM/* 17 | **/ARM64/* 18 | **/x64/* 19 | bin 20 | jniLibs -------------------------------------------------------------------------------- /AndroidPackaging/.gitignore: -------------------------------------------------------------------------------- 1 | build.gradle 2 | settings.gradle 3 | AndroidManifest.xml 4 | gradle-wrapper.properties -------------------------------------------------------------------------------- /AndroidPackaging/AndroidPackaging.androidproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | ARM 7 | 8 | 9 | Release 10 | ARM 11 | 12 | 13 | Debug 14 | ARM64 15 | 16 | 17 | Release 18 | ARM64 19 | 20 | 21 | Debug 22 | x86 23 | 24 | 25 | Release 26 | x86 27 | 28 | 29 | Debug 30 | x64 31 | 32 | 33 | Release 34 | x64 35 | 36 | 37 | 38 | Gradle 39 | NativeAndroidActivity 40 | 14.0 41 | 1.0 42 | {b43c9f0a-c5e0-4a79-85c3-4c1c20da691d} 43 | <_PackagingProjectWithoutNativeComponent>true 44 | com.NativeAndroidActivity.NativeAndroidActivity 45 | src\main\java 46 | 47 | 48 | 49 | Application 50 | 51 | 52 | Application 53 | 54 | 55 | Application 56 | 57 | 58 | Application 59 | 60 | 61 | Application 62 | 63 | 64 | Application 65 | 66 | 67 | Application 68 | 69 | 70 | Application 71 | 72 | 73 | 74 | 75 | $(ProjectDir)app 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /AndroidPackaging/AndroidPackaging.androidproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | android.app.NativeActivity 5 | AndroidDebugger 6 | 7 | 8 | android.app.NativeActivity 9 | AndroidDebugger 10 | 11 | 12 | android.app.NativeActivity 13 | AndroidDebugger 14 | 15 | -------------------------------------------------------------------------------- /AndroidPackaging/app/build.gradle.template: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.$(ConfigurationType)' 2 | 3 | android { 4 | compileSdkVersion $(AndroidAPILevelNumber) 5 | buildToolsVersion '$(AndroidBuildToolsVersion)' 6 | 7 | defaultConfig.with { 8 | $(ApplicationId) 9 | minSdkVersion 20 10 | targetSdkVersion 33 11 | } 12 | 13 | compileOptions.with { 14 | sourceCompatibility=JavaVersion.VERSION_1_7 15 | targetCompatibility=JavaVersion.VERSION_1_7 16 | } 17 | 18 | // Just for release build 19 | signingConfigs { 20 | release { 21 | storeFile file("release-key.keystore") 22 | storePassword "password" 23 | keyAlias "alias_name" 24 | keyPassword "password" 25 | 26 | // Optional, specify signing versions used 27 | v1SigningEnabled true 28 | v2SigningEnabled true 29 | } 30 | } 31 | 32 | buildTypes { 33 | release { 34 | minifyEnabled true 35 | debuggable true 36 | signingConfig signingConfigs.release 37 | } 38 | } 39 | 40 | flavorDimensions 'cpuArch' 41 | productFlavors { 42 | create("arm7") { 43 | ndk.abiFilters.add("armeabi-v7a") 44 | } 45 | create("arm8") { 46 | ndk.abiFilters.add("arm64-v8a") 47 | } 48 | create("x86") { 49 | ndk.abiFilters.add("x86") 50 | } 51 | create("x86-64") { 52 | ndk.abiFilters.add("x86_64") 53 | } 54 | create("all") 55 | } 56 | } 57 | 58 | dependencies { 59 | api fileTree(dir: 'libs', include: ['*.jar']) 60 | $(AarDependencies) 61 | } 62 | -------------------------------------------------------------------------------- /AndroidPackaging/app/release-key.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lectorguard/Android-CMake-VisualStudio-Sample/bc55581419ff7a41f09309d0a042951adb12c01f/AndroidPackaging/app/release-key.keystore -------------------------------------------------------------------------------- /AndroidPackaging/app/src/main/AndroidManifest.xml.template: -------------------------------------------------------------------------------- 1 | 2 | 6 | 11 | 15 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /AndroidPackaging/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | AndroidPackaging 4 | 5 | -------------------------------------------------------------------------------- /AndroidPackaging/build.gradle.template: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:$(GradlePlugin)' 10 | 11 | // NOTE: Do not place your application dependencies here; they belong 12 | // in the individual module build.gradle files 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | google() 19 | mavenCentral() 20 | } 21 | } 22 | 23 | task clean(type: Delete) { 24 | delete rootProject.buildDir 25 | } -------------------------------------------------------------------------------- /AndroidPackaging/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lectorguard/Android-CMake-VisualStudio-Sample/bc55581419ff7a41f09309d0a042951adb12c01f/AndroidPackaging/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /AndroidPackaging/gradle/wrapper/gradle-wrapper.properties.template: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-$(GradleVersion)-all.zip 6 | -------------------------------------------------------------------------------- /AndroidPackaging/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | @rem Execute Gradle 88 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 89 | 90 | :end 91 | @rem End local scope for the variables with windows NT shell 92 | if "%ERRORLEVEL%"=="0" goto mainEnd 93 | 94 | :fail 95 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 96 | rem the _cmd.exe /c_ return code! 97 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 98 | exit /b 1 99 | 100 | :mainEnd 101 | if "%OS%"=="Windows_NT" endlocal 102 | 103 | :omega 104 | -------------------------------------------------------------------------------- /AndroidPackaging/settings.gradle.template: -------------------------------------------------------------------------------- 1 | include ':app' $(AarDependenciesSettings) 2 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.4.1) 2 | 3 | project(AndroidNativeActivity) 4 | 5 | cmake_path(SET ANDROID_NDK_PATH ${CMAKE_ANDROID_NDK}) 6 | 7 | 8 | # build native_app_glue as a static lib 9 | set(${CMAKE_C_FLAGS}, "${CMAKE_C_FLAGS}") 10 | add_library(native_app_glue STATIC ${ANDROID_NDK_PATH}/sources/android/native_app_glue/android_native_app_glue.c) 11 | 12 | set(CMAKE_CXX_FLAGS -std=gnu++20) 13 | # Debug Information format, gdwarf2 supports local variables in debugger 14 | set(CMAKE_CXX_FLAGS_DEBUG "-g2 -gdwarf-2") 15 | 16 | # Export ANativeActivity_onCreate(), 17 | # Refer to: https://github.com/android-ndk/ndk/issues/381. 18 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate") 19 | 20 | add_library(native-activity SHARED Source/main.cpp) 21 | 22 | target_include_directories(native-activity PRIVATE ${ANDROID_NDK_PATH}/sources/android/native_app_glue) 23 | 24 | # add lib dependencies 25 | target_link_libraries(native-activity 26 | android 27 | native_app_glue 28 | EGL 29 | GLESv1_CM 30 | log) 31 | -------------------------------------------------------------------------------- /GenerateVisualStudioProject.bat: -------------------------------------------------------------------------------- 1 | git clean -fdX 2 | 3 | mkdir bin 4 | cd bin 5 | cmake -G "Visual Studio 17 2022" -A ARM ^ 6 | -DCMAKE_SYSTEM_NAME=Android ^ 7 | -DCMAKE_SYSTEM_VERSION=24 ^ 8 | -DCMAKE_ANDROID_ARCH_ABI=armeabi-v7a ^ 9 | -DCMAKE_ANDROID_NDK=C:/Microsoft/AndroidNDK/android-ndk-r23c ^ 10 | -DCMAKE_ANDROID_STL_TYPE=c++_static ^ 11 | .. 12 | cd .. 13 | @echo off 14 | rem Command makes sure the build.gradle file uses the same SDK version as the cmake project. Not really necessary, we can just use the latest supported sdk version from Visual studio, because of backwards compatibility 15 | rem powershell -Command "& { $file = 'AndroidPackaging\app\build.gradle.template'; $find = Get-Content $file | Select-String targetSdkVersion | Select-Object -ExpandProperty Line; $replace = ' targetSdkVersion %AndroidSDKVersion%';(Get-Content $file).replace($find, $replace) | Set-Content $file;}" 16 | echo on 17 | 18 | devenv bin\AndroidNativeActivity.sln ^ 19 | /Command "File.AddExistingProject %cd%\AndroidPackaging\AndroidPackaging.androidproj" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | >[!WARNING] 2 | >I would no longer recommend using Visual Studio to build native Android apps. The last version of the Android NDK that is compatible with the Visual Studio Android App Template is r23c. 3 | >All later versions either require modifications to the NDK or are incompatible with the current Visual Studio implementation. This also applies to the cross-platform library options. 4 | >The main reasons for the lack of support are structural changes and the replacement of gdb with lldb as the debugger in later NDK releases (NDK r23 is the last version with gdb support). The last NDK version that works out of the box is 3 years old, making native Android development with VS inconvenient. 5 | >For more information, see the Visual Studio Community threads: [Support for LLDB](https://developercommunity.visualstudio.com/t/Support-for-Android-NDK-25-and-LLDB/10326202), 6 | > [NDK Header Search Path Issue](https://developercommunity.visualstudio.com/t/Visual-Studio-2022-cannot-build-an-Andro/10692622). 7 | >A good alternative with support for the latest Android SDKs and NDKs including a well working debugger using Visual Studio Code can be found [here](https://github.com/lectorguard/Android-CMake-VSCode-Sample). 8 | 9 | 10 | # Android Cmake Visual Studio Sample Project 11 | 12 | This is a small example android native-activity project using cmake and the gradle visual studio template project to create and debug android applications 13 | 14 | ## Features 15 | 16 | * C++ 20 support 17 | * Create Android native applications without any Java/Kotlin code using glue 18 | * Use Cmake to generate Visual Studio project 19 | * Visual Studio full debugging functionality for Release and Debug builds 20 | * Fully configurable 21 | * Easy to set up 22 | * No Android Studio 23 | 24 | ## Prerequisites 25 | 26 | * Cmake 3.4.1 or higher 27 | * Visual Studio 22 17.4 Preview 2 or higher 28 | * Visual Studio Module : Mobile Development with C++ 29 | * Visual Studio Module : Desktop Development with C++ 30 | * devenv.exe must be set as environment variable (should be similar to C:\Program Files\Microsoft Visual Studio\2022\Preview\Common7\IDE) 31 | 32 | 33 | ## Installation Guide 34 | 35 | * Open GenerateVisualStudioProject.bat in a text editor of your choice 36 | * Make sure the cmake arguments fit your system 37 | 38 | ``` 39 | -DCMAKE_SYSTEM_NAME=Android 40 | -DCMAKE_SYSTEM_VERSION=24 // Android SDK Version 41 | -DCMAKE_ANDROID_ARCH_ABI=armeabi-v7a // Architecture, you can change this setting within Visual Studio 42 | -DCMAKE_ANDROID_NDK=C:/Microsoft/AndroidNDK/android-ndk-r23c // Make sure this path is correct 43 | // The ndk is installed by Visual Studio, you might need to update this path 44 | -DCMAKE_ANDROID_STL_TYPE=c++_static 45 | ``` 46 | * Double check the path of the android ndk, maybe your visual studio installation uses another ndk 47 | * Execute the GenerateProjectFiles.bat file 48 | * After Visual Studio is open go to tools -> options and search for android. The paths in there should look like the following : Please update the paths in case it is wrong 49 | 50 | 51 | ``` 52 | Android SDK : C:\Program Files (x86)\Android\android-sdk 53 | Android NDK : C:\Microsoft\AndroidNDK\android-ndk-r23c 54 | Java SE Development Kit : C:\Program Files\Microsoft\jdk-11.0.12.7-hotspot 55 | Apache Ant : 56 | ``` 57 | 58 | * Now you can create and debug apks :) In case you want to debug on your device, you need to enable usb debugging and install the correct driver (see Setup USB Debugging section) 59 | 60 | * In case debugging on your device is not working (unable to install apk), restart the ide by opening the **.sln** file in the **bin** folder 61 | * Double check if all configurations are working (Debug and Release), make sure to install the app on your device before switching the configuration 62 | 63 | ## Setup USB Debugging 64 | 65 | ### Method 1: 66 | 67 | - Download the compatible device USB driver (search online for device name + usb driver) 68 | - Unzip the file to any location on your PC 69 | - Double click on the .exe file 70 | - Follow the instructions 71 | - Click finish 72 | - You can select now your usb connected device inside visual studio as target 73 | 74 | ### Method 2: 75 | 76 | - Download the compatible device USB driver (search online for device name + usb driver) 77 | - Unzip the file to any location on your PC 78 | - Open the device manager and click on your computer 79 | - On the top left click action -> add legacy hardware 80 | - Select Install the hardware that I manually select from a list 81 | - Select All devices 82 | - Select Have Disk and set the path to your .inf file inside your extracted driver file 83 | - Select Install the downloaded driver file 84 | - Follow the remaining instructions and restart your pc 85 | - You can select now your usb connected device inside visual studio as target 86 | 87 | ## Troubleshooting installation errors 88 | 89 | ``` 90 | The package manager failed to install the apk: '/data/local/tmp/NativeAndroidActivity.apk' with the error code: 'Unknown' 91 | ``` 92 | 93 | * In order to get more information install the apk via adb 94 | * You can browse to the adb.exe location or set the path as environment var 95 | ``` 96 | .\adb.exe install C:\TestAndroidBuild\AndroidPackaging\ARM\Debug\NativeAndroidActivity.apk 97 | ``` 98 | 99 | * In many cases uninstalling the app and rebuilding the visual studio project fixes the issue 100 | * When switching configuaration, you always need to uninstall the old apk on your device 101 | 102 | ### Segmentation Fault when installing the apk 103 | 104 | * If you changed the CMakeLists.txt, make sure you update the reference from the gradle android project to the native-activity project 105 | * You can easily do this by first removing the native-activity reference from the gradle project and afterwards adding it again 106 | 107 | 108 | ## Signing apks for realease builds 109 | 110 | * In order to install a release build of an apk on a device, you need to sign the apk 111 | * This project has an automated signing process via gradle with a keystore 112 | * This is **not** production ready and should just be used for debugging release builds 113 | * You find the password to the keystore inside AndroidPackaging/app/build.gradle under signingConfigs 114 | 115 | * [Create keystore](https://stackoverflow.com/questions/3997748/how-can-i-create-a-keystore) 116 | * [Setup gradle to automatically sign apk](https://developer.android.com/studio/build/building-cmdline) 117 | 118 | ## Details about this project 119 | 120 | This project is based on the C++ only native-activity sample project on [github](https://github.com/android/ndk-samples/tree/master/native-activity). Different to the sample project, we are using the [cmake android toolchain](https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html?highlight=toolchains#cross-compiling-for-android-with-the-ndk) 121 | in combination with the [android gradle template project](https://devblogs.microsoft.com/cppblog/build-your-android-applications-in-visual-studio-using-gradle/) from visual studio. The idea behind the project is to create a shared library (.so) via the cmake project and to pack this library into the apk generated from the gradle template project from visual studio. 122 | Inside the **GenerateProjectFiles.bat** the cmake project and the gradle visual studio project are combined into a single sln file. You can also do this manually by adding the existing project to the cmake generated solution file. 123 | Inside the AndroidManifest.xml of the VS gradle project we are setting our generated shared library as main source. So when changing the name of cmake project, the name inside the AndroidManifest.xml must be updated. The gradle project must also reference the shared library project in visual studio. Make sure to update the reference when changing the shared library project name. 124 | After modifying the default AndroidManifest.xml from the VS template project our file looks like this: 125 | 126 | ``` 127 | // There is no java code, we will link directly with a shared lib (.so) and use glue as a layer between java and c++ 132 | // Keyboard is hidden by default 136 | // This is the name of the generated .so file. The name must be correct otherwise the app wont start. 138 | // You can use the everything tool to find the correct name of your .so file 139 | 140 | 141 | 142 | 143 | 144 | 145 | ``` 146 | 147 | Next to the packaging process, this project supports C++ 20 features. We can simply add that support by passing the correct compile flags from cmake to Visual Studio. But unfortunatly the settings are displayed not correctly inside Visual Studio properties. 148 | Nevertheless, the settings are applied correctly inside Visual Studio. 149 | 150 | ``` 151 | set(CMAKE_CXX_FLAGS -std=gnu++20) 152 | ``` 153 | 154 | ## License 155 | 156 | MIT License 157 | 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /Source/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | //BEGIN_INCLUDE(all) 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | #include 31 | #include 32 | #include 33 | 34 | #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "native-activity", __VA_ARGS__)) 35 | #define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "native-activity", __VA_ARGS__)) 36 | 37 | template 38 | concept Addable = requires(T a, T b) 39 | { 40 | a + b; 41 | }; 42 | 43 | /** 44 | * Our saved state data. 45 | */ 46 | struct saved_state { 47 | float angle; 48 | int32_t x; 49 | int32_t y; 50 | }; 51 | 52 | /** 53 | * Shared state for our app. 54 | */ 55 | struct engine { 56 | struct android_app* app; 57 | 58 | ASensorManager* sensorManager; 59 | const ASensor* accelerometerSensor; 60 | ASensorEventQueue* sensorEventQueue; 61 | 62 | int animating; 63 | EGLDisplay display; 64 | EGLSurface surface; 65 | EGLContext context; 66 | int32_t width; 67 | int32_t height; 68 | struct saved_state state; 69 | }; 70 | 71 | /** 72 | * Initialize an EGL context for the current display. 73 | */ 74 | static int engine_init_display(struct engine* engine) { 75 | // initialize OpenGL ES and EGL 76 | 77 | /* 78 | * Here specify the attributes of the desired configuration. 79 | * Below, we select an EGLConfig with at least 8 bits per color 80 | * component compatible with on-screen windows 81 | */ 82 | const EGLint attribs[] = { 83 | EGL_SURFACE_TYPE, EGL_WINDOW_BIT, 84 | EGL_BLUE_SIZE, 8, 85 | EGL_GREEN_SIZE, 8, 86 | EGL_RED_SIZE, 8, 87 | EGL_NONE 88 | }; 89 | EGLint w, h, format; 90 | EGLint numConfigs; 91 | EGLConfig config = nullptr; 92 | EGLSurface surface; 93 | EGLContext context; 94 | 95 | EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); 96 | 97 | eglInitialize(display, nullptr, nullptr); 98 | 99 | /* Here, the application chooses the configuration it desires. 100 | * find the best match if possible, otherwise use the very first one 101 | */ 102 | eglChooseConfig(display, attribs, nullptr,0, &numConfigs); 103 | std::unique_ptr supportedConfigs(new EGLConfig[numConfigs]); 104 | assert(supportedConfigs); 105 | eglChooseConfig(display, attribs, supportedConfigs.get(), numConfigs, &numConfigs); 106 | assert(numConfigs); 107 | auto i = 0; 108 | for (; i < numConfigs; i++) { 109 | auto& cfg = supportedConfigs[i]; 110 | EGLint r, g, b, d; 111 | if (eglGetConfigAttrib(display, cfg, EGL_RED_SIZE, &r) && 112 | eglGetConfigAttrib(display, cfg, EGL_GREEN_SIZE, &g) && 113 | eglGetConfigAttrib(display, cfg, EGL_BLUE_SIZE, &b) && 114 | eglGetConfigAttrib(display, cfg, EGL_DEPTH_SIZE, &d) && 115 | r == 8 && g == 8 && b == 8 && d == 0 ) { 116 | 117 | config = supportedConfigs[i]; 118 | break; 119 | } 120 | } 121 | if (i == numConfigs) { 122 | config = supportedConfigs[0]; 123 | } 124 | 125 | if (config == nullptr) { 126 | LOGW("Unable to initialize EGLConfig"); 127 | return -1; 128 | } 129 | 130 | /* EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is 131 | * guaranteed to be accepted by ANativeWindow_setBuffersGeometry(). 132 | * As soon as we picked a EGLConfig, we can safely reconfigure the 133 | * ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID. */ 134 | eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format); 135 | surface = eglCreateWindowSurface(display, config, engine->app->window, nullptr); 136 | context = eglCreateContext(display, config, nullptr, nullptr); 137 | 138 | if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) { 139 | LOGW("Unable to eglMakeCurrent"); 140 | return -1; 141 | } 142 | 143 | eglQuerySurface(display, surface, EGL_WIDTH, &w); 144 | eglQuerySurface(display, surface, EGL_HEIGHT, &h); 145 | 146 | engine->display = display; 147 | engine->context = context; 148 | engine->surface = surface; 149 | engine->width = w; 150 | engine->height = h; 151 | engine->state.angle = 0; 152 | 153 | // Check openGL on the system 154 | auto opengl_info = {GL_VENDOR, GL_RENDERER, GL_VERSION, GL_EXTENSIONS}; 155 | for (auto name : opengl_info) { 156 | auto info = glGetString(name); 157 | LOGI("OpenGL Info: %s", info); 158 | } 159 | // Initialize GL state. 160 | glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST); 161 | glEnable(GL_CULL_FACE); 162 | glShadeModel(GL_SMOOTH); 163 | glDisable(GL_DEPTH_TEST); 164 | 165 | return 0; 166 | } 167 | 168 | /** 169 | * Just the current frame in the display. 170 | */ 171 | static void engine_draw_frame(struct engine* engine) { 172 | if (engine->display == nullptr) { 173 | // No display. 174 | return; 175 | } 176 | 177 | // Just fill the screen with a color. 178 | glClearColor(((float)engine->state.x)/engine->width, engine->state.angle, 179 | ((float)engine->state.y)/engine->height, 1); 180 | glClear(GL_COLOR_BUFFER_BIT); 181 | 182 | eglSwapBuffers(engine->display, engine->surface); 183 | } 184 | 185 | /** 186 | * Tear down the EGL context currently associated with the display. 187 | */ 188 | static void engine_term_display(struct engine* engine) { 189 | if (engine->display != EGL_NO_DISPLAY) { 190 | eglMakeCurrent(engine->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); 191 | if (engine->context != EGL_NO_CONTEXT) { 192 | eglDestroyContext(engine->display, engine->context); 193 | } 194 | if (engine->surface != EGL_NO_SURFACE) { 195 | eglDestroySurface(engine->display, engine->surface); 196 | } 197 | eglTerminate(engine->display); 198 | } 199 | engine->animating = 0; 200 | engine->display = EGL_NO_DISPLAY; 201 | engine->context = EGL_NO_CONTEXT; 202 | engine->surface = EGL_NO_SURFACE; 203 | } 204 | 205 | /** 206 | * Process the next input event. 207 | */ 208 | static int32_t engine_handle_input(struct android_app* app, AInputEvent* event) { 209 | auto* engine = (struct engine*)app->userData; 210 | if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) { 211 | engine->animating = 1; 212 | engine->state.x = AMotionEvent_getX(event, 0); 213 | engine->state.y = AMotionEvent_getY(event, 0); 214 | return 1; 215 | } 216 | return 0; 217 | } 218 | 219 | /** 220 | * Process the next main command. 221 | */ 222 | static void engine_handle_cmd(struct android_app* app, int32_t cmd) { 223 | auto* engine = (struct engine*)app->userData; 224 | switch (cmd) { 225 | case APP_CMD_SAVE_STATE: 226 | // The system has asked us to save our current state. Do so. 227 | engine->app->savedState = malloc(sizeof(struct saved_state)); 228 | *((struct saved_state*)engine->app->savedState) = engine->state; 229 | engine->app->savedStateSize = sizeof(struct saved_state); 230 | break; 231 | case APP_CMD_INIT_WINDOW: 232 | // The window is being shown, get it ready. 233 | if (engine->app->window != nullptr) { 234 | engine_init_display(engine); 235 | engine_draw_frame(engine); 236 | } 237 | break; 238 | case APP_CMD_TERM_WINDOW: 239 | // The window is being hidden or closed, clean it up. 240 | engine_term_display(engine); 241 | break; 242 | case APP_CMD_GAINED_FOCUS: 243 | // When our app gains focus, we start monitoring the accelerometer. 244 | if (engine->accelerometerSensor != nullptr) { 245 | ASensorEventQueue_enableSensor(engine->sensorEventQueue, 246 | engine->accelerometerSensor); 247 | // We'd like to get 60 events per second (in us). 248 | ASensorEventQueue_setEventRate(engine->sensorEventQueue, 249 | engine->accelerometerSensor, 250 | (1000L/60)*1000); 251 | } 252 | break; 253 | case APP_CMD_LOST_FOCUS: 254 | // When our app loses focus, we stop monitoring the accelerometer. 255 | // This is to avoid consuming battery while not being used. 256 | if (engine->accelerometerSensor != nullptr) { 257 | ASensorEventQueue_disableSensor(engine->sensorEventQueue, 258 | engine->accelerometerSensor); 259 | } 260 | // Also stop animating. 261 | engine->animating = 0; 262 | engine_draw_frame(engine); 263 | break; 264 | default: 265 | break; 266 | } 267 | } 268 | 269 | /* 270 | * AcquireASensorManagerInstance(void) 271 | * Workaround ASensorManager_getInstance() deprecation false alarm 272 | * for Android-N and before, when compiling with NDK-r15 273 | */ 274 | #include 275 | ASensorManager* AcquireASensorManagerInstance(android_app* app) { 276 | 277 | if(!app) 278 | return nullptr; 279 | 280 | typedef ASensorManager *(*PF_GETINSTANCEFORPACKAGE)(const char *name); 281 | void* androidHandle = dlopen("libandroid.so", RTLD_NOW); 282 | auto getInstanceForPackageFunc = (PF_GETINSTANCEFORPACKAGE) 283 | dlsym(androidHandle, "ASensorManager_getInstanceForPackage"); 284 | if (getInstanceForPackageFunc) { 285 | JNIEnv* env = nullptr; 286 | app->activity->vm->AttachCurrentThread(&env, nullptr); 287 | 288 | jclass android_content_Context = env->GetObjectClass(app->activity->clazz); 289 | jmethodID midGetPackageName = env->GetMethodID(android_content_Context, 290 | "getPackageName", 291 | "()Ljava/lang/String;"); 292 | auto packageName= (jstring)env->CallObjectMethod(app->activity->clazz, 293 | midGetPackageName); 294 | 295 | const char *nativePackageName = env->GetStringUTFChars(packageName, nullptr); 296 | ASensorManager* mgr = getInstanceForPackageFunc(nativePackageName); 297 | env->ReleaseStringUTFChars(packageName, nativePackageName); 298 | app->activity->vm->DetachCurrentThread(); 299 | if (mgr) { 300 | dlclose(androidHandle); 301 | return mgr; 302 | } 303 | } 304 | 305 | typedef ASensorManager *(*PF_GETINSTANCE)(); 306 | auto getInstanceFunc = (PF_GETINSTANCE) 307 | dlsym(androidHandle, "ASensorManager_getInstance"); 308 | // by all means at this point, ASensorManager_getInstance should be available 309 | assert(getInstanceFunc); 310 | dlclose(androidHandle); 311 | 312 | return getInstanceFunc(); 313 | } 314 | 315 | 316 | /** 317 | * This is the main entry point of a native application that is using 318 | * android_native_app_glue. It runs in its own thread, with its own 319 | * event loop for receiving input events and doing other things. 320 | */ 321 | void android_main(struct android_app* state) { 322 | struct engine engine{}; 323 | 324 | memset(&engine, 0, sizeof(engine)); 325 | state->userData = &engine; 326 | state->onAppCmd = engine_handle_cmd; 327 | state->onInputEvent = engine_handle_input; 328 | engine.app = state; 329 | 330 | // Prepare to monitor accelerometer 331 | engine.sensorManager = AcquireASensorManagerInstance(state); 332 | engine.accelerometerSensor = ASensorManager_getDefaultSensor( 333 | engine.sensorManager, 334 | ASENSOR_TYPE_ACCELEROMETER); 335 | engine.sensorEventQueue = ASensorManager_createEventQueue( 336 | engine.sensorManager, 337 | state->looper, LOOPER_ID_USER, 338 | nullptr, nullptr); 339 | 340 | if (state->savedState != nullptr) { 341 | // We are starting with a previous saved state; restore from it. 342 | engine.state = *(struct saved_state*)state->savedState; 343 | } 344 | 345 | // loop waiting for stuff to do. 346 | 347 | while (Addable) { 348 | // Read all pending events. 349 | int ident; 350 | int events; 351 | struct android_poll_source* source; 352 | 353 | // If not animating, we will block forever waiting for events. 354 | // If animating, we loop until all events are read, then continue 355 | // to draw the next frame of animation. 356 | while ((ident=ALooper_pollAll(engine.animating ? 0 : -1, nullptr, &events, 357 | (void**)&source)) >= 0) { 358 | 359 | // Process this event. 360 | if (source != nullptr) { 361 | source->process(state, source); 362 | } 363 | 364 | // If a sensor has data, process it now. 365 | if (ident == LOOPER_ID_USER) { 366 | if (engine.accelerometerSensor != nullptr) { 367 | ASensorEvent event; 368 | while (ASensorEventQueue_getEvents(engine.sensorEventQueue, 369 | &event, 1) > 0) { 370 | LOGI("accelerometer: x=%f y=%f z=%f", 371 | event.acceleration.x, event.acceleration.y, 372 | event.acceleration.z); 373 | } 374 | } 375 | } 376 | 377 | // Check if we are exiting. 378 | if (state->destroyRequested != 0) { 379 | engine_term_display(&engine); 380 | return; 381 | } 382 | } 383 | 384 | if (engine.animating) { 385 | // Done with events; draw next animation frame. 386 | engine.state.angle += .01f; 387 | if (engine.state.angle > 1) { 388 | engine.state.angle = 0; 389 | } 390 | 391 | // Drawing is throttled to the screen update rate, so there 392 | // is no need to do timing here. 393 | engine_draw_frame(&engine); 394 | } 395 | } 396 | } 397 | //END_INCLUDE(all) 398 | --------------------------------------------------------------------------------