├── .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 |
--------------------------------------------------------------------------------