From afc00e60f246e425754651b7fa32c396c7ced49a Mon Sep 17 00:00:00 2001 From: Luis Palacios Date: Sun, 12 Apr 2026 10:06:31 -0700 Subject: [PATCH 1/3] Add list_installed_system_images and use it in verify_system_image --- src/briefcase/integrations/android_sdk.py | 31 ++++++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/src/briefcase/integrations/android_sdk.py b/src/briefcase/integrations/android_sdk.py index d0352c051..5c462b114 100644 --- a/src/briefcase/integrations/android_sdk.py +++ b/src/briefcase/integrations/android_sdk.py @@ -733,6 +733,27 @@ def verify_avd(self, avd: str): except KeyError: self.tools.console.debug(f"Device {avd!r} doesn't define a skin.") + def list_installed_system_images(self) -> set[str]: + """Returns a set of installed system image package identifiers. + + e.g., ``{"system-images;android-31;default;x86_64"}`` + """ + try: + output = self.tools.subprocess.check_output( + [self.sdkmanager_path, "--list_installed"], + env=self.env, + ) + except subprocess.CalledProcessError as e: + raise BriefcaseCommandError( + "Unable to invoke the Android SDK manager" + ) from e + + return { + line.split("|")[0].strip() + for line in output.splitlines() + if line.strip().startswith("system-images") + } + def verify_system_image(self, system_image: str): """Verify that the required system image is installed. @@ -766,13 +787,9 @@ def verify_system_image(self, system_image: str): """ ) - # Convert the system image into a path where that system image - # would be expected, and see if the location exists. - system_image_path = self.root_path - for part in system_image_parts: - system_image_path = system_image_path / part - - if system_image_path.exists(): + # Use sdkmanager to verify the system image is fully installed, + # not just that the directory exists. + if system_image in self.list_installed_system_images(): # Found the system image. return From e900d22c87044cf6831f625f446d4d58bc569d37 Mon Sep 17 00:00:00 2001 From: Luis Palacios Date: Sun, 12 Apr 2026 15:38:58 -0700 Subject: [PATCH 2/3] Add tests for list_installed_system_images and update verify_system_image tests --- .../AndroidSDK/test_list_installed_images.py | 49 +++++++++++++++++++ .../AndroidSDK/test_verify_system_image.py | 9 ++-- 2 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 tests/integrations/android_sdk/AndroidSDK/test_list_installed_images.py diff --git a/tests/integrations/android_sdk/AndroidSDK/test_list_installed_images.py b/tests/integrations/android_sdk/AndroidSDK/test_list_installed_images.py new file mode 100644 index 000000000..a95ec1f63 --- /dev/null +++ b/tests/integrations/android_sdk/AndroidSDK/test_list_installed_images.py @@ -0,0 +1,49 @@ +import subprocess + +import pytest + +from briefcase.exceptions import BriefcaseCommandError + + +def test_list_installed_system_images(mock_tools, android_sdk): + """Returns a set of installed system image package identifiers.""" + + mock_tools.subprocess.check_output.return_value = ( + "Installed packages:\n" + " Path | Version | Description | Location\n" + " ------- | ------- | ------- | -------\n" + " system-images;android-31;default;x86_64 | 5 | Intel x86_64 Atom System Image | system-images/android-31/default/x86_64\n" + " emulator | 35.4.9 | Android Emulator | emulator\n" + ) + + result = android_sdk.list_installed_system_images() + + assert result == {"system-images;android-31;default;x86_64"} + mock_tools.subprocess.check_output.assert_called_once_with( + [android_sdk.sdkmanager_path, "--list_installed"], + env=android_sdk.env, + ) + + +def test_no_installed_system_images(mock_tools, android_sdk): + """If no system images are installed, an empty set is returned.""" + mock_tools.subprocess.check_output.return_value = ( + "Installed packages:\n" + " Path | Version | Description | Location\n" + " ------- | ------- | ------- | -------\n" + " emulator | 35.4.9 | Android Emulator | emulator\n" + ) + + result = android_sdk.list_installed_system_images() + + assert result == set() + + +def test_list_installed_system_images_failure(mock_tools, android_sdk): + """If sdkmanager fails, an error is raised.""" + mock_tools.subprocess.check_output.side_effect = subprocess.CalledProcessError( + 1, "" + ) + + with pytest.raises(BriefcaseCommandError): + android_sdk.list_installed_system_images() diff --git a/tests/integrations/android_sdk/AndroidSDK/test_verify_system_image.py b/tests/integrations/android_sdk/AndroidSDK/test_verify_system_image.py index d3a75ab12..26ca2f223 100644 --- a/tests/integrations/android_sdk/AndroidSDK/test_verify_system_image.py +++ b/tests/integrations/android_sdk/AndroidSDK/test_verify_system_image.py @@ -81,9 +81,12 @@ def test_existing_system_image(mock_tools, android_sdk): # Mock the host arch mock_tools.host_arch = "AMD64" if platform.system() == "Windows" else "x86_64" - # Mock the existence of a system image - (android_sdk.root_path / "system-images/android-31/default/x86_64").mkdir( - parents=True + # Mock sdkmanager reporting the system image as installed + mock_tools.subprocess.check_output.return_value = ( + "Installed packages:\n" + " Path | Version | Description | Location\n" + " ------- | ------- | ------- | -------\n" + " system-images;android-31;default;x86_64 | 5 | Intel x86_64 Atom System Image | system-images/android-31/default/x86_64\n" ) # Verify the system image that we already have From cc3051ce105d9278d108a974402c12fda7c4c1a7 Mon Sep 17 00:00:00 2001 From: Luis Palacios Date: Sun, 12 Apr 2026 15:42:34 -0700 Subject: [PATCH 3/3] Add change file 1895. --- changes/1895.bugfix.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/1895.bugfix.md diff --git a/changes/1895.bugfix.md b/changes/1895.bugfix.md new file mode 100644 index 000000000..3aefe18eb --- /dev/null +++ b/changes/1895.bugfix.md @@ -0,0 +1 @@ +Android system image verification now uses ``sdkmanager --list_installed`` to confirm the image is fully installed, rather than only checking if the directory exists. If the image is not installed, it will be downloaded automatically.