Skip to content

Latest commit

 

History

30 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

image

macOS Subsystem for Android (MSA)

Inspired by WSA (Windows Subsystem for Android), macOS Subsystem for Android (MSA) runs the official Android Emulator as a VM backend and gives each Android app its own native macOS NSWindow.

There is no full AOSP checkout or build required: a single platform-signed privileged host agent is added on top of the stock Android 16 AOSP userdebug emulator image.

Status: Early-stage and actively developed. Core app streaming, input, and live window resizing all work today — see Known Limitations for details.

macOS / AppKit NSWindow
  H.264 video ◀──── adb TCP forward ────▶ input events
                                             │
Android Emulator (unmodified AOSP framework) │
  MsaAgent priv-app                          │
    VirtualDevice                            │
      ├─ VirtualDisplay → MediaCodec H.264 ──┘
      ├─ VirtualTouchscreen
      ├─ VirtualMouse
      └─ VirtualKeyboard
            └─ target Activity on that display

Table of Contents


Features

  • Virtual Display & Device Isolation: Creates a VirtualDevice and VirtualDisplay per app via Android 16's VirtualDeviceManager (1 connection → 1 VirtualDevice → 1 NSWindow).
  • Target Activity Routing: Launches the target Activity directly on its assigned virtual display using ActivityOptions.setLaunchDisplayId().
  • Low-Latency Input Forwarding: Injects macOS mouse, touch, and keyboard input into Android's VirtualTouchscreen, VirtualMouse, and VirtualKeyboard.
  • Hardware-Accelerated Streaming: Encodes video via MediaCodec (H.264) and decodes/renders frames smoothly in AppKit.
  • Audio Routing: Routes Android playback and microphone input through macOS CoreAudio.
  • Bidirectional Clipboard: Shares text and images bidirectionally between macOS and Android clipboards.
  • Camera Bridging: Maps selectable macOS cameras to Android's front and back cameras.
  • Live Window Resizing: Dynamically resizes the VirtualDisplay, density, encoder, and touchscreen to track NSWindow aspect ratio and size changes live.
  • Automated Companion Pairing: Sets up the Companion Device app-streaming association and role automatically.
  • Secure Authentication Forwarding: Forwards FLAG_SECURE authentication screens to macOS using a secure VirtualDisplay.
  • Native App Bundling: Packages any installed Android app as a native, double-clickable macOS .app bundle.

Known Limitations

  • Not Yet Implemented: System notifications, IME composition, and DRM-protected video streaming.
  • Unauthenticated Wire Protocol: The wire protocol has no built-in authentication and must only run behind adb forward's localhost tunnel (see Security Notes).
  • Development Key Signing: The priv-app is signed using an AOSP development test key, not a production signing key.
  • Root-Induced GMS Constraints: Google Play / GMS setup requires a rooted emulator, which breaks Play Integrity, hardware DRM, and Wallet-style features (see Google Play and GMS Setup).

System Requirements

  • Hardware: Apple Silicon Mac (M1/M2/M3/M4 series).
  • Android SDK & Emulator: Android SDK with command-line tools and emulator (defaults to ~/Library/Android/sdk; override with ANDROID_SDK_ROOT).
  • Developer Tools: Xcode Command Line Tools (providing Swift 6).
  • Runtime: Java 17 (OpenJDK 17 or Android Studio bundled JDK).

Prerequisites Installation

  1. Install command-line tools and Java:

    xcode-select --install                 # Xcode Command Line Tools (provides Swift 6)
    brew install openjdk@17                # or use the JDK bundled with Android Studio
  2. Install Android SDK components: Install Android SDK via Android Studio (simplest — default SDK path matches script expectations) or via command-line tools. Ensure the following packages are installed:

    "$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" \
      "platform-tools" "emulator" "platforms;android-36" "build-tools;36.0.0"

    (Note: scripts/install-system-image.sh installs the actual emulator system image separately in the initial setup).

  3. Configure PATH: Add platform-tools to your PATH (required because scripts like forward.sh and smoke-test.sh invoke adb directly):

    echo 'export PATH="$HOME/Library/Android/sdk/platform-tools:$PATH"' >> ~/.zshrc
    source ~/.zshrc

Repository Layout

Directory / File Description
android-agent/ Gradle-built, platform-signed privileged app (MsaAgent).
macos-host/ Swift Package / AppKit host application (no Xcode project required).
scripts/ Shell scripts for SDK setup, AVD creation, priv-app installation, execution, and verification.
docs/protocol.md Specification of the wire protocol between the macOS host and Android agent.
docs/storage-resize.md In-depth architecture and recovery guide for data-preserving storage expansion.
aosp/ Fallback configuration for building a full AOSP product; not needed for regular use.

Disk Usage

The primary storage footprint consists of the Android SDK's API 36 ARM64 system image, AVD image data, and Gradle build cache. Initial installation requires a few gigabytes (approximately 4 GB in testing).


Quick Start

Execute all commands from the repository root.

1. Initial Setup

Download required prebuilts and compile both agent and host:

scripts/install-system-image.sh
scripts/fetch-android-prebuilts.sh
scripts/build-agent.sh
scripts/build-host.sh

Note

fetch-android-prebuilts.sh downloads only the Android 16 system API stubs and the AOSP test platform key — never a full AOSP source tree. This test key is for local development only and cannot sign production distribution builds.

2. Start the Emulator & Install Agent

  1. Launch the emulator:

    scripts/start-emulator.sh

    This launches the emulator with -gpu host to utilize Apple Silicon GPU acceleration. The video stream runs at a fixed 60 fps.

  2. Install the privileged agent: In a separate terminal, once the guest finishes booting:

    ANDROID_SERIAL=emulator-5556 scripts/install-priv-app.sh

    This script runs adb root/adb remount on the userdebug image, copies the APK and priv-app permission allowlist into /system/priv-app, reboots the guest, and provisions the Companion Device association, app-streaming role, and TCP port forwarding.

3. Launch an App

Establish the port forward and run the host client (e.g., launching Android Settings):

ANDROID_SERIAL=emulator-5556 scripts/forward.sh
scripts/run-host.sh com.android.settings

Replace com.android.settings with any installed Android package name. Launching multiple packages creates independent VirtualDisplays and NSWindows for each.


Native macOS App Integration (.app)

Convert any Android app installed on the emulator into a native macOS .app bundle launchable from Finder or Spotlight.

  • Naming & Icons: Display names are resolved from the APK manifest. Icons are extracted via Android's PackageManager as PNG and converted to .icns (supporting XML adaptive icons, vector drawables, and standard image assets). Bundles are installed into ~/Applications by default.
  • Embedded Configuration: Generated bundles embed the target package name and emulator settings active for ANDROID_SERIAL at creation time (no APK or Google Play binaries are bundled).
  • Independent Host: The generated app contains the lightweight MSAAppHost. Emulator VM lifecycle management is handled exclusively by MSA Emulator.app.

Creating a macOS App Bundle

# Basic creation (installed to ~/Applications)
ANDROID_SERIAL=emulator-5558 scripts/create-macos-app.sh com.android.vending

# Custom display name and output directory
ANDROID_SERIAL=emulator-5558 scripts/create-macos-app.sh com.example.app "My Android App" "$HOME/Desktop"

# Update an existing bundle with fresh host binaries and icons
ANDROID_SERIAL=emulator-5558 scripts/create-macos-app.sh --replace com.android.vending

To run the generated app:

open "$HOME/Applications/Google Play Store.app"

Opening the .app instructs the manager to start the emulator headlessly if not already running, then displays the app window once boot completes.

Automatic App Conversion

Once the Android agent and MSA Emulator.app are active, newly installed launchable Android apps are converted automatically:

  1. The agent queues package installation events in device-protected storage across reboots.
  2. The manager converts each package, signs the .app, places it alongside the manager app, and sends an acknowledgment (ACK).
  3. Existing bundles for that package are replaced automatically.

Menu-Bar Manager (MSA Emulator.app)

MSA Emulator.app runs as a persistent menu-bar application controlling the VM lifecycle and hardware bridging:

  • Emulator Lifecycle Control: Start, restart, or stop the emulator.
    • Power Management: 30 seconds after the last Android app window closes, the manager pauses the VM to save CPU. After 5 minutes of inactivity, it shuts down the emulator, saving its Quick Boot state.
    • Cold Boot Fallback: If booting from a saved snapshot fails, the manager automatically falls back to a cold boot.
  • Location Sharing: Enabled by default (prompts for macOS Location permission on first use). Injects an initial location on boot and updates coordinates only while an active Android app declares ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION in its manifest. Updates pause when the VM is paused/stopped; resuming does not unconditionally inject location.
  • Camera Selection: Select which macOS cameras map to Android's front and back cameras. Settings persist per emulator; modifying a selection restarts the emulator to attach the new device.
  • Microphone & Audio: Android playback and microphone input route through macOS CoreAudio. Because the emulator's CoreAudio backend lacks per-process input selection, the manager sets the macOS default input device (prompting for user confirmation first). Select Refresh Media Devices after plugging/unplugging media hardware.
  • Storage Expansion ("Increase Storage…"):
    • Allows entering a larger data-partition maximum capacity (in GiB).
    • Storage is allocated dynamically as Android writes data (thin provisioning).
    • Performs verified offline AVD backup, expands QCOW2, and uses a temporary ramdisk hook before Android mounts /data. User data, encryption keys, and UUID are preserved; snapshots are retired. Requires Android NDK and free host disk space.
    • See docs/storage-resize.md for full safety, backup, and recovery details.

In-App Controls & Menus

Each app window provides an Android menu in the macOS menu bar:

  • Navigation & Audio:
    • Back (Command-[)
    • Volume Up, Volume Down, Mute (controls Android guest volume, not macOS host volume).
  • Clipboard Paste:
    • Command-V sends Control-V into the focused Android input field, pasting synchronized text or images from the macOS clipboard.
  • Status Indicators: Displays VM states: Idle, Paused, Saving Snapshot, and Stopped.

Standalone Manager Creation & Configuration

Create or update the manager app independently:

ANDROID_SERIAL=emulator-5558 scripts/create-emulator-manager-app.sh
open "$HOME/Applications/MSA Emulator.app"

Environment Variables

  • ANDROID_SERIAL: Defaults to mapping emulator-5558 to AVD msa-gms-api36, and any other serial to msa-api36.
  • MSA_AVD_NAME: Override target AVD name.
  • MSA_WRITABLE_SYSTEM: Toggle -writable-system (1 or 0).
ANDROID_SERIAL=emulator-5560 MSA_AVD_NAME=my-avd MSA_WRITABLE_SYSTEM=0 \
  scripts/create-macos-app.sh com.example.app

Google Play and GMS Setup (rootAVD)

Google Play Store and Google Mobile Services (GMS) run in an isolated AVD named msa-gms-api36. The SDK's Google Play system image is cloned using an APFS copy-on-write duplicate; rootAVD/Magisk patches only that copy's ramdisk, leaving original system images and Pixel AVDs untouched.

Initial GMS Provisioning

  1. Create and initialize the GMS AVD:

    scripts/setup-gms-avd.sh
    GMS_EMULATOR_WINDOW=1 scripts/start-gms-emulator.sh
  2. Root the emulator with rootAVD: In a separate terminal (the emulator exits automatically upon completion):

    ANDROID_SERIAL=emulator-5558 scripts/root-gms-emulator.sh
  3. Complete Magisk setup:

    • Launch with window: GMS_EMULATOR_WINDOW=1 scripts/start-gms-emulator.sh.
    • Open the Magisk app inside Android and accept the additional setup prompt.
    • After the automatic reboot, navigate to Magisk's Superuser screen and enable [SharedUID] Shell.
  4. Install the agent module:

    ANDROID_SERIAL=emulator-5558 scripts/install-gms-agent.sh
  5. Headless daily operation:

    scripts/start-gms-emulator.sh
    ANDROID_SERIAL=emulator-5558 scripts/forward.sh
    scripts/run-host.sh com.android.settings
    scripts/run-host.sh com.android.vending

Warning

This setup is for local development only. Do not redistribute Google Play system images, GMS, Magisk, rootAVD, or the agent APK in build artifacts. Play Integrity, DRM, and Wallet features may fail on rooted emulators.

Secure Display Capture & Android Auto Override

The agent uses role permission CAPTURE_SECURE_VIDEO_OUTPUT and a secure VirtualDisplay to mirror FLAG_SECURE authentication and password screens to macOS.

  • A systemless Runtime Resource Overlay (RRO) transfers the SYSTEM_AUTOMOTIVE_PROJECTION role holder from Android Auto to MsaAgent. Consequently, Android Auto is unusable inside the emulator while this override is active.
  • Video streams unauthenticated over localhost TCP. Do not run on multi-user systems where untrusted local processes could listen.

Toggle secure capture (triggers emulator reboot):

ANDROID_SERIAL=emulator-5558 scripts/set-secure-capture.sh disable
ANDROID_SERIAL=emulator-5558 scripts/set-secure-capture.sh enable

To completely roll back secure capture modifications: execute disable above, then check out commit 62be958.


Verification & Diagnostics

Verify that virtual devices, displays, and inputs are operating properly:

# Run automated smoke test
ANDROID_SERIAL=emulator-5556 scripts/smoke-test.sh com.android.settings

# Inspect Android subsystem state
adb -s emulator-5556 shell dumpsys virtualdevice
adb -s emulator-5556 shell dumpsys display
adb -s emulator-5556 shell dumpsys input
adb -s emulator-5556 shell dumpsys activity activities
adb -s emulator-5556 logcat -s MsaAgent

Expected Results:

  • dumpsys virtualdevice lists the active Virtual Device.
  • dumpsys display displays an MSA:<package> virtual display entry.
  • dumpsys input registers Touchscreen, Mouse, and Keyboard virtual devices.
  • dumpsys activity shows the target Activity assigned to the virtual display ID.
  • Closing the NSWindow cleanly tears down the encoder, virtual inputs, display, and virtual device.

Troubleshooting

Issue / Error Message Cause Resolution
No CDM association; run scripts/provision-emulator.sh Agent app data was cleared or reset without reinstalling the priv-app. Run ANDROID_SERIAL=<serial> scripts/provision-emulator.sh to recreate the Companion Device association and streaming role.
adb: command not found (in forward.sh or smoke-test.sh) platform-tools directory is missing from your shell's PATH. Add SDK tools to PATH: export PATH="$HOME/Library/Android/sdk/platform-tools:$PATH" (see Prerequisites).
aapt2 / apksigner / android.jar not found Required SDK platforms or build tools are missing. Install them via sdkmanager: sdkmanager "platforms;android-36" "build-tools;36.0.0". Note that install-system-image.sh only installs the emulator system image.

Security Notes

  • Transport Authentication: The wire protocol currently has no authentication. TCP ports must never be exposed to external networks or LANs; use exclusively via adb forward localhost tunnels.
  • Signing Key: The priv-app is signed with an AOSP development test platform key, not a production signing key.
  • Production Gaps: Authenticated IPC, verified production signing keys, secure update mechanisms, and fine-grained permission scoping are required before deployment outside trusted local development machines.

Contributing

Contributions, bug reports, and pull requests are welcome! The project codebase is compact and modular:

  • Android privileged agent: android-agent/
  • Swift macOS host: macos-host/
  • Wire protocol specification: docs/protocol.md
  • Helper automation: scripts/

License

Distributed under the MIT License. See LICENSE for details.

About

macOS Subsystem for Android - use Android apps as native apps in macOS

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages