mirror of
				https://github.com/luanti-org/luanti.git
				synced 2025-11-04 09:15:29 +01: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:
		
				
					committed by
					
						
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							409543566a
						
					
				
				
					commit
					3e5d9782cc
				
			@@ -22,14 +22,19 @@ package net.minetest.minetest;
 | 
			
		||||
 | 
			
		||||
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.ActivityNotFoundException;
 | 
			
		||||
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.util.Log;
 | 
			
		||||
import android.view.KeyEvent;
 | 
			
		||||
import android.view.View;
 | 
			
		||||
import android.view.WindowManager;
 | 
			
		||||
import android.view.inputmethod.InputMethodManager;
 | 
			
		||||
import android.widget.Button;
 | 
			
		||||
@@ -91,6 +96,9 @@ public class GameActivity extends SDLActivity {
 | 
			
		||||
		saveSettings();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private NotificationManager mNotifyManager;
 | 
			
		||||
	private boolean gameNotificationShown = false;
 | 
			
		||||
 | 
			
		||||
	public void showTextInputDialog(String hint, String current, int editType) {
 | 
			
		||||
		runOnUiThread(() -> showTextInputDialogUI(hint, current, editType));
 | 
			
		||||
	}
 | 
			
		||||
@@ -263,4 +271,67 @@ public class GameActivity extends SDLActivity {
 | 
			
		||||
	public boolean hasPhysicalKeyboard() {
 | 
			
		||||
		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 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 static final String SETTINGS = "MinetestSettings";
 | 
			
		||||
 
 | 
			
		||||
@@ -51,7 +51,6 @@ public class UnzipService extends IntentService {
 | 
			
		||||
	public static final int SUCCESS = -1;
 | 
			
		||||
	public static final int FAILURE = -2;
 | 
			
		||||
	public static final int INDETERMINATE = -3;
 | 
			
		||||
	private final int id = 1;
 | 
			
		||||
	private NotificationManager mNotifyManager;
 | 
			
		||||
	private boolean isSuccess = true;
 | 
			
		||||
	private String failureMessage;
 | 
			
		||||
@@ -100,11 +99,14 @@ public class UnzipService extends IntentService {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO: share code with GameActivity.updateGameNotification
 | 
			
		||||
	@NonNull
 | 
			
		||||
	private Notification.Builder createNotification() {
 | 
			
		||||
		Notification.Builder builder;
 | 
			
		||||
		if (mNotifyManager == null)
 | 
			
		||||
		if (mNotifyManager == null) {
 | 
			
		||||
			mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		Notification.Builder builder;
 | 
			
		||||
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
 | 
			
		||||
			builder = new Notification.Builder(this, MainActivity.NOTIFICATION_CHANNEL_ID);
 | 
			
		||||
		} else {
 | 
			
		||||
@@ -128,7 +130,7 @@ public class UnzipService extends IntentService {
 | 
			
		||||
				.setOngoing(true)
 | 
			
		||||
				.setProgress(0, 0, true);
 | 
			
		||||
 | 
			
		||||
		mNotifyManager.notify(id, builder.build());
 | 
			
		||||
		mNotifyManager.notify(MainActivity.NOTIFICATION_ID_UNZIP, builder.build());
 | 
			
		||||
		return builder;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -200,14 +202,14 @@ public class UnzipService extends IntentService {
 | 
			
		||||
			} else {
 | 
			
		||||
				notificationBuilder.setProgress(100, progress, false);
 | 
			
		||||
			}
 | 
			
		||||
			mNotifyManager.notify(id, notificationBuilder.build());
 | 
			
		||||
			mNotifyManager.notify(MainActivity.NOTIFICATION_ID_UNZIP, notificationBuilder.build());
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void onDestroy() {
 | 
			
		||||
		super.onDestroy();
 | 
			
		||||
		mNotifyManager.cancel(id);
 | 
			
		||||
		mNotifyManager.cancel(MainActivity.NOTIFICATION_ID_UNZIP);
 | 
			
		||||
		publishProgress(null, R.string.loading, isSuccess ? SUCCESS : FAILURE);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@
 | 
			
		||||
	<string name="notification_channel_description">Notifications from Luanti</string>
 | 
			
		||||
	<string name="unzip_notification_title">Loading Luanti</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="no_web_browser">No web browser found</string>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -1021,6 +1021,10 @@ void Game::run()
 | 
			
		||||
	const bool initial_window_maximized = !g_settings->getBool("fullscreen") &&
 | 
			
		||||
			g_settings->getBool("window_maximized");
 | 
			
		||||
 | 
			
		||||
#ifdef __ANDROID__
 | 
			
		||||
	porting::setPlayingNowNotification(true);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	auto framemarker = FrameMarker("Game::run()-frame").started();
 | 
			
		||||
 | 
			
		||||
	while (m_rendering_engine->run()
 | 
			
		||||
@@ -1103,6 +1107,10 @@ void Game::run()
 | 
			
		||||
 | 
			
		||||
	framemarker.end();
 | 
			
		||||
 | 
			
		||||
#ifdef __ANDROID__
 | 
			
		||||
	porting::setPlayingNowNotification(false);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	RenderingEngine::autosaveScreensizeAndCo(initial_screen_size, initial_window_maximized);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -187,6 +187,18 @@ void shareFileAndroid(const std::string &path)
 | 
			
		||||
	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()
 | 
			
		||||
{
 | 
			
		||||
	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);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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:
 | 
			
		||||
 * 1. Text input (single/multi-line text and password field)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user