From ccc8f118f5d8675353c20919c9c230ded41a1ffa Mon Sep 17 00:00:00 2001 From: proneon267 Date: Sun, 7 Jul 2024 09:37:12 -0700 Subject: [PATCH] Fixed Android backend --- android/src/toga_android/window.py | 74 ++++++++++++++++++----------- android/tests_backend/window.py | 16 +++---- testbed/tests/app/test_desktop.py | 8 +++- testbed/tests/window/test_window.py | 52 ++++++++++++++++++++ 4 files changed, 112 insertions(+), 38 deletions(-) diff --git a/android/src/toga_android/window.py b/android/src/toga_android/window.py index a116296ad6..2fbda0852c 100644 --- a/android/src/toga_android/window.py +++ b/android/src/toga_android/window.py @@ -145,7 +145,11 @@ def get_visible(self): ###################################################################### def get_window_state(self): - # Windows are always full screen + # window.state is called in _close(), which itself sometimes + # is called during early stages of app startup, during which + # the app attribute may not exist. In such cases, return NORMAL. + if getattr(self, "app", None) is None: + return WindowState.NORMAL decor_view = self.app.native.getWindow().getDecorView() system_ui_flags = decor_view.getSystemUiVisibility() if system_ui_flags & ( @@ -162,33 +166,49 @@ def get_window_state(self): def set_window_state(self, state): current_state = self.get_window_state() decor_view = self.app.native.getWindow().getDecorView() - # On Android Maximized state is same as the Normal state - if state in {WindowState.NORMAL, WindowState.MAXIMIZED}: - if current_state in { - WindowState.FULLSCREEN, - WindowState.PRESENTATION, - }: - decor_view.setSystemUiVisibility(0) - if current_state == WindowState.PRESENTATION: - # Marking this as no branch, since the testbed can't create a simple - # window, so we can't test the other branch. - if self._actionbar_shown_by_default: # pragma: no branch - self.app.native.getSupportActionBar().show() - self._is_presentation_mode = False - else: + if ( + current_state != WindowState.NORMAL + and state != WindowState.NORMAL + and (getattr(self, "_pending_window_state_transition", None) is None) + ): + # Set Window state to NORMAL before changing to other states as some + # states block changing window state without first exiting them or + # can even cause rendering glitches. + self._pending_window_state_transition = state self.set_window_state(WindowState.NORMAL) - if state in {WindowState.FULLSCREEN, WindowState.PRESENTATION}: - decor_view.setSystemUiVisibility( - decor_view.SYSTEM_UI_FLAG_FULLSCREEN - | decor_view.SYSTEM_UI_FLAG_HIDE_NAVIGATION - | decor_view.SYSTEM_UI_FLAG_IMMERSIVE - ) - if state == WindowState.PRESENTATION: - # Marking this as no branch, since the testbed can't create a simple - # window, so we can't test the other branch. - if self._actionbar_shown_by_default: # pragma: no branch - self.app.native.getSupportActionBar().hide() - self._is_presentation_mode = True + + elif state in {WindowState.FULLSCREEN, WindowState.PRESENTATION}: + decor_view.setSystemUiVisibility( + decor_view.SYSTEM_UI_FLAG_FULLSCREEN + | decor_view.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | decor_view.SYSTEM_UI_FLAG_IMMERSIVE + ) + if state == WindowState.PRESENTATION: + # Marking this as no branch, since the testbed can't create a simple + # window, so we can't test the other branch. + if self._actionbar_shown_by_default: # pragma: no branch + self.app.native.getSupportActionBar().hide() + self._is_presentation_mode = True + + else: + # On Android Maximized state is same as the Normal state + if state in {WindowState.NORMAL, WindowState.MAXIMIZED}: + if current_state in { + WindowState.FULLSCREEN, + WindowState.PRESENTATION, + }: + decor_view.setSystemUiVisibility(0) + if current_state == WindowState.PRESENTATION: + # Marking this as no branch, since the testbed can't create a simple + # window, so we can't test the other branch. + if self._actionbar_shown_by_default: # pragma: no branch + self.app.native.getSupportActionBar().show() + self._is_presentation_mode = False + + # Complete any pending window state transition. + if getattr(self, "_pending_window_state_transition", None) is not None: + self.set_window_state(self._pending_window_state_transition) + del self._pending_window_state_transition ###################################################################### # Window capabilities diff --git a/android/tests_backend/window.py b/android/tests_backend/window.py index 28a06b6752..294e3dc553 100644 --- a/android/tests_backend/window.py +++ b/android/tests_backend/window.py @@ -23,18 +23,14 @@ def content_size(self): ) def get_window_state(self): - # Windows are always full screen decor_view = self.native.getWindow().getDecorView() system_ui_flags = decor_view.getSystemUiVisibility() - if ( - system_ui_flags - & ( - decor_view.SYSTEM_UI_FLAG_FULLSCREEN - | decor_view.SYSTEM_UI_FLAG_HIDE_NAVIGATION - | decor_view.SYSTEM_UI_FLAG_IMMERSIVE - ) - ) != 0: - if not self.native.getSupportActionBar().isShowing(): + if system_ui_flags & ( + decor_view.SYSTEM_UI_FLAG_FULLSCREEN + | decor_view.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | decor_view.SYSTEM_UI_FLAG_IMMERSIVE + ): + if self.window._impl._is_presentation_mode: current_state = WindowState.PRESENTATION else: current_state = WindowState.FULLSCREEN diff --git a/testbed/tests/app/test_desktop.py b/testbed/tests/app/test_desktop.py index 9b6138c1c4..dca1356216 100644 --- a/testbed/tests/app/test_desktop.py +++ b/testbed/tests/app/test_desktop.py @@ -346,9 +346,15 @@ async def test_presentation_mode_with_excess_windows_list(app, app_probe): ], ) async def test_presentation_mode_exit_on_window_state_change( - app, app_probe, new_window_state + app, app_probe, main_window_probe, new_window_state ): """Changing window state exits presentation mode and sets the new state.""" + if ( + new_window_state == WindowState.MINIMIZED + and not main_window_probe.supports_minimize + ): + pytest.xfail("This backend doesn't reliably support minimized window state.") + try: window1 = toga.MainWindow( title="Test Window 1", position=(150, 150), size=(200, 200) diff --git a/testbed/tests/window/test_window.py b/testbed/tests/window/test_window.py index c24a3b2fc6..6b8d9c18b8 100644 --- a/testbed/tests/window/test_window.py +++ b/testbed/tests/window/test_window.py @@ -217,6 +217,50 @@ async def test_window_state_presentation(main_window, main_window_probe): assert main_window_probe.get_window_state() != WindowState.PRESENTATION assert main_window_probe.get_window_state() == WindowState.NORMAL + @pytest.mark.parametrize( + "initial_state, final_state", + [ + # Direct switch from FULLSCREEN: + (WindowState.FULLSCREEN, WindowState.PRESENTATION), + # Direct switch from PRESENTATION: + (WindowState.PRESENTATION, WindowState.FULLSCREEN), + ], + ) + async def test_window_state_direct_change( + app, initial_state, final_state, main_window, main_window_probe + ): + assert main_window_probe.get_window_state() == WindowState.NORMAL + assert main_window_probe.get_window_state() != initial_state + assert main_window_probe.get_window_state() != final_state + try: + # Set to initial state + main_window.state = initial_state + await main_window_probe.wait_for_window( + f"Main window is in {initial_state}" + ) + + assert main_window_probe.get_window_state() != WindowState.NORMAL + assert main_window_probe.get_window_state() == initial_state + assert main_window_probe.get_window_state() != final_state + + # Set to final state + main_window.state = final_state + await main_window_probe.wait_for_window(f"Main window is in {final_state}") + + assert main_window_probe.get_window_state() != WindowState.NORMAL + assert main_window_probe.get_window_state() == final_state + assert main_window_probe.get_window_state() != initial_state + finally: + # Set to NORMAL state + main_window.state = WindowState.NORMAL + await main_window_probe.wait_for_window( + "Main window is in WindowState.NORMAL" + ) + + assert main_window_probe.get_window_state() == WindowState.NORMAL + assert main_window_probe.get_window_state() != final_state + assert main_window_probe.get_window_state() != initial_state + async def test_screen(main_window, main_window_probe): """The window can be relocated to another screen, using both absolute and relative screen positions.""" assert main_window.screen.origin == (0, 0) @@ -820,6 +864,14 @@ async def test_window_state_presentation(second_window, second_window_probe): async def test_window_state_direct_change( app, initial_state, final_state, second_window, second_window_probe ): + if ( + WindowState.MINIMIZED in {initial_state, final_state} + and not second_window_probe.supports_minimize + ): + pytest.xfail( + "This backend doesn't reliably support minimized window state." + ) + second_window.toolbar.add(app.cmd1) second_window.content = toga.Box(style=Pack(background_color=CORNFLOWERBLUE)) second_window.show()