Skip to content

Commit 104baae

Browse files
authored
Merge pull request ddnet#9392 from Robyt3/Android-Server
Support running local server on Android
2 parents 02b3a89 + cef2c26 commit 104baae

File tree

17 files changed

+761
-111
lines changed

17 files changed

+761
-111
lines changed

CMakeLists.txt

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -689,8 +689,8 @@ endif()
689689
if(CLIENT AND NOT(SDL2_FOUND))
690690
message(SEND_ERROR "You must install SDL2 to compile the ${CMAKE_PROJECT_NAME} client")
691691
endif()
692-
if(TARGET_OS STREQUAL "android" AND CLIENT AND NOT(CRYPTO_FOUND))
693-
message(SEND_ERROR "You must install OpenSSL to compile the ${CMAKE_PROJECT_NAME} client")
692+
if(TARGET_OS STREQUAL "android" AND NOT(CRYPTO_FOUND))
693+
message(SEND_ERROR "You must install OpenSSL to compile ${CMAKE_PROJECT_NAME}")
694694
endif()
695695
if(NOT(GTEST_FOUND))
696696
if(DOWNLOAD_GTEST)
@@ -741,7 +741,7 @@ elseif(TARGET_OS STREQUAL "android")
741741
src/android/android_main.cpp
742742
)
743743
set(PLATFORM_LIBS ${TW_ANDROID_LIBS})
744-
set(PLATFORM_CLIENT_LIBS ${PLATFORM_LIBS})
744+
set(PLATFORM_CLIENT_LIBS ${TW_ANDROID_LIBS_CLIENT})
745745
set(PLATFORM_CLIENT_INCLUDE_DIRS)
746746
else()
747747
find_package(Notify)
@@ -2782,14 +2782,25 @@ if(SERVER)
27822782
)
27832783

27842784
# Target
2785-
add_executable(game-server
2786-
${DEPS}
2787-
${SERVER_SRC}
2788-
${SERVER_ICON}
2789-
$<TARGET_OBJECTS:engine-shared>
2790-
$<TARGET_OBJECTS:game-shared>
2791-
$<TARGET_OBJECTS:rust-bridge-shared>
2792-
)
2785+
if(TARGET_OS STREQUAL "android")
2786+
add_library(game-server SHARED
2787+
${DEPS}
2788+
${SERVER_SRC}
2789+
${SERVER_ICON}
2790+
$<TARGET_OBJECTS:engine-shared>
2791+
$<TARGET_OBJECTS:game-shared>
2792+
$<TARGET_OBJECTS:rust-bridge-shared>
2793+
)
2794+
else()
2795+
add_executable(game-server
2796+
${DEPS}
2797+
${SERVER_SRC}
2798+
${SERVER_ICON}
2799+
$<TARGET_OBJECTS:engine-shared>
2800+
$<TARGET_OBJECTS:game-shared>
2801+
$<TARGET_OBJECTS:rust-bridge-shared>
2802+
)
2803+
endif()
27932804
set_property(TARGET game-server
27942805
PROPERTY OUTPUT_NAME ${SERVER_EXECUTABLE}
27952806
)
@@ -3600,6 +3611,13 @@ foreach(target ${TARGETS_OWN})
36003611
if(CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
36013612
target_compile_definitions(${target} PRIVATE CONF_WEBASM)
36023613
endif()
3614+
if(TARGET_OS STREQUAL "android")
3615+
if(ANDROID_PACKAGE_NAME)
3616+
target_compile_definitions(${target} PRIVATE ANDROID_PACKAGE_NAME=${ANDROID_PACKAGE_NAME})
3617+
else()
3618+
message(FATAL_ERROR "ANDROID_PACKAGE_NAME must define the package name when compiling for Android (using underscores instead of dots, e.g. org_example_app)")
3619+
endif()
3620+
endif()
36033621
endforeach()
36043622

36053623
foreach(target ${TARGETS_DEP})

cmake/FindAndroid.cmake

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,16 @@ FIND_LIBRARY(ANDROID_LIBRARY_OPENSLES
2727
OpenSLES
2828
)
2929

30-
set(TW_ANDROID_LIBS ${ANDROID_LIBRARY_EGL} ${ANDROID_LIBRARY_GLES1} ${ANDROID_LIBRARY_GLES2} ${ANDROID_LIBRARY_GLES3} ${ANDROID_LIBRARY_LOG} ${ANDROID_LIBRARY_ANDROID} ${ANDROID_LIBRARY_OPENSLES})
30+
set(TW_ANDROID_LIBS
31+
${ANDROID_LIBRARY_LOG}
32+
${ANDROID_LIBRARY_ANDROID}
33+
)
34+
35+
set(TW_ANDROID_LIBS_CLIENT
36+
${TW_ANDROID_LIBS}
37+
${ANDROID_LIBRARY_EGL}
38+
${ANDROID_LIBRARY_GLES1}
39+
${ANDROID_LIBRARY_GLES2}
40+
${ANDROID_LIBRARY_GLES3}
41+
${ANDROID_LIBRARY_OPENSLES}
42+
)

scripts/android/cmake_android.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,14 +153,15 @@ function build_for_type() {
153153
-DANDROID_NDK="$ANDROID_NDK_HOME" \
154154
-DANDROID_ABI="${2}" \
155155
-DANDROID_ARM_NEON=TRUE \
156+
-DANDROID_PACKAGE_NAME="${PACKAGE_NAME//./_}" \
156157
-DCMAKE_ANDROID_NDK="$ANDROID_NDK_HOME" \
157158
-DCMAKE_SYSTEM_NAME=Android \
158159
-DCMAKE_SYSTEM_VERSION="$ANDROID_API_LEVEL" \
159160
-DCMAKE_ANDROID_ARCH_ABI="${2}" \
160161
-DCARGO_NDK_TARGET="${3}" \
161162
-DCARGO_NDK_API="$ANDROID_API_LEVEL" \
162163
-B"${BUILD_FOLDER}/$ANDROID_SUB_BUILD_DIR/$1" \
163-
-DSERVER=OFF \
164+
-DSERVER=ON \
164165
-DTOOLS=OFF \
165166
-DDEV=TRUE \
166167
-DCMAKE_CROSSCOMPILING=ON \
@@ -170,7 +171,7 @@ function build_for_type() {
170171
cd "${BUILD_FOLDER}/$ANDROID_SUB_BUILD_DIR/$1" || exit 1
171172
# We want word splitting
172173
# shellcheck disable=SC2086
173-
cmake --build . --target game-client $BUILD_FLAGS
174+
cmake --build . --target game-client game-server $BUILD_FLAGS
174175
)
175176
}
176177

@@ -228,6 +229,7 @@ log_info "Copying libraries..."
228229
function copy_libs() {
229230
mkdir -p "lib/$2"
230231
cp "$ANDROID_SUB_BUILD_DIR/$1/libDDNet.so" "lib/$2" || exit 1
232+
cp "$ANDROID_SUB_BUILD_DIR/$1/libDDNet-Server.so" "lib/$2" || exit 1
231233
}
232234

233235
if [[ "${ANDROID_BUILD}" == "arm" || "${ANDROID_BUILD}" == "all" ]]; then

scripts/android/files/AndroidManifest.xml

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,21 +41,27 @@
4141
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
4242
<uses-permission android:name="android.permission.INTERNET" />
4343

44+
<!-- Local server runs in a foreground service with a notification to control it -->
45+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
46+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
47+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
48+
4449
<!-- usesCleartextTraffic because unencrypted UDP packets -->
4550
<application
4651
android:usesCleartextTraffic="true"
4752
android:label="@string/app_name"
4853
android:hasCode="true"
4954
android:supportsRtl="true"
50-
android:isGame="true"
55+
android:appCategory="game"
5156
android:icon="@mipmap/ic_launcher"
5257
android:roundIcon="@mipmap/ic_launcher_round"
5358
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
5459
android:hardwareAccelerated="true">
5560
<activity
56-
android:name="org.ddnet.client.NativeMain"
61+
android:name="org.ddnet.client.ClientActivity"
5762
android:alwaysRetainTaskState="true"
5863
android:exported="true"
64+
android:process=":client_process"
5965
android:configChanges="layoutDirection|locale|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
6066
android:preferMinimalPostProcessing="true"
6167
android:screenOrientation="landscape"
@@ -75,5 +81,17 @@
7581
android:name="android.app.shortcuts"
7682
android:resource="@xml/shortcuts" />
7783
</activity>
84+
<!-- Server service must run in a different process because it needs to be terminated to restart the server -->
85+
<service
86+
android:name="org.ddnet.client.ServerService"
87+
android:exported="false"
88+
android:process=":server_process"
89+
android:foregroundServiceType="specialUse" />
90+
<property
91+
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
92+
android:value="This service is used for hosting a LAN server to play the game in a local area network as well as without internet. A foreground service is used because the server should also continue running while the client activity is not in the foreground as other players may be connected to the server." />
93+
<meta-data
94+
android:name="android.app.lib_name"
95+
android:value="DDNet-Server" />
7896
</application>
7997
</manifest>

scripts/android/files/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ android {
6161
lintOptions {
6262
abortOnError false
6363
}
64+
dependencies {
65+
implementation 'androidx.core:core:1.13.1'
66+
}
6467
}
6568

6669
allprojects {

scripts/android/files/build.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ if [ "${APK_PACKAGE_FOLDER}" != "org/ddnet/client" ]; then
5959
mv src/main/java/org/ddnet/client src/main/java/"${APK_PACKAGE_FOLDER}"
6060
fi
6161

62-
sed -i "s/org.ddnet.client/${APK_PACKAGE_NAME}/g" src/main/java/"${APK_PACKAGE_FOLDER}"/NativeMain.java
62+
sed -i "s/org.ddnet.client/${APK_PACKAGE_NAME}/g" src/main/java/"${APK_PACKAGE_FOLDER}"/ClientActivity.java
63+
sed -i "s/org.ddnet.client/${APK_PACKAGE_NAME}/g" src/main/java/"${APK_PACKAGE_FOLDER}"/ServerService.java
6364
sed -i "s/org.ddnet.client/${APK_PACKAGE_NAME}/g" proguard-rules.pro
6465

6566
# disable hid manager for now
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package org.ddnet.client;
2+
3+
import android.app.NativeActivity;
4+
import android.content.*;
5+
import android.content.pm.ActivityInfo;
6+
import android.os.*;
7+
8+
import androidx.core.content.ContextCompat;
9+
10+
import org.libsdl.app.SDLActivity;
11+
12+
public class ClientActivity extends SDLActivity {
13+
14+
private static final int COMMAND_RESTART_APP = SDLActivity.COMMAND_USER + 1;
15+
16+
private String[] launchArguments = new String[0];
17+
18+
private final Object serverServiceMonitor = new Object();
19+
private Messenger serverServiceMessenger = null;
20+
private final ServiceConnection serverServiceConnection = new ServiceConnection() {
21+
@Override
22+
public void onServiceConnected(ComponentName name, IBinder service) {
23+
synchronized(serverServiceMonitor) {
24+
serverServiceMessenger = new Messenger(service);
25+
}
26+
}
27+
28+
@Override
29+
public void onServiceDisconnected(ComponentName name) {
30+
synchronized(serverServiceMonitor) {
31+
serverServiceMessenger = null;
32+
}
33+
}
34+
};
35+
36+
@Override
37+
protected String[] getLibraries() {
38+
return new String[] {
39+
"DDNet",
40+
};
41+
}
42+
43+
@Override
44+
public void onCreate(Bundle savedInstanceState) {
45+
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
46+
47+
Intent intent = getIntent();
48+
if(intent != null) {
49+
String gfxBackend = intent.getStringExtra("gfx-backend");
50+
if(gfxBackend != null) {
51+
if(gfxBackend.equals("Vulkan")) {
52+
launchArguments = new String[] {"gfx_backend Vulkan"};
53+
} else if(gfxBackend.equals("GLES")) {
54+
launchArguments = new String[] {"gfx_backend GLES"};
55+
}
56+
}
57+
}
58+
59+
super.onCreate(savedInstanceState);
60+
}
61+
62+
@Override
63+
protected void onDestroy() {
64+
super.onDestroy();
65+
synchronized(serverServiceMonitor) {
66+
if(serverServiceMessenger != null) {
67+
unbindService(serverServiceConnection);
68+
}
69+
}
70+
}
71+
72+
@Override
73+
protected String[] getArguments() {
74+
return launchArguments;
75+
}
76+
77+
@Override
78+
protected boolean onUnhandledMessage(int command, Object param) {
79+
switch(command) {
80+
case COMMAND_RESTART_APP:
81+
restartApp();
82+
return true;
83+
}
84+
return false;
85+
}
86+
87+
private void restartApp() {
88+
Intent restartIntent =
89+
Intent.makeRestartActivityTask(
90+
getPackageManager().getLaunchIntentForPackage(
91+
getPackageName()
92+
).getComponent()
93+
);
94+
restartIntent.setPackage(getPackageName());
95+
startActivity(restartIntent);
96+
}
97+
98+
// Called from native code, see android_main.cpp
99+
public void startServer() {
100+
synchronized(serverServiceMonitor) {
101+
if(serverServiceMessenger != null) {
102+
return;
103+
}
104+
Intent startIntent = new Intent(this, ServerService.class);
105+
ContextCompat.startForegroundService(this, startIntent);
106+
bindService(startIntent, serverServiceConnection, 0);
107+
}
108+
}
109+
110+
// Called from native code, see android_main.cpp
111+
public void executeCommand(String command) {
112+
synchronized(serverServiceMonitor) {
113+
if(serverServiceMessenger == null) {
114+
return;
115+
}
116+
try {
117+
Message message = Message.obtain(null, ServerService.MESSAGE_CODE_EXECUTE_COMMAND, 0, 0);
118+
message.getData().putString(ServerService.MESSAGE_EXTRA_COMMAND, command);
119+
serverServiceMessenger.send(message);
120+
} catch (RemoteException e) {
121+
// Connection broken
122+
unbindService(serverServiceConnection);
123+
}
124+
}
125+
}
126+
127+
// Called from native code, see android_main.cpp
128+
public boolean isServerRunning() {
129+
synchronized(serverServiceMonitor) {
130+
return serverServiceMessenger != null;
131+
}
132+
}
133+
}

scripts/android/files/java/org/ddnet/client/NativeMain.java

Lines changed: 0 additions & 65 deletions
This file was deleted.

0 commit comments

Comments
 (0)