Skip to content

feat: add user configurable device serial for adb based emulators#630

Open
martinmiglio wants to merge 3 commits intomasterfrom
feature/user-config-adb
Open

feat: add user configurable device serial for adb based emulators#630
martinmiglio wants to merge 3 commits intomasterfrom
feature/user-config-adb

Conversation

@martinmiglio
Copy link
Member

@martinmiglio martinmiglio commented Jan 17, 2026

Summary

Fixes ADB multi-device conflicts that caused false positives for offline emulator detection and app installation checks. Users with multiple ADB devices (USB phones, other emulators, etc.) can now specify which device to target.

Key changes:

  • Added user-configurable device serial dropdowns for Google Play and BlueStacks emulators
  • Refactored ADB command execution to automatically scope commands with -s device_serial
  • Consolidated duplicate adb() implementations into a single base class method
  • Added input validation to prevent command injection via device serial

Problem

When multiple ADB devices are connected, commands like adb shell pm list packages fail with:

adb.exe: more than one device/emulator

This caused:

  • False "Emulator is offline" errors when other devices were offline
  • "com.supercell.clashroyale not installed" errors even when the app was installed
  • General unreliability for users with Android phones or multiple emulators connected

Solution

All ADB commands are now scoped to a specific device using the -s flag. Users can:

  1. Leave the device field empty for auto-detection (existing behavior)
  2. Select a specific device from a dropdown that refreshes on open
  3. Manually enter a device address (e.g., localhost:6520, 127.0.0.1:5567)

Architecture

                    ┌──────────────────────────────┐
                    │    BaseEmulatorController    │
                    │          (ABC)               │
                    │──────────────────────────────│
                    │ + click()                    │
                    │ + swipe()                    │
                    │ + screenshot()               │
                    │ + start_app()                │
                    │ + stop()                     │
                    │ + create()                   │
                    └──────────────┬───────────────┘
                                   │
                                   │ inherits
                                   ▼
                    ┌──────────────────────────────┐
                    │     AdbBasedController       │
                    │          (ABC)               │
                    │──────────────────────────────│
                    │ device_serial: str | None    │
                    │ adb_path: str = "adb"        │
                    │ adb_server_port: int | None  │
                    │ adb_env: dict | None         │
                    │──────────────────────────────│
                    │ + adb(command) ◄─────────────┼── Shared implementation
                    │ + _check_app_installed()     │   Auto-adds -s flag
                    │ + list_devices()             │
                    │ + list_online_devices()      │
                    │ + click() / swipe() / ...    │
                    └──────────────┬───────────────┘
                                   │
           ┌───────────────────────┼───────────────────────┐
           │                       │                       │
           ▼                       ▼                       ▼
┌──────────────────┐   ┌──────────────────┐   ┌──────────────────┐
│  AdbController   │   │ GooglePlayEmu... │   │ BlueStacksEmu... │
│──────────────────│   │──────────────────│   │──────────────────│
│ Uses system adb  │   │ Uses bundled adb │   │ Uses bundled adb │
│ No custom port   │   │ Default: 6520    │   │ Private server   │
│──────────────────│   │──────────────────│   │ Port: 5041       │
│ device_serial:   │   │ device_serial:   │   │──────────────────│
│   user-provided  │   │   user-provided  │   │ device_serial:   │
│   or discovered  │   │   or default     │   │   user-provided  │
└──────────────────┘   └──────────────────┘   │   or auto-detect │
                                              └──────────────────┘

Command flow:
─────────────
self.adb("shell pm list packages")
         │
         ▼
AdbBasedController.adb() builds:
  "{adb_path}" [-P {port}] [-s {serial}] {command}
         │
         ▼
Subprocess executes:
  "C:\...\adb.exe" -s localhost:6520 shell pm list packages

Test plan

  • Test Google Play emulator with default device (localhost:6520)
  • Test Google Play emulator with custom device serial
  • Test BlueStacks with auto-detected device (leave field empty)
  • Test BlueStacks with user-specified device serial
  • Test ADB controller with multiple devices connected
  • Verify device dropdown refreshes when opened
  • Verify invalid device serial shows error before starting
  • Test with USB-connected Android phone alongside emulator

@martinmiglio martinmiglio added the enhancement New feature or request label Jan 17, 2026
@martinmiglio martinmiglio force-pushed the feature/user-config-adb branch from 49e4db7 to 3e00461 Compare January 17, 2026 03:00
@martinmiglio martinmiglio force-pushed the feature/user-config-adb branch 2 times, most recently from 582aa15 to 08d3547 Compare January 17, 2026 03:39
@martinmiglio martinmiglio force-pushed the feature/user-config-adb branch from 08d3547 to 42f221c Compare January 17, 2026 03:40
@martinmiglio martinmiglio marked this pull request as ready for review January 17, 2026 04:07
@martinmiglio martinmiglio force-pushed the feature/user-config-adb branch 3 times, most recently from f9ebf73 to 144f5f5 Compare January 17, 2026 08:27
Copy link
Contributor

@JJamesWWang JJamesWWang left a comment

Choose a reason for hiding this comment

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

changes generally lgtm, with some minor comments.
tested with google play and changes work as intended 👍🏼

Comment on lines 146 to 180
if job_dictionary.get("emulator") == EmulatorType.ADB:
device_serial = job_dictionary.get(UIField.ADB_SERIAL.value)
connected_devices = AdbController.list_devices()
connected_devices = AdbController.discover_system_devices()
if not device_serial or device_serial not in connected_devices:
logger.change_status(f"Start cancelled: ADB device '{device_serial}' not connected.")
return None

if job_dictionary.get("emulator") == EmulatorType.GOOGLE_PLAY:
device_serial = job_dictionary.get(UIField.GP_DEVICE_SERIAL.value)
if device_serial:
adb_path = GooglePlayEmulatorController.find_adb() or "adb"
connected_devices = AdbController.discover_system_devices(adb_path)
if device_serial not in connected_devices:
logger.change_status(f"Start cancelled: Device '{device_serial}' not connected.")
return None

if job_dictionary.get("emulator") == EmulatorType.BLUESTACKS:
device_serial = job_dictionary.get(UIField.BS_DEVICE_SERIAL.value)
if device_serial:
adb_path = BlueStacksEmulatorController.find_adb() or "adb"
connected_devices = AdbController.discover_system_devices(adb_path)
if device_serial not in connected_devices:
logger.change_status(f"Start cancelled: Device '{device_serial}' not connected.")
return None
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: there's room for this to be refactored into something like check_device_exists(emulator_type, key_serial, emulator_controller)

Comment on lines 69 to 72
c = command.strip()
if c.startswith("-s "):
return True # Already device-scoped
first = c.split()[0] if c else ""
Copy link
Contributor

@JJamesWWang JJamesWWang Jan 17, 2026

Choose a reason for hiding this comment

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

nit: it may be more correct to regex -s <...>; right now your check is:

        if not self._is_server_command(command) and self.device_serial:
            parts.append(f"-s {self.device_serial}")

and running self._is_server_command(full_command) will still return false because you append -s at the end of the command, while here you're checking that the command starts with -s

@martinmiglio martinmiglio force-pushed the feature/user-config-adb branch from 144f5f5 to 57c23c9 Compare January 25, 2026 05:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants