Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,25 @@ on:
- 'v*'
workflow_dispatch:

permissions: {}

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: "3.11"

- name: Setup Bazel
uses: bazel-contrib/setup-bazel@0.19.0
uses: bazel-contrib/setup-bazel@c5acdfb288317d0b5c0bbd7a396a3dc868bb0f86 # 0.19.0
with:
bazelisk-cache: true
disk-cache: true
Expand All @@ -29,7 +35,7 @@ jobs:
bazel build //:android_env_wheel

- name: Upload artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: releases
path: bazel-bin/**/*.whl
Expand All @@ -42,10 +48,10 @@ jobs:
id-token: write
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: releases
path: dist

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # release/v1
5 changes: 4 additions & 1 deletion BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
load("@rules_python//python:defs.bzl", "py_binary")

exports_files(["requirements.txt"])
exports_files([
"README.md",
"requirements.txt",
])

alias(
name = "android_env_wheel",
Expand Down
1 change: 1 addition & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ pip.parse(
use_repo(pip, "pip")

# Hermetic TypeScript and JavaScript Toolchain
bazel_dep(name = "aspect_rules_esbuild", version = "0.25.1")
bazel_dep(name = "aspect_rules_ts", version = "3.5.0")
bazel_dep(name = "aspect_rules_js", version = "2.2.0")
bazel_dep(name = "rules_nodejs", version = "6.3.3")
Expand Down
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ Install AndroidEnv from PyPI:
$ python3 -m pip install android-env
```

Or clone the repository from `main` and build/test with Bazel:
Or clone the repository from `main` and build/test with Bazel (ensure
`ANDROID_HOME` points to a local Android SDK installation):

```shell
$ git clone https://github.com/google-deepmind/android_env/
Expand Down Expand Up @@ -142,11 +143,17 @@ with android_device_loader.load_android_device(config) as device:
### 3. Launch the Interactive Web Playground

Explore the device screen, inspect live accessibility trees, and run ADB
commands from your browser:
commands from your browser (after `pip install "android-env[playground]"`):

```shell
$ bazel run //android_env/playground:launch -- \
--config=android_env/configs/emulator_config.py
$ android-env-playground --serial=emulator-5554
```

Or launch a local AVD from a source checkout with Bazel:

```shell
$ bazel run //android_env/playground:playground -- \
--config=android_env/playground/configs/emulator_config.py
```

### 4. Reinforcement Learning (`dm_env`)
Expand Down
14 changes: 10 additions & 4 deletions README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
$ python3 -m pip install android-env
```

或者从 `main` 分支克隆仓库代码并通过 Bazel 进行编译与测试:
或者从 `main` 分支克隆仓库代码并通过 Bazel 进行编译与测试(请确保环境变量 `ANDROID_HOME` 指向本地安装的 Android SDK 目录):

```shell
$ git clone https://github.com/google-deepmind/android_env/
Expand Down Expand Up @@ -105,11 +105,17 @@ with android_device_loader.load_android_device(config) as device:

### 3. 启动交互式 Web Playground

直接在浏览器中实时浏览设备屏幕、检查无障碍树节点并执行 ADB 命令:
直接在浏览器中实时浏览设备屏幕、检查无障碍树节点并执行 ADB 命令(通过 `pip install "android-env[playground]"` 安装后):

```shell
$ bazel run //android_env/playground:launch -- \
--config=android_env/configs/emulator_config.py
$ android-env-playground --serial=emulator-5554
```

或在源码仓库中通过 Bazel 启动本地 AVD:

```shell
$ bazel run //android_env/playground:playground -- \
--config=android_env/playground/configs/emulator_config.py
```

### 4. 强化学习接入 (`dm_env`)
Expand Down
45 changes: 44 additions & 1 deletion android_env/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,35 @@ load("@rules_python//python:packaging.bzl", "py_package", "py_wheel")

package(default_visibility = ["//visibility:public"])

py_library(
name = "android_env",
srcs = ["__init__.py"],
deps = [
":env_interface",
":loader",
"//android_env/components:android_device",
"//android_env/components:android_device_loader",
"//android_env/components:config_classes",
"//android_env/rl:environment",
],
)

py_test(
name = "init_test",
srcs = ["init_test.py"],
legacy_create_init = False,
deps = [
":android_env",
":env_interface",
":loader",
"//android_env/components:android_device",
"//android_env/components:android_device_loader",
"//android_env/components:config_classes",
"//android_env/rl:environment",
"@pip//absl_py",
],
)

py_library(
name = "env_interface",
srcs = ["env_interface.py"],
Expand Down Expand Up @@ -49,10 +78,13 @@ py_package(
name = "android_env_pkg",
packages = ["android_env"],
deps = [
":android_env",
":loader",
"//android_env/components:android_device_loader",
"//android_env/mcp:android_mcp_server_lib",
"//android_env/mcp:android_mcp_tools",
"//android_env/playground:playground_lib",
"//android_env/playground/configs:emulator_config",
"//android_env/rl:environment",
"//android_env/wrappers:discrete_action_wrapper",
"//android_env/wrappers:flat_interface_wrapper",
Expand All @@ -70,13 +102,23 @@ py_package(
py_wheel(
name = "android_env_wheel",
author = "DeepMind",
description_content_type = "text/markdown",
description_file = "//:README.md",
distribution = "android_env",
entry_points = {
"console_scripts": [
"android-env-mcp = android_env.mcp.android_mcp_server:run",
"android-env-playground = android_env.playground.playground:run",
],
},
extra_requires = {
"gym": ["gymnasium>=0.29.1"],
"mcp": ["mcp>=1.26,<2"],
"playground": ["ml-collections"],
},
homepage = "https://github.com/google-deepmind/android_env",
license = "Apache License, Version 2.0",
python_requires = ">=3.10",
requires = [
"absl-py>=2.1.0",
"dm-env>=1.6",
Expand All @@ -87,7 +129,8 @@ py_wheel(
"opencv-python-headless>=4.10.0",
"websocket-client>=1.8.0",
],
summary = "AndroidEnv: A Gym/Gymnasium Environment for Android",
summary = "AndroidEnv: The Android Interaction & Learning Platform",
twine_binary = None,
version = "2.0.0",
deps = [":android_env_pkg"],
)
39 changes: 39 additions & 0 deletions android_env/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,42 @@
# See the License for the specific language governing permissions and
# limitations under the License.

"""AndroidEnv: The Android Interaction & Learning Platform."""

from android_env import env_interface
from android_env import loader
from android_env.components import android_device
from android_env.components import android_device_loader
from android_env.components import config_classes
from android_env.rl import environment as rl_environment

__version__ = '2.0.0'

AdbControllerConfig = config_classes.AdbControllerConfig
AndroidDevice = android_device.AndroidDevice
AndroidDeviceConfig = config_classes.AndroidDeviceConfig
AndroidEnv = rl_environment.AndroidEnv
AndroidEnvConfig = config_classes.AndroidEnvConfig
AndroidEnvInterface = env_interface.AndroidEnvInterface
DeviceConnectionConfig = config_classes.DeviceConnectionConfig
EmulatorLauncherConfig = config_classes.EmulatorLauncherConfig
FilesystemTaskConfig = config_classes.FilesystemTaskConfig
TaskConfig = config_classes.TaskConfig
load = loader.load
load_android_device = android_device_loader.load_android_device

__all__ = (
'__version__',
'AdbControllerConfig',
'AndroidDevice',
'AndroidDeviceConfig',
'AndroidEnv',
'AndroidEnvConfig',
'AndroidEnvInterface',
'DeviceConnectionConfig',
'EmulatorLauncherConfig',
'FilesystemTaskConfig',
'TaskConfig',
'load',
'load_android_device',
)
70 changes: 70 additions & 0 deletions android_env/init_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# coding=utf-8
# Copyright 2026 DeepMind Technologies Limited.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Tests for top-level android_env package exports."""

from absl.testing import absltest
import android_env
from android_env import env_interface
from android_env import loader
from android_env.components import android_device
from android_env.components import android_device_loader
from android_env.components import config_classes
from android_env.rl import environment as rl_environment


class InitTest(absltest.TestCase):

def test_version(self):
self.assertEqual(android_env.__version__, '2.0.0')

def test_public_exports(self):
self.assertIs(
android_env.AdbControllerConfig, config_classes.AdbControllerConfig
)
self.assertIs(android_env.AndroidDevice, android_device.AndroidDevice)
self.assertIs(
android_env.AndroidDeviceConfig, config_classes.AndroidDeviceConfig
)
self.assertIs(android_env.AndroidEnv, rl_environment.AndroidEnv)
self.assertIs(android_env.AndroidEnvConfig, config_classes.AndroidEnvConfig)
self.assertIs(
android_env.AndroidEnvInterface, env_interface.AndroidEnvInterface
)
self.assertIs(
android_env.DeviceConnectionConfig,
config_classes.DeviceConnectionConfig,
)
self.assertIs(
android_env.EmulatorLauncherConfig,
config_classes.EmulatorLauncherConfig,
)
self.assertIs(
android_env.FilesystemTaskConfig, config_classes.FilesystemTaskConfig
)
self.assertIs(android_env.TaskConfig, config_classes.TaskConfig)
self.assertIs(android_env.load, loader.load)
self.assertIs(
android_env.load_android_device,
android_device_loader.load_android_device,
)

def test_all_exports_present_on_module(self):
for symbol in android_env.__all__:
self.assertTrue(hasattr(android_env, symbol), msg=f'Missing {symbol}')


if __name__ == '__main__':
absltest.main()
24 changes: 22 additions & 2 deletions android_env/mcp/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ py_test(
],
)

py_binary(
name = "android_mcp_server",
py_library(
name = "android_mcp_server_lib",
srcs = ["android_mcp_server.py"],
deps = [
":android_mcp_tools",
Expand All @@ -52,3 +52,23 @@ py_binary(
"@pip//mcp",
],
)

py_binary(
name = "android_mcp_server",
srcs = ["android_mcp_server.py"],
deps = [
":android_mcp_server_lib",
],
)

py_test(
name = "android_mcp_server_test",
srcs = ["android_mcp_server_test.py"],
legacy_create_init = False,
deps = [
":android_mcp_server_lib",
"//android_env/components:android_device",
"//android_env/components:config_classes",
"@pip//absl_py",
],
)
8 changes: 7 additions & 1 deletion android_env/mcp/android_mcp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
and interaction.

Usage:
android-env-mcp --serial=emulator-5554
python -m android_env.mcp.android_mcp_server --serial=emulator-5554
"""

Expand Down Expand Up @@ -60,5 +61,10 @@ def main(argv: Sequence[str]) -> None:
mcp_server.run(transport='stdio')


if __name__ == '__main__':
def run() -> None:
"""Console-script entry point for `android-env-mcp`."""
app.run(main)


if __name__ == '__main__':
run()
Loading
Loading