We are currently migrating Bugzilla to GitHub issues.
Any changes made to the bug tracker now will be lost, so please do not post new bugs or make changes to them.
When we're done, all bug URLs will redirect to their equivalent location on the new bug tracker.

Bug 3445 - Improved Android patch to disable system bars
Summary: Improved Android patch to disable system bars
Status: ASSIGNED
Alias: None
Product: SDL
Classification: Unclassified
Component: video (show other bugs)
Version: HG 2.1
Hardware: x86_64 Windows 10
: P2 normal
Assignee: Sam Lantinga
QA Contact: Sam Lantinga
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2016-10-04 18:55 UTC by Daniel Sobe
Modified: 2017-11-13 09:47 UTC (History)
3 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Daniel Sobe 2016-10-04 18:55:41 UTC
Hi,

some time ago somebody posted a patch to dim the system bars on Android. Nevertheless, I was using the patch successfully for a while.

Now that Android supports a "sticky immersive mode", I have improved the patch to use this mode if available.

Please ignore the additional WinRT code in SDL_hints.h. Also, the import of the AbsoluteLayout is not necessary anymore (got fixed meanwhile).

Regards,

Daniel

-------------------------------------------------------------------------------------------------------------


--- SDL_snapshot/android-project/AndroidManifest.xml    2016-10-02 09:27:31.000000000 +0200
+++ SDL_patches/android-project/AndroidManifest.xml    2016-10-02 13:29:01.203126644 +0200
@@ -9,12 +9,13 @@
       android:installLocation="auto">
 
     <!-- Android 2.3.3 -->
-    <uses-sdk android:minSdkVersion="10" android:targetSdkVersion="12" />
+    <uses-sdk android:minSdkVersion="10" android:targetSdkVersion="19" />
 
     <!-- OpenGL ES 2.0 -->
     <uses-feature android:glEsVersion="0x00020000" />
 
-    <!-- Allow writing to external storage -->
+    <!-- Allow reading & writing to external storage -->
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 
     <!-- if you want to capture audio, uncomment this. -->

--- SDL_snapshot/android-project/src/org/libsdl/app/SDLActivity.java    2016-10-02 09:27:31.000000000 +0200
+++ SDL_patches/android-project/src/org/libsdl/app/SDLActivity.java    2016-10-02 14:51:36.873309659 +0200
@@ -17,6 +17,7 @@
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
+import android.widget.AbsoluteLayout;
 import android.widget.RelativeLayout;
 import android.widget.Button;
 import android.widget.LinearLayout;
@@ -36,6 +37,8 @@
 public class SDLActivity extends Activity {
     private static final String TAG = "SDL";
 
+    private static final String SDL_HINT_ANDROID_HIDE_SYSTEM_BARS = "SDL_ANDROID_HIDE_SYSTEM_BARS";
+
     // Keep track of the paused state
     public static boolean mIsPaused, mIsSurfaceReady, mHasFocus;
     public static boolean mExitCalledFromJava;
@@ -166,6 +169,13 @@
         // Set up the surface
         mSurface = new SDLSurface(getApplication());
 
+        nativeAddHintCallback(SDL_HINT_ANDROID_HIDE_SYSTEM_BARS, new SDLHintCallback() {
+            @Override
+            public void callback(String name, String oldValue, String newValue) {
+                updateSystemBarsStatus(newValue);
+            }
+        });
+       
         if(Build.VERSION.SDK_INT >= 12) {
             mJoystickHandler = new SDLJoystickHandler_API12();
         }
@@ -213,6 +223,8 @@
         }
 
         SDLActivity.handleResume();
+       
+        updateSystemBarsStatus(nativeGetHint(SDL_HINT_ANDROID_HIDE_SYSTEM_BARS));
     }
 
 
@@ -325,6 +337,30 @@
         mSingleton.finish();
     }
 
+    void updateSystemBarsStatus(String value) {
+        if ("1".equals(value)) {
+            runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    // first try immersive mode (sticky immersive)
+                    if (Build.VERSION.SDK_INT >= 19) {
+                        getWindow().getDecorView().setSystemUiVisibility(
+                                View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+                                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                                | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+                                | View.SYSTEM_UI_FLAG_FULLSCREEN
+                                | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+                    } else {
+                        // if not available, use at least low profile mode
+                        if (Build.VERSION.SDK_INT >= 14) {
+                            getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
+                        }
+                    }
+                }
+            });
+        }
+    }
 
     // Messages from the SDLMain thread
     static final int COMMAND_CHANGE_TITLE = 1;
@@ -437,6 +473,10 @@
                                                int is_accelerometer, int nbuttons,
                                                int naxes, int nhats, int nballs);
     public static native int nativeRemoveJoystick(int device_id);
+    interface SDLHintCallback {
+        void callback(String name, String oldValue, String newValue);
+    }
+    public static native void nativeAddHintCallback(String name, SDLHintCallback callback);
     public static native String nativeGetHint(String name);
 
     /**

--- SDL_snapshot/include/SDL_hints.h    2016-10-02 09:27:31.000000000 +0200
+++ SDL_patches/include/SDL_hints.h    2016-10-02 11:38:21.092649566 +0200
@@ -689,6 +689,30 @@
 #define SDL_HINT_BMP_SAVE_LEGACY_FORMAT "SDL_BMP_SAVE_LEGACY_FORMAT"
 
 /**
+ * \brief A hint to control whether the system shall remember the preferred fullscreen mode.
+ *
+ * This hint will work for WinRT only.
+ *
+ * The variable can be set to the following values:
+ *    "0"       - No action. System does not remember whether the app wants to run in fullscreen.
+ *    "1"       - Remember preferred app setting (fullscreen or windowed).
+ *
+ * The default is "0".
+ *
+ */
+#define SDL_HINT_WINRT_REMEMBER_WINDOW_FULLSCREEN_PREFERENCE "SDL_WINRT_REMEMBER_WINDOW_FULLSCREEN_PREFERENCE"
+
+/**
+ * \brief A hint to control whether an Android app shall try to dim resp. remove the system bars.
+ *
+ * This hint, when set to 1, will try to set "low profile mode" or "sticky immersive mode",
+ * depending on which Android API was found. If the API version is too low for either,
+ * nothing will happen.
+ *
+ */
+#define SDL_HINT_ANDROID_HIDE_SYSTEM_BARS "SDL_ANDROID_HIDE_SYSTEM_BARS"
+
+/**
  *  \brief  An enumeration of hint priorities
  */
 typedef enum

--- SDL_snapshot/src/core/android/SDL_android.c    2016-10-02 09:27:31.000000000 +0200
+++ SDL_patches/src/core/android/SDL_android.c    2016-10-02 11:49:15.985195517 +0200
@@ -421,6 +421,32 @@
     return result;
 }
 
+void Android_JNI_HintCallback(void *userdata, const char *name, const char *oldValue, const char *newValue) {
+    JNIEnv *env = Android_JNI_GetEnv();
+
+    jobject callback = (jobject)userdata;
+   jclass cls = (*env)->GetObjectClass(env, callback);
+    jmethodID method = (*env)->GetMethodID(env, cls, "callback", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
+
+    jstring javaName     = (*env)->NewStringUTF(env, name);
+    jstring javaOldValue = (*env)->NewStringUTF(env, oldValue);
+    jstring javaNewValue = (*env)->NewStringUTF(env, newValue);
+
+    (*env)->CallVoidMethod(env, callback, method, javaName, javaOldValue, javaNewValue);
+
+    (*env)->DeleteLocalRef(env, javaName);
+    (*env)->DeleteLocalRef(env, javaOldValue);
+    (*env)->DeleteLocalRef(env, javaNewValue);
+}
+
+void Java_org_libsdl_app_SDLActivity_nativeAddHintCallback(JNIEnv* env, jclass cls, jstring name, jobject callback) {
+    const char *utfname = (*env)->GetStringUTFChars(env, name, NULL);
+
+    SDL_AddHintCallback(utfname, Android_JNI_HintCallback, (*env)->NewGlobalRef(env, callback));
+
+    (*env)->ReleaseStringUTFChars(env, name, utfname);   
+}
+
 /*******************************************************************************
              Functions called by SDL into Java
 *******************************************************************************/
Comment 1 Alex Szpakowski 2016-10-04 23:39:28 UTC
Should immersive mode automatically activate when the SDL window is fullscreen and/or borderless? That's effectively what happens on iOS.
Comment 2 Daniel Sobe 2016-10-05 07:16:40 UTC
I'm not sure everybody would like the navigation bar at the bottom to disappear by default, because unlike iOS there's a "back" button which the app might want to make use of. 

However, the back button is typically used to cycle between activities, thus the main usage should be to leave the SDL app. So maybe most of the developers would want it to be hidden by default in their app.

Maybe the hint should be "the other way 'round", making immersive mode default, unless the developer explicitly requests the bar to be permanently visible?

I cannot say anything about the impact of Android 7's new features where windows are not always fullscreen. Maybe somebody else already has experience with this?
Comment 3 Sylvain 2017-11-03 08:47:18 UTC
there was this commit https://hg.libsdl.org/SDL/rev/cf6fe791a6ee
(and bug 3937)
Comment 4 Sam Lantinga 2017-11-05 05:18:35 UTC
This is a great idea (and the hint listener is fantastic!)

I'm going to make this dynamic based on the application state of the window and play with this a bit.

Thanks!
Comment 5 Daniel Sobe 2017-11-05 08:56:38 UTC
I'm wondering whether the commit mentioned in comment #3 already does the same like this patch. If this is the case, the patch here can be disregarded.

The difference I see is that the patch here checks for the API versions (14 and 19) before taking appropriate actions. The committed version does this without any checks, but in case the additional flags are simply being ignored on older devices with older APIs, this would work as well.

Last thing is the hint, which is used in this patch. The committed version doesn't use any, e.g. makes the behaviour default. This can be seen as reasonable choice for a gaming library.

I guess both patches require a target API >= 19, so this is not a difference. Minimum API of 10 probably still works, although the number of devices will be relatively small nowadays.
Comment 6 Diego 2017-11-09 00:59:42 UTC
With this new fullscreen mode the app goes into fullscreen when started but after leaving the app, such as with the home or overview button, and then returning to the app causes the navigation bar to be show and not return to fullscreen.
Comment 7 Daniel Sobe 2017-11-13 09:47:25 UTC
Yes, the patch here restores the "sticky immersive mode" (resp low profile mode on lower API versions) on every restore of the app, not just on initial launch.

No matter whether this patch is used or the currently merged approach - both require target API 19 to compile. This patch needs it on the Java side, the merged patch on the native side.