AndroidWorld Environment Project

repository·main·Indexed 21 days ago

https://github.com/google-research/android_world

An environment for building and benchmarking autonomous computer control agents using a live Android emulator. It provides reproducible benchmarks across real-world apps and web-based MiniWoB++ tasks. The project includes tools for agent implementation via the EnvironmentInteractingAgent class, episode management with run_episode(), and result persistence using the Checkpointer interface and IncrementalCheckpointer.

Tokens
7.5K
Snippets
20
Records
27
Agent score
73%

What's inside AndroidWorld

  1. How to create a custom agent

    main

    To implement and test your own agent in AndroidWorld, follow these steps:

    1. Implement the Agent Class: Create a class that inherits from EnvironmentInteractingAgent. You must implement the step method.

      • Inside step, gather information (screenshots, UI elements) via the AndroidEnv instance.
      • Select and execute supported actions.
      • Return an AgentInteractionResult. Set the done property to True when the task is finished.
    2. Register the Agent:

      • Import your agent class into run.py.
      • Add your agent to the _get_agent method in run.py, mapping your agent's name to its instance.
    3. Run the Benchmark: Execute run.py using the --agent_name flag set to your agent's registered name.

  2. Quickstart with minimal_task_runner.py

    main

    To see the basic mechanics of AndroidWorld (initializing the environment, setting up a task, and running the default M3A agent), run the minimal_task_runner.py script.

    If you want to try open-source apps not included with the standard Android OS, you must run the script with the --perform_emulator_setup flag (though this note specifically refers to the run.py workflow, ensure your environment is prepared).

    Note on Model Cost: The script uses gpt-4-turbo-2024-04-09 by default, which can be expensive. You can modify the model_name in the script to use a more cost-effective model.

    python minimal_task_runner.py --task=ContactsAddContact
  3. Install AndroidWorld

    main

    Follow these steps to set up the AndroidWorld environment:

    1. Set up Android Emulator:

      • Download Android Studio.
      • Create an Android Virtual Device (AVD) with these specific settings:
        • Hardware: Pixel 6
        • System Image: Tiramisu, API Level 33
        • AVD Name: AndroidWorldAvd
    2. Launch Emulator via CLI: Launch the emulator using the -grpc 8554 flag (required for accessibility forwarding). Do not use the Android Studio UI.

      # Path varies by OS
      EMULATOR_NAME=AndroidWorldAvd
      ~/Library/Android/sdk/emulator/emulator -avd $EMULATOR_NAME -no-snapshot -grpc 8554
    3. Python Environment Setup (Recommended using conda):

      conda create -n android_world python=3.11.8
      conda activate android_world
    4. Install AndroidWorld Package:

      git clone https://github.com/google-research/android_world.git
      cd ./android_world
      pip install -r requirements.txt
      python setup.py install
    5. Configure API Keys: Set your model provider keys as environment variables (e.g., in .bashrc):

      export OPENAI_API_KEY=your-key
      export GCP_API_KEY=your-key
    6. Install ffmpeg:

      • Linux (Ubuntu/Debian): sudo apt update && sudo apt install ffmpeg
      • macOS: brew install ffmpeg
    # Example launch command
    EMULATOR_NAME=AndroidWorldAvd
    ~/Library/Android/sdk/emulator/emulator -avd $EMULATOR_NAME -no-snapshot -grpc 8554
  4. Create and validate a new task using file system storage

    main

    For apps using file system storage, extend task_eval.TaskEval and use file_validators for verification.

    1. Define the Task Class: Inherit from task_eval.TaskEval.
    2. Configure Metadata: Set app_names, complexity, schema (e.g., file_validators.CreateFile.schema), and a template string.
    3. Initialize Validators: In __init__, instantiate a validator (e.g., file_validators.CreateFile) passing the params and the app's data directory (e.g., device_constants.MARKOR_DATA).
    4. Lifecycle Methods: Implement initialize_task, is_successful, and tear_down by delegating to the validator's corresponding methods.
    5. Parameter Generation: Implement generate_random_params to provide the necessary data for the task template.
    class MarkorCreateNote(task_eval.TaskEval):
      app_names = ("markor",)
      complexity = 2
      schema = file_validators.CreateFile.schema
      template = "Create a new note in Markor named {file_name} with the following text: {text}"
    
      def __init__(self, params: dict[str, Any]):
        super().__init__(params)
        self.create_file_task = file_validators.CreateFile(
            params, device_constants.MARKOR_DATA
        )
    
      def initialize_task(self, env: interface.AsyncEnv) -> None:
        super().initialize_task(env)
        self.create_file_task.initialize_task(env)
    
      def is_successful(self, env: interface.AsyncEnv) -> float:
        super().is_successful(env)
        return self.create_file_task.is_successful(env)
    
      @classmethod
      def generate_random_params(cls) -> dict[str, str | int]:
        return {"file_name": _generate_random_file_name(), "text": _generate_random_file_text()}
    
      def tear_down(self, env: interface.AsyncEnv) -> None:
        super().tear_down(env)
        self.create_file_task.tear_down(env)
  5. Run the AndroidWorld Benchmark

    main

    Use run.py to execute the benchmark suite.

    Important: The first time you run this, you must include --perform_emulator_setup to install necessary apps and set permissions. This is a one-time setup.

    Key Arguments:

    • --suite_family: Set to android_world for standard tasks or miniwob for web-based tasks.
    • --agent_name: The name of the agent to evaluate (e.g., t3a_gpt4).
    • --tasks: (Optional) A comma-separated list of specific tasks to run (e.g., ContactsAddContact,ClockStopWatchRunning). If omitted, the entire suite runs.
    • --perform_emulator_setup: Required for the initial app/permission setup.
    • --checkpoint_dir: Use this to resume a failed run by pointing to the original output directory.

    Running MiniWoB++: Set --suite_family=miniwob. These tasks render common web elements as native Android UI widgets (like time-pickers) to test agent generalization.

    python run.py \
      --suite_family=android_world \
      --agent_name=t3a_gpt4 \
      --perform_emulator_setup \
      --tasks=ContactsAddContact,ClockStopWatchRunning
  6. Determine how an app stores its data

    main

    Before creating a new task, identify if the target application uses SQLite or the file system for data storage using ADB commands.

    1. Access the app's data directory:

      adb shell ls data/data/<package_name>/

      Replace <package_name> with the actual package name (e.g., com.simplemobiletools.calendar.pro).

    2. Identify SQLite usage: Look for a databases folder. If it contains .db files, the app uses SQLite.

      adb shell ls data/data/<package_name>/databases/
    3. Identify File System usage: If no databases folder exists, or if data is stored elsewhere, look for a files directory containing .txt, .json, or other data files.

      adb shell ls data/data/<package_name>/files/
    adb shell ls data/data/<package_name>/
  7. Create and validate a new task using SQLite

    main

    To extend AndroidWorld with SQLite-based tasks, follow these steps to leverage the sqlite_validators abstractions.

    1. Define a Data Class: Create a @dataclasses.dataclass(frozen=True) that mirrors the SQLite table structure.
    2. Create a Base Task Class: Inherit from sqlite_validators.SQLiteApp. Specify app_name_with_db, app_names, db_key, db_path, table_name, and row_type (your data class).
    3. Implement Task Logic: Create a class that extends both your base class and sqlite_validators.AddMultipleRows. Define:
      • complexity: Integer value.
      • template: A string with placeholders for task parameters.
      • _get_random_target_row(): Returns a target data instance.
      • validate_addition_integrity(): Uses sqlite_validators.validate_rows_addition_integrity to compare before, after, and reference_rows using specific compare_fields.
      • generate_random_params(): Returns a dictionary containing task parameters and special keys like sqlite_validators.ROW_OBJECTS and sqlite_validators.NOISE_ROW_OBJECTS.

    Note: AndroidWorld automatically handles state via initialize_state and tear_down.

    @dataclasses.dataclass(frozen=True)
    class CalendarEvent:
      start_ts: int
      end_ts: int
      title: str
      location: str = ''
      description: str = ''
      repeat_interval: int = 0
      repeat_rule: int = 0
    
    class _SimpleCalendar(sqlite_validators.SQLiteApp):
      app_name_with_db = "simple calendar pro"
      app_names = ("simple calendar pro",)
      db_key = "id"
      db_path = "data/data/com.simplemobiletools.calendar.pro/databases/events.db"
      table_name = "events"
      row_type = CalendarEvent
    
    class SimpleCalendarAddOneEvent(sqlite_validators.AddMultipleRows, _SimpleCalendar):
      complexity = 2
      template = "In Simple Calendar Pro, create a calendar event on {year}-{month}-{day}..."
    
      def validate_addition_integrity(self, before, after, reference_rows) -> bool:
         return sqlite_validators.validate_rows_addition_integrity(
              before, after, reference_rows,
              compare_fields=['start_ts', 'end_ts', 'title', 'location', 'description']
          )
  8. Explore an app's internal structure

    main

    Once the storage type is identified, use ADB to inspect the schema or file contents.

    Exploring SQLite databases

    • View Schema: Examine the structure of a specific table (columns and types).
      adb shell "sqlite3 data/data/<package_name>/databases/<db_name>.db '.schema <table_name>'"
    • Query Data: Retrieve records to understand the data format.
      adb shell "sqlite3 data/data/<package_name>/databases/<db_name>.db 'SELECT * FROM <table_name>;'"

    Exploring File System storage

    • Inspect Contents: View the content of a specific file.
      adb shell cat data/data/<package_name>/files/<file_name>.txt
    • Download Files: Pull files to your local machine for advanced analysis.
      adb pull data/data/<package_name>/files/<file_name>.txt /local/directory/
  9. Use AndroidWorld with Docker (Experimental)

    main

    Docker support allows you to run the Android environment and server within a container for a consistent environment.

    1. Build the image:

      docker build -t android_world:latest .
    2. Run the container:

      docker run --privileged -p 5000:5000 -it android_world:latest

      This starts the emulator and a FastAPI server at http://localhost:5000.

    3. Interact: Use scripts/run_suite_on_docker.py as an example client.

    Apple Silicon Users: To avoid performance issues or installation errors on ARM chips, build for the amd64 platform:

    docker buildx build --platform linux/amd64 -t android-emulator:latest .
    docker build -t android_world:latest .
    docker run --privileged -p 5000:5000 -it android_world:latest
  10. Understand the Checkpointer interface

    main

    The Checkpointer is an abstract base class (ABC) that defines the contract for saving and loading evaluation results. Any custom checkpointing implementation must implement:

    • save_episodes(task_episodes: list[Episode], task_name: str) -> None
    • load(fields: list[str] | None = None) -> list[Episode]

    An Episode is defined as a dict[str, Any].

  11. How the Suite and Task instantiation works

    main

    In AndroidWorld, a single task type (e.g., GoogleSearchTask) can be evaluated multiple times with different parameters to ensure robustness.

    1. Task Registry: A mapping of names to TaskEval classes.
    2. Instantiation: When create_suite is called, it iterates through the registry. For each task, it calls _instantiate_task multiple times (n_task_combinations).
    3. Parameter Generation: If specific params are not provided, the system calls task.generate_random_params() to create unique settings for that instance. A seed can be provided to make this parameter generation deterministic.
    4. Suite Structure: The resulting Suite object groups these instances by their task name, allowing for organized evaluation of task families.
  12. Process suite results with `process_episodes`

    main

    Use process_episodes to aggregate and summarize the results returned by run (or _run_task_suite). It converts raw episode metadata into a structured pandas.DataFrame containing metrics like success rates, episode lengths, and runtimes.

    If print_summary=True, it prints a formatted table to the console, including an '=== Average ===' row and a breakdown of success rates by task tags and difficulty levels.

    Input format expected: A list of dictionaries where each dictionary contains keys like task_template, is_successful, episode_length, run_time, etc.

    from android_world.suite_utils import process_episodes
    
    # results comes from run()
    summary_df = process_episodes(results, print_summary=True)