Skip to content

Commit 0a77735

Browse files
committed
ANDROID: fix issue with back key in android-16
1 parent c0221f6 commit 0a77735

File tree

4 files changed

+84
-9
lines changed

4 files changed

+84
-9
lines changed

src/platform/android/app/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ android {
99
applicationId 'net.sourceforge.smallbasic'
1010
minSdkVersion 21
1111
targetSdkVersion 36
12-
versionCode 79
12+
versionCode 80
1313
versionName '12.30'
1414
resourceConfigurations += ['en']
1515
}

src/platform/android/app/src/main/java/net/sourceforge/smallbasic/MainActivity.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
import android.view.View;
3737
import android.view.inputmethod.InputMethodManager;
3838
import android.widget.Toast;
39+
import android.window.OnBackInvokedCallback;
40+
import android.window.OnBackInvokedDispatcher;
3941

4042
import androidx.annotation.RequiresPermission;
4143
import androidx.core.app.ActivityCompat;
@@ -118,6 +120,7 @@ public class MainActivity extends NativeActivity {
118120
public static native void consoleLog(String value);
119121
public static native boolean libraryMode();
120122
public static native void onActivityPaused(boolean paused);
123+
public static native void onBack();
121124
public static native void onResize(int width, int height);
122125
public static native void onUnicodeChar(int ch);
123126
public static native boolean optionSelected(int index);
@@ -766,6 +769,7 @@ protected void onCreate(Bundle savedInstanceState) {
766769
super.onCreate(savedInstanceState);
767770
setImmersiveMode();
768771
setupStorageEnvironment();
772+
setupPredictiveBack();
769773
if (!libraryMode()) {
770774
processIntent();
771775
processSettings();
@@ -1055,6 +1059,24 @@ private void setImmersiveMode() {
10551059
}
10561060
}
10571061

1062+
//
1063+
// Hook into Predictive Back (Android 13+)
1064+
//
1065+
private void setupPredictiveBack() {
1066+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
1067+
getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
1068+
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
1069+
new OnBackInvokedCallback() {
1070+
@Override
1071+
public void onBackInvoked() {
1072+
Log.d(TAG, "onBackInvoked");
1073+
onBack();
1074+
}
1075+
}
1076+
);
1077+
}
1078+
}
1079+
10581080
private void setupStorageEnvironment() {
10591081
_storage = new Storage();
10601082
setenv("EXTERNAL_DIR", _storage.getExternal());

src/platform/android/jni/runtime.cpp

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030

3131
Runtime *runtime = nullptr;
3232

33+
// Pipe file descriptors: g_backPipe[0] is read-end, g_backPipe[1] is write-end
34+
static int g_backPipe[2] = {-1, -1};
35+
3336
// the sensorTypes corresponding to _sensors[] positions
3437
constexpr int SENSOR_TYPES[MAX_SENSORS] = {
3538
ASENSOR_TYPE_ACCELEROMETER,
@@ -106,6 +109,48 @@ void handleCommand(android_app *app, int32_t cmd) {
106109
}
107110
}
108111

112+
static void pushBackEvent() {
113+
auto *maEvent = new MAEvent();
114+
maEvent->nativeKey = AKEYCODE_BACK;
115+
maEvent->type = EVENT_TYPE_KEY_PRESSED;
116+
runtime->pushEvent(maEvent);
117+
}
118+
119+
//
120+
// Callback registered with ALooper that is triggered when the pipe receives data.
121+
// This is what wakes the blocked ALooper_pollOnce() and lets us run pushBackEvent().
122+
//
123+
static int pipeCallback(int fd, int events, void *data) {
124+
// clear the byte that woke the pipe, then return 1 to stay registered
125+
logEntered();
126+
char buf[1];
127+
read(fd, buf, 1);
128+
pushBackEvent();
129+
return 1;
130+
}
131+
132+
//
133+
// Set up the pipe and register its read-end (g_backPipe[0]) with the ALooper.
134+
// This allows us to wake the looper from Java code by writing to the pipe.
135+
//
136+
static void setupBackWakePipe(ALooper *looper) {
137+
if (pipe(g_backPipe) == 0) {
138+
// Make read-end non-blocking to avoid stalling the loop
139+
fcntl(g_backPipe[0], F_SETFL, O_NONBLOCK);
140+
141+
// Register the pipe with the looper so it wakes up when there's input
142+
ALooper_addFd(looper,
143+
g_backPipe[0], // fd to watch
144+
0, // arbitrary/unused identifier
145+
ALOOPER_EVENT_INPUT, // watch for input readiness
146+
pipeCallback, // callback to run on wake
147+
nullptr); // no additional data
148+
trace("Back pipe registered with looper");
149+
} else {
150+
trace("Failed to create back pipe");
151+
}
152+
}
153+
109154
// see http://stackoverflow.com/questions/15913080
110155
static void process_input(android_app *app, android_poll_source *source) {
111156
AInputEvent* event = nullptr;
@@ -114,12 +159,8 @@ static void process_input(android_app *app, android_poll_source *source) {
114159
AKeyEvent_getKeyCode(event) == AKEYCODE_BACK) {
115160
// prevent AInputQueue_preDispatchEvent from attempting to close
116161
// the keypad here to avoid a crash in android 4.2 + 4.3.
117-
if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN &&
118-
runtime->isActive()) {
119-
auto *maEvent = new MAEvent();
120-
maEvent->nativeKey = AKEYCODE_BACK;
121-
maEvent->type = EVENT_TYPE_KEY_PRESSED;
122-
runtime->pushEvent(maEvent);
162+
if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN && runtime->isActive()) {
163+
pushBackEvent();
123164
}
124165
AInputQueue_finishEvent(app->inputQueue, event, true);
125166
} else if (!AInputQueue_preDispatchEvent(app->inputQueue, event)) {
@@ -128,6 +169,18 @@ static void process_input(android_app *app, android_poll_source *source) {
128169
}
129170
}
130171

172+
extern "C" JNIEXPORT void JNICALL Java_net_sourceforge_smallbasic_MainActivity_onBack
173+
(JNIEnv *env, jclass clazz) {
174+
if (runtime != nullptr) {
175+
logEntered();
176+
if (g_backPipe[1] >= 0) {
177+
// write a placeholder byte to trigger the read and wake ALooper_pollOnce
178+
char buf = 'x';
179+
write(g_backPipe[1], &buf, 1);
180+
}
181+
}
182+
}
183+
131184
// callbacks from MainActivity.java
132185
extern "C" JNIEXPORT void JNICALL Java_net_sourceforge_smallbasic_MainActivity_onActivityPaused
133186
(JNIEnv *env, jclass jclazz, jboolean paused) {
@@ -234,6 +287,7 @@ Runtime::Runtime(android_app *app) :
234287
_looper = ALooper_forThread();
235288
_sensorManager = ASensorManager_getInstance();
236289
memset(&_sensors, 0, sizeof(_sensors));
290+
setupBackWakePipe(_looper);
237291
}
238292

239293
Runtime::~Runtime() {
@@ -1075,4 +1129,3 @@ void osd_beep(void) {
10751129
osd_sound(1000, 30, 100, 0);
10761130
osd_sound(500, 30, 100, 0);
10771131
}
1078-

src/ui/image_codec.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ ImageCodec::~ImageCodec() {
5757
_pixels = nullptr;
5858
}
5959

60-
const char *ImageCodec::getLastError(void) {
60+
const char *ImageCodec::getLastError() {
6161
return g_last_error;
6262
}
6363

0 commit comments

Comments
 (0)