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:
parent
409543566a
commit
3e5d9782cc
7 changed files with 111 additions and 8 deletions
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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…</string>
|
<string name="unzip_notification_description">Less than 1 minute…</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>
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue