From 11006075fc7921a61bf43174b8ea965494c744ca Mon Sep 17 00:00:00 2001 From: "jose.pereda" Date: Wed, 8 Apr 2026 13:02:38 +0200 Subject: [PATCH 1/3] Add setScreenAlwasyOn API to DisplayService --- gradle/native-build.gradle | 2 +- .../attach/display/DisplayService.java | 15 ++++++++++- .../display/impl/AndroidDisplayService.java | 27 ++++++++++++++++++- .../display/impl/DesktopDisplayService.java | 7 ++++- .../display/impl/IOSDisplayService.java | 25 ++++++++++++++--- .../src/main/native/android/c/display.c | 12 ++++++++- .../android/dalvik/DalvikDisplayService.java | 22 ++++++++++++++- modules/display/src/main/native/ios/Display.m | 13 ++++++++- 8 files changed, 113 insertions(+), 10 deletions(-) diff --git a/gradle/native-build.gradle b/gradle/native-build.gradle index acc45a1d..7692c94b 100644 --- a/gradle/native-build.gradle +++ b/gradle/native-build.gradle @@ -151,7 +151,7 @@ ext.aarBuild = { buildDir, projectDir, name, os -> def javaSources = [sourcesUtilDir.listFiles()].flatten() def javac = "$JAVAHOME/bin/javac" def javacArgs = ["-d", file("$tempDir/classes").getAbsolutePath(), - "-source", "1.7", "-target", "1.7", + "-source", "1.8", "-target", "1.8", "-bootclasspath", androidJar, javaSources].flatten() exec { diff --git a/modules/display/src/main/java/com/gluonhq/attach/display/DisplayService.java b/modules/display/src/main/java/com/gluonhq/attach/display/DisplayService.java index 6fd77210..c4e5740f 100644 --- a/modules/display/src/main/java/com/gluonhq/attach/display/DisplayService.java +++ b/modules/display/src/main/java/com/gluonhq/attach/display/DisplayService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Gluon + * Copyright (c) 2016, 2026, Gluon * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -174,4 +174,17 @@ static Optional create() { */ ReadOnlyObjectProperty systemBarsInsetsProperty(); + /** + * Sets whether the device screen should remain always on, preventing the + * device from going to sleep or dimming the display. + * + *

The default value is {@code false}. When the application is closed + * or paused, the screen will revert to its default behavior.

+ * + * @param alwaysOn if {@code true}, the screen will stay on; if {@code false}, + * the default screen timeout behavior is restored + * @since 4.0.25 + */ + void setScreenAlwaysOn(boolean alwaysOn); + } diff --git a/modules/display/src/main/java/com/gluonhq/attach/display/impl/AndroidDisplayService.java b/modules/display/src/main/java/com/gluonhq/attach/display/impl/AndroidDisplayService.java index 3f4b8ba2..9d56c3a0 100644 --- a/modules/display/src/main/java/com/gluonhq/attach/display/impl/AndroidDisplayService.java +++ b/modules/display/src/main/java/com/gluonhq/attach/display/impl/AndroidDisplayService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Gluon + * Copyright (c) 2016, 2026, Gluon * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,6 +28,8 @@ package com.gluonhq.attach.display.impl; import com.gluonhq.attach.display.DisplayService; +import com.gluonhq.attach.lifecycle.LifecycleEvent; +import com.gluonhq.attach.lifecycle.LifecycleService; import com.gluonhq.attach.util.Util; import javafx.application.Platform; import javafx.beans.property.ReadOnlyObjectProperty; @@ -48,11 +50,27 @@ public class AndroidDisplayService implements DisplayService { private static final boolean debug = Util.DEBUG; + private boolean screenAlwaysOn; + static { System.loadLibrary("display"); } public AndroidDisplayService() { + LifecycleService.create().ifPresent(l -> + l.addListener(LifecycleEvent.PAUSE, () -> { + if (screenAlwaysOn) { + setScreenAlwaysOnNative(false); + } + }) + ); + LifecycleService.create().ifPresent(l -> + l.addListener(LifecycleEvent.RESUME, () -> { + if (screenAlwaysOn) { + setScreenAlwaysOnNative(true); + } + }) + ); } @Override @@ -127,10 +145,17 @@ public ReadOnlyObjectProperty systemBarsInsetsProperty() { return insetsProperty.getReadOnlyProperty(); } + @Override + public void setScreenAlwaysOn(boolean alwaysOn) { + this.screenAlwaysOn = alwaysOn; + setScreenAlwaysOnNative(alwaysOn); + } + // native private native static boolean isPhoneFactor(); private native static double[] screenSize(); private native static boolean screenRound(); + private native static void setScreenAlwaysOnNative(boolean alwaysOn); // callback private static void notifyInsets(double top, double right, double bottom, double left) { diff --git a/modules/display/src/main/java/com/gluonhq/attach/display/impl/DesktopDisplayService.java b/modules/display/src/main/java/com/gluonhq/attach/display/impl/DesktopDisplayService.java index e43a9b1e..bef65536 100644 --- a/modules/display/src/main/java/com/gluonhq/attach/display/impl/DesktopDisplayService.java +++ b/modules/display/src/main/java/com/gluonhq/attach/display/impl/DesktopDisplayService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Gluon + * Copyright (c) 2016, 2026, Gluon * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -109,6 +109,11 @@ public ReadOnlyObjectProperty systemBarsInsetsProperty() { return insetsProperty.getReadOnlyProperty(); } + @Override + public void setScreenAlwaysOn(boolean alwaysOn) { + // no-op + } + private static void log(String message, Throwable cause) { LOG.log(Level.FINE, message); if (LOG.isLoggable(Level.FINE)) { diff --git a/modules/display/src/main/java/com/gluonhq/attach/display/impl/IOSDisplayService.java b/modules/display/src/main/java/com/gluonhq/attach/display/impl/IOSDisplayService.java index 99cec6fd..11dfb8e8 100644 --- a/modules/display/src/main/java/com/gluonhq/attach/display/impl/IOSDisplayService.java +++ b/modules/display/src/main/java/com/gluonhq/attach/display/impl/IOSDisplayService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Gluon + * Copyright (c) 2016, 2026, Gluon * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -47,11 +47,23 @@ public class IOSDisplayService implements DisplayService { private static final ReadOnlyObjectWrapper insetsProperty = new ReadOnlyObjectWrapper<>(); + private boolean screenAlwaysOn; + public IOSDisplayService() { notch = new ReadOnlyObjectWrapper<>(Notch.UNKNOWN); LifecycleService.create().ifPresent(l -> { - l.addListener(LifecycleEvent.PAUSE, IOSDisplayService::stopObserver); - l.addListener(LifecycleEvent.RESUME, IOSDisplayService::startObserver); + l.addListener(LifecycleEvent.PAUSE, () -> { + stopObserver(); + if (screenAlwaysOn) { + setScreenAlwaysOnNative(false); + } + }); + l.addListener(LifecycleEvent.RESUME, () -> { + startObserver(); + if (screenAlwaysOn) { + setScreenAlwaysOnNative(true); + } + }); }); startObserver(); } @@ -108,6 +120,12 @@ public ReadOnlyObjectProperty systemBarsInsetsProperty() { return insetsProperty.getReadOnlyProperty(); } + @Override + public void setScreenAlwaysOn(boolean alwaysOn) { + this.screenAlwaysOn = alwaysOn; + setScreenAlwaysOnNative(alwaysOn); + } + // native private static native void initDisplay(); @@ -119,6 +137,7 @@ public ReadOnlyObjectProperty systemBarsInsetsProperty() { private static native void startObserver(); private static native void stopObserver(); + private static native void setScreenAlwaysOnNative(boolean alwaysOn); // callback private static void notifyDisplay(String o) { diff --git a/modules/display/src/main/native/android/c/display.c b/modules/display/src/main/native/android/c/display.c index 80883e63..d20ef98a 100644 --- a/modules/display/src/main/native/android/c/display.c +++ b/modules/display/src/main/native/android/c/display.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Gluon + * Copyright (c) 2020, 2026, Gluon * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -33,6 +33,7 @@ static jmethodID jDisplayServiceWidthMethod; static jmethodID jDisplayServiceHeightMethod; static jmethodID jDisplayServiceFactorMethod; static jmethodID jDisplayServiceRoundMethod; +static jmethodID jDisplayServiceScreenAlwaysOnMethod; // Graal handles static jclass jGraalDisplayClass; @@ -46,6 +47,7 @@ static void initializeDisplayDalvikHandles() { jDisplayServiceHeightMethod = (*dalvikEnv)->GetMethodID(dalvikEnv, jDisplayServiceClass, "screenHeight", "()D"); jDisplayServiceFactorMethod = (*dalvikEnv)->GetMethodID(dalvikEnv, jDisplayServiceClass, "isPhoneFactor", "()Z"); jDisplayServiceRoundMethod = (*dalvikEnv)->GetMethodID(dalvikEnv, jDisplayServiceClass, "isScreenRound", "()Z"); + jDisplayServiceScreenAlwaysOnMethod = (*dalvikEnv)->GetMethodID(dalvikEnv, jDisplayServiceClass, "setScreenAlwaysOn", "(Z)V"); jobject jActivity = substrateGetActivity(); jobject jtmpobj = (*dalvikEnv)->NewObject(dalvikEnv, jDisplayServiceClass, jDisplayServiceInitMethod, jActivity); @@ -120,6 +122,14 @@ JNIEXPORT jboolean JNICALL Java_com_gluonhq_attach_display_impl_AndroidDisplaySe return answer; } +JNIEXPORT void JNICALL Java_com_gluonhq_attach_display_impl_AndroidDisplayService_setScreenAlwaysOnNative +(JNIEnv *env, jclass jClass, jboolean alwaysOn) +{ + ATTACH_DALVIK(); + (*dalvikEnv)->CallVoidMethod(dalvikEnv, jDalvikDisplayService, jDisplayServiceScreenAlwaysOnMethod, alwaysOn); + DETACH_DALVIK(); +} + /////////////////////////// // From Dalvik to native // /////////////////////////// diff --git a/modules/display/src/main/native/android/dalvik/DalvikDisplayService.java b/modules/display/src/main/native/android/dalvik/DalvikDisplayService.java index 2bf5c985..a0c2208b 100644 --- a/modules/display/src/main/native/android/dalvik/DalvikDisplayService.java +++ b/modules/display/src/main/native/android/dalvik/DalvikDisplayService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Gluon + * Copyright (c) 2020, 2026, Gluon * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -99,5 +99,25 @@ private void notifyInsets(WindowInsetsCompat insets, double density) { systemBars.bottom / density, systemBars.left / density); } + private void setScreenAlwaysOn(final boolean alwaysOn) { + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + Window window = activity.getWindow(); + if (alwaysOn) { + if (Util.isDebug()) { + Log.v(TAG, "Setting screen always on"); + } + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } else { + if (Util.isDebug()) { + Log.v(TAG, "Clearing screen always on"); + } + window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + } + }); + } + private native void notifyInsets(double top, double right, double bottom, double left); } \ No newline at end of file diff --git a/modules/display/src/main/native/ios/Display.m b/modules/display/src/main/native/ios/Display.m index a7195a07..2050f49b 100644 --- a/modules/display/src/main/native/ios/Display.m +++ b/modules/display/src/main/native/ios/Display.m @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Gluon + * Copyright (c) 2016, 2026, Gluon * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -146,6 +146,17 @@ return; } +JNIEXPORT void JNICALL Java_com_gluonhq_attach_display_impl_IOSDisplayService_setScreenAlwaysOnNative +(JNIEnv *env, jclass jClass, jboolean alwaysOn) +{ + dispatch_async(dispatch_get_main_queue(), ^{ + if (debugAttach) { + AttachLog(@"Setting idleTimerDisabled to %s", alwaysOn ? "YES" : "NO"); + } + [UIApplication sharedApplication].idleTimerDisabled = alwaysOn ? YES : NO; + }); +} + void sendNotch() { NSString *notch = [_display getNotch]; if (debugAttach) { From 01292a325f78d29bee47a9c9725dd96ea7572cb4 Mon Sep 17 00:00:00 2001 From: "jose.pereda" Date: Wed, 8 Apr 2026 18:12:15 +0200 Subject: [PATCH 2/3] restore java version for Android --- gradle/native-build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/native-build.gradle b/gradle/native-build.gradle index 7692c94b..acc45a1d 100644 --- a/gradle/native-build.gradle +++ b/gradle/native-build.gradle @@ -151,7 +151,7 @@ ext.aarBuild = { buildDir, projectDir, name, os -> def javaSources = [sourcesUtilDir.listFiles()].flatten() def javac = "$JAVAHOME/bin/javac" def javacArgs = ["-d", file("$tempDir/classes").getAbsolutePath(), - "-source", "1.8", "-target", "1.8", + "-source", "1.7", "-target", "1.7", "-bootclasspath", androidJar, javaSources].flatten() exec { From 692617f83c404d92a246adca859edd5a9958d59f Mon Sep 17 00:00:00 2001 From: "jose.pereda" Date: Wed, 8 Apr 2026 18:21:29 +0200 Subject: [PATCH 3/3] address feedback --- .../attach/display/impl/AndroidDisplayService.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/modules/display/src/main/java/com/gluonhq/attach/display/impl/AndroidDisplayService.java b/modules/display/src/main/java/com/gluonhq/attach/display/impl/AndroidDisplayService.java index 9d56c3a0..d20fc296 100644 --- a/modules/display/src/main/java/com/gluonhq/attach/display/impl/AndroidDisplayService.java +++ b/modules/display/src/main/java/com/gluonhq/attach/display/impl/AndroidDisplayService.java @@ -57,20 +57,18 @@ public class AndroidDisplayService implements DisplayService { } public AndroidDisplayService() { - LifecycleService.create().ifPresent(l -> + LifecycleService.create().ifPresent(l -> { l.addListener(LifecycleEvent.PAUSE, () -> { if (screenAlwaysOn) { setScreenAlwaysOnNative(false); } - }) - ); - LifecycleService.create().ifPresent(l -> + }); l.addListener(LifecycleEvent.RESUME, () -> { if (screenAlwaysOn) { setScreenAlwaysOnNative(true); } - }) - ); + }); + }); } @Override