1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-06-27 16:36:03 +00:00

Android: Persistent notification while ingame (#13125)

Co-authored-by: Lars Müller <34514239+appgurueu@users.noreply.github.com>
Co-authored-by: grorp <grorp@posteo.de>
This commit is contained in:
Muhammad Rifqi Priyo Susanto 2025-04-24 18:38:52 +07:00 committed by GitHub
parent 409543566a
commit 3e5d9782cc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 111 additions and 8 deletions

View file

@ -22,14 +22,19 @@ package net.minetest.minetest;
import org.libsdl.app.SDLActivity; import org.libsdl.app.SDLActivity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.text.InputType; import android.text.InputType;
import android.util.Log; import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.View;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.widget.Button; import android.widget.Button;
@ -91,6 +96,9 @@ public class GameActivity extends SDLActivity {
saveSettings(); saveSettings();
} }
private NotificationManager mNotifyManager;
private boolean gameNotificationShown = false;
public void showTextInputDialog(String hint, String current, int editType) { public void showTextInputDialog(String hint, String current, int editType) {
runOnUiThread(() -> showTextInputDialogUI(hint, current, editType)); runOnUiThread(() -> showTextInputDialogUI(hint, current, editType));
} }
@ -263,4 +271,67 @@ public class GameActivity extends SDLActivity {
public boolean hasPhysicalKeyboard() { public boolean hasPhysicalKeyboard() {
return getContext().getResources().getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS; return getContext().getResources().getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS;
} }
// TODO: share code with UnzipService.createNotification
private void updateGameNotification() {
if (mNotifyManager == null) {
mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
}
if (!gameNotificationShown) {
mNotifyManager.cancel(MainActivity.NOTIFICATION_ID_GAME);
return;
}
Notification.Builder builder;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder = new Notification.Builder(this, MainActivity.NOTIFICATION_CHANNEL_ID);
} else {
builder = new Notification.Builder(this);
}
Intent notificationIntent = new Intent(this, GameActivity.class);
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_SINGLE_TOP);
int pendingIntentFlag = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
pendingIntentFlag = PendingIntent.FLAG_MUTABLE;
}
PendingIntent intent = PendingIntent.getActivity(this, 0,
notificationIntent, pendingIntentFlag);
builder.setContentTitle(getString(R.string.game_notification_title))
.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(intent)
.setOngoing(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// This avoids a stuck notification if the app is killed while
// in-game: (1) if the user closes the app from the "Recents" screen
// or (2) if the system kills the app while it is in background.
// onStop is called too early to remove the notification and
// onDestroy is often not called at all, so there's this hack instead.
builder.setTimeoutAfter(16000);
// Replace the notification just before it expires as long as the app is
// running (and we're still in-game).
final Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(new Runnable() {
@Override
public void run() {
if (gameNotificationShown) {
updateGameNotification();
}
}
}, 15000);
}
mNotifyManager.notify(MainActivity.NOTIFICATION_ID_GAME, builder.build());
}
public void setPlayingNowNotification(boolean show) {
gameNotificationShown = show;
updateGameNotification();
}
} }

View file

@ -43,6 +43,8 @@ import static net.minetest.minetest.UnzipService.*;
public class MainActivity extends AppCompatActivity { public class MainActivity extends AppCompatActivity {
public static final String NOTIFICATION_CHANNEL_ID = "Minetest channel"; public static final String NOTIFICATION_CHANNEL_ID = "Minetest channel";
public static final int NOTIFICATION_ID_UNZIP = 1;
public static final int NOTIFICATION_ID_GAME = 2;
private final static int versionCode = BuildConfig.VERSION_CODE; private final static int versionCode = BuildConfig.VERSION_CODE;
private static final String SETTINGS = "MinetestSettings"; private static final String SETTINGS = "MinetestSettings";

View file

@ -51,7 +51,6 @@ public class UnzipService extends IntentService {
public static final int SUCCESS = -1; public static final int SUCCESS = -1;
public static final int FAILURE = -2; public static final int FAILURE = -2;
public static final int INDETERMINATE = -3; public static final int INDETERMINATE = -3;
private final int id = 1;
private NotificationManager mNotifyManager; private NotificationManager mNotifyManager;
private boolean isSuccess = true; private boolean isSuccess = true;
private String failureMessage; private String failureMessage;
@ -100,11 +99,14 @@ public class UnzipService extends IntentService {
} }
} }
// TODO: share code with GameActivity.updateGameNotification
@NonNull @NonNull
private Notification.Builder createNotification() { private Notification.Builder createNotification() {
Notification.Builder builder; if (mNotifyManager == null) {
if (mNotifyManager == null)
mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
}
Notification.Builder builder;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder = new Notification.Builder(this, MainActivity.NOTIFICATION_CHANNEL_ID); builder = new Notification.Builder(this, MainActivity.NOTIFICATION_CHANNEL_ID);
} else { } else {
@ -128,7 +130,7 @@ public class UnzipService extends IntentService {
.setOngoing(true) .setOngoing(true)
.setProgress(0, 0, true); .setProgress(0, 0, true);
mNotifyManager.notify(id, builder.build()); mNotifyManager.notify(MainActivity.NOTIFICATION_ID_UNZIP, builder.build());
return builder; return builder;
} }
@ -200,14 +202,14 @@ public class UnzipService extends IntentService {
} else { } else {
notificationBuilder.setProgress(100, progress, false); notificationBuilder.setProgress(100, progress, false);
} }
mNotifyManager.notify(id, notificationBuilder.build()); mNotifyManager.notify(MainActivity.NOTIFICATION_ID_UNZIP, notificationBuilder.build());
} }
} }
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
mNotifyManager.cancel(id); mNotifyManager.cancel(MainActivity.NOTIFICATION_ID_UNZIP);
publishProgress(null, R.string.loading, isSuccess ? SUCCESS : FAILURE); publishProgress(null, R.string.loading, isSuccess ? SUCCESS : FAILURE);
} }
} }

View file

@ -6,6 +6,7 @@
<string name="notification_channel_description">Notifications from Luanti</string> <string name="notification_channel_description">Notifications from Luanti</string>
<string name="unzip_notification_title">Loading Luanti</string> <string name="unzip_notification_title">Loading Luanti</string>
<string name="unzip_notification_description">Less than 1 minute&#8230;</string> <string name="unzip_notification_description">Less than 1 minute&#8230;</string>
<string name="game_notification_title">Luanti is running</string>
<string name="ime_dialog_done">Done</string> <string name="ime_dialog_done">Done</string>
<string name="no_web_browser">No web browser found</string> <string name="no_web_browser">No web browser found</string>
</resources> </resources>

View file

@ -1021,6 +1021,10 @@ void Game::run()
const bool initial_window_maximized = !g_settings->getBool("fullscreen") && const bool initial_window_maximized = !g_settings->getBool("fullscreen") &&
g_settings->getBool("window_maximized"); g_settings->getBool("window_maximized");
#ifdef __ANDROID__
porting::setPlayingNowNotification(true);
#endif
auto framemarker = FrameMarker("Game::run()-frame").started(); auto framemarker = FrameMarker("Game::run()-frame").started();
while (m_rendering_engine->run() while (m_rendering_engine->run()
@ -1103,6 +1107,10 @@ void Game::run()
framemarker.end(); framemarker.end();
#ifdef __ANDROID__
porting::setPlayingNowNotification(false);
#endif
RenderingEngine::autosaveScreensizeAndCo(initial_screen_size, initial_window_maximized); RenderingEngine::autosaveScreensizeAndCo(initial_screen_size, initial_window_maximized);
} }

View file

@ -187,6 +187,18 @@ void shareFileAndroid(const std::string &path)
jnienv->CallVoidMethod(activity, url_open, jurl); jnienv->CallVoidMethod(activity, url_open, jurl);
} }
void setPlayingNowNotification(bool show)
{
jmethodID play_notification = jnienv->GetMethodID(activityClass,
"setPlayingNowNotification", "(Z)V");
FATAL_ERROR_IF(play_notification == nullptr,
"porting::setPlayingNowNotification unable to find Java setPlayingNowNotification method");
jboolean jshow = show;
jnienv->CallVoidMethod(activity, play_notification, jshow);
}
AndroidDialogType getLastInputDialogType() AndroidDialogType getLastInputDialogType()
{ {
jmethodID lastdialogtype = jnienv->GetMethodID(activityClass, jmethodID lastdialogtype = jnienv->GetMethodID(activityClass,

View file

@ -36,6 +36,13 @@ void showComboBoxDialog(const std::string *optionList, s32 listSize, s32 selecte
*/ */
void shareFileAndroid(const std::string &path); void shareFileAndroid(const std::string &path);
/**
* Shows/hides notification that the game is running
*
* @param show whether to show/hide the notification
*/
void setPlayingNowNotification(bool show);
/* /*
* Types of Android input dialog: * Types of Android input dialog:
* 1. Text input (single/multi-line text and password field) * 1. Text input (single/multi-line text and password field)