Skip to content

Commit

Permalink
For mozilla-mobile#11527 - Use deprecated APIs for immersive mode sin…
Browse files Browse the repository at this point in the history
…ce they work better

Opened https://issuetracker.google.com/u/2/issues/214012501 for what seems a
bug with the new insets based APIs.
In the meantime we are using the old OnSystemUiVisibilityChangeListener to
know when to restore immersive mode.

Removed the onWindowFocusChangeListener extension property since by having to
offer it through a getter the current implementation would always leak the old
one.
Fenix wasn't using it when this APIs allowed Fenix to pass such a listener and
there was no issue observed so there should be no observable negative impact.
  • Loading branch information
Mugurell committed Jan 13, 2022
1 parent 4891abb commit 4eaf4d5
Show file tree
Hide file tree
Showing 3 changed files with 13 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@
package mozilla.components.support.ktx.android.view

import android.app.Activity
import android.os.Build
import android.view.View
import android.view.ViewTreeObserver
import android.view.WindowInsets.Type.statusBars
import android.view.WindowManager
import androidx.annotation.VisibleForTesting
import androidx.core.view.WindowInsetsCompat
Expand All @@ -17,11 +14,7 @@ import mozilla.components.support.base.log.logger.Logger

/**
* Attempts to enter immersive mode - fullscreen with the status bar and navigation buttons hidden.
* This will automatically register and use an
* - an inset listener: [View.OnApplyWindowInsetsListener] on API 30+ or
* [View.OnSystemUiVisibilityChangeListener] for below APIs
* - a focus listener: [ViewTreeObserver.OnWindowFocusChangeListener]
*
* This will automatically register and use a [View.OnSystemUiVisibilityChangeListener]
* to restore immersive mode if interactions with various other widgets like the keyboard or dialogs
* got the activity out of immersive mode without [exitImmersiveModeIfNeeded] being called.
*/
Expand All @@ -47,21 +40,12 @@ internal fun Activity.setAsImmersive() {
*/
@VisibleForTesting
internal fun Activity.enableImmersiveModeRestore() {
window.decorView.viewTreeObserver?.addOnWindowFocusChangeListener(onWindowFocusChangeListener)

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.decorView.setOnApplyWindowInsetsListener { _, insets ->
if (insets.isVisible(statusBars())) {
setAsImmersive()
}
insets
}
} else {
@Suppress("DEPRECATION") // insets.isVisible(int) is available only starting with API 30
window.decorView.setOnSystemUiVisibilityChangeListener { newFlags ->
if (newFlags and View.SYSTEM_UI_FLAG_FULLSCREEN == 0) {
setAsImmersive()
}
// Still using the now deprecated approach until a resolution of https://issuetracker.google.com/issues/214012501
// Previous approach based on which the bug was discovered is available in history.
@Suppress("DEPRECATION")
window.decorView.setOnSystemUiVisibilityChangeListener { newFlags ->
if (newFlags and View.SYSTEM_UI_FLAG_FULLSCREEN == 0) {
setAsImmersive()
}
}
}
Expand All @@ -75,32 +59,17 @@ fun Activity.exitImmersiveModeIfNeeded() {
return
}

window.decorView.viewTreeObserver?.removeOnWindowFocusChangeListener(onWindowFocusChangeListener)

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.decorView.setOnApplyWindowInsetsListener(null)
} else {
@Suppress("DEPRECATION")
window.decorView.setOnSystemUiVisibilityChangeListener(null)
}
// Still using the now deprecated approach until a resolution of https://issuetracker.google.com/issues/214012501
// Previous approach based on which the bug was discovered is available in history.
@Suppress("DEPRECATION")
window.decorView.setOnSystemUiVisibilityChangeListener(null)

window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
window.getWindowInsetsController().apply {
show(WindowInsetsCompat.Type.systemBars())
}
}

/**
* OnWindowFocusChangeListener used to ensure immersive mode is not broken by other views interactions.
*/
@VisibleForTesting
internal val Activity.onWindowFocusChangeListener: ViewTreeObserver.OnWindowFocusChangeListener
get() = ViewTreeObserver.OnWindowFocusChangeListener { hasFocus ->
if (hasFocus) {
setAsImmersive()
}
}

/**
* Calls [Activity.reportFullyDrawn] while also preventing crashes under some circumstances.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ class ActivityTest {
verify(decorView).systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN
verify(decorView).systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
// verify that the immersive mode restoration is set as expected
verify(window.decorView.viewTreeObserver).addOnWindowFocusChangeListener(any())
verify(window.decorView).setOnSystemUiVisibilityChangeListener(any())
}

Expand All @@ -65,15 +64,13 @@ class ActivityTest {
verify(window).addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
verify(decorView).systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN
verify(decorView).systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
verify(window.decorView.viewTreeObserver, never()).addOnWindowFocusChangeListener(any())
verify(window.decorView, never()).setOnSystemUiVisibilityChangeListener(any())
}

@Test
fun `check enableImmersiveModeRestore sets focus and insets listeners`() {
activity.enableImmersiveModeRestore()

verify(window.decorView.viewTreeObserver).addOnWindowFocusChangeListener(any())
verify(window.decorView).setOnSystemUiVisibilityChangeListener(any())
}

Expand All @@ -97,26 +94,6 @@ class ActivityTest {
verify(decorView).systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
}

@Test
fun `check enableImmersiveModeRestore set focus listener has the correct behavior`() {
val focusListenerCaptor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()

activity.enableImmersiveModeRestore()
verify(window.decorView.viewTreeObserver).addOnWindowFocusChangeListener(focusListenerCaptor.capture())

focusListenerCaptor.value.onWindowFocusChanged(false)
// If the activity is not focused restoration is needed.
// Cannot test if "setAsImmersive()" was called it being an extension function but we can check the effect of that call.
verify(window, never()).addFlags(anyInt())
verify(decorView, never()).systemUiVisibility = anyInt()
verify(decorView, never()).systemUiVisibility = anyInt()

focusListenerCaptor.value.onWindowFocusChanged(true)
verify(window).addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
verify(decorView).systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN
verify(decorView).systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
}

@Test
fun `check exitImmersiveModeIfNeeded sets the correct flags`() {
val attributes = mock(WindowManager.LayoutParams::class.java)
Expand All @@ -129,17 +106,14 @@ class ActivityTest {
verify(decorView, never()).systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN.inv()
verify(decorView, never()).systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION.inv()
verify(decorView, never()).setOnSystemUiVisibilityChangeListener(null)
verify(window.decorView.viewTreeObserver, never()).removeOnWindowFocusChangeListener(any())

attributes.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON

activity.exitImmersiveModeIfNeeded()

verify(window).clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
verify(decorView, times(2)).systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE
verify(window.decorView.viewTreeObserver).removeOnWindowFocusChangeListener(any())
verify(decorView).setOnSystemUiVisibilityChangeListener(null)
verify(window.decorView.viewTreeObserver).removeOnWindowFocusChangeListener(any())
}

@Test
Expand All @@ -151,12 +125,10 @@ class ActivityTest {
activity.exitImmersiveModeIfNeeded()

verify(decorView, never()).setOnSystemUiVisibilityChangeListener(null)
verify(window.decorView.viewTreeObserver, never()).removeOnWindowFocusChangeListener(any())

attributes.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
activity.exitImmersiveModeIfNeeded()

verify(decorView).setOnSystemUiVisibilityChangeListener(null)
verify(window.decorView.viewTreeObserver).removeOnWindowFocusChangeListener(any())
}
}
2 changes: 2 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ permalink: /changelog/
* [Gecko](https://github.com/mozilla-mobile/android-components/blob/main/buildSrc/src/main/java/Gecko.kt)
* [Configuration](https://github.com/mozilla-mobile/android-components/blob/main/.config.yml)

* **support-ktx**
* 🚒 Bug fixed [issue #11527](https://github.com/mozilla-mobile/android-components/issues/11527) - Using now deprecated APIs to fix immersive mode not being applied all the time. https://issuetracker.google.com/u/2/issues/214012501 can be followed for what seems a framework issue.

# 97.0.0
* [Commits](https://github.com/mozilla-mobile/android-components/compare/v96.0.0...v97.0.0)
Expand Down

0 comments on commit 4eaf4d5

Please sign in to comment.