Skip to content

Android sys img picker#2843

Open
moondial-pal wants to merge 10 commits into
beeware:mainfrom
moondial-pal:android-sys-img-picker
Open

Android sys img picker#2843
moondial-pal wants to merge 10 commits into
beeware:mainfrom
moondial-pal:android-sys-img-picker

Conversation

@moondial-pal
Copy link
Copy Markdown
Contributor

Added two step selection flow that fetches all available compatible images from sdkmanager --list.
Hard coded version is set if default is selected.

This enables the user to select an android version when creating a new emulator.

Fixes #737

PR Checklist:

  • I will abide by the BeeWare Code of Conduct
  • I have read and have followed the CONTRIBUTING.md file
  • This PR was generated or assisted using an AI tool

Assisted-by: Claude Sonnet 4.6

@moondial-pal
Copy link
Copy Markdown
Contributor Author

The approach was revised during the PyCon sprint after a discussion with @mhsmith.
Am going to work on aligning tests and see if I can nail this down.

@moondial-pal
Copy link
Copy Markdown
Contributor Author

Are the 3 CI failures pre-existing or am I goofing something here?

  • docs-lint broken https://www.x.org/ link in x11passthrough.md, not touched by my PR
  • Windows ARM PySide6 build, similar failure visible in recent main branch CI runs
  • ReadTheDocs appears to be an infrastructure build failure?

I checked recent CI runs on main and found some of these issues crop up there.
I'm not sure how to get them to pass at this point 😅

@moondial-pal moondial-pal marked this pull request as ready for review May 23, 2026 16:36
@mhsmith
Copy link
Copy Markdown
Member

mhsmith commented May 25, 2026

Yes, the failures aren't related to this PR, so you don't need to worry about them.

Thanks for the updates, I'll look at this in a couple of days.

@mhsmith mhsmith self-requested a review May 25, 2026 13:40

def list_available_system_images(
self, min_version: int = ANDROID_MIN_OS_VERSION
) -> list[str]:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no need to declare a default argument which is never used in production code; this only obscures the actual source of the default.

On the other hand, the test code should test the behavior of the argument; it looks like it currently doesn't.

from briefcase.integrations.subprocess import SubprocessArgT

DEVICE_NOT_FOUND = re.compile(r"^error: device '[^']*' not found")
ANDROID_MIN_OS_VERSION = 26
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why 26? The template currently defaults to 24.

Copy link
Copy Markdown
Contributor Author

@moondial-pal moondial-pal Jun 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. I chose 26 because of a comment Russell made in issue #737: #737 (comment)
Would you recommend I stick with the template default of 24?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a typo on my part - we should definitely stick to the 24 minimum.

Comment on lines +711 to +715
:param min_version: The minimum Android version to include. Defaults to
``ANDROID_MIN_OS_VERSION``.
e.g., ``["system-images;android-31;default;x86_64", "system-
images;android-34;default;x86_64", "system-
images;android-34;google_apis;x86_64"]``
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the default argument is removed, it should be removed from the docstring as well.

Our docstrings now use Markdown, so code should be marked with single backquotes. The spacing and line breaking in the code probably won't render correctly either, but RTD obviously has a problem with the current commit, so we can't see for sure. If you merge from the main branch, that will probably fix the current error.

Also, if you intend the "e.g." paragraph to be part of the main docstring, then it should go before the param list, not after.

for line in output.splitlines():
package = line.split("|")[0].strip()
if not package.startswith("system-images"):
continue
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code above this point could be replaced with a call to list_installed_system_images.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, not replaced exactly, but the duplicate code should be factored out.

system_image = self.DEFAULT_SYSTEM_IMAGE
# Get available images, raise an error if none found.
available_images = self.list_available_system_images(
min_version=getattr(app, "min_os_version", ANDROID_MIN_OS_VERSION)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommend factoring out this getattr expression into a function, and using it in output_format_template_context. That would eliminate any risk of the two locations going out of sync.

)

# Ask the user to select an Android version.
versions = sorted({img.split(";")[1].split("-")[1] for img in available_images})
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The split expressions in this function are difficult to understand. Suggest creating a parsing function which converts a system image name into an object with fields for Android version, image type and ABI. Then we can either use that function both here and in list_available_system_images, or maybe even make list_available_system_images return the parsed objects.

versions = sorted({img.split(";")[1].split("-")[1] for img in available_images})
version = self.tools.console.selection_question(
intro="Select the Android version for the emulator:",
description="Android version",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid confusion, should say "API level" rather than "Android version".

{
f"{img.split(';')[1]};{img.split(';')[2]}"
for img in available_images
if img.split(";")[1].startswith(f"android-{version}")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the version included in the type here, and why are we using startswith? I thought it might be because of decimal versions, but that doesn't quite seem right either. For example, if the version question has choices for 36 and 36.1, and the user chose 36, the second question will still include the images for 36.1. It's probably better not to do anything clever about that, and treat every version independently.

skin = self.DEFAULT_DEVICE_SKIN

# TODO: Provide a list of options for system images.
system_image = self.DEFAULT_SYSTEM_IMAGE
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DEFAULT_SYSTEM_IMAGE is still used in one other place, but it should be updated to make sure it's consistent with the default Android version and image type used in this function. This would probably involve creating DEFAULT properties for those two things, and using them in both locations.


# Create a mock app
app = MagicMock()
del app.min_os_version # ensure getattr fallback is used
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be at least one test of the attribute being present.

@moondial-pal
Copy link
Copy Markdown
Contributor Author

Thanks for the detailed feedback. I'll work through each point.
The suggestion to factor out the parsing logic and treat versions independently makes a lot of sense. I'll get back to you with updates.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve flexibility of Android emulator system image

3 participants