Repository: beeware/briefcase
Pull request #1519 (2023-11-01 16:48:33)

Support downloading Android emulator system image on Linux aarch64
## Changes
- Adds support to create an AVD on Linux for aarch64

## Notes
- This was the final major issue I had from my experimentation in #449
  - There's still the need to add `android.aapt2FromMavenOverride` to the `gradle.properties` file....but that's manageable as a manual thing....especially since you've still gotta get a working Android SDK first
- The changenote is `misc` so as not to give the impression that Android on Linux for aarch64 works without quite a bit of prerequisite work

## PR Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
- [X] All new features have been tested
- [X] All new features have been documented
- [X] I have read the **CONTRIBUTING.md** file
- [X] I will abide by the code of conduct


Patch:
diff --git a/changes/1519.misc.rst b/changes/1519.misc.rst
new file mode 100644
index 00000000..9323549c
--- /dev/null
+++ b/changes/1519.misc.rst
@@ -0,0 +1,1 @@
+It is now possible to create an Android emulator AVD on Linux for arm64.
diff --git a/src/briefcase/integrations/android_sdk.py b/src/briefcase/integrations/android_sdk.py
index 5c7d6075..ddfbc772 100644
--- a/src/briefcase/integrations/android_sdk.py
+++ b/src/briefcase/integrations/android_sdk.py
@@ -142,15 +142,25 @@ class AndroidSDK(ManagedTool):
     @property
     def emulator_abi(self) -> str:
         """The ABI to use for the Android emulator."""
-        if self.tools.host_arch == "arm64" and self.tools.host_os == "Darwin":
-            return "arm64-v8a"
-        if self.tools.host_arch in ("x86_64", "AMD64"):
-            return "x86_64"
-
-        raise BriefcaseCommandError(
-            "The Android emulator does not currently support "
-            f"{self.tools.host_os} {self.tools.host_arch} hardware."
-        )
+        try:
+            return {
+                "Linux": {
+                    "x86_64": "x86_64",
+                    "aarch64": "arm64-v8a",
+                },
+                "Darwin": {
+                    "x86_64": "x86_64",
+                    "arm64": "arm64-v8a",
+                },
+                "Windows": {
+                    "AMD64": "x86_64",
+                },
+            }[self.tools.host_os][self.tools.host_arch]
+        except KeyError:
+            raise BriefcaseCommandError(
+                "The Android emulator does not currently support "
+                f"{self.tools.host_os} {self.tools.host_arch} hardware."
+            )
 
     @property
     def DEFAULT_DEVICE_TYPE(self) -> str:


Test patch:
diff --git a/tests/integrations/android_sdk/AndroidSDK/test__create_emulator.py b/tests/integrations/android_sdk/AndroidSDK/test__create_emulator.py
index a6e6b740..9a304e67 100644
--- a/tests/integrations/android_sdk/AndroidSDK/test__create_emulator.py
+++ b/tests/integrations/android_sdk/AndroidSDK/test__create_emulator.py
@@ -38,8 +38,9 @@ def android_sdk(android_sdk) -> AndroidSDK:
     [
         ("Darwin", "x86_64", "x86_64"),
         ("Darwin", "arm64", "arm64-v8a"),
-        ("Windows", "x86_64", "x86_64"),
+        ("Windows", "AMD64", "x86_64"),
         ("Linux", "x86_64", "x86_64"),
+        ("Linux", "aarch64", "arm64-v8a"),
     ],
 )
 def test_create_emulator(
@@ -117,8 +118,9 @@ def test_create_emulator(
     [
         ("Darwin", "x86_64", "x86_64"),
         ("Darwin", "arm64", "arm64-v8a"),
-        ("Windows", "x86_64", "x86_64"),
+        ("Windows", "AMD64", "x86_64"),
         ("Linux", "x86_64", "x86_64"),
+        ("Linux", "aarch64", "arm64-v8a"),
     ],
 )
 def test_create_emulator_with_defaults(
diff --git a/tests/integrations/android_sdk/AndroidSDK/test_create_emulator.py b/tests/integrations/android_sdk/AndroidSDK/test_create_emulator.py
index 1227a6ef..bddbcef0 100644
--- a/tests/integrations/android_sdk/AndroidSDK/test_create_emulator.py
+++ b/tests/integrations/android_sdk/AndroidSDK/test_create_emulator.py
@@ -35,8 +35,9 @@ def android_sdk(android_sdk) -> AndroidSDK:
     [
         ("Darwin", "x86_64", "x86_64"),
         ("Darwin", "arm64", "arm64-v8a"),
-        ("Windows", "x86_64", "x86_64"),
+        ("Windows", "AMD64", "x86_64"),
         ("Linux", "x86_64", "x86_64"),
+        ("Linux", "aarch64", "arm64-v8a"),
     ],
 )
 def test_create_emulator(
diff --git a/tests/integrations/android_sdk/AndroidSDK/test_properties.py b/tests/integrations/android_sdk/AndroidSDK/test_properties.py
index c37e6e74..9960c91d 100644
--- a/tests/integrations/android_sdk/AndroidSDK/test_properties.py
+++ b/tests/integrations/android_sdk/AndroidSDK/test_properties.py
@@ -107,9 +107,9 @@ def test_managed_install(mock_tools, android_sdk):
     [
         ("Darwin", "x86_64", "x86_64"),
         ("Darwin", "arm64", "arm64-v8a"),
-        ("Windows", "x86_64", "x86_64"),
         ("Windows", "AMD64", "x86_64"),
         ("Linux", "x86_64", "x86_64"),
+        ("Linux", "aarch64", "arm64-v8a"),
     ],
 )
 def test_emulator_abi(mock_tools, android_sdk, host_os, host_arch, emulator_abi):
diff --git a/tests/integrations/android_sdk/AndroidSDK/test_verify.py b/tests/integrations/android_sdk/AndroidSDK/test_verify.py
index 012f36c3..43aa14a4 100644
--- a/tests/integrations/android_sdk/AndroidSDK/test_verify.py
+++ b/tests/integrations/android_sdk/AndroidSDK/test_verify.py
@@ -104,6 +104,7 @@ def test_unsupported_arch(mock_tools):
         ("Darwin", "arm64"),
         ("Darwin", "x86_64"),
         ("Linux", "x86_64"),
+        ("Linux", "aarch64"),
         ("Windows", "AMD64"),
     ],
 )
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 ecf121c1..6acc5e54 100644
--- a/tests/integrations/android_sdk/AndroidSDK/test_verify_system_image.py
+++ b/tests/integrations/android_sdk/AndroidSDK/test_verify_system_image.py
@@ -1,4 +1,5 @@
 import os
+import platform
 from subprocess import CalledProcessError
 
 import pytest
@@ -10,7 +11,7 @@ from briefcase.exceptions import BriefcaseCommandError
     "host_os, host_arch",
     [
         ("Windows", "arm64"),
-        ("Linux", "arm64"),
+        ("Linux", "armv7l"),
     ],
 )
 def test_unsupported_abi(mock_tools, android_sdk, host_os, host_arch):
@@ -56,7 +57,7 @@ def test_incompatible_abi(mock_tools, android_sdk, capsys):
     """If the system image doesn't match the emulator ABI, warn the user, but
     continue."""
     # Mock the host arch
-    mock_tools.host_arch = "x86_64"
+    mock_tools.host_arch = "AMD64" if platform.system() == "Windows" else "x86_64"
 
     # Verify a system image that doesn't match the host architecture
     android_sdk.verify_system_image("system-images;android-31;default;anything")
@@ -79,7 +80,7 @@ def test_incompatible_abi(mock_tools, android_sdk, capsys):
 def test_existing_system_image(mock_tools, android_sdk):
     """If the system image already exists, don't attempt to download it again."""
     # Mock the host arch
-    mock_tools.host_arch = "x86_64"
+    mock_tools.host_arch = "AMD64" if platform.system() == "Windows" else "x86_64"
 
     # Mock the existence of a system image
     (
@@ -96,7 +97,7 @@ def test_existing_system_image(mock_tools, android_sdk):
 def test_new_system_image(mock_tools, android_sdk):
     """If the system image doesn't exist locally, it will be installed."""
     # Mock the host arch
-    mock_tools.host_arch = "x86_64"
+    mock_tools.host_arch = "AMD64" if platform.system() == "Windows" else "x86_64"
 
     # Verify the system image, triggering a download
     android_sdk.verify_system_image("system-images;android-31;default;x86_64")
@@ -116,7 +117,7 @@ def test_new_system_image(mock_tools, android_sdk):
 def test_problem_downloading_system_image(mock_tools, android_sdk):
     """If there is a failure downloading the system image, an error is raised."""
     # Mock the host arch
-    mock_tools.host_arch = "x86_64"
+    mock_tools.host_arch = "AMD64" if platform.system() == "Windows" else "x86_64"
 
     # Mock a failure condition on subprocess.run
     mock_tools.subprocess.run.side_effect = CalledProcessError(