Skip to content

Comments

feat(app): add Android device/emulator support to macOS menu bar app#9547

Draft
fortmarek wants to merge 37 commits intomainfrom
feat/android-macos-previews
Draft

feat(app): add Android device/emulator support to macOS menu bar app#9547
fortmarek wants to merge 37 commits intomainfrom
feat/android-macos-previews

Conversation

@fortmarek
Copy link
Member

Summary

  • Extends the macOS menu bar app to discover and display Android devices/emulators alongside Apple devices and simulators
  • Supports launching Android APK previews via ADB (install + launch)
  • Android physical devices appear in the "Connected" section with iOS devices; Android emulators appear alongside simulators with pinning support
  • ADB resolution now checks well-known paths (mise, Homebrew, Android Studio) in addition to ANDROID_HOME/ANDROID_SDK_ROOT env vars
  • Gracefully handles missing ADB — no Android devices shown, Apple devices still work normally

Test plan

  • xcsiftbuild build -workspace Tuist.xcworkspace -scheme TuistApp passes
  • xcsiftbuild test -workspace Tuist.xcworkspace -scheme TuistApp -only-testing TuistMenuBarTests passes (17/17)
  • Manual: start Android emulator, verify it appears in "Other devices" section
  • Manual: connect Android physical device via USB, verify it appears in "Connected" section
  • Manual: select Android emulator, click a preview tile with Android build, verify APK installs and launches
  • Manual: pin an Android emulator, verify it appears in pinned "Android Emulators" subsection

🤖 Generated with Claude Code

fortmarek and others added 30 commits February 18, 2026 13:04
Users can now share Android APK builds through Tuist Previews, the same
way they share iOS .ipa files today. Running `tuist share app.apk`
extracts metadata via aapt2 and uploads the APK for distribution.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Android SDK's build-tools directory is never added to PATH by
standard installation methods (mise, Homebrew). Instead of requiring
users to manually add it, look for aapt2 in ANDROID_HOME, ANDROID_SDK_ROOT,
and well-known installation paths before falling back to PATH.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Android APK builds use a SHA256 hash as binary_id, and there's no
strict requirement to prevent re-uploading the same APK. Replace the
unconditional unique index with a partial one that only applies to
non-APK build types.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… tuist run

Extract share and run commands into cross-platform TuistShareCommand and
TuistRunCommand modules (following the TuistBuildCommand pattern). Create
TuistAndroid module with ADB device discovery, APK install, and app launch.

- TuistAndroid: AdbController with device discovery, app install/launch
  using am start instead of monkey for reliable activity resolution
- TuistShareCommand: moved from TuistKit, APK sharing always compiled,
  Apple builds behind #if os(macOS)
- TuistRunCommand: moved from TuistKit, Android device selection and
  APK install/launch cross-platform, Apple simulator/device behind
  #if os(macOS)
- Restructured PreviewsUploadService for cross-platform APK upload
- Added GetPreviewInfoService, ServerPreviewInfo, PreviewUploadResult
- Better error messages from adb commands (extracts stderr from
  CommandError)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts:
#	server/lib/tuist_web/components/previews/platform_icon.ex
#	server/priv/gettext/dashboard.pot
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove hardcoded fallback paths (mise, Android Studio, Homebrew) — rely
on the standard ANDROID_HOME / ANDROID_SDK_ROOT environment variables
and bare PATH lookup instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…solution

Removes the `servicePlatform` computed property from `Target+PlatformResolution.swift`
and inlines the platform resolution logic at each call site in BuildService, TestService,
and RunCommandService.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ommandService

Replaces platform-specific FileHandler/AbsolutePath.current branching with
the cross-platform Environment.pathRelativeToWorkingDirectory helper.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On Linux only preview URLs are supported, so --generate, --clean,
--configuration, --os, --rosetta, and passthrough arguments are now
only available on macOS.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Show platform-appropriate help for the runnable argument and reorder
to list the most common options first.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…hods

The cross-platform method only accepts path, runnable, and device.
The macOS-only method adds generate, clean, configuration, osVersion,
rosetta, and arguments parameters.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…types

Replace the macOS-only booted simulator shortcut with a cross-platform
isReady check. Android devices and physical devices are always ready,
simulators only when booted. If exactly one ready device exists, it is
selected automatically without prompting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Inline the platform checks directly at the call sites.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove custom ServerPreviewInfo and PreviewUploadResult wrapper types,
using Components.Schemas.Preview from the generated OpenAPI client instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…eview

Rename the server operationId from downloadPreview to getPreview and
consolidate into a single cross-platform GetPreviewService returning
Components.Schemas.Preview. Remove the duplicate macOS-only service
and update the app to convert at call sites.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Eliminate the separate APKPreviewUploadService by making
PreviewsUploadService cross-platform. APK upload methods are always
compiled while IPA/appBundle methods remain behind #if canImport(TuistCore).
Git info is now passed as parameters instead of resolved internally,
removing the TuistGit dependency from TuistServer.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the failable `init?` with a throwing `init` that reports
exactly which URL or date failed to parse via ServerPreviewError,
instead of silently returning nil.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move configuration, platforms, and derivedDataPath options behind
the platform guard since they only apply to Apple builds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…share

Move TuistGit from macOS-only to cross-platform targets since all its
dependencies are already cross-platform. Use GitController.gitInfo()
in ShareCommandService instead of manual CommandRunner git calls,
gaining CI environment variable fallbacks for branch/ref detection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…module cross-platform

Move APK metadata parsing (aapt2 resolution, badging output parsing) from
ShareCommandService into a dedicated APKMetadataService in the TuistAndroid
module. Make TuistAndroid cross-platform (macOS + iOS) with Command-using
implementations behind #if canImport(Command). Replace custom path() helper
with Environment.current.pathRelativeToWorkingDirectory(). Add TuistAndroid
as a dependency of TuistServer.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Parse application-icon-* lines from aapt2 output to find the highest
density icon path, then unzip the APK and upload the icon via
UploadPreviewIconService. This brings Android previews to parity with
Apple previews which already extract and upload app icons.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fortmarek and others added 7 commits February 19, 2026 17:26
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…Command

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extend the macOS menu bar app to display Android devices and emulators
alongside Apple devices and simulators, and support launching Android
APK previews via ADB.

- Add TuistAndroid dependency to TuistMenuBar target
- Add androidDevice case to Device/SelectedDevice enums
- Add AdbController integration to DeviceService for device discovery
  and APK install/launch
- Show Android physical devices in "Connected" section with iOS devices
- Show Android emulators in pinned/unpinned sections with simulators
- Add AndroidDeviceRow view component with pinning support for emulators
- Add well-known paths (mise, Homebrew, Android Studio) to ADB resolution
- Add Codable/Hashable conformance to AndroidDevice for AppStorage
- Gracefully handle missing ADB (no Android devices shown)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

1 participant