immersiveSticky and display.screenOriginX

Dave, have you experimented with this issue in native code? I haven’t yet myself, but I may take a shot at it.

I suppose it’s tempting, but no.  Despite my frustrated/accusatory “tone” at times, I don’t actually think I’m any “smarter” than the Corona engineers (fe Joshua) who’ve attempted to resolve the problem.  So I trust that they know what they’re doing, so my efforts would just be a duplication/waste of time.  In fact, even if I did manage to find a work-around, there’d be no guarantee that it could be “patched” into the rest of Corona’s “framework” as is, so Corona would still have to do all “same” work.

The best I could hope for is to “prove” that a workaround exists – but as I’ve already elaborated, the app store is already chock full of such “proof”.

For example, I find this an interesting case:  prior to v5, Unity itself did not support immersive, so their community took up the task.  I just downloaded a precompiled demo from (what appears to be) the most respected such plugin:

https://play.google.com/store/apps/details?id=com.ruudlenders.immersivemode

It works flawlessly re the no black bar on resume. (on my same ‘problem device’ that so faithfully reproduces it)  So that plugin author must have figured out a workaround that at least worked for Unity 4.

p.s. Ruud Lenders (author of that Unity 4 immersive plugin) just shared his source w me, which would be a jump-start for such experimenting:  comment out bit by bit til problem resurfaces, put back a bit to verify it resolves, then say “aha!”  :)  but to what end?  i’d need enterprise (and don’t want it) to actually benefit from that knowledge.

Dave… perhaps you could share the source (with Ruud’s permission) so that maybe Corona’s engineers (and even some of us engineers who don’t work for Corona) could identify a solution.

yep, was just awaiting his reply, now have his permission.

attachments seem to be disallowed, got: (You can upload up to  Uploading is not allowed  of files (Max. single file size:  230MB ))

i guess it’s small enough for a code block, his ActivityLIstener.java:

package com.ruudlenders.immersivemode; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Activity; import android.app.Application.ActivityLifecycleCallbacks; import android.os.Build; import android.os.Bundle; import android.view.ActionMode; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.Window; import android.view.ActionMode.Callback; import android.view.WindowManager.LayoutParams; import android.view.accessibility.AccessibilityEvent; @TargetApi(19) public class ActivityListener implements ActivityLifecycleCallbacks, Window.Callback, View.OnSystemUiVisibilityChangeListener { private static final String TAG = "ActivityListener"; private Activity activity; private Window window; private Window.Callback callback; private View decorView; private int visibility; public Activity getActivity() { return activity; } public ActivityListener(Activity activity) { Log.vBegin(TAG, "ImmersiveListener"); this.activity = activity; if (activity != null) { activity.getApplication().registerActivityLifecycleCallbacks(this); window = activity.getWindow(); if (window != null) { callback = window.getCallback(); window.setCallback(this); decorView = window.getDecorView(); if (decorView != null) { if (Build.VERSION.SDK\_INT \>= 16) { visibility = decorView.getSystemUiVisibility() & (View.SYSTEM\_UI\_FLAG\_LOW\_PROFILE | View.SYSTEM\_UI\_FLAG\_FULLSCREEN | View.SYSTEM\_UI\_FLAG\_LAYOUT\_STABLE | View.SYSTEM\_UI\_FLAG\_LAYOUT\_FULLSCREEN); } final ActivityListener listener = this; activity.runOnUiThread(new Runnable() { @Override public void run() { decorView .setOnSystemUiVisibilityChangeListener(listener); apply(); } }); } } } Log.vEnd(TAG, "ctor"); } public void close() { Log.vBegin(TAG, "close()"); if (activity != null) { activity.getApplication() .unregisterActivityLifecycleCallbacks(this); if (window != null) { window.setCallback(callback); if (decorView != null) { decorView.post(new Runnable() { @Override public void run() { decorView .setOnSystemUiVisibilityChangeListener(null); apply(); decorView.setSystemUiVisibility(visibility); } }); } } } Log.vEnd(TAG, "void"); } public void apply() { Log.vBegin(TAG, "apply()"); if (decorView != null) { int visibility = this.visibility; if (Build.VERSION.SDK\_INT \>= 14) { if (ImmersiveMode.isLowProfile()) { visibility |= View.SYSTEM\_UI\_FLAG\_LOW\_PROFILE; } if (Build.VERSION.SDK\_INT \>= 16) { visibility |= View.SYSTEM\_UI\_FLAG\_LAYOUT\_STABLE; if (Build.VERSION.SDK\_INT \>= 19) { visibility |= View.SYSTEM\_UI\_FLAG\_HIDE\_NAVIGATION | View.SYSTEM\_UI\_FLAG\_LAYOUT\_HIDE\_NAVIGATION | View.SYSTEM\_UI\_FLAG\_IMMERSIVE\_STICKY; } } } decorView.setSystemUiVisibility(visibility); } Log.vEnd(TAG, "void"); } @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { } @Override public void onActivityStarted(Activity activity) { } @Override public void onActivityResumed(Activity activity) { apply(); } @Override public void onActivityPaused(Activity activity) { } @Override public void onActivityStopped(Activity activity) { } @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) { } @Override public void onActivityDestroyed(Activity activity) { } @Override public boolean dispatchGenericMotionEvent(MotionEvent event) { return callback != null && callback.dispatchGenericMotionEvent(event); } @Override public boolean dispatchKeyEvent(KeyEvent event) { return callback != null && callback.dispatchKeyEvent(event); } @Override public boolean dispatchKeyShortcutEvent(KeyEvent event) { return callback != null && callback.dispatchKeyShortcutEvent(event); } @Override public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { return callback != null && callback.dispatchPopulateAccessibilityEvent(event); } @Override public boolean dispatchTouchEvent(MotionEvent event) { return callback != null && callback.dispatchTouchEvent(event); } @Override public boolean dispatchTrackballEvent(MotionEvent event) { return callback != null && callback.dispatchTrackballEvent(event); } @Override public void onActionModeFinished(ActionMode mode) { callback.onActionModeFinished(mode); } @Override public void onActionModeStarted(ActionMode mode) { callback.onActionModeStarted(mode); } @Override public void onAttachedToWindow() { callback.onAttachedToWindow(); } @Override public void onContentChanged() { callback.onContentChanged(); } @Override public boolean onCreatePanelMenu(int featureId, Menu menu) { return callback != null && callback.onCreatePanelMenu(featureId, menu); } @Override public View onCreatePanelView(int featureId) { return callback != null ? callback.onCreatePanelView(featureId) : null; } @Override @SuppressLint("MissingSuperCall") public void onDetachedFromWindow() { if (callback != null) { callback.onDetachedFromWindow(); } } @Override public boolean onMenuItemSelected(int featureId, MenuItem item) { return callback != null && callback.onMenuItemSelected(featureId, item); } @Override public boolean onMenuOpened(int featureId, Menu menu) { return callback != null && callback.onMenuOpened(featureId, menu); } @Override public void onPanelClosed(int featureId, Menu menu) { callback.onPanelClosed(featureId, menu); } @Override public boolean onPreparePanel(int featureId, View view, Menu menu) { return callback != null && callback.onPreparePanel(featureId, view, menu); } @Override public boolean onSearchRequested() { return callback != null && callback.onSearchRequested(); } @Override public void onWindowAttributesChanged(LayoutParams attrs) { callback.onWindowAttributesChanged(attrs); } @Override public void onWindowFocusChanged(boolean hasFocus) { Log.vBegin(TAG, "onWindowFocusChanged(" + hasFocus + ")"); callback.onWindowFocusChanged(hasFocus); if (hasFocus && decorView != null) { decorView.postDelayed(new Runnable() { @Override public void run() { apply(); } }, 1000); } Log.vEnd(TAG, "void"); } @Override public ActionMode onWindowStartingActionMode(Callback callback) { return this.callback != null ? this.callback .onWindowStartingActionMode(callback) : null; } @Override public void onSystemUiVisibilityChange(int visibility) { Log.vBegin(TAG, "onSystemUiVisibilityChange(" + visibility + ")"); apply(); Log.vEnd(TAG, "void"); } }

i only have the java side of the actual plugin, not the .cs scripts on the unity side, but all those do is “configure it” (fe ImmersiveMode.isLowProfile), all the important stuff happens here

I haven’t studied it “deeply”, but on a casual read it doesn’t appear to be doing anything not covered here:

http://developer.android.com/training/system-ui/immersive.html

he’s using the expected flags, has the three listeners (resume,focus,uivis), and start/stop of course…

anyone reading this thread have an enterprise project they can tinker with?

a tidbit as fyi:  orientation doesn’t seem to matter - i can replicate the problem with Corona in either portrait or landscape.  and i can (or rather have, already, in this thread) point to other non-Corona immersive apps in either orientation that don’t experience the problem.  so just as a variable you can probably rule out.

I’m interested in learning if this proves useful to the Corona engineers - it seems like it would.

Just to be clear to everyone on this thread, engineering is not going to dig into this issue in the next couple of months.  Our *top* priority is Android 6.0 and dynamic permission support… which is a huge job in itself.

Thanks for posting the above code, but we’re already effectively doing exactly that.  We suspect that the OS has an issue with how the activity window is configured.  Unfortunately, issues like this are never easy.

my own purpose here is just a hope to get it relisted as a still-open issue, regardless of timeline to address, as opposed to an “os defect/won’t fix” closed issue

Fyi, from https://code.google.com/p/android/issues/detail?id=170752

It seems this has been (at least partially) fixed in Android 6.0.1 (Cant reproduce bug.)

I will test later today hopefully.

Now Dave, before you blow a gasket, I know this isn’t a fix for most existing users, but it’s worth looking at and considering.

i’ll be glad to have it resolved in 6, tho old os’s tend to stay around a lot longer on android than ios.

and no gasket blown, cuz that still wouldn’t indicate a true flaw in the os.  perhaps it’s just semantics, but if it’s possible (and it is) to get it working on 4 and 5, then where is the “flaw”?  just because it’s possible to implement something in a way that doesn’t fully function correctly doesn’t mean there’s an os flaw.

more likely it’s just a really really finicky feature with poor documentation of some “missing step” in the implementation, and perhaps that missing step is no longer required by 6 to function properly.  (“missing step” as generic, since we don’t really have any idea of the true cause, it could even be some bizarre interrelationship with some other build setting that you’d never logically guess at)

given those semantics, then maybe 6 just “simplifies the implementation” rather than “fixes a flaw” == no gasket blown, and i’d still like to have it fixed/patched/worked-around/“whatever” on 4/5 when engineering time allows.

Just another “clue” for engineers, if/when they ever revisit this problem…

This “bad state” occurs following a resume from task list – whether or not you’ve ever set immersive previously.  That is, the conditions for subsequent failure are produced during resume, though the failure itself is not manifested until next attempt to set immersive.  I don’t see any reason to suspect the OS itself is in a “bad state”, rather that the app is in a “bad state”.

It’s a subtle cause/effect distinction, and I’ve tried to make the point before but not sure I succeeded, perhaps some code will help.  (apologies again, but uploads are still disabled)  Here’s a simple/minimal app with no “automatic” immersive code, just some manual buttons, there will be two tests to run with it (below):

main.lua

display.setStatusBar(display.HiddenStatusBar) local background = display.newRect(display.contentCenterX, display.contentCenterY, 5000, 5000) background:setFillColor(0,0,1) local widget = require("widget") widget.setTheme("widget\_theme\_android\_holo\_light") local uivisDefaultButton = widget.newButton({ left = 100, top = 100, label = "default", onPress = function() native.setProperty("androidSystemUiVisibility","default") end }) local uivisImmersiveStickyButton = widget.newButton({ left = 100, top = 200, label = "immersiveSticky", onPress = function() native.setProperty("androidSystemUiVisibility","immersiveSticky") end })

config.lua

application = { content = { -- don't need any of this, use unscaled device resolution }, }

build.settings

settings = { orientation = { default = "portrait", supported = {"portrait"} } }

Prep:  find a device that reliably reproduces problem, build & install app.

The first “success” test:  launch the app fresh (not previously running), press buttons to set/unset immersive, all works as expected.  done, kill the task/force quit.

The second “failure” test:  launch the app fresh (not previously running), press the circle home button on nav bar to suspend, press the square task list button and select this app to resume.  App is right now in the “bad state” although it may not be obvious because it hasn’t yet been manifested by attempting immersive, _and even though we’ve not yet used immersive at all _, but any subsequent attempts to use immersive during this activation will fail with black bar - so now press the immersive button and witness.  Now power off, power on, resuming app.  The “bad state” is no longer present, and again immersive will work just fine for the duration of this activation.

So it’s this resume from task list itself that somehow appears to set things up for subsequent failure.  Using immersive at any other time, via any other method of launching or resuming, works just fine.  But some “condition” of the app must be different following a resume from task list.

The subtle distinction is:  immersive seems to be the victim of this “bad state” rather than the cause of it.

The cause seems to be some difference in the app following a resume from task list.  Resume from any other method (power cycle, icon) and the bad state is again cleared and immersive works properly.

The reason for trying to highlight this subtlety is,… maybe there’s nothing wrong at all with the way immersive itself is implemented.  Maybe it’d be a total waste of engineering time to reconsider that specific code.  Because maybe there’s something wrong “elsewhere”, say in the resume logic somehow, that perhaps leaves the window/view/layout/glsurface/other in this “bad state” - a state whose “bad-ness” lies dormant until manifested by subsequent immersive attempts.  So maybe that’s where engineering time should be spent?

Hi, any progress on this issue with immersive mode causing static black navigation toolbar when returning from Task Switcher menu? Many non-Corona apps seem to do some workaround for this and work just fine.

We have a portrait-only app that is suffering from the same issue, but it feels like that we would only need to tell the app to reset the orientation on the “applicationResume” event, but Corona SDK does not support manual control for the orientation (e.g. something like setRequestedOrientation function?).

Nexus 7 tablet (Android 6.0) allows changing between portrait and portraitUpSideDown modes and that fixes the problem. Additionally, based on some googling, resetting the orientation when resuming the app has helped others too with the issue.

Ok, after many edits I know when this is happening.

If you use:

local function onSystemEvent( event ) if event.type == "applicationResume" then native.setProperty( "androidSystemUiVisibility", "immersiveSticky" ) end end Runtime:addEventListener( "system", onSystemEvent )

It will work fine, except when you resume the application from the Task Menu while the phone is in landscape view (i’ve tested this only in landscape).

This is why when you resume your application from your app logo (from the phone menu or the “desktop”) the black bar isnt there, the phone is currently in portrait mode (original mode for the phone) and it changes to landscape for your application. In that case, the black bar is not showing.

This also works for the Task Menu when you are, for example, using chrome in portrait mode and you press the “square button” and then enter your application. The black bar is not showing there.

So, even if the code above works, in those scenarios where the user is playing another game in landscape mode, or when he is inside your application and presses the square button to check the task menu but goes back to your game, the black bar is there screwing up your application.

Note1: I’m using a Moto G (2013) with Android 5.1

Note2: We already know that sleep mode when playing your application and then resuming works fine, there is no black bar. Now, even if the black bar showed, if the application goes to sleep mode and you resume it, the bugged bar is done.

Conclusion: There is no oficial fix, but we can probably find a workaround. The idea is to reset the position somehow or trick the application with sleep mode or something like that (read note2, its important)

If you have any question please let me know, it’s late and I might not be explaining it as good as I can.

By the way, showing interstitial ads (at least adMob) triggers the soft keys, even if it has an “X” button, you can dismiss it either the back button or “X” button. If you use the function in main.lua as a global, when the ads finish, the applicationResume system event triggers and sends it back to immersiveSticky.

Hi, any progress on this issue with immersive mode causing static black navigation toolbar when returning from Task Switcher menu? Many non-Corona apps seem to do some workaround for this and work just fine.

We have a portrait-only app that is suffering from the same issue, but it feels like that we would only need to tell the app to reset the orientation on the “applicationResume” event, but Corona SDK does not support manual control for the orientation (e.g. something like setRequestedOrientation function?).

Nexus 7 tablet (Android 6.0) allows changing between portrait and portraitUpSideDown modes and that fixes the problem. Additionally, based on some googling, resetting the orientation when resuming the app has helped others too with the issue.