# FIRERPA / lamda Documentation (llms-full.txt)
> This file contains the complete documentation for FIRERPA / lamda, concatenated for LLM processing.
--- START OF CONTENT ---
--- DOCUMENT: intro.md ---
--- SOURCE: https://device-farm.com/docs/content/en/intro.md ---
# Intro
FIRERPA is an **all-in-one device control platform** for Android. The server runs directly on the device, **requiring no extra runtime dependencies**, and **works across multiple Android generations, with or without root**. The PC side orchestrates everything through a Python client library, consolidating capabilities such as UI automation, remote operations, traffic capture, hook-based reverse engineering, network proxy, distributed networking, AI Agent / **MCP** and more into a single service and API. Compared with piecing together self-written scripts and various operation tools like Appium, mitmproxy, frida-server, adb, uiautomator2, FIRERPA’s advantage lies in: **same origin capabilities, unified configuration, connected chains, and suitability for long-term multi-device operation and engineering management**.
```{hint}
This document introduces the platform positioning and core capabilities of FIRERPA. To install and get started, continue reading the [Quick Start](./quick-start.md) chapter.
```
## Remote Desktop & Real‑time Screen Casting
FIRERPA provides a browser‑based remote desktop that lets you view and control the device screen in real time without installing dedicated clients on the PC, supporting both LAN and cross‑network access. The screen casting pipeline supports both **MJPEG** and **H.264** encoding, with software and hardware encoding backends. You can adjust frame rate (up to 60 fps), resolution scaling, bit rate and quality according to device compute power and network conditions, significantly reducing bandwidth and improving smoothness in weak networks. It also supports **WebRTC** transport with configurable STUN/TURN servers to improve NAT traversal and end‑to‑end latency, making it suitable for public‑network and cross‑region remote debugging.
Remote desktop supports **concurrent multi‑user access**, facilitating collaborative troubleshooting, demonstrations, and training. It provides **bidirectional clipboard** sharing and **real‑time audio** streaming (Android 10+), and integrates operations like terminal, drag‑and‑drop file upload, directory browsing and download within the same web interface — daily operations can be done over a single port. The built‑in **visual layout inspector** highlights elements, enables Tab‑key traversal, shows coordinates and RGB values, and exports the XML layout tree. You can verify selectors and automation logic directly inside the remote desktop, drastically shortening the “look at the UI → write script” loop.
The remote desktop and RPC interfaces support **TLS end‑to‑end encryption** and service certificate access control, and you can define a custom WebUI login password to reduce exposure risks. Remote desktop capabilities can also be embedded into your own web application via WebSocket (real‑time video, touch, terminal, keys, etc.) and integrated cross‑origin through an `allow_origin` configuration, making it ideal for productizing real‑device control.
## UI Automation
FIRERPA provides a complete selector‑based automation system that supports common matching methods like text, resourceId, description, scrollable, and also **child / sibling** chain positioning to handle repetitive elements, complex hierarchies, or controls without distinct features. At the element level it supports fine‑grained operations such as screenshot, wait for appearance/disappearance, corner/center coordinate retrieval, and exists checks. It supports Unicode input, step‑based swipes, fling for quick page turning, scroll‑to‑bottom, and other interactions. It can **coexist with other accessibility services** on Android 8.0+ and enhances WebView node discovery, boosting automation success rates in hybrid apps.
**Watcher** can monitor UI changes in real time and automatically perform clicks, key presses, or counting when predefined conditions are met — useful for automatically dismissing protocol dialogs, update prompts, ad pages, and other interrupting flows. It supports combining multiple selector conditions, independent start/stop of watchers, and counting how many times a fleeting screen appears.
**Virtual Display** is one of FIRERPA’s differentiating features: you can create an independent background display on the device, run apps and automation scripts inside the virtual screen **without affecting normal use of the main screen** — for example, the virtual screen auto‑processes messages in the background while you watch videos or manually operate on the main screen. The virtual screen API is highly consistent with the main device API; existing `d.xxx` scripts can be smoothly switched to `vd.xxx`. Watchers can also be limited to a virtual display. The WebUI supports **multi‑screen display and switching**, allowing you to operate or view different Displays from the remote desktop, ideal for background keep‑alive, parallel tasks, and “foreground manual + background machine” hybrid scenarios.
For scenarios without a standard View tree (e.g., games, custom‑drawn UIs), FIRERPA provides **OCR** and **image matching** capabilities. OCR supports paddleocr, easyocr, and custom HTTP backends. In cluster deployments, recognition compute can be centralized on a server to avoid reloading models on each PC, with GPU acceleration and multiple text matching modes. Image matching runs **on the device** using template matching or SIFT, consuming no PC resources; SIFT is more robust to rotation, scaling, and lighting changes. It also offers **multi‑finger touch** capabilities, supporting trajectory recording, replay, code construction, and binary persistence, allowing expression of complex gestures and pressure values.
Architecturally, FIRERPA adopts a C/S model. Compared with solutions like AutoJS that run scripts solely on the device, FIRERPA is better suited for **centralized scheduling, version management, and cluster control**. It is more lightweight than Appium and offers **better stability** than uiautomator2 in multi‑device scenarios. FIRERPA is positioned as a **functional superset** of common Android automation approaches — automation, packet capture, hooking, and operations can be chained with code on the same platform, rather than switching between multiple tools.
## Packet Capture & MITM
FIRERPA offers **one‑click MITM packet capture**: automatically installs system‑level CA certificates, configures the proxy, handles Android version differences, and automatically restores the device network state on exit — no manual certificate installation or proxy switching required. It supports global capture, per‑package capture, real‑time packet modification, a shared mitmweb interface, and can route **international traffic** through a specified upstream HTTP proxy for capture. Built‑in **QUIC downgrade** reduces QUIC’s impact on capture. Even when PC and device can only communicate through a single FIRERPA port (e.g., ADB connect, frp forwarding), capture can still be performed, adapting to harsh network topologies.
Beyond one‑click scripts, all MITM‑related capabilities are also accessible through APIs: system‑level CA installation/removal works with formats from mitmproxy, Fiddler, Charles, etc. Capture flows can be embedded into automation pipelines and completed in the same task alongside UI operations and Frida hooks. A Windows `startmitm.exe` is provided without requiring Python, lowering the environment barrier.
## Networking, Proxy & Mesh
FIRERPA provides complete proxy capabilities on the device side, supporting **HTTP / HTTPS / SOCKS5 / Shadowsocks** (multiple encryption algorithms). It can proxy per app (including multi‑profile users), supports DNS proxy, UDP proxy, LAN bypass, coexistence with OpenVPN, and can **auto‑connect** a preset proxy when the service starts. Proxy capabilities support **IPv6 and UDP**, adapting to more complex network environments.
**Device HTTP bridge proxy (tunnel2)** provides reverse proxy: after pointing your PC or browser’s HTTP proxy to the phone, traffic is forwarded through the phone’s network exit, making the external IP the same as the phone’s, with optional Wi‑Fi or 4G (rmnet) as the outbound interface. This suits joint debugging, international business, multi‑device IP pools, etc.
Built‑in **OpenVPN client** supports certificate, username/password, and mixed authentication modes and can coexist with the system proxy. An **OpenVPN Docker image** and configuration generation script are provided to automatically produce device‑side connection code and auto‑start configuration, reducing errors when writing OpenVPN configs manually. A built‑in **frp client** can forward device services to a public server with `fwd.*` configuration, supporting encryption and TLS.
**mDNS service discovery** is provided: when enabled, services like remote desktop can be accessed via `{device_id}.local` domain names, with optional TXT metadata (model, ABI, device ID, etc.) for easy bulk discovery and management on the LAN.
## Frida & Reverse Engineering
FIRERPA **includes the latest Frida built‑in**, so there’s no need to deploy frida‑server separately. Frida commands can be used directly in the remote desktop terminal without cumbersome `-U`, `-H` flags. It integrates open‑source hiding patches and proprietary stealth capabilities, with continuous iteration to counter detection. Connections use token authentication and are securely accessed through the FIRERPA port. Objection patches and a Frida 17.x java‑bridge wrapper tool are provided to reduce migration costs caused by official changes.
**Frida script persistence** is supported: scripts can be automatically re‑injected after an app crashes or exits. YAML offline persistence, directory monitoring, and hot‑reload are supported. Frida RPC can be **mapped as Python methods** and supports **HTTP + JSON‑RPC 2.0** remote invocation, executing in JVM threads, UI main thread, or pure JS contexts. Data emitted by a script via `emit` can be reported to **HTTP / Redis / RabbitMQ (MQTT)**, carrying device and app metadata, with support for zlib compression, HTTP retry, and MQTT TLS.
## ADB, SSH & Built-in Terminal
FIRERPA provides a **built‑in ADB service independent of the system ADB**, allowing high‑privilege ADB connections over the network **without enabling Developer Mode**, helping to avoid “Developer options” type detections. A Magisk module can pre‑install `adb_keys` for default authorization. A built‑in **SSH service** and `ssh.sh` / `scp.sh` helper scripts facilitate remote shell and file transfer.
The **built‑in terminal** comes pre‑installed with common analysis tools like strace, ltrace, tcpdump, scapy, fsmon, frida‑tools, MemDumper, and integrates Python ecosystem libraries such as Crypto, cv2, unicorn, capstone, keystone, redis, grpc. sqlite3 supports **wxsqlite / sqlcipher / sqlcrypto**, providing a path to read encrypted databases like those of WeChat, WeCom, Alibaba apps, etc. The built‑in terminal is unified with remote desktop, SSH, and ADB, allowing analysis and self‑control to be performed directly on the device, reducing back‑and‑forth between PC and device.
## Distributed & Multi‑Device Management
FIRERPA’s **Starlink Platform (Hub + hub-bridge)** provides a centralized multi‑device access solution, supporting unified management and **P2P** access for both local and remote devices, ideal for “device at home, person away” and multi‑machine collaboration. Hub v3 uses **SAPI** for unified requests and can connect to both local and remote devices simultaneously. Besides Hub, two additional self‑hosted solutions — **frp forwarding** and **OpenVPN networking** — are supported, with complete documentation and Docker accompaniments.
Since v9.0, **P2P Bridge endpoint interconnection** and a **built‑in distributed task system** are supported; since v7.50, a networking subscription service is available, eliminating the need to self‑host frp/OpenVPN. The overall design targets **bulk device, always‑online, unattended** scenarios such as cloud phone pools, real device farms, and emulator clusters.
## AI / MCP / Agent
FIRERPA **includes an MCP server** (API path `/mcp/`), using the **streamable-http** protocol, supporting tool calls, resource read, prompts, progress notifications, and logging. It can integrate with MCP clients such as **Claude, Cursor**, etc. Official MCP extension modules are provided, and custom plugins can be placed under `~/modules/extension`.
The built‑in **`agent` command** connects to any OpenAI‑compatible API + tool call, driving device operations in **natural language**, with **Vision** mode support. An **OpenAI semantic task executor** is also provided, which can be combined with **crontab** to schedule AI tasks, embedding large model capabilities into real‑device automation pipelines.
## System‑Level Control & Extensions
FIRERPA offers **160+ Python APIs** covering app operations, file I/O, command execution, device status, WiFi, proxy, OpenVPN, shutdown/reboot, service logs, and more. `setprop` can write **`ro.*`** read‑only system properties; the Settings API can read/write brightness, developer mode, and other system settings. **SELinux** support includes domain creation, permissive toggling, and fine‑grained allow/disallow rules. The full API works with multi‑profile apps, silent permission granting/revoking, launching unexported activities, and disabling apps to save resources.
**Binary patching** (hex wildcards, glob paths, dry‑run), along with collection of **runtime metrics** such as disk, battery, CPU, memory, network I/O, are provided. WAV audio playback and internal storage APIs are supported. **tflite‑runtime** is integrated on the device and can use hardware acceleration for on‑device inference.
A **virtual Debian environment** can run a full Debian distribution with apt inside the device, allowing compilation of BPF, deployment of OpenSSH, and execution of arbitrary Python dependencies, breaking the limitation that the built‑in terminal cannot use pip/apt.
## Deployment, Security & Governance
FIRERPA supports multiple deployment methods: **one‑click APP deployment** (root or Shizuku), **auto‑start Magisk module**, manual extraction, and **ROM integration** — all designed for **7×24** always‑on operation. The APP can **auto‑generate service certificates**; Magisk zip files can pre‑package properties configuration, lamda.pem certificate, and adb_keys. All behavior is centrally managed through an **ini‑format service configuration**: port, WebUI parameters, certificates, OpenVPN, gproxy, frp, tunnel2, cron, sshd, adb, mdns, etc. The WebUI supports visual editing, raw text mode, and hot‑reload of services via “Reload Service”, as well as loading configurations from a remote file server.
Dual‑identity operation (root and shell) is supported. It covers arm64, armeabi‑v7a, x86, x86_64 architectures and is compatible with real devices, emulators like LDPlayer/Nox, Redroid, WSA, AVD, cloud phones, and other environments. Uninstall follows a clean path, leaving no junk files scattered across the system. An **API exclusive lock** prevents multiple clients from competing for the same device. The privacy policy clearly states that no personal private data such as contacts, SMS, or location is collected, and **offline authorization** is supported.
## Continuous Evolution
FIRERPA has been continuously evolving for over **6 years**, covering Android 14–16 compatibility fixes, with ongoing iteration in areas like multi‑user remote desktop, standardized touch API, MCP protocol, Frida versioning and stealth, proxy IPv6/UDP, P2P Bridge, distributed tasks, and more.
FIRERPA’s core advantage is not a single feature, but the integration of **remote desktop (WebRTC/H.264/multi‑user), UI automation (including Virtual Display, Watcher, OCR, image matching, multi‑finger touch), one‑click capture, built‑in Frida, full‑stack proxy and VPN/frp/P2P networking, ADB/SSH/built‑in terminal, AI MCP/Agent, system‑level control, virtual Debian, persistent KV, script encryption** and more on the same Android device, within the same service and the same Python client. This allows security testing, compliance analysis, protocol reconstruction, bulk management, and automation to be completed in **one pipeline** rather than repeatedly stitching together multiple tools.
--- END OF intro.md ---
--- DOCUMENT: quick-start.md ---
--- SOURCE: https://device-farm.com/docs/content/en/quick-start.md ---
# Quick Start
This article guides you through getting started with FIRERPA in the simplest way possible. Before you begin, make sure you have an Android phone. To take full advantage of all FIRERPA features, it is recommended that your Android device has root permissions. Of course, FIRERPA also supports running under shell (non-root) permissions. Some privileged features will be limited, but you can still use most of the main features.
```{hint}
Please ensure that you are using the latest version of the relevant software. The documentation content is progressive, and it is recommended that you read from Chapter 1 in order.
```
To simplify your usage, before you begin, you need to determine which environment your device has and treat them accordingly.
## With Root Permissions
Please click the link to download our service application [firerpa.apk](/assets/apk/firerpa.apk). After installation, open the APP and grant the application `root` permissions, then wait for the service download and installation to complete. Click the start service button. If you need the service to start on boot, enable the auto-start switch.
## Without Root Permissions
If your device does not have root permissions or you are not sure what root is, you need to install the service using the following method. You still need to install our service application first
[firerpa.apk](/assets/apk/firerpa.apk). After installation, download [Shizuku.apk](/assets/apk/shizuku-v13.6.0.r1086.2650830c-release.apk) and install it. After installation, refer to the official documentation [Shizuku User Manual](https://shizuku.rikka.app/guide/setup/) and [How to unlock developer options on an Android Phone](https://www.ifixit.com/Guide/How+to+unlock+developer+options+on+an+Android+Phone/152630) to complete the service startup and setup. Then, reopen our app, wait for the service download and installation to complete, ensure Shizuku is authorized, and after everything is done, click the start service button.
```{attention}
For some device models, the default auto-start behavior of the APP may be blocked by the system. In this case, you may need to find relevant settings to allow our APP to auto-start or add our app to the auto-start whitelist; otherwise, the service may not start normally after a reboot.
```
If everything is normal, you will see the above app interface after opening the APP. You can click to copy the Console URL and enter that address in your computer browser, which will give you access to the remote desktop shown below. At this point, you have completed the installation.
```{hint}
When in doubt, read the documentation first; if still clueless, join the group to ask questions. You can find contact information in the [Technical Support](./tech-support.md) section.
```
Of course, this is just our most basic installation operation and remote desktop feature. The features we provide go far beyond this. FIRERPA supports not only APP deployment but also manual or ROM built-in deployment. There are more APIs and tools for you to use. Continue exploring the subsequent documentation to learn more!
--- END OF quick-start.md ---
--- DOCUMENT: basics.md ---
--- SOURCE: https://device-farm.com/docs/content/en/basics.md ---
# Basic Knowledge
This chapter introduces the fundamental knowledge related to Android automation. Please be sure to read this chapter carefully, as it will not be repeated later. Android automation differs significantly from conventional web automation, but they also share many similarities. In conventional web automation, you can easily view the page layout, element IDs, and other information through the F12 developer tools, then use XPath to locate elements for clicks, waits, and other operations. The logic for the Android environment is similar; you can also select elements using something called a selector and perform clicks, judgments, and other operations, so you don't need to worry about it being difficult to get started.
## Similarities and Differences Between Mobile and Web Automation
There are many similarities between mobile and web automation, as well as some differences. Let's take Selenium as an example: usually, three things are needed to control a web page with Selenium: first, a browser; second, a WebDriver; third, Selenium. The mobile end is analogous: the phone acts as the browser, FIRERPA acts as the WebDriver, and FIRERPA's Python client library `lamda` acts as Selenium. They all share the same goal: to simulate user operations for testing, data collection, or automated task execution. They all drive through scripts, locating elements, clicking, taking screenshots, making judgments, and so on. From this perspective, they are quite similar.
However, they also have differences. First, mobile automation requires a phone and a computer, while web automation can be done on your own computer. Second, they use different sets of tools. Common web tools include Selenium, Puppeteer, and Playwright; common mobile tools include FIRERPA, AutoJS, Appium, uiautomator2, etc.
For web, common element location methods are mainly XPath or CSS selectors based on the HTML DOM structure, and the element hierarchy is relatively intuitive. For mobile, the common location method is the selector; of course, Android application interfaces also use XML layouts, so you can also use XPath via XML. Generally, web automation doesn't require much consideration of compatibility issues; in most cases, fixing the browser version and launch resolution can resolve compatibility problems caused by device differences. But for Android, differences in brand, model, screen size, system version, etc., may affect the compatibility of automation code. However, don't worry too much—while there is an impact, it is limited.
## Differences Between Various Automation Tools
As mentioned earlier, there are also significant differences among the commonly used Android automation tools we cited. First, let us state our position: FIRERPA is the most stable, feature-complete, powerful, and most suitable for project-based management and application among all automation tools.
```{note}
Our position is not biased; it is formed through 6 years of continuous exploration and optimization. We have basically experienced the routes you have taken and the pitfalls you have encountered.
```
::: tabs
@tab AutoJS
Common AutoJS and its derivative products belong to the "self-control" category, requiring an APK to be installed on the device and scripts written in JavaScript to perform operations. AutoJS is generally only capable of automated operations, with the advantage of being suitable for beginners or hobbyist use, with a low entry barrier. However, its design is not suitable for large-scale script control, management, and updating, and it exists in a decentralized, unmanaged state, making precise large-scale control impossible.
@tab Appium
Appium, commonly used by testers, adopts a C/S architecture and is more suitable for cluster control than AutoJS. But it has obvious disadvantages: due to the need to support multiple platforms (including Android and iOS), it is bloated and very unsuitable for large-scale deployment.
@tab u2
Finally, uiautomator2 also adopts a C/S architecture, is more streamlined than Appium, and its functionality is just right. However, we ultimately abandoned it mainly because it is not stable enough in multi-device scenarios; secondly, although its automatic installation logic is friendly to beginners, it becomes redundant and difficult to control in professional cluster control, and lacks maintenance.
Of course, they are all suitable for regular use. But in business, we often go beyond just automation. For example, when testing a certain APP, you need to record requests, responses, request times, etc. Think about how you would do it? Your solution might involve a lot of additional manual operations, or face instability and compatibility issues. In the world of FIRERPA, all operations can be done with code; you only need to focus on the code, and stability and compatibility are handled by FIRERPA. Actually, FIRERPA should not be simply compared with other tools, because it is functionally a superset of all the above solutions, covering all the pitfalls and journeys we have experienced.
:::
## Basic Automation Process
Usually, you need to first research the solution: whether to perform only conventional automation, or to obtain application runtime data while automating. There are usually two data acquisition methods: first, intercepting HTTP/S communication via a man-in-the-middle approach; second, intercepting data via hooking. The man-in-the-middle method is relatively simple and suitable for general use, but may not work for some applications. The hooking method requires significant reverse engineering knowledge, is difficult for beginners, and is suitable for edge cases.
### Man-in-the-Middle Data Acquisition
The man-in-the-middle approach is relatively simple. You just need to refer to the documentation for [Installing the MITM Certificate](./install-mitm-cert.md) and [Setting Up the Proxy](./set-proxy.md), and use them with mitmproxy to achieve this. If you're not sure, you can refer to the official `startmitm.py` script, which already contains all the logic, ready for you to copy or reuse.
### Hook Data Acquisition
The hooking method requires at least a beginner level of reverse engineering ability. If you haven't encountered it before, you can put it aside for now. In general, the hooking approach involves writing Frida scripts to hook relevant function calls, capture parameters or return values and submit them, then inject them into the application, and so on. You can find simple demos and usage in the [Using Frida to Report Data](./frida-report.md) chapter.
### Automation Code
Automation code is also indispensable, because you need automation to trigger the relevant logic. Writing automation code generally follows this process. First, open FIRERPA's remote desktop, and you will see the following interface.

Now, open the APP you want to automate, then click the "eye" icon in the upper right corner of the remote desktop. You will see the interface below. At this point, select the element you want to operate, and click to view the element information.
```{tip}
Of course, you can also open it through code; we'll cover that later.
```

You can see the element information on the right, such as text, resourceId, etc. Suppose we want to click this element; you can write code like the following, which means "click the element with text '同意' (Agree)".
```python
d(text="同意").click()
```
```{note}
This is just an example; there are many ways to write selectors. Here we only introduce the simplest one.
```
Alright, now you know the simplest way to write it. Next, by writing control logic like if/else, combined with interfaces like `exists`, you can implement a complete set of automated operation processes. See, it's not that difficult.
## Interface Layout Inspection
Under normal circumstances, writing automation code cannot be done without interface layout inspection, which is also the only way to obtain selector conditions. First, open the device's remote desktop in a browser. Then click the eye icon in the upper right corner of the remote desktop to enter layout inspection. At this point, you can click on the dashed boxes on the left screen to view the corresponding element information, using its attributes as parameters for the selector. Clicking the eye icon again will close layout inspection. Please note that layout inspection does not automatically refresh with page changes; it always stays on the screen layout at the moment you entered inspection. If you need a refresh, manually press the shortcut `Ctrl + R`.

```{hint}
You can also press the Tab key in the layout inspection interface to cycle through all elements.
```
## Interface Selector
The interface selector (Selector) is used to manipulate Android elements. You can think of it as similar to XPath rules; although the mechanism is different, the purpose is similar. In FIRERPA, the selector class is `Selector`, but in most cases you won't need to interact with it directly. In the previous text, you have already seen its usage. A complete Selector includes the following optional parameters.
| Match Type | Description |
|-------------------------|----------------------------|
| text | Exact text match |
| textContains | Text contains match |
| textStartsWith | Text starts with match |
| className | Class name match |
| description | Exact description match |
| descriptionContains | Description contains match |
| descriptionStartsWith | Description starts with match |
| clickable | Is clickable |
| longClickable | Is long-clickable |
| scrollable | Is scrollable |
| resourceId | Resource ID match |
In most cases, only resourceId, text, description, textContains, etc., are used as parameters. If the element has a proper resourceId, you should use it as the Selector preferentially, e.g., `d(resourceId="com.xxx:id/mobile_signal")`; otherwise, you can use text, e.g., `d(text="点击进入")`, or a fuzzier one like `d(textContains="点击")`. description is similar to text, but description is used less frequently.
```{hint}
A Selector is composed of the main parameters you obtain through the interface layout inspection feature described above.
```
## Screen Coordinate Definition
In the process of automation, there will inevitably be situations where you need to operate based on precise coordinates or area coordinates. You might not be familiar with the concept of coordinates yet, so here we introduce knowledge about Android screen coordinates. Just like an image, a screen has a resolution. For Android screens, whether in portrait, landscape, or auto-rotate mode, the top-left corner is uniformly taken as the origin (0,0), the right direction is the positive direction of the X-axis, and the downward direction is the positive direction of the Y-axis, forming a coordinate system, as shown in the figure.
From the diagram, we can see that the top-left corner is (0,0), top-right is (1080,0), bottom-left is (0,1920), and bottom-right is (1080,1920). You can calculate the coordinates of any point on the screen based on this information.
```{note}
Regardless of whether the screen's native orientation is portrait, landscape, or auto-rotate, the top-left corner of the current orientation is uniformly used as the origin.
```
### Points on the Screen
In FIRERPA, some operations (such as clicking or taking screenshots) require you to provide area or coordinate information. For common coordinate points, we use the following definition, which represents a point on the screen with coordinates (100,100).
```python
Point(x=100, y=100)
```
### Definition of an Area
An area refers to a rectangular region on the screen. Its definition is a bit more complex; please read carefully. We use `Bound` to represent an area on the screen, which requires four parameters: `top`, `left`, `bottom`, `right`. You might be a bit confused; please be sure to understand carefully: `top` represents the pixel distance from the top edge of the rectangle to the top of the screen, `left` represents the pixel distance from the left edge of the rectangle to the left of the screen, `right` represents the distance from the right edge of the rectangle to the left of the screen, and `bottom` represents the distance from the bottom edge of the rectangle to the top of the screen. In short, all distances are X/Y axis distances based on the top-left corner origin of the screen. The following figure helps to understand; the phone screen is still 1080x1920, currently in portrait mode.
Now suppose the screen is divided into four equal parts, and we need to get the definitions of the top-left and bottom-right areas shown in the figure. According to the rules, Area 1: the distance from the top of the rectangle to the screen top is 0 pixels, the distance from the left to the screen left is 0 pixels, the distance from the bottom to the screen top is 960 pixels (1920÷2), and the distance from the right to the screen left is 540 pixels (1080÷2), so its definition is
```python
Bound(top=0, left=0, right=540, bottom=960)
```
Similarly, Area 2: the distance from the top of the rectangle to the screen top is 960 pixels, the left to the screen left is 540 pixels, the right to the screen left is 1080 pixels, and the bottom to the screen top is 1920 pixels, so its definition is
```python
Bound(top=960, left=540, right=1080, bottom=1920)
```
## Data of Android Applications
Every Android application has its dedicated data directory on the device. Usually, application data is stored under the /data directory. You can obtain the application's data directory by calling the `d.application("com.example").info()` interface. In most cases, you can also directly `cd` to `/data/user/0/com.example.test` to enter the user directory. Besides /data, some applications also store multimedia and other files in the /sdcard/Android directory.
### Viewing the SMS Database
Sometimes, you may want to check where the SMS received by the device is stored. This is very simple; you can even write an extension to read the content directly and get it in real time via an HTTP interface! We will introduce it in the conventional Android way; if your situation is different, please expand accordingly. On Android, the package name of the SMS app is usually `com.android.mms`, so you can switch to the directory `/data/user/0/com.android.mms`. Through the following operations, you will see several databases under the `databases` directory, among which `mmssms.db` is the one we are looking for.
```text
λ 10:12 /data/user/0/com.android.mms ➥ ls -la
total 82
drwx------ 7 u0_a78 u0_a78 3452 Jan 2 2021 .
drwxrwx--x 381 system system 53248 May 2 16:46 ..
drwxrws--x 3 u0_a78 u0_a78_c 3452 Jan 2 2021 cache
drwxrws--x 2 u0_a78 u0_a78_c 3452 Jan 2 2021 code_cache
drwxrwx--x 2 u0_a78 u0_a78 3452 Jan 2 2021 databases
drwxrwx--x 7 u0_a78 u0_a78 24576 Feb 26 13:43 files
drwxrwx--x 2 u0_a78 u0_a78 3452 May 4 10:12 shared_prefs
λ 10:12 /data/user/0/com.android.mms ➥ ls -l databases/
total 504
-rw-rw---- 1 u0_a78 u0_a78 24576 Jan 2 2021 dynamic_bubble
-rw------- 1 u0_a78 u0_a78 0 Jan 2 2021 dynamic_bubble-journal
-rw-rw---- 1 u0_a78 u0_a78 491520 Feb 27 04:18 mmssms.db
-rw------- 1 u0_a78 u0_a78 0 Jan 2 2021 mmssms.db-journal
λ 10:12 /data/user/0/com.android.mms ➥
```
Reading is very simple, because conventional application databases on Android are all SQLite. However, applications with higher security often encrypt their databases. But FIRERPA is powerful; besides supporting regular SQLite, it also supports **real-time reading** of WeChat (SqlCipher) AES-256, WeCom AES-128, Alibaba series SqlCrypto (AES-128) and other encrypted databases (provided you find the key yourself). Below we demonstrate a simple read of system SMS content; one command is enough, and you can also write an extension to read it.
```bash
sqlite3 databases/mmssms.db .dump
```
The output may be a lot, but you can quickly locate the table where the required data resides and then write SQL queries yourself. This method works for 98% of Android applications; the remaining 2% are encrypted databases.
### Viewing Encrypted Databases
For encrypted databases, you need to find the database key or its generation method yourself. Below we briefly introduce how to read the databases of relevant applications, only covering how to use `PRAGMA` to preset keys. If you're not clear what this is, please learn about SQLite first.
> WeChat series (sqlcipher)
```sql
PRAGMA cipher = "sqlcipher";
PRAGMA legacy = 1;
PRAGMA key = "database-key";
```
> WeCom (wxsqlite)
```sql
PRAGMA cipher = "aes128cbc";
PRAGMA hexkey = "database-key"
```
> Alibaba series (sqlcrypto)
```sql
PRAGMA cipher = "sqlcrypto";
PRAGMA key = "database-key"
```
```{hint}
Note that the database of an Android application is not necessarily placed only in the databases directory.
```
### Viewing Other Data
Of course, the application data directory contains not only databases but also application parameters, configurations, cache, files, etc., such as shared_prefs (XML files). We won't elaborate further; you can explore on your own.
## Automation Auxiliary Measures
In automation business, not all applications are suitable for selector-based positioning. Some interfaces, such as games, are rendered in real time and have no Android-level page layout. For such applications, you can only rely on OCR or image matching for detection. Please refer to the [Text Recognition](./ocr.md) and [Image Matching](./image-match.md) chapters for specific interfaces and usage.
--- END OF basics.md ---
--- DOCUMENT: faq.md ---
--- SOURCE: https://device-farm.com/docs/content/en/faq.md ---
# FAQ Summary
This section summarizes some common issues and their solutions or handling methods. In any case, you should prioritize solving issues by upgrading the service and client to the latest version. If that does not work, please check according to the following plans.
## Common Issues
Some software cannot properly display the interface layout, and there are no operable elements on the interface.
Please download this script [user-home/modules/script/enhanced_automation_wechat.yaml](https://github.com/firerpa/lamda/blob/HEAD/examples/user-home/modules/script/enhanced_automation_wechat.yaml) and place it in the device's `~/modules/script` directory.
After installing firerpa, other software cannot open normally.
Older versions may have this problem in some environments. Please switch to the latest version.
The service cannot start normally, with error messages pointing to avtab or unsupported policy database format.
This situation may occur on Android 16, but lower versions are not excluded. First, please ensure you are using the latest version of the server. If the problem persists, check if the root software you are using is KernelSU. The crash is because an older version of ksu damaged the system's SELinux policy image; please try using the latest version of ksu. If it still crashes, switch to Magisk or run with downgraded shell permissions.
After running the service, specific applications cannot open, crash, or are detected as abnormal.
This may be because some APPs use app-zygote to detect Frida. In **versions below 10.0**, you can try configuring `enhanced-stealth-mode=true` in properties.local to circumvent this issue (the side effect is that Frida's spawn-related features cannot be used). **Version 10.0 and above have removed this configuration item**. Please upgrade to the latest version and contact us with specific feedback.
FAQ about packet capture features.
There is no need to ask whether the packet capture function is perfect; FIRERPA has arranged everything, and it has already completed any process required for packet capture for you. If other packet capture software you use cannot catch packets, FIRERPA will definitely catch them; if FIRERPA cannot catch them, **then no software with the same logic can**. Don't worry about certificate trust issues; FIRERPA will automatically install a system-level root certificate for the application during packet capture, without manual operation. For QUIC downgrade, startmitm will automatically disable the UDP protocol; normally when the APP cannot use UDP, it will automatically downgrade and not use QUIC, all of which require no manual intervention.
After running the packet capture script, no data packets are captured.
Possible reasons: First, the APP itself has a certificate verification or certificate pinning mechanism; second, the pre- and post-processing was not done correctly. How to determine if the APP has a certificate verification mechanism: enable global packet capture, then open the browser or other APPs to see if they capture normally. Test several internet-connected APPs to confirm: if some apps can be captured and others cannot, it's highly likely that the apps that cannot be captured use proprietary protocols or certificate verification mechanisms, which usually output `Client TLS handshake failed` and other information in startmitm's logs, but this is not the focus. If you confirm the APP has certificate verification, you need to dynamically bypass the verification mechanism through reverse engineering or other means to continue capturing. Regarding incorrect pre- and post-processing: Normally, some users may not have disabled the system firewall, causing the proxy port to be unreachable by the phone without any response. Another situation is that the APP established necessary network connections before you started capturing, so the traffic after capturing still uses these existing connections without going through the proxy. You need to manually force close the APP after starting startmitm, then reopen it.
After using one-click packet capture, the phone seems to be offline.
First, check if the firewall is disabled. Second, check if the one-click packet capture script outputs logs when accessing websites from the phone's browser. If an error like `No route to host` appears and corresponds to the browser access (i.e., the script outputs this error every time you access a website), the possible reason is: the phone itself supports IPv6 network access, and the default DNS resolution also uses IPv6, but the computer running the script does not have IPv6 enabled or does not support it, resulting in `No route to host`. If your broadband supports IPv6, manually enable it in the computer's network settings; or try specifying `--proxy-dns 114.114.114.114` in the script command line and test if it returns to normal. If both methods are ineffective, contact support.
Using startmitm to capture packets, shows "No route to host" and the app has no network.
This issue is the same as the above. You can first test if other APPs can be captured normally to rule out service issues. If they can be proxied normally, then this situation might be because the device supports IPv6, but the machine running startmitm does not have a valid IPv6 address. If your network has a usable public IPv6, assign one to your computer; or completely disable IPv6 on the router.
Packet capture script shows "Client TLS handshake failed, does not trust the proxy's certificate".
If you can capture data packets from the relevant app normally, you don't need to worry about this output; it may be logs generated by the certificate verification mechanism of other system APPs or third-party SDKs in the APP.
After running the service, some detection software detected abnormal features.
Please confirm that this problem only occurred after starting FIRERPA. If confirmed, and the issue persists after upgrading to the latest version, contact us. (The `enhanced-stealth-mode` configuration has been removed since version 10.0 and is no longer applicable.)
I installed the auto-start APK, but the service did not start normally and cannot be accessed.
The auto-start APK, limited by different system settings, may not automatically start with the system. If it cannot be accessed after booting, manually start it by clicking the **Manual Start** button in the APP, wait for one minute, and then try accessing again. If it still cannot start, try manual installation or module installation methods.
Using Python interface or packet capture shows "Service Unavailable".
Please ensure you have completed the relevant setup in the [Environment Preparation](./environment-setup.md) section, then try restarting the device several times (about 3 times).
--- END OF faq.md ---
--- DOCUMENT: environment-setup.md ---
--- SOURCE: https://device-farm.com/docs/content/en/environment-setup.md ---
# Environment Preparation
```{note}
This chapter provides documentation for manual deployment, suitable for situations where you need to embed the server into a ROM or manually start the FIRERPA service. If you have already started the server via the APP and have no such requirements, you can skip this chapter.
```
This document introduces the device, system, and network environment preparations required before installing and running FIRERPA. FIRERPA supports a wide variety of device types and architectures. Here, we list common devices and the settings that need to be configured in advance. You can choose the appropriate method according to your actual situation. FIRERPA is divided into a client and a server: the client mainly consists of Python-related libraries and interfaces, while the server is a service running on the target phone. You can install the Python libraries on a computer to control the phone; however, if you need to control the device via Python, you must install and run the Android server on the phone.
## Basic Requirements
The ideal operating environment for FIRERPA is an Android device or emulator with more than 4 GB of RAM and more than 4 GB of available storage. If you do not have a physical Android phone, you can also use the latest versions of NoxPlayer, LDPlayer, or the AVD emulator. To utilize all features of FIRERPA, your device needs root permissions. If the device does not have root permissions, it can still run with adb shell permissions via Developer Mode, but some privileged features will be limited or unavailable.
## System Settings
Typically, your phone also requires the following settings. These are not mandatory, but to ensure success on the first try, we recommend completing these settings if time permits.
### Check Timezone and Time
Open system settings, find Date & Time, and check if it is automatically set to **China Standard Time** or your local timezone, and verify that the time is correct or within an acceptable margin of error. If not, turn off **Use network-provided timezone** and **Network time**, then manually set the timezone and time to the appropriate values for your current location.
### Disable Accessibility Services
Open system settings, find Accessibility (located under System or More settings), and disable or uninstall all applications that use accessibility services (e.g., TalkBack, AutoJS, etc.).
```{note}
Accessibility services may cause conflicts with related functions. Only check this environment setting if you are unable to use FIRERPA's remote desktop or related interfaces normally.
```
## Network Settings
Physical phones, cloud phones, emulators, and other devices have different network conditions. You need to complete the following corresponding settings according to your actual situation; otherwise, you may not be able to access the service normally after installation.
For physical phones, simply ensure that the computer and the phone are on the same network. For emulators, the default created emulator usually does not share the network with the host machine. If you are using an Android-x86 virtual machine based on VMware, set the network mode to bridged in the virtual machine settings. For emulators like LDPlayer and NoxPlayer, you need to install the driver in the instance settings as prompted, enable bridge mode, and restart the system. For Android Studio virtual devices, as they lack relevant settings, to connect to FIRERPA inside the AVD, you need to run `adb forward tcp:65000 tcp:65000` and then connect using `localhost`.
```{note}
For service ports that are forwarded or originally bound to the loopback address, we recommend using `localhost` instead of `127.0.0.1`, because some of our tools (such as the man-in-the-middle packet capture) rely on `localhost` to determine whether the connection is made via USB.
```
## Other Settings
### WSA (Windows Subsystem for Android)
If you are using WSA (Windows Subsystem for Android), ensure that the WSA version is not lower than 2210.40000. Then, open WSA settings -> Subsystem Resources -> select Continuous, turn off Advanced Networking. Next, select Developer, enable Developer mode and expand the tab, check "Support single machine UI automation", and finally restart the WSA subsystem.
### AVD (Android Studio Virtual Device)
If you are using an AVD (Android Studio Virtual Device), first expand the default storage space and memory size.
Click Virtual Device Manager in Android Studio to create a new virtual device. Then find the corresponding device and click the edit button (pencil icon) behind it, click Show Advanced Settings, find Storage -> Internal Storage and set it to at least 2 GB, and set RAM memory size to at least 4 GB.
### Redroid (android in docker)
```{attention}
The following method may not apply to other Linux distributions.
```
If you are using Redroid (android in docker), taking the officially recommended `Ubuntu 20.04` as an example, first install the linux-modules-extra related modules.
```bash
apt install linux-modules-extra-`uname -r`
```
Edit the file `/etc/modules`, copy the following names and insert them at the bottom of the file, then restart the host machine.
```bash
# redroid modules
mac80211_hwsim
binder_linux
ashmem_linux
```
Finally, start using the following command. Adjust `redroid_gpu_mode` and other related parameters according to your actual situation.
```{attention}
Note: The following command differs from the official guide; please be sure to use it as follows.
```
```bash
docker run -itd --rm --privileged --pull always -v /lib/modules:/lib/modules:ro -v ~/redroid:/data -p 127.0.0.1:5555:5555 -p 127.0.0.1:65001:65000 redroid/redroid:12.0.0-latest androidboot.redroid_gpu_mode=guest
```
In the above command, mapping container port 65000 to host port 65001 is because some tools need to temporarily bind to port 65000 on the host. To avoid conflicts, we use port 65001.
```{note}
For the Android redroid container started with the above command, due to port mapping, port 65000 inside the system is mapped to port 65001 on the host. Access it on the host via http://127.0.0.1:65001.
```
--- END OF environment-setup.md ---
--- DOCUMENT: install-server.md ---
--- SOURCE: https://device-farm.com/docs/content/en/install-server.md ---
# Installing the Server
This chapter explains how to install the FIRERPA server. The FIRERPA server is the most crucial component and the fundamental underlying service. You can choose to install it via the APK we provide (supports auto-start on boot), the Magisk module (supports auto-start on boot), or manually. Their installation complexity increases in order.
```{important}
By default, the installed FIRERPA has no authentication enabled, allowing anyone to access any content on the device. If you are deploying using the APK, you can enable authentication in **Settings**. For other installation methods, please refer to the [Server Certificate](./server-certificate.md) chapter to learn how to enable certificates. If you choose not to enable authentication, be sure to use it only within a trusted network; otherwise, it increases the risk of the device being compromised.
```
## Installing via APK
First, we introduce the simplest installation method. This method requires no extra steps; you only need to install our auto-install and auto-start APK. For detailed installation steps, please refer to the [Quick Start](./quick-start.md) chapter.
```{note}
The following content is documentation related to manual deployment, suitable for situations where you need to embed the server into a ROM or manually start the FIRERPA service. If you have already started the server using the APK and have no such requirements, you do not need to read this chapter further.
```
## Getting the Device Architecture
Since FIRERPA supports multiple CPU architectures, downloading an incorrect server version will prevent it from executing properly. Therefore, you need to obtain the device's architecture before installation. You can run the following command:
```bash
getprop ro.product.cpu.abi
```
For modern physical phones, this command typically outputs `arm64-v8a`; for emulators (such as LDPlayer), when creating a new emulator, you can choose a 32-bit or 64-bit Android system, where the 32-bit system corresponds to `x86` and the 64-bit system to `x86_64`; some older Xiaomi models may output `armeabi-v7a`. Remember this output value.
## Service Configuration
Please refer to the [Service Configuration](./properties.md) chapter to learn how to configure service startup items. Relevant configuration details are also covered in the introductions of various features.
## Installing via Magisk
If your device uses Magisk, you can complete the installation in the simplest way, and FIRERPA can **auto-start on boot**. The Magisk version must be ≥ 20.4, and installation is only supported through the **Magisk App**.
Download `lamda-magisk-module.zip` from the [lamda/releases](https://github.com/firerpa/lamda/releases) page and push it to `/sdcard`. Open the Magisk App, navigate to "Modules" → "Install from storage", select `lamda-magisk-module.zip`, and wait a moment.
```{tip}
If any error occurs causing installation failure, it may be because the module zip file is too large. Use compression software to directly open the zip file, delete the server files corresponding to architectures you do not need from the server directory, and then reinstall without extracting and recompressing.
```
After successfully flashing, please restart the device. After reboot, the FIRERPA service will start 30 seconds after boot, not immediately. Please wait 1~2 minutes after boot before attempting to connect to FIRERPA.
If you want the service to automatically set a proxy or connect to FRP forwarding upon startup, please refer to the [Service Configuration](./properties.md) chapter to write the corresponding configuration. Save the configuration in standard **ini** format, rename the file to `properties`, and place it into the `common` folder inside the module zip file `lamda-magisk-module.zip`. To preinstall a service certificate during Magisk module installation, you can generate a PEM certificate as described in the [Server Certificate](./server-certificate.md) chapter, rename it to `lamda.pem`, and also drag it into the module's `common` folder. If you need to preauthorize [Built-in ADB](./built-in-adb.md), write the content of your local `adbkey.pub` file into a file named `adb_keys` and drag it into the `common` folder.
```{attention}
It is recommended to open `lamda-magisk-module.zip` using **compression software**; do not extract and recompress. Be sure to perform operations directly inside the compression software by drag-and-drop, or ensure that the directory hierarchy of files inside the module remains exactly the same, otherwise the module may not function properly.
```
## Manual Installation
Download the latest version of `lamda-server-arm64-v8a.tar.gz` from the release page [lamda/releases](https://github.com/firerpa/lamda/releases), and use the adb push command to push the file to the device's temporary directory `/data/local/tmp`.
```bash
adb push lamda-server-arm64-v8a.tar.gz /data/local/tmp
```
Once done, enter `adb shell`. If the device has root privileges, first run the `su` command to switch users; otherwise, you can directly execute the following commands with the current shell identity:
```bash
INSTALLDIR=$( [ $(id -u) -eq 0 ] && echo /data/ || echo /data/local/tmp )
echo "Server install directory: $INSTALLDIR/server"
tar -C $INSTALLDIR -xzf /data/local/tmp/lamda-server-arm64-v8a.tar.gz
rm /data/local/tmp/lamda-server-arm64-v8a.tar.gz
```
```{attention}
Some older devices may not be able to extract `.tar.gz` files using the built-in `tar` command, so a `busybox` is provided as a supplement; you may need to download this busybox as well. Download `busybox-arm64-v8a` from [lamda/releases/tag/v10.0](https://github.com/firerpa/lamda/releases/tag/v10.0), temporarily push the file to /data/local/tmp, and use `/data/local/tmp/busybox-arm64-v8a tar` for extraction.
```
## Manual Startup
```{attention}
FIRERPA installed via Magisk or the APK will start automatically on boot.
```
```{hint}
There is a chance that remote desktop keeps loading on the first startup. If this happens, first try rebooting the device and restarting the FIRERPA service. If it still cannot be used after waiting for a long time, please report this issue to us.
```
```{hint}
The FIRERPA service supports running as root or as shell. If you need full capabilities, run the server as root.
```
Enter adb shell, and according to the current device status, choose to run the following command as `shell` or `root`. After executing, wait for the command to return; if the output shows `llllaamDaa started`, it means the service started successfully and has entered background mode. You can then safely exit adb shell and access the FIRERPA service. Otherwise, troubleshoot based on the error messages below.
```bash
sh /data/server/bin/launch.sh
```
| Error Output | Explanation |
|--------------------------|----------------------------------------|
| already running | The service is already running |
| not run as root/shell | Please run as root or shell |
| unsupported sdk | This Android version is not supported |
| abi not match | A tar.gz package of the wrong architecture was used |
| file broken | File is corrupted, please reinstall |
## Uninstalling the Service
FIRERPA manages its own data in a very standardized manner and absolutely does not randomly place files in your system. For Magisk installations, simply remove the module in the Magisk app; for APK installations, use the uninstall function within the APK.
```{danger}
Use these features with caution. These directories contain data related to your use of the FIRERPA service. The uninstall operation will completely remove FIRERPA and all data generated by FIRERPA, and this is irreversible.
```
Before uninstalling, please first [shut down the service](./shutdown-reboot.md#shutting-down-the-service) and wait at least 30 seconds to ensure the service exits gracefully.
```bash
rm -rf /data/server /data/usr # /data/local/tmp/usr
```
This will completely remove FIRERPA from your device. It is recommended to reboot the device after execution.
--- END OF install-server.md ---
--- DOCUMENT: properties.md ---
--- SOURCE: https://device-farm.com/docs/content/en/properties.md ---
BEGIN_TRANSLATE
# Service Configuration
## File Format
We need to explain the parsing logic of the server when reading the service configuration to avoid confusion. The service configuration uses the standard **ini** format, and must contain a `[DEFAULT]` section. You can write configuration items directly in the DEFAULT section, or create your own sections for grouping, making it easy to know where a configuration comes from rather than having everything mixed together.
Now let's consider the following configuration. You can see that each section has a `port` configuration, which is allowed. The service will ultimately use `65001` as the port. Because if there are duplicate configurations in different sections, the server will use the configuration from the last section when reading. Therefore, if you feel that a configuration is not taking effect, check whether it is being overridden by a later configuration.
```ini
[DEFAULT]
port=65000
[abc]
port=65001
```
So, following this standard, you can also group configurations for each service, for example like the following, creating a section for the `fwd` service.
```ini
[DEFAULT]
port=65000
[fwd]
fwd.enable=true
fwd.host=123.123.123.123
fwd.port=9911
fwd.token=abc123
fwd.rport=8080
```
## Configuration Methods
Below we introduce several ways to configure the service. Generally, we recommend using the WebUI for configuration, as it is the least error-prone. However, if you have higher requirements such as batch deployment, the WebUI may not suit you, and you can refer to **Manual Configuration**.
### In-App Configuration
In-App configuration is relatively simple, but you need to write the configuration file in advance and copy it as text to the phone where the app is installed. After opening the app, go to the configuration page, paste the configuration string into the edit box, and click the apply configuration button. However, you must ensure to keep the `[DEFAULT]` section. Of course, if you worry about mistakes, you can also configure via the WebUI.
### WebUI Configuration
You can directly open the WebUI remote desktop and click the configuration icon in the upper right corner to configure, as shown in the figure below. You can perform visual editing or click the `Raw Text` mode for text-mode editing. After configuration, you can directly click `Apply Config` to save the configuration. However, service-type configurations such as proxy, FRP, etc., will not take effect immediately. If you want them to take effect immediately, check `Reload Service` before clicking the `Apply Config` button, which will completely reload the server, and your remote desktop will temporarily disconnect.
### Manual Configuration
For manual configuration, you need to ensure that the configuration file format is valid; otherwise, the service will not use your configuration. First, you must ensure that the configuration file contains a `[DEFAULT]` section, even if that section may have no configurations. Second, make sure the editor you use does not add extra hidden characters; do not use basic editors like Notepad. A valid configuration file should use UTF-8 encoding and strictly ensure line breaks are `\n` or `\r\n`. We recommend using `\n` as the line break. An example configuration file is as follows.
```ini
[DEFAULT]
[fwd]
fwd.enable=true
fwd.host=123.123.123.123
fwd.port=9911
fwd.token=abc123
fwd.rport=8080
```
Or if you find it troublesome, you can just put everything into the DEFAULT section, which is also perfectly fine.
```ini
[DEFAULT]
fwd.enable=true
fwd.host=123.123.123.123
fwd.port=9911
fwd.token=abc123
fwd.rport=8080
```
After your configuration file is written, you need to manually place it in a specific directory on the phone. By default, there are two cases: if your server is intended to start as root, the configuration file should be stored in `/data/usr/.local/`; if as shell, it should be stored in `/data/local/tmp/usr/.local/`. You just need to ensure that the corresponding directory exists; if not, you can create it manually, then rename the configuration file to `properties` and use adb push to the corresponding directory.
```{attention}
```
## Configuration Parameters
The following configuration items are the complete set available for the service. Do not copy this content directly; only select the parts or services you need and copy and modify them. Copying the entire content directly may cause service anomalies.
```ini
[DEFAULT]
port=65000 ; Set the service listening port.
; webui.* keys are only allowed in the DEFAULT section. You can preset various parameters for the remote desktop.
webui.darkmode = off ; Whether to enable the dark theme for the remote desktop.
webui.audio = on ; Whether to enable remote desktop audio (requires Android 10+).
webui.upload-path = /data/local/tmp/uploads ; Set the default directory for drag-and-drop uploads on the remote desktop (the directory must exist).
webui.share-clipboard = off ; Whether to enable bidirectional clipboard sharing on the remote desktop.
webui.touch.use-screen-size = off ; Use the original screen size to calculate touch coordinates (enable only in special cases).
webui.webrtc = on ; Whether to enable WebRTC for the remote desktop.
; Set WebRTC STUN/TURN server addresses (multiple addresses separated by commas).
; Example stun server: stun:stun.device-farm.com
; Example turn server: turn://user:pass@example.com:3478?transport=tcp
webui.webrtc.ice-server = stun:stun.device-farm.com
webui.video.h264 = on ; Whether to enable H264 encoding.
webui.video.backend = 0 ; Set video backend implementation (0=default, 1=hardware).
webui.video.scale = 0.5 ; Set video scaling ratio (0.1-1.0, 1.0 means full resolution).
webui.video.quality = 50 ; Set video quality (10-100).
webui.video.fps = 35 ; Set desired video frame rate (10-60).
; Set the service certificate. It will encrypt your remote desktop and API traffic and enable password authentication. You can obtain the following configuration value by executing 'base64 -w0 lamda.pem'.
cert=TEFNREEgU1NMIENFUlRJRklDQVRFIChDTj10ZXN0LFBBU1NXRD1hMWMwZTNlYTcwN2E1NGRlN2EwZjk1KQotLS0tLUJFR0lOIFBSSVZBVEUgS0VZLS0tLS0KTUlJRXZnSUJBREFOQmdrcWhraUc5dzBCQVFFRkFBU0NCS2d3Z2dTa0FnRUFBb0lCQVFEVmNMWlA5b0xRWkRIRgp5V0pTa2U0Z0crSUpmSCtMWlk0cXUzdS9OckRwSHZCN2k5V01rMWxRL2FMSGI0V3ZqelBLK1RITm9rRzc2MENRClhBTUpWS0dmYXRwcmNLUW93MGhvWDZ2NlhsTVlZUlNRbW9wN3pSaUtnN3ZxKzV2S09DQ2RKcDFlSVNiZXcyTEgKTmYxL3JZelpwa1Q1bHoxTGZkem00eXJBS3VNa0tyZ3pnTzJRcE9CQVdYdmdiWG9BMDdidDdOODZZOGdNZFUwdAp6Ui9EcmhLTi9JMVdYSk11MU4wQW5UbDJRdEhEb0dCN0UyS0xpdmwybDZJdnRrYWJ4RE55Y2lHbUxOUGlMRklrCllPSHlnMUg3MUJ2NU04NE5TWDc1c2xuOXVNUGUzOVNFVlJoU0ptNHcvT2tXZnA5dGpZRUx3dHphdFhoSWJoS3MKaC9OZU1lOWRBZ01CQUFFQ2dnRUFCYlFuWUdlcFdKYjAydURtSnhLNGx2OFNhL0o1dzJpSldMYjk0dW1SUExRKwpTa1E5c1Zpc0JiQU1JNHc2dWFyNEFBVTh3WGxaTndPekMvM2V6dWNHRXFreEFReXM0VDB4SXRPUkF2WExxVlowCkl1WnpxNW53Si9OeFFzeEtmaWhBZkRLYlRmZjdmcG5MWlV0dlpNbG5LWUhQVExtRlFua3drckwyRE5YdDVVOTYKYXJUUDVOY0x1aDQ2dU93alJUOWhaNytYQi9ubU9LeTV4V3hoNWVMQmJJUTJrS0UvdWViUlVYRGZNdG5tbTh2aApRSG9VM3N5dzlZcE1xRDlWQWppcGlqQXRwbUlwa2w1emRWSE52Q1dCSGk2NjZxKzF6cUpHeFVUODBseHo1N2R1ClRvRFFQc1l0OFFEL3ZjNGkxajd0bDZyRzNQWkJNM05LNVR5ZFYyRnlnUUtCZ1FEYUcycnV3aWxyYUdZRzZNQWwKNEF2WW1BY0hHQWUwQjR0ZmtkdS9QandlRWQyWUF2TjJIQWV1Z1ZUSWg1eFplUlIwNFE4ZVNGenBTaEpwREpkNAp1TEhHcXJ2cmpXL1greEVIc081NnNjNjRiem1weWJWQXBlQnA4NlFGSTk4V3FmZkFyN3FzbzhweFJjNmdTRU1uCk5TcXV1Z2psYU05TlJmcUo4ci82RklmQkxRS0JnUUQ2aGJ0Z2dIdVJoTHhHYWNNaERvOVppcXJlNi9HN1dvZnQKR2FGZmFQM2xZNTNmM0hPdSsyNTZMWDY1ZWFYRTdrbXlQOGRDM2VWL24xT3dvTHdBYm1BZ2pEWVd1N09KdGk4QQpPbG9VNmtnTkVwNWcwOHpVWlBaR3NSMkZMd2VpUkgrQ3ZOdFBZakJydmFIQUtVU2lLa3BKdEpWeFpIdUl6SlpGCjVUZkM2VjNrOFFLQmdEeWp0TlpPKzA4V2hvOVROT0VTNnBnOHBHK1BlY3pPOEN3UkZJU1dYQWFNTnd6bGZTVVEKWS81YmpPUDMrRHRVRTZEdlZkRzRrc1IxeUtxV1NxTFF6dlNLVVpjTEN0YUV3bFplRmQvZEFibDdpdyt1dWdzUQpVMVdCM005bENzaDFWeUdtZWdNM3dyZzlqVlk0NFJyTWlHSnQ3TDFEcDZjM1ZwSDJBUFFac3lpOUFvR0JBTk9pCmpmeWtEYitNNXBDRllEWlkybmpHVURzcUQzZzZyb0Y2R1gxRWNOaU1JeDZ1V1h3RkkvdEsyN2RNTU9JQWUzbDkKcjVPcGFPczdhYlBZMVhsM3hQVTUvYWVPd2NrZ2d1d3FYMWN6NDlKSFhFeG9JSzE4N1NBakY5RWZQYyt6RmhVWAovaDA5MGJIeTdPWXM5cklZRDlIY0lETStzNjJKUjVtY1hsTG1Xay9CQW9HQkFKRVhQV05IWEwra1l6My91R1c3CnRKd0hUQzFlbEJjclcvaHpJMWt4ZEhXem5VaXNTWlcyVnA5b0wwSWNrQXVWQkx6eGUvR1h6OGJRTjZkOWwyZDAKdGtmUmo1TmpDOTUzS2N1cTNSekRmVU40cTcyUlVWTWlFOHVvSTBkVVZpalczN0tVMEhLcm1pbDBocU01eW9iNQpVZlhPQ2Q5SlRRSWx5Y2dNWER6Tm00S3oKLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQotLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJQ3FEQ0NBWkNnQXdJQkFnSVJBUHNjMVBRNXBuSDNhNk1GZkdVTXA2WXdEUVlKS29aSWh2Y05BUUVMQlFBdwpFREVPTUF3R0ExVUVDZ3dGVEVGTlJFVXdIaGNOTWpBd01UQXhNREF3TURBeFdoY05Namt4TWpJNU1EQXdNREF4CldqQVBNUTB3Q3dZRFZRUUREQVIwWlhOME1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0MKQVFFQTFYQzJUL2FDMEdReHhjbGlVcEh1SUJ2aUNYeC9pMldPS3J0N3Z6YXc2Ujd3ZTR2VmpKTlpVUDJpeDIrRgpyNDh6eXZreHphSkJ1K3RBa0Z3RENWU2huMnJhYTNDa01LTklhRityK2w1VEdHRVVrSnFLZTgwWWlvTzc2dnViCnlqZ2duU2FkWGlFbTNzTml4elg5ZjYyTTJhWkUrWmM5UzMzYzV1TXF3Q3JqSkNxNE00RHRrS1RnUUZsNzRHMTYKQU5PMjdlemZPbVBJREhWTkxjMGZ3NjRTamZ5TlZseVRMdFRkQUowNWRrTFJ3NkJnZXhOaWk0cjVkcGVpTDdaRwptOFF6Y25JaHBpelQ0aXhTSkdEaDhvTlIrOVFiK1RQT0RVbCsrYkpaL2JqRDN0L1VoRlVZVWladU1QenBGbjZmCmJZMkJDOExjMnJWNFNHNFNySWZ6WGpIdlhRSURBUUFCTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFBWEQ0L1cKQjBhSW1aWGpQbTRxUnBOazJmUnpjU1g4MGw2TlZaWWxJV3ZYalFxUXdXZnMvSGczZDVzYUpickFmcWVPa1lQdQpjeXJEWFZPdC9RTEVDOTFBSGtjRWJ1R0dPMGNFU2YyOHdUM1UzRnJJb2cxS1VyTURqWFFIb09vZEJpOGdNaVBmCmROcWhMSTdkNDJBTXJKU3dZUTlSUG9vWG9UZ2xDa0d3R291RDhuS0V5MmNHeVMxM3lQcDRseC9TWTR1QkRFU0sKRlErR0ZRTExGQktQZHZNc2x0cHYyQWFMWmR3clF4aFQ2aTU1U1puNStLb3c1TGxYL0RHdUw5UnRPdmZ2T0tzZQpRZ3pOQUg3QkYzbGdvQmJjYk9yZkVQazY1ZEZRN0NXYi91aDZjVmlmSjdxQzkvL0xhdElmb1VQVnJiRXdZL2dRCk5BRXFYclduMGZuYUc0cUEKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQotLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJQ2xEQ0NBWHdDQVFBd0RRWUpLb1pJaHZjTkFRRUxCUUF3RURFT01Bd0dBMVVFQ2d3RlRFRk5SRUV3SGhjTgpNakF3TVRBeE1EQXdNREF4V2hjTk1qa3hNakk1TURBd01EQXhXakFRTVE0d0RBWURWUVFLREFWTVFVMUVRVENDCkFTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTG5xZkJadnJHWmFxZ2s1bXNDUlJwUHoKcC8rNDY0akJrbmxtVEtldE9ja0RUVXE4VjZmSC8yR2ZiNkhqam9ENXBrQ3RENW1TS2thSE5odXhMWHNGZkVmYwpLbG1ubjNacGp5Tk9IRUEvaUFPMkR5RVlhMDh4U2V2TTdXb2piRjdjTmo1L0RZZzdlYjBpMCsvL2JCbGg4bmxPCmdoU1VoQ1RNNVBDb2ZMRFU4c1ZYdVlBaUdVNlV6QnJJQzB2SEVsdERraUpWTHBjQ3RzS2pFWk9za1BkQWM3dTYKL2FBMFA1R29uWjVVa1JEWXBhK2plSlVhYnFXWlFRRWd0bXZqbG1VVWlYd3UwalJuajFuMFQzZlBRRDNnQStMSQp2QUU5dmd2cFk1WFFqNm90cEJ2c1ozTUpKTktjVU1RdTF6T0FOVHpPMThUbEE4S29CTnNCeThaOURRWktYRjhDCkF3RUFBVEFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBZUduL055cUlSSS8wQUdxdkhQOTdLdFE3NlRqNmFjaGIKMzBMSVhXcCtZSFVhTWVBVmpkMlo3alNRcDVtWlpGbCtrMWZiMzM3SWVhR1hvZlZJMjFlSzUyUVgydGVOb0JrQQovVi9PMUh1MzUvK2FpejB4c2RENndXdndvNEZ5MWpsbWFlSmh3ZFFhY0JsREdGQTJqRkp4dUVwYWhmeFp2VXNiCjNqNXpVMFdLVFVDZkVEZ1hGd0J3MTJ4a3UvN1RNZENFYlJzWWFaM3pGVEMyMjZsUWJVRE43d2VxRndTRCt0QjYKUnVoSXhlOCtjRndBc0FXSENsZXJLZ1pucjN0NVFGMDc4cFcyR0h5OENzSjdWM01aVDVsWjQzbFM1TklCOUp6WgpTWXhaL2l6aFJ5aDVxUjczdUFnc0phTDU2QmorY1Fxbm9UcWhMWlZsN0orTTZXaFdLem9qc0E9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
; Set a custom remote desktop login password, 6-32 characters, easy to remember. This password only takes effect when a service certificate is used.
ssl-web-credential=password123
; Set the Access-Control-Allow-Origin header for firerpa WebUI and its API, allowing you to embed firerpa functionality into your web application.
; See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
allow_origin=https://example.com
; If the touch screen on the remote desktop shows offset or no response, set touch.backend to system. Available options: system | native (default)
touch.backend=system
; Set the service log file (the directory must exist)
logfile=/data/local/tmp/server.log
; Do not manually write the following configuration. You should use our companion OpenVPN server deployment solution to set it up and use its built-in commands to generate this configuration.
[openvpn]
openvpn.proto=udp
openvpn.cipher=AES-256-GCM
openvpn.host=123.123.123.123
openvpn.port=1190
openvpn.ca=LS0tLS1CRU...
openvpn.cert=LS0tLS1CRU...
openvpn.key=LS0tLS1CRU...
openvpn.tls_encryption=
openvpn.tls_key_direction=
openvpn.tls_key=
; The following configuration items can only be manually configured.
openvpn.global=false ; Whether to enable global VPN.
openvpn.enable=true ; Whether to enable the service true | false
; This configuration is used to make the device automatically use a proxy server on startup.
[gproxy]
gproxy.enable=true ; Whether to enable the service true | false
gproxy.type=http-connect ; Proxy type, options: http-connect, https-connect, socks5, shadowsocks
gproxy.host=172.1.1.1 ; Proxy server address
gproxy.port=8080 ; Proxy server port
gproxy.password= ; Proxy server login password (leave blank if no authentication required)
gproxy.login= ; Proxy server login username (leave blank if no authentication required; if type is shadowsocks, this is the encryption method)
gproxy.nameserver= ; Custom DNS server
gproxy.drop_udp=false ; Whether to drop UDP packets.
gproxy.udp_proxy=false ; Whether to proxy UDP packets (requires proxy support, e.g., socks5, shadowsocks).
gproxy.bypass_local_subnet=true ; Do not proxy local LAN traffic.
gproxy.dns_proxy=false ; Proxy DNS queries through the proxy (requires a DNS server supporting UDP and TCP).
gproxy.uid= ; Only proxy for the specified UID (default is global).
[cron]
cron.enable=true ; Whether to enable the service true | false
[sshd]
sshd.enable=true ; Whether to enable the service true | false
[fwd]
fwd.enable=true ; Whether to enable the service true | false
fwd.rport=0 ; Port forwarded to remote (0 means randomly assigned)
fwd.host=123.123.123.123 ; FRP server address
fwd.port=9911 ; FRP server port
fwd.protocol=tcp ; FRP protocol
fwd.token=abc123 ; FRP login authentication (token)
[adb]
adb.enable=true ; Whether to enable the service true | false
adb.directory=/data/local/tmp ; Default working directory for the built-in ADB (adb shell working directory)
; Warning: If set to true, ADB connections will have root privileges; otherwise, they will have shell privileges. When this option is set to false, you will use a shell similar to native adb shell and will not be able to use built-in commands. Please note that since ADB does not use TLS connections, traffic may be monitored. For security reasons, when the service is started with a certificate, this value defaults to false. However, if you specify it in a properties.local file, that configuration will take precedence, and you are responsible for ensuring security.
adb.privileged=true
[tunnel2]
tunnel2.enable=true ; Whether to enable the service true | false
; Login credentials for the bridge proxy are required only if both login and password are set; if either is blank, no authentication is required.
tunnel2.login=lamda
tunnel2.password=1234 ; Bridge proxy login password
; Outbound interface (rmnet|wlan). When outbound interface is rmnet, the proxy will attempt to forward your requests over mobile data. When outbound interface is wlan, requests will be forwarded over the wlan interface. If left empty, the default network will be used to forward requests.
tunnel2.iface=rmnet
[mdns]
mdns.enable=false ; Enable or disable true | false
mdns.meta=false ; Add TXT metadata for mDNS. When enabled, it supports querying device information like model, ABI, and device ID using tools like python-zeroconf. Disabled by default.
mdns.name=DEVICEID-UNIQUE.lamda ; Set the broadcast domain name using a locally unique ID, defaults to {DEVICEID-UNIQUE}.lamda. If name conflicts in the local network, a suffix ID will be automatically appended.
mdns.service=lamda ; Set the broadcast service name, defaults to lamda, i.e., _lamda._tcp.local.
```
--- END OF properties.md ---
--- DOCUMENT: server-certificate.md ---
--- SOURCE: https://device-farm.com/docs/content/en/server-certificate.md ---
# Service Certificate
```{attention}
The service certificate in this chapter is different from the MITM capture certificate described in [Installing MITM Certificate](./install-mitm-cert.md); the latter is installed in the Android system trust store to make the app trust the proxy for traffic interception. The service certificate is a PEM format file loaded by the FIRERPA server and held by the client, used to encrypt the FIRERPA communication link and verify the visitor's identity.
```
This chapter introduces the generation and deployment of the FIRERPA service certificate. The FIRERPA server does not enable authentication by default; anyone on the same network who knows the device IP and port can connect to the remote desktop, call the API, or use SSH. If you need to use it in an untrusted network environment or require TLS encryption for communication, you should enable access control via a service certificate.
```{note}
When installing the server via the APP, you can enable authentication in the APP's **Configuration**, and the APP will automatically generate and set the certificate, eliminating the need to follow the generation steps below. For Magisk module or manual deployment, you need to follow the steps in this chapter to generate and manually deploy the certificate.
```
Before generating the certificate, you must complete [Tool Preparation](./tools-prepare.md) on the PC, clone the `lamda` repository, and install Python dependencies in the `tools` directory. In the `tools` directory, run `cert.py`, replacing the argument with the hostname used to access the device.
```bash
python3 cert.py mydevice.local
```
After successful execution, the current directory will generate `mydevice.local.pem` and `root.crt`, `root.key`. The `.pem` file is used by the server to enable TLS and by the client to verify identity; `root.crt` and `root.key` are the root certificate key pair, which are not normally used for day-to-day connections but should be kept safe for future replacement or troubleshooting.
The first line of the generated PEM file is a metadata comment, where the `PASSWD` field value is the default remote desktop login credential. Do not manually edit the private key or certificate content in the PEM file, as this may cause the server to fail to start or client verification to be abnormal. The certificate file format is as follows, with each block being `key`, `cert`, `CA` in strict order.
```text
LAMDA SSL CERTIFICATE (CN=mydevice.local,PASSWD=e908d358...)
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA33YUKkfUkLeWtsCe7A1yzIZsqOTd1a8XWr9+Vh0ombOdtnqK...
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIC1DCCAbygAwIBAgIQBKjY0w1FbPJooD5mJ1CWwDANBgkqhkiG9w0BAQsFADAz...
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDAzCCAeugAwIBAgIQR9OCJbQGGQT5Pgp7PmLrDTANBgkqhkiG9w0BAQsFADAz...
-----END CERTIFICATE-----
```
After certificate generation, you also need to configure the FIRERPA server to load the certificate at startup for access control to take effect. For Magisk deployment, rename the PEM to `lamda.pem` and place it into the module package as described in the [Installing Server](./install-server.md) Magisk section. After the configuration takes effect and the service restarts, the remote desktop, API, and supporting tools will operate under HTTPS and certificate requirements.
For manual installation, you can pass the certificate by adding the command-line argument `--certificate=/path/to/lamda.pem` after `launch.sh`, or by setting the `cert` configuration item in [Service Configuration](./properties.md).
```{note}
After enabling the service certificate, [Remote Desktop](./remote-desktop.md) must be accessed using the https:// protocol, and the above password must be entered in the WebUI. You can also set an alternative login password via the `ssl-web-credential` item in [Service Configuration](./properties.md). When connecting with the Python client, specify the PEM path in the Device constructor (see [API Preparation](./api-prepare.md)). For command-line tools in the tools directory, specify the path via the CERTIFICATE environment variable (see [Tool Preparation](./tools-prepare.md)).
```
--- END OF server-certificate.md ---
--- DOCUMENT: remote-desktop.md ---
--- SOURCE: https://device-farm.com/docs/content/en/remote-desktop.md ---
# Remote Desktop
The FIRERPA remote desktop feature provides remote device access within your network or across networks. You can view and operate the device screen in real time as if it were right beside you. The remote desktop is designed for Chrome 95+ and **supports simultaneous access by multiple users**. It is recommended to use the latest version of the Chrome browser. Port `65000` on the device is the standard public port for this service. Please obtain the **IP address of the current device** from the WLAN settings (the following examples assume `192.168.0.2`)
## Open Remote Desktop
You can access the remote desktop by opening the link `http://192.168.0.2:65000` directly in the browser. From there you can operate the device screen in real time and execute commands through the built‑in terminal emulator.
```{attention}
Remote desktop supports concurrent access by multiple users, and the command window is also shared. Connecting too many users at the same time may cause stuttering.
```
If you specified a [service certificate](./server-certificate.md) when starting the server, the remote desktop will require a password before you can proceed, and you must change `http://` in the link to `https://` to access it over HTTPS. After opening, you still need to enter the password to continue. You can open the generated service certificate with a text editor and find the default login password in the first line. Of course, you can also customize the remote desktop login password by setting `ssl-web-credential=12345` in [service configuration](./properties.md).

## File Upload
You can upload files directly in the remote desktop. To upload a file or a folder, simply **drag the file or directory** onto the **black terminal area on the right side** of the page. Multiple files or folders can be uploaded simultaneously. The size of a single file must not exceed 2 GB, and up to 4,000 files can be uploaded at the same time. Any uploaded file will have a permission of 644 and will always be stored in the `~/uploads` directory (or another directory specified in [service configuration](./properties.md)).

## File Download
You can open `http://192.168.0.2:65000/fs/` directly in your browser (note that it must end with `/`). From this page you can browse directories or select files to download. Alternatively, you can click the folder icon in the upper‑right corner of the remote desktop, choose the file you want to download, and click to download it.

## Remote Desktop Settings
After logging into the remote desktop, click the **gear icon** in the upper‑right corner of the page to open the settings panel. The following options can all be adjusted directly in the interface.

| Setting | Description |
| --- | --- |
| upload_path | The directory where files are saved when drag‑and‑drop uploading. Defaults to `~/uploads`. |
| enable_darkmode | Switch between light and dark theme for the remote desktop interface. |
| enable_audio | Forward device audio to the browser. Requires Android 10 or higher. |
| share_clipboard | Synchronize the clipboard between the browser and the device (**HTTPS** protocol required). |
| enable_webrtc | Enable WebRTC transport. This usually reduces latency and improves smoothness when accessing over the public internet or across regions. |
| ice_server | Set the WebRTC STUN/TURN server. |
| video_h264 | Enable H.264 video encoding. Saves bandwidth; do not force enable if the device encoding performance is insufficient. |
| video_backend | Choose the encoding implementation: **Default** for software encoding, **System** for hardware encoding. |
| video_scale | Scaling ratio of the original screen. It is not recommended to adjust unless the device resolution is too low (e.g., below 720p). |
| video_quality | Lower values result in lower bitrate, which usually makes the picture smoother. |
| video_fps | Desired frame rate, adjustable between 10 and 60. |
```{hint}
By default, the remote desktop streams the screen via WebSocket. The above parameters are not necessarily "higher is better". Adjust them gradually according to device performance, browser version, and network conditions. If enabling H.264 or hardware encoding causes stuttering or screen artifacts, disable H.264 or lower the resolution scaling and frame rate.
```
--- END OF remote-desktop.md ---
--- DOCUMENT: device-discovery.md ---
--- SOURCE: https://device-farm.com/docs/content/en/device-discovery.md ---
# Device Discovery
You can use the built-in mDNS of our service to discover all online FIRERPA devices on the local network, including their addresses, versions, and other information. You can even directly access FIRERPA remote desktop and other services in a browser using a domain name like `{ro.serialno}.local`. Note: Using mDNS domain names requires that your system supports mDNS. **The mDNS service is disabled by default**; you need to configure `mdns.enable=true` to enable it. For configuration instructions, refer to the [Service Configuration](./properties.md) section.
```{warning}
If your network has a large number of devices running FIRERPA, enabling mdns simultaneously may cause a broadcast storm, slowing down the network!
```
## Discover All Devices
You can use the mdns-beacon tool library to quickly list all devices. First, run `pip install mdns-beacon` to install the tool. After installation, execute the following command, which will list all devices running FIRERPA on your current network. This feature may be affected by your actual network setup or certain special device models, leading to failure to discover devices.
```bash
mdns-beacon listen --service _lamda._tcp.local.
```

If your device supports mDNS (best on Mac; Windows and Linux may require additional configuration), you can directly enter the Server name with the port in a browser, such as `ad12cf9d6d15385f.local:65000`, to access the remote desktop. The string you see, like `ad12cf9d6d15385f`, is the Android system's `android_id`. You can obtain it with the following command.
```bash
adb shell settings get secure android_id
```
Of course, you can also use the `zeroconf` tool library to perform device discovery programmatically, such as [python-zeroconf](https://github.com/python-zeroconf/python-zeroconf). In addition to programmatic service discovery, it can also enumerate some Android device information (requires configuration).
## Discovery Service Configuration
You can adjust parts of the service discovery via [Service Configuration](./properties.md), such as changing the service type or making each device use a fixed Server name.
You can use the `mdns.meta` configuration item to control whether to broadcast your device's own information (such as device ID, ABI, Android version, model, etc.). By default, it is not broadcast.
```ini
mdns.meta=true
```
Set the mDNS service name for the current device. You can modify it to enhance the stealth capability of FIRERPA. The default is `lamda`.
```ini
mdns.service=lamda
```
Set a fixed Server name for the current device, which is the Server field name you see in the image above. The name you set does not need to include the `.local` suffix; that suffix is required and will be automatically added. You need to set a fixed and unique name for each device.
```ini
mdns.name=DEVICEID-UNIQUE-NAME
```
--- END OF device-discovery.md ---
--- DOCUMENT: built-in-terminal.md ---
--- SOURCE: https://device-farm.com/docs/content/en/built-in-terminal.md ---
# Built-in Terminal
The built-in terminal is one of the most commonly used features in FIRERPA. You can connect to the built-in terminal to execute commands in real time. The built-in terminal refers to the terminal accessed via remote desktop, SSH, or built-in ADB. It includes pre-installed commands and some commonly used Python modules, allowing you to execute commands or run Python code directly, enabling self-control directly in the terminal.
```{attention}
For compatibility and security reasons, terminals connected via built-in ADB do not support features such as partial command hinting.
```
Now please open the remote desktop or connect to SSH or built-in ADB terminal; you should see a Linux terminal. Execute the command `cd` to switch to the home directory, which is your workspace where you can store files. The terminal supports command completion but not argument completion. You can also type part of a command and then use the up and down arrow keys to auto-complete historical commands.
## Common Aliases
Aliases are similar to commands, allowing you to quickly execute some commonly used commands. The following command aliases are available in the built-in terminal, along with their functions.
| Abbreviation | Command |
|--------------|-----------------------------|
| l | ls |
| ll | ls -l |
| la | ls -la |
| py | python |
| .. | Switch to parent directory |
| ... | Switch to grandparent directory |
| t | Switch to /data/local/tmp |
| p | Switch to previous directory |
## Common Commands
Common commands refer to some commonly used Linux or industry commands built into FIRERPA. The supported commands are as follows. Of course, besides the commands described below, most common Linux commands are also supported, but we will not list them exhaustively in the following table.
This environment comes with a rich set of pre-installed Linux command tools, covering common scenarios such as file operations, network communication, system debugging, and text processing. The complete list is as follows:
| Command | Description |
|---------|-------------|
| [ | Conditional test (linked to coreutils) |
| adb | Android Debug Bridge |
| awk | Text pattern scanning and processing language |
| b2sum | BLAKE2 checksum (linked to coreutils) |
| base32 | Base32 encode/decode (linked to coreutils) |
| base64 | Base64 encode/decode (linked to coreutils) |
| basename | Remove path prefix (linked to coreutils) |
| basenc | Multiple encoding conversions (linked to coreutils) |
| bash | Bourne Again Shell |
| cat | Concatenate and output files (linked to coreutils) |
| celery | Distributed task queue CLI |
| chcon | Modify SELinux context (linked to coreutils) |
| chgrp | Change file group (linked to coreutils) |
| chmod | Change file permissions (linked to coreutils) |
| chown | Change file owner (linked to coreutils) |
| chroot | Switch root directory (linked to coreutils) |
| cksum | Checksum calculation (linked to coreutils) |
| comm | Compare two sorted files (linked to coreutils) |
| coreutils | GNU core utilities |
| cp | Copy files (linked to coreutils) |
| crontab | Scheduled task management |
| csplit | Split files by pattern (linked to coreutils) |
| curl | HTTP/FTP command-line client |
| cut | Cut text by columns (linked to coreutils) |
| date | Date/time display/set (linked to coreutils) |
| dd | Data conversion and copy (linked to coreutils) |
| df | Disk space viewing (linked to coreutils) |
| dir | List directories (linked to coreutils) |
| dircolors | Directory color settings (linked to coreutils) |
| dirname | Get directory part (linked to coreutils) |
| dnsmasq | Lightweight DNS/DHCP service |
| du | Disk usage statistics (linked to coreutils) |
| echo | Output text (linked to coreutils) |
| env | Environment variable management (linked to coreutils) |
| expand | Convert tabs to spaces (linked to coreutils) |
| expr | Expression evaluation (linked to coreutils) |
| factor | Prime factorization (linked to coreutils) |
| false | Return non-zero status code (linked to coreutils) |
| fuser | Display process IDs using specified resources |
| find | File search tool |
| fmt | Text formatting (linked to coreutils) |
| fold | Wrap lines by width (linked to coreutils) |
| free | View memory usage |
| frpc | frp intranet penetration client |
| fsmon | File system monitor |
| grep | Text pattern search |
| groups | View user groups (linked to coreutils) |
| head | Output file head (linked to coreutils) |
| hexedit | Hex editor |
| htop | Interactive process viewer |
| id | User identity information (linked to coreutils) |
| ifconfig | Network interface configuration |
| ip | Network routing/tunnel/device management |
| iperf | Network bandwidth test (linked to iperf3) |
| iperf3 | Network bandwidth testing tool |
| join | Merge two sorted files (linked to coreutils) |
| kill | Send signal to terminate process |
| killall | Kill all processes by name |
| link | Create hard link (linked to coreutils) |
| ln | Create links (linked to coreutils) |
| logname | Current login username (linked to coreutils) |
| ls | List directory contents (linked to coreutils) |
| ltrace | Library function call trace |
| md5sum | MD5 checksum (linked to coreutils) |
| memdumper | Memory dump tool (32-bit) |
| memdumper64 | Memory dump tool (64-bit) |
| mkdir | Create directory (linked to coreutils) |
| mkfifo | Create named pipe (linked to coreutils) |
| mknod | Create device node (linked to coreutils) |
| mksh | MirBSD Korn Shell |
| mktemp | Create temporary file (linked to coreutils) |
| mv | Move/rename files (linked to coreutils) |
| nano | Simple text editor |
| nc | Netcat network tool |
| ncdu | Interactive disk usage viewer |
| nice | Adjust process priority (linked to coreutils) |
| nl | Add line numbers (linked to coreutils) |
| nohup | Run ignoring hangup signal (linked to coreutils) |
| nproc | View number of CPU cores (linked to coreutils) |
| numfmt | Number format conversion (linked to coreutils) |
| od | Octal/hex viewer (linked to coreutils) |
| openssl | SSL/TLS encryption tool |
| paste | Merge file lines (linked to coreutils) |
| pathchk | Pathname check (linked to coreutils) |
| pgrep | Find process PID by name |
| pidof | Get process PID |
| pinky | User information view (linked to coreutils) |
| pip | Python package installer |
| pkill | Terminate process by name |
| pr | Text pagination formatting (linked to coreutils) |
| printenv | Print environment variables (linked to coreutils) |
| printf | Formatted output (linked to coreutils) |
| ps | Process status view |
| pstree | Process tree view |
| ptx | Generate permuted index (linked to coreutils) |
| pwd | Current directory path (linked to coreutils) |
| pwdx | Query process working directory |
| python | Python interpreter (linked to python3.12) |
| python3 | Python 3 interpreter (linked to python3.12) |
| python3.12 | Python 3.12 interpreter |
| readlink | Read symbolic link target (linked to coreutils) |
| realpath | Resolve absolute path (linked to coreutils) |
| redir | Port redirect tool |
| rm | Delete files (linked to coreutils) |
| rmdir | Remove empty directory (linked to coreutils) |
| route | Routing table view/configure |
| rsync | Remote file synchronization |
| runcon | Run with SELinux context (linked to coreutils) |
| scapy | Python network packet CLI |
| scp | Secure file copy (SSH) |
| sed | Stream editor |
| seq | Generate numeric sequence (linked to coreutils) |
| sha1sum | SHA-1 checksum (linked to coreutils) |
| sha224sum | SHA-224 checksum (linked to coreutils) |
| sha256sum | SHA-256 checksum (linked to coreutils) |
| sha384sum | SHA-384 checksum (linked to coreutils) |
| sha512sum | SHA-512 checksum (linked to coreutils) |
| shelldo | Shell command execution tool |
| shred | Secure deletion overwrite (linked to coreutils) |
| shuf | Randomly permute lines (linked to coreutils) |
| sleep | Delay for specified time (linked to coreutils) |
| socat | Multi-purpose network tool (enhanced netcat) |
| sort | Sort (linked to coreutils) |
| split | Split files (linked to coreutils) |
| sqlite3 | SQLite database CLI |
| ssh | SSH client |
| stat | File status view (linked to coreutils) |
| stdbuf | Control buffers (linked to coreutils) |
| strace | System call trace |
| stty | Terminal settings (linked to coreutils) |
| stunnel | SSL tunnel wrapper |
| sum | File checksum (linked to coreutils) |
| sync | Sync disk buffers (linked to coreutils) |
| sysctl | Kernel parameters view/configure |
| tac | Output files in reverse (linked to coreutils) |
| tail | Output file tail (linked to coreutils) |
| tcpdump | Network packet capture and analysis |
| tee | Redirect and output (linked to coreutils) |
| test | Conditional test (linked to coreutils) |
| timeout | Run with timeout (linked to coreutils) |
| top | Real-time process monitoring |
| touch | Modify file timestamps (linked to coreutils) |
| tr | Character translation/deletion (linked to coreutils) |
| true | Return zero status code (linked to coreutils) |
| truncate | File size adjustment (linked to coreutils) |
| tsort | Topological sort (linked to coreutils) |
| tty | Terminal device name (linked to coreutils) |
| uname | System information (linked to coreutils) |
| unexpand | Convert spaces to tabs (linked to coreutils) |
| uniq | Deduplicate (linked to coreutils) |
| unlink | Delete file (linked to coreutils) |
| uptime | System uptime |
| vdir | Verbose directory listing (linked to coreutils) |
| vi | Text editor (linked to vim) |
| view | Vim read-only mode (linked to vim) |
| vim | Enhanced text editor |
| vimdiff | Vim file diff (linked to vim) |
| vmstat | Virtual memory/system statistics |
| watch | Execute command periodically |
| wc | Line/word/character count (linked to coreutils) |
| who | Current logged-in users (linked to coreutils) |
| whoami | Current username (linked to coreutils) |
| xargs | Build arguments from standard input and execute |
| yes | Repeat output (linked to coreutils) |
## Python Libraries
The pre-installed third-party libraries in this environment cover common fields such as network communication, encryption security, database connectivity/storage, message queues, image processing, system management, and asynchronous high-performance, ready to use out of the box.
| Library | Version | Description |
|---------------------|-----------|-------------------------------------|
| Brotli | 1.2.0 | Brotli compression/decompression |
| Jinja2 | 3.1.0 | Template engine |
| Pillow | 8.0.0 | Image processing (PIL fork) |
| PyMySQL | 1.2.0 | MySQL database driver |
| PyNaCl | 1.6.2 | Encryption and signatures (NaCl wrapper) |
| PySocks | 1.7.1 | SOCKS proxy client |
| PyYAML | 6.0.2 | YAML parsing and generation |
| amqp | 5.3.1 | AMQP protocol client (RabbitMQ) |
| async-timeout | 5.0.0 | Async timeout control |
| av | 17.0.0 | Audio/video processing (PyAV) |
| bcrypt | 3.2.0 | Password hashing (Blowfish) |
| bidict | 0.23.1 | Bidirectional dictionary data structure |
| cachetools | 7.1.0 | Cache utilities |
| celery | 5.5.2 | Distributed task queue |
| cffi | 2.1.0 | C language interface calls |
| colorama | 0.4.6 | Terminal colored output |
| crc32c | 2.8 | CRC32c checksum algorithm |
| cryptography | 3.3 | Cryptography toolkit (Fernet, X.509, etc.) |
| dnspython | 2.7.0 | DNS resolution library |
| evdev | 1.9.2 | Linux input device event handling |
| frida | 17.15.2 | Dynamic instrumentation tool (injection/debugging) |
| frida-tools | 14.10.4 | Frida command-line tool suite |
| gevent | 21.1.2 | Coroutine-based network library |
| greenlet | 3.5.3 | Low-level coroutine support |
| grpc-interceptor | 0.15.2 | gRPC interceptor extension |
| grpcio | 1.72.2 | gRPC framework core |
| grpcio-tools | 1.72.2 | gRPC protobuf code generation tool |
| iniconfig | 2.3.0 | INI config file parsing |
| jmespath | 1.1.0 | JSON query language (JMESPath) |
| lxml | 6.1.0 | High-performance XML/HTML parsing |
| mmkv | 1.3.14 | Tencent open-source key-value store (MMKV) |
| msgpack | 1.2.1 | MessagePack binary serialization |
| numpy | 2.5.0 | Numerical computing and multi-dimensional arrays |
| opencv-python | 4.11.0 | Computer vision (OpenCV) |
| paho-mqtt | 2.1.0 | MQTT protocol client |
| paramiko | 5.0.0 | SSHv2 protocol client |
| peewee | 4.2.0 | Lightweight ORM |
| pem | 23.1.0 | PEM format certificate parsing |
| pika | 1.4.1 | RabbitMQ client |
| pilk | 0.2.4 | WeChat voice codec |
| protobuf | 4.25.8 | Protocol Buffers serialization |
| psycopg | 3.3.4 | PostgreSQL database driver (async support) |
| psycopg-pool | 3.3.1 | psycopg connection pool |
| pyaxmlparser | 0.3.31 | Android XML (AXML) parser |
| pycparser | 3.0 | C language parser |
| pycryptodome | 3.9.9 | Cryptographic algorithm library (Crypto replacement) |
| pyelftools | 0.33 | ELF file parsing |
| pyinotify | 0.9.6 | Linux inotify file event monitoring |
| pyOpenSSL | 23.1.0 | Python wrapper for OpenSSL |
| pyotp | 2.9.0 | One-time passwords (TOTP/HOTP) |
| python-dateutil | 2.8.2 | Date and time extensions (relative times, etc.) |
| pytz | 2026.2 | Timezone handling (Olson database) |
| pyzbar | 0.1.9 | QR code / barcode decoding |
| qrcode | 8.1 | QR code generation |
| redis | 5.2.1 | Redis database client |
| requests | 2.34.2 | HTTP client (simple API) |
| scapy | 2.5.0 | Network packet construction and sniffing |
| sqlalchemy | 2.0.50 | ORM |
| tflite-runtime | 2.15.0 | TensorFlow Lite inference runtime |
| tornado | 6.5.6 | Async web framework and networking library |
| ujson | 4.0.0 | High-performance JSON encoding/decoding |
| urllib3 | 2.7.0 | HTTP client (low-level, requests dependency) |
| uvloop | 0.21.0 | asyncio event loop acceleration (libuv) |
| websocket-client | 1.9.0 | WebSocket client (synchronous) |
| websockets | 13.1 | WebSocket client/server (asynchronous) |
| xmltodict | 0.12.0 | XML to Python dict conversion |
| zstandard | 0.25.0 | Zstandard compression algorithm |
You can use the `pip` command to install third-party libraries implemented in pure Python, but you cannot install compiled libraries or those with .so files. For a full experience, we recommend using the [Virtual Debian Environment](./virtual-debian.md).
--- END OF built-in-terminal.md ---
--- DOCUMENT: built-in-adb.md ---
--- SOURCE: https://device-farm.com/docs/content/en/built-in-adb.md ---
# Built-in ADB
The built-in ADB service is completely independent of the system's own ADB. Before use, you need to manually install the ADB public key into the service, otherwise the connection will show as unauthorized (the key authorized in system developer mode is not shared with this built-in ADB). With this feature, you can connect wirelessly to a highest-privilege ADB **without enabling system developer mode**, which can be used to hide developer mode detection.
```{attention}
If you intend to use adb for JDWP application debugging, because JDWP debugging related functions have system uniqueness and conflict with the system's built-in JDWP debugging function, this built-in ADB is currently unable to support it.
```
## Install Key (API)
To install the key, you need to have the ADB public key ready. It is usually located in the `~/.android` or `C:\\Users\\name\\.android` directory, with the filename `adbkey.pub`. If you only have `adbkey` without the `.pub` file, you can generate it manually by executing the command `adb pubkey adbkey >adbkey.pub`.
```python
d.install_adb_pubkey("/path/to/adbkey.pub")
```
## Uninstall Key (API)
```python
d.uninstall_adb_pubkey("/path/to/adbkey.pub")
```
## Use the Tool Script to Install/Uninstall the Public Key
The following command will install your current local machine's ADB public key into the FIRERPA service.
```{note}
Before running adb_pubkey.py, please complete the environment configuration in [Tool Preparation](./tools-prepare.md) (such as CERTIFICATE, PORT, etc.).
```
```bash
python3 -u adb_pubkey.py install 192.168.1.2
```
Uninstall the public key:
```bash
python3 -u adb_pubkey.py uninstall 192.168.1.2
```
## Connect to Built-in ADB
After installing the public key, execute the following command to connect to the built-in ADB service.
```bash
adb connect 192.168.1.2:65000
```
--- END OF built-in-adb.md ---
--- DOCUMENT: device-http-proxy.md ---
--- SOURCE: https://device-farm.com/docs/content/en/device-http-proxy.md ---
# Device HTTP Proxy
FIRERPA provides an HTTP proxy on the phone (the bridging proxy in service configuration, i.e., `tunnel2.*`). When you point your computer or browser's proxy server to the phone, computer traffic to websites will first go through the phone and then exit from the phone's network, so the external IP appears the same as the phone's. It is the opposite of [Setting Proxy](./set-proxy.md), as compared below:
| | **Device HTTP Proxy** (this chapter) | **Setting Proxy** |
| --- | --- | --- |
| **Network Direction** | **Computer** → via **phone** to the internet | **App on phone** → via **external proxy** to the internet |
| **Typical Use** | Allow the computer to use the phone's IP | Assign a proxy IP to the phone |
Common uses include letting a computer and phone share the same egress IP for joint debugging and troubleshooting, pointing mitmproxy's upstream to the phone when capturing traffic so that traffic still originates from the device, or using multiple devices' networks as IP sources.
## Using the Proxy
This feature is enabled by default. The proxy shares the same service port as remote desktop, defaulting to `65000`. Check the current IP in the phone's WLAN settings (the example below uses `192.168.0.2`). If you have changed the `port`, please replace the port in the examples accordingly. The computer must be able to access the phone's FIRERPA service port, which usually means being on the same LAN or forwarding the service port to the public network.
You can quickly verify with the following command on the computer (replace the IP with your device's address):
```bash
curl -x http://192.168.0.2:65000 https://httpbin.org/ip
```
If the returned IP matches the phone's external IP, it is working correctly. In Firefox, you can set the HTTP proxy to `192.168.0.2`, port `65000`, and check "Also use this proxy for HTTPS". In Python, you can write:
```python
requests.get("https://httpbin.org/ip", proxies={"http": "http://192.168.0.2:65000", "https": "http://192.168.0.2:65000"})
```
When the service certificate is not enabled, the proxy requires no authentication by default. If you started with a service certificate, you need to use the username `lamda` and the password from the first line of the service certificate PEM file (the same as the HTTPS remote desktop login password, but not the WebUI custom password). You can also set `tunnel2.password` in [Service Configuration](./properties.md) to specify a separate proxy password. In this case, the curl example becomes:
```bash
curl --proxy-insecure -x https://lamda:cert-password@192.168.0.2:65000 https://httpbin.org/ip
```
And the Python example:
```python
requests.get("https://httpbin.org/ip", verify=False, proxies={"http": "https://lamda:cert-password@192.168.0.2:65000", "https": "https://lamda:cert-password@192.168.0.2:65000"})
```
## Configuring the Proxy
You can adjust the bridging proxy in [Service Configuration](./properties.md). For example, set `tunnel2.login` and `tunnel2.password` to customize the login credentials:
```ini
tunnel2.login=lamda
tunnel2.password=your-new-password
```
`tunnel2.iface` specifies the egress network interface: `wlan` for Wi-Fi, `rmnet` for mobile data (4G/5G; even if Wi-Fi is connected, mobile data will be preferred). When not configured, the system default network is used. If the selected interface currently has no network, the proxy will be unavailable.
```ini
tunnel2.iface=rmnet
```
--- END OF device-http-proxy.md ---
--- DOCUMENT: client.md ---
--- SOURCE: https://device-farm.com/docs/content/en/client.md ---
# Installing the Client
This chapter describes how to install the Python client library **lamda** that accompanies FIRERPA. Before you begin, ensure that Python version 3.6 to 3.13 is installed on your computer, then run the following command:
```bash
pip3 install -U lamda[full]
```
If the above command throws an error, try the version below. The only difference is the added single quotes, because on different systems or Python versions, the command may be interpreted differently. So you can try both methods.
```bash
pip3 install -U 'lamda[full]'
```
```{tip}
You can use the -i parameter to set the pip source to a mirror server closest to you (e.g., the USTC mirror) to greatly speed up the installation.
```
## Troubleshooting
After installation, you can run the command `python3 -m lamda.client` to verify that it was installed correctly. If you encounter an encoding error such as `UnicodeEncodeError: 'ascii' codec can't encode characters in...`, it may be because the path contains Chinese characters, causing gRPC to fail to handle them properly. You can run the following code to verify whether this is the issue.
```python
import sys
print(sys.path)
```
```{note}
If the printed sys.path contains one or more paths with Chinese characters, check your system environment variables, PYTHONPATH, or other Python-related environment variables, and remove any paths that contain Chinese characters.
```
If you encounter other import errors such as `ImportError`, run the following command to force a reinstall.
```bash
pip3 install -U --force-reinstall 'lamda[full]'
```
```{tip}
If these problems persist, we recommend creating a virtualenv to use.
```
```{attention}
After installation, you may need to use pip to update all third-party libraries that depend on frida, such as frida-tools, objection, etc. Otherwise, subtle anomalies may occur when you use these tools later.
```
--- END OF client.md ---
--- DOCUMENT: api-prepare.md ---
--- SOURCE: https://device-farm.com/docs/content/en/api-prepare.md ---
# API Introduction
This section introduces basic API usage, allowing you to learn another way to use FIRERPA.
FIRERPA provides up to 160 programmatic API interfaces, enabling you to manage and operate Android devices with meticulous detail, including command execution, system settings, system status, application-related operations, automation, proxies, files, and more than a dozen other categories. A well-packaged Python library is provided to help you get started quickly. Before beginning, please ensure that the FIRERPA server is running normally on your phone and that you have installed the FIRERPA client library `lamda` as required.
```{tip}
Don't worry if you can't write or understand the code – use the Agent Skills we provide [github.com/firerpa/skills](https://github.com/firerpa/skills) to automatically develop with large models.
```
```{hint}
Many API methods provided by the FIRERPA Python client library return native proto classes. You can directly access field values via the properties of the output result, or check the proto definitions in the `rpc` directory to see which attributes are available in different results. For example, if a method returns the following result, you can directly access specific fields as shown.
```
```python
>>> result = status.get_battery_info()
>>> print (result)
batt_charging: true
batt_percent: 100
batt_temperature: 26.899999618530273
>>> print (result.batt_temperature)
26.899999618530273
```
## Connecting to a Device
Before connecting to a device, you need to prepare some necessary information, such as the IP address that can reach your phone, and whether you used a [server certificate](./server-certificate.md) when starting FIRERPA. Once you have this information ready, you can proceed with the steps below.
Instantiate the device; by default you only need to provide an accessible IP address.
```python
from lamda.client import *
d = Device("192.168.0.2")
```
If you enabled the server certificate or need to change the port when starting the FIRERPA server, connect like this.
```python
from lamda.client import *
d = Device("192.168.0.2", certificate="/path/to/lamda.pem", port=65000)
```
From now on, the variable `d` in the following text will always refer to this Device instance.
## Simple Warm-up
Now, execute the following code – it will launch the Settings app for you.
```python
d.application('com.android.settings').start()
```
You can also use the following method to make the device beep. This is very convenient for locating it when you have multiple devices.
```python
d.beep()
```
Alright, now you have understood the basic usage. Continue reading to learn about other available interfaces.
--- END OF api-prepare.md ---
--- DOCUMENT: ui-basics.md ---
--- SOURCE: https://device-farm.com/docs/content/en/ui-basics.md ---
# Basic Automation
This chapter describes the basic automation features provided by FIRERPA. More advanced features will be introduced in later chapters. Here we present some commonly used basic function interfaces.
## Get Device Information
Retrieve current device information, such as device name, screen size, screen orientation, and the currently running application.
```python
d.device_info()
```
```python
>>> d.device_info()
productName: "bumblebee"
sdkInt: 34
displayHeight: 2400
displaySizeDpX: 411
displaySizeDpY: 914
displayWidth: 1080
screenOn: true
naturalOrientation: true
currentPackageName: "com.android.launcher3"
```
```python
>>> result = d.device_info()
>>> print (result.displayWidth)
1080
```
## Get Service Information
Get information about the current service, such as version, unique device ID, ABI, etc.
```python
d.server_info()
```
```python
>>> d.server_info()
uniqueId: "673abbe0-ff7b-9d82-1792-8876cb72cf56"
version: "9.25"
architecture: "arm64-v8a"
uptime: 293
secure: false
```
```{note}
Fields like version, secure, etc. are subject to the actual output of `d.server_info()`. When the [server certificate](./server-certificate.md) is not enabled, `secure` is usually `false`.
```
```python
>>> result = d.server_info()
>>> print (result.secure)
False
```
## Screen Off
The following interface will turn off the current device screen, equivalent to pressing the power button when the screen is on.
```python
d.sleep()
```
## Screen On
The following interface will turn on the current device screen, equivalent to pressing the power button when the screen is off.
```python
d.wake_up()
```
## Check if Screen is On
You can use the following interface to check whether the current device screen is on, to determine whether interaction is possible.
```python
d.is_screen_on()
```
## Check if Screen is Locked
You can use the following interface to check whether the current device screen is locked, to determine whether interaction is possible.
```python
d.is_screen_locked()
```
## Show Toast
You can use the following interface to display a Toast message “Hello from Lamda!” on the phone screen.
```python
d.show_toast("Hello from Lamda!")
```
## Read Clipboard
The following interface reads the current content of the device clipboard. Android 10+ is not yet supported.
```python
d.get_clipboard()
```
## Write Clipboard
The following interface writes content to the clipboard of the current device.
```python
d.set_clipboard("clipboard content")
```
## Physical Keys
You can use the following methods to simulate key presses. More than ten key codes are supported, such as KEY_BACK, KEY_DOWN, KEY_HOME, etc.
```python
d.press_key(Keys.KEY_BACK)
d.press_key(Keys.KEY_CAMERA)
d.press_key(Keys.KEY_CENTER)
d.press_key(Keys.KEY_DELETE)
d.press_key(Keys.KEY_DOWN)
d.press_key(Keys.KEY_ENTER)
d.press_key(Keys.KEY_HOME)
d.press_key(Keys.KEY_LEFT)
d.press_key(Keys.KEY_MENU)
d.press_key(Keys.KEY_POWER)
d.press_key(Keys.KEY_RECENT)
d.press_key(Keys.KEY_RIGHT)
d.press_key(Keys.KEY_SEARCH)
d.press_key(Keys.KEY_UP)
d.press_key(Keys.KEY_VOLUME_DOWN)
d.press_key(Keys.KEY_VOLUME_MUTE)
d.press_key(Keys.KEY_VOLUME_UP)
```
In addition, to support more key codes, you can also use the following method. You can find all supported key names in the official Android documentation: [https://developer.android.com/reference/android/view/KeyEvent](https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_0).
```python
d.press_keycode(KeyCodes.KEYCODE_CALL)
```
## Screenshot
We provide a screenshot method so you can record processes or perform image recognition tasks. The following example shows how to capture a screenshot with a quality of 60 and save it as `screenshot.png` in the current directory.
```python
d.screenshot(60).save("screenshot.png")
```
```python
>>> result = d.screenshot(60)
>>> result
>>> result.save("screenshot.png")
52917
>>> result.getvalue()
b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01....'
```
Currently, we also support region and element screenshots. Element screenshots are not covered in this chapter; here we introduce region screenshots. You need to understand the concept and definition of a region: in our API, a region is defined by `Bound`, which contains four parameters: top, left, right, and bottom. The Android screen coordinate system is different from the usual Cartesian system. The top-left corner of the screen is the origin, with coordinates x=0, y=0. Suppose the screen is in portrait mode with a resolution of 1080×1920. To take a full-screen region screenshot, you can do:
```python
d.screenshot(60, bound=Bound(top=0, left=0, right=1080, bottom=1920)).save("screenshot.png")
```
This will produce a full-screen screenshot, though doing so is unnecessary because the default is already a full-screen capture. How about capturing a 200×200 image at the top-left corner? That's also simple; you just need to do the following:
```python
d.screenshot(60, bound=Bound(top=0, left=0, right=200, bottom=200)).save("screenshot.png")
```
Here we need to explain the meaning of these parameters: `top` represents the offset in pixels downward along the Y-axis, `bottom` represents the offset downward along the Y-axis to the bottom edge, and `left` and `right` represent offsets in pixels to the right along the X-axis. `top` is always less than `bottom`, and `left` is always less than `right`. Let's demonstrate another example: capturing a 200×200 image at the bottom-right corner of the screen. You only need to call the API like this:
```python
d.screenshot(60, bound=Bound(top=1920-200, bottom=1920, left=1080-200, right=1080)).save("screenshot.png")
```
## Click a Point on the Screen
You can simulate a screen tap using the following interface. In the example, we click the point at coordinates (100, 100) on the screen.
```python
d.click(Point(x=100, y=100))
```
## Tap Point A and Drag to Point B
The following interface allows you to drag an object from point A to point B, for example, dragging an icon into a folder.
```python
A = Point(x=100, y=100)
B = Point(x=500, y=500)
d.drag(A, B)
```
## Swipe from Point A to Point B
The following interface achieves a swipe from screen coordinates (100, 100) to (500, 500).
```python
A = Point(x=100, y=100)
B = Point(x=500, y=500)
d.swipe(A, B)
```
## Slightly More Complex Multi-Point Swipe
The following interface can simulate multi-point swipes. For example, the call below will swipe from (100, 100) to (500, 500) and back to (200, 200). The demonstration only uses three points, but you can actually provide more points, even to realize a pattern unlock.
```python
p1 = Point(x=100, y=100)
p2 = Point(x=500, y=500)
p3 = Point(x=200, y=200)
d.swipe_points(p1, p2, p3)
```
## Open Quick Settings
The following call opens the quick settings panel, but only partially (half-open state).
```python
d.open_quick_settings()
```
## Open Notification Area
The following call opens the notification panel on the screen.
```python
d.open_notification()
```
## Get Page Layout
The following call retrieves the XML layout of the current page. You can parse it yourself to support XPath-based automation.
```python
d.dump_window_hierarchy()
```
```python
>>> result = d.dump_window_hierarchy()
>>> result
>>> result.getvalue()
b'\r\n\r\n >> result = d.get_last_toast()
>>> print (result)
timestamp: 1700000000000
package: "com.android.settings"
message: "\346\202\250\345\267\262\345\244\204\344\272\216\345\274\200\345\217\221\350\200\205\346\250\241\345\274\217\357\274\214\346\227\240\351\234\200\350\277\233\350\241\214\346\255\244\346\223\215\344\275\234\343\200\202"
>>> print (result.message)
您已处于开发者模式,无需进行此操作。
```
--- END OF ui-basics.md ---
--- DOCUMENT: ui-advanced.md ---
--- SOURCE: https://device-farm.com/docs/content/en/ui-advanced.md ---
# Advanced Interface
This chapter introduces more advanced automation interfaces. You can use these interfaces to perform a variety of detailed operations. The content in this chapter is extensive. If this is your first encounter, we recommend reading through each section patiently.
```{tip}
When writing automation code, you can directly enter the command `lamda` in the terminal on the right side of the remote desktop and execute the following test code inside it, or manually select elements, perform click tests, etc., to speed up both coding and verification.
```
## Getting Elements
You may have already gained some understanding of this in the Basics or earlier sections. You need to locate relevant elements via selectors to perform operations. You should also have seen where to obtain selector parameters. The following introduction will revolve around these elements. You can see information about the element “同意” (Agree) on the right side of this image.

```{attention}
The element you click directly in the left interface may not be the actual element because it might overlap in size and position with other elements. Typically, multiple overlapping elements will be listed in the information panel on the right; you can scroll up and down to see which one you actually need. You can also manually traverse all elements by pressing the TAB key in the left selection interface.
```
For the above element, we generally obtain it via `text`. Using `text` works if there is no other element on the current screen that also has the text “同意”—this is the simplest method. Alternatively, you can use `resourceId`, but note that `resourceId` here does not represent a unique ID; it represents a resource ID, and a single screen may contain many elements with the same resource ID. Other fields like `packageName`, `checkable`, etc., are not commonly used, but if `text`, `resourceId`, `description`, etc., are unavailable, you can try these fields. We can locate this element in the following ways:
```python
element = d(text="同意")
element = d(text="同意", resourceId="com.tencent.news:id/btm_first_agree")
element = d(resourceId="com.tencent.news:id/btm_first_agree")
```
## Element Click
Call the following interface to perform a normal element click, which will achieve the effect of clicking “同意” (Agree) manually in the context.
```python
element.click()
```
If you need to specify the click position on the element, you can provide the `corner` parameter when calling the click method. For example, `Corner.COR_CENTER` means clicking the center of the element, and you can also click its top-left corner or bottom-right corner (`Corner.COR_BOTTOMRIGHT`).
```python
element.click_exists(corner=Corner.COR_TOPLEFT)
```
Perform a long click on this element, raising an exception if it does not exist. This interface also supports `corner`, but you cannot specify the long‑click duration.
```python
element.long_click()
```
Click the element if it exists; if not, calling this method will not raise an exception. This interface also supports `corner`.
```python
element.click_exists()
```
```python
>>> element.click_exists()
True
```
## Existence Check
In many situations, it is necessary to check the existence of an element before proceeding with further operations; otherwise, the subsequent flow may encounter exceptions or even perform wrong actions on an incorrect screen. You can use the following interface to determine existence.
```python
element.exists()
```
## Element Information
In some cases, you may want to retrieve part of an element’s information, such as its coordinates, region information, or the text, description, etc., displayed on the element. You can read element information with the following interface.
```python
element.info()
```
For our test element above, the output information is as follows.
```python
>>> info = element.info()
>>> print (info)
bounds { ... }
className: "android.widget.TextView"
clickable: true
enabled: true
focusable: true
packageName: "com.tencent.news"
resourceName: "com.tencent.news:id/btn_first_agree"
text: "\345\220\214\346\204\217"
visibleBounds { ... }
```
```{hint}
You may notice that the printed information is missing some fields, such as `description`. This usually indicates that the field value is empty or false; you can still access the related field normally via the attribute to obtain its value.
```
As you can see, this information is somewhat complex, in the default protobuf printing format. You can directly access the corresponding attributes to print their actual values. For example, to read the element’s text value, you can simply use the following.
```python
>>> info = element.info()
>>> print (info.text)
同意
```
Of course, there is also element region/coordinate information, which you can likewise access. For instance, if you want to obtain the region information corresponding to this element, you can print the region as shown below, or save it as a variable for later operations.
```python
>>> info = element.info()
>>> print (info.bounds)
top: 947
left: 338
bottom: 997
right: 743
```
The output or returned value is a region object (`Bounds`). You will find that this parameter is also used by some screenshot interfaces. You can pass this parameter to a screenshot interface to capture only that element. However, we have already provided a more convenient method for you.
You may also want to obtain the element’s width and height to calculate offsets, such as relative offsets of other elements. You can use:
```python
>>> info = element.info()
>>> print (info.bounds.width, info.bounds.height)
484 138
```
Or obtain the center or corner points of the element, such as top-left, bottom-right, etc. The following interfaces typically return a `Point` object, from which you can retrieve the corresponding X and Y screen coordinates.
```python
>>> info = element.info()
>>> print (info.bounds.center())
x: 792
y: 1908
>>> print (info.bounds.center().x)
792
```
The following call obtains the coordinates of a corner point. This example gets the coordinates of the top-left corner; additionally, you can obtain the coordinates of the other three corners: `bottom-right`, `top-right`, and `bottom-left`.
```python
>>> info = element.info()
>>> print (info.bounds.corner("top-left"))
x: 550
y: 1839
>>> print (info.bounds.corner("top-left").x)
550
```
## Element Traversal
You can also traverse all elements matched by a selector. Under normal circumstances, this selector in the current context may match only one element. If you want to test traversal, choose a selector that matches multiple elements. You can directly use a `for` loop or other methods on the selector to iterate.
```python
for i in element: print (i.info())
```
Or if you know there are multiple matching elements and want a specific Nth match, you can obtain it as follows.
```python
element_3rd = element.get(3)
```
## Element Count
You usually will not use this interface directly. The following call returns the number of elements matched by your current selector.
```python
>>> element.count()
1
```
## Element Screenshot
We support element‑level screenshots; you can capture just the element’s image without taking a full‑screen screenshot and then cropping it.
```python
element.screenshot(quality=60)
```
After capturing, you can directly use the `getvalue()` method to obtain the binary data of the screenshot, or pass it directly to `PIL.Image`.
```python
>>> element.screenshot(quality=60).getvalue()
b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xff\xe2\x02(ICC_PROFILE\x00\x01\x01\x00\x00\x02\x18\x00\x00\x00\x00\x02\x10\x00\x00mntrRGB XYZ \x00\x00...
```
Or, if you do not need further processing, you can save the screenshot directly to a local file.
```python
>>> element.screenshot(quality=60).save("image.png")
```
## Waiting for an Element
In some situations, you may need to determine whether the current page has finished loading. This can often be judged by checking whether related elements have appeared. The following example waits for the “同意” element to appear, with a maximum wait time of 10 seconds.
```{hint}
The wait duration here is in milliseconds, so 10 seconds means `*1000`; 10 seconds = 10000 milliseconds.
```
```python
element.wait_for_exists(10*1000)
```
```python
>>> element.wait_for_exists(10*1000)
True
```
Additionally, we support waiting for an element to disappear, i.e., waiting until the element is gone from the screen.
```python
element.wait_until_gone(10*1000)
```
```python
>>> element.wait_until_gone(10*1000)
False
```
## Text Input
Text input requires special attention. You cannot input text onto a button because it is a button. Now we will reselect an input field element to introduce this; the basic information of this element is as follows.

```{attention}
There are some points to note when capturing an input field element: please note that when locating an input field element, your input method must be in **pop‑up state**, and then you should search for the related element. It is recommended to look carefully, otherwise you might not find the real input field.
```
```{hint}
In an automation flow, all you need to do to make the input method pop up is to first click the input field displayed in the parent container in your code.
```
For the above input field, we can call the following interface to enter the string “你好世界” (Hello World). You can also input English or other Unicode strings; simply use it as follows to input text into the box.
```python
>>> element = d(text="搜索感兴趣的内容")
>>> element.set_text("你好世界")
True
```
If you want to retrieve the currently displayed text content of that input field, you can call it as follows.
```{attention}
Please note that here we have changed the selector. The initial selector used the `text` attribute, but after entering text, the element’s content changed, causing the original selector to no longer match; therefore we switched to another selector. Choosing the right selector is important, but this example is for demonstration only, so it will do as such.
```
```python
>>> element = d(className="android.widget.EditText")
>>> element.get_text()
'你好世界'
```
You can also clear the currently input content. Usually, inputting text automatically clears the existing text, but you can also clear it manually.
```{hint}
Pressing the BACKSPACE key repeatedly via the key interface in a loop can achieve a similar effect.
```
```python
>>> element = d(className="android.widget.EditText")
>>> element.clear_text_field( )
True
```
```{note}
In extreme cases, there may be some places where this interface cannot be used to input text normally; we are working on supporting these.
```
## Normal Swiping
Use the following interface to perform swipe operations on the screen, such as scrolling up and down in a list. The following call performs an upward swipe; the larger the `step` value, the slower the swipe, making it suitable for swipes requiring higher precision.
```{attention}
In simple cases, this operation does not require providing a selector parameter. If you encounter situations where swiping does not work, set the selector condition to a suitable element, such as an element with the `scrollable` attribute or the first‑level container of the list.
```
```python
d().swipe(direction=Direction.DIR_UP, step=32)
```
```python
>>> element = d(resourceId="com.tencent.news:id/important_list_content")
>>> element.swipe(direction=Direction.DIR_UP, step=32)
True
```
| Direction | Description |
|-------------------------|--------------------|
| Direction.DIR_UP | Swipe up |
| Direction.DIR_LEFT | Swipe left |
| Direction.DIR_DOWN | Swipe down |
| Direction.DIR_RIGHT | Swipe right |
## Fling (Fast Swipe)
Flinging resembles a human’s fast swipe behavior. This operation will quickly swipe the screen and is suitable for simulating rapid browsing actions. The following example flings from top to bottom; the selector is empty in the example, but you still need to decide whether to provide a selector based on the actual situation.
```python
d().fling_from_top_to_bottom()
```
Fling from bottom to top:
```python
d().fling_from_bottom_to_top()
```
Fling from left to right:
```python
d().fling_from_left_to_right()
```
Fling from right to left:
```python
d().fling_from_right_to_left()
```
```{attention}
In simple cases, this operation does not require providing a selector parameter. If you encounter situations where flinging does not work, set the selector condition to a suitable element, such as an element with the `scrollable` attribute or the first‑level container of the list.
```
```python
>>> element = d(resourceId="com.tencent.news:id/important_list_content")
>>> element.fling_from_bottom_to_top()
True
```
## Element Dragging
Drag an element to the position of another element (e.g., dragging an app icon into a folder).
```python
element.drag_to(Selector(text="购物")) # Drag to the target element's location
```
## Child and Sibling Queries
For repeated or featureless elements, you can first locate a parent container, then use `child` to get child elements and `sibling` to get sibling elements, thus narrowing down the scope.
```python
form = d(resourceId="login_form") # Locate the parent container
form.child().get(1) # Get the first child element under form
form.sibling(textContains="找回密码") # Get a sibling element of form whose text contains "找回密码"
# Here is a slightly more complex query
# Obtain the first result matching resourceId=com.example.com:id/resource, select its child node with resourceId=com.example.com:id/abc, and within that child node, query for an element whose description contains "一天内", then output its information.
d(resourceId="com.example.com:id/resource").get(0).child(resourceId="com.example.com:id/abc").child(descriptionContains="一天内").info()
```
For the example layout information below, you can use the following query to precisely select that element.
```python
d(resourceId="com.zhiliaoapp.musically:id/bxa").child().get(3).child().get(1).info()
```
```{hint}
In most cases, you do not need such precise child/sibling query statements; a single `d(text="Continue with Google")` is sufficient, except when it is truly impossible to locate by text.
```
## Fling to Edge
Continuously fling in one direction until no further scrolling is possible; it may not always detect reaching the end, so you must specify `max_swipes`.
```python
d().fling_from_top_to_bottom_to_end(max_swipes=32) # Fling down to the edge
d().fling_from_bottom_to_top_to_end(max_swipes=32) # Fling up
d().fling_from_left_to_right_to_end(max_swipes=32) # Fling right
d().fling_from_right_to_left_to_end(max_swipes=32) # Fling left
```
## Scroll (Constant-speed Swipe)
Scroll with a fixed step size `step`, more mechanical than `swipe`, suitable for scenarios requiring steady stepping.
```python
d().scroll_from_top_to_bottom(step=60) # Down
d().scroll_from_bottom_to_top(step=60) # Up
d().scroll_from_left_to_right(step=60) # Right
d().scroll_from_right_to_left(step=60) # Left
```
## Scroll to Edge
Similar to flinging to the edge, but using constant‑speed scrolling instead of `fling`. Again you must specify `max_swipes` and `step`.
```python
d().scroll_from_top_to_bottom_to_end(max_swipes=32, step=60) # Scroll down to the edge
d().scroll_from_bottom_to_top_to_end(max_swipes=32, step=60) # Scroll up
d().scroll_from_left_to_right_to_end(max_swipes=32, step=60) # Scroll right
d().scroll_from_right_to_left_to_end(max_swipes=32, step=60) # Scroll left
```
--- END OF ui-advanced.md ---
--- DOCUMENT: ui-watcher.md ---
--- SOURCE: https://device-farm.com/docs/content/en/ui-watcher.md ---
# Interface Watcher
The watcher is used to monitor changes in the current display interface in real time and, when preset conditions are met, execute preset actions such as clicking elements, pressing keys, or recording occurrences. You can think of it as the underlying implementation of functionality like **Li Tiaotiao** or similar automatic ad-skipping features. Because this feature automatically performs click or key operations, unexpected actions may occur when manual intervention is required, so please use with caution.
```{tip}
Normally, watchers and event registration should be performed at the very beginning of the script and generally not during execution.
```
## Enable Watcher
The following interface will enable the interface watcher. By default, newly registered watchers are in a disabled/off state.
```{attention}
Before enabling the watcher or a monitoring event, any changes on the interface that match the monitoring conditions will not be automatically processed.
```
```python
d.set_watcher_loop_enabled(True)
```
## Get Whether Watcher is Enabled
You can use the following interface to check whether the interface watcher is currently enabled.
```python
d.get_watcher_loop_enabled()
```
## Disable Watcher
After disabling the watcher loop, registered events such as automatic clicks will no longer be automatically processed.
```python
d.set_watcher_loop_enabled(False)
```
```{attention}
Registering too many monitoring events may affect system performance or operation real-time performance.
```
## Remove All Watchers
Remove all applied monitoring events. This can clear rules applied by scripts that ran previously or were abnormally terminated. It is recommended to execute this once before each script startup to prevent events registered by the previous run or abnormally interrupted script from affecting the current normal processing flow.
```python
d.remove_all_watchers()
```
## Get All Watchers
Get all registered watchers, including all states (enabled and disabled watchers).
```python
>>> d.get_watchers()
[name: "ClickAcceptWhenShowAggrement"
, name: "PressBackWhenHomePageShows"
enabled: true
, name: "RecordElementAppearTimes"
enabled: true
]
>>> d.get_watchers()[0].enabled
False
>>>
```
## Get All Enabled Watchers
Get all watchers that are in the enabled state.
```python
>>> d.get_enabled_watchers( )
[name: "PressBackWhenHomePageShows"
enabled: true
, name: "RecordElementAppearTimes"
enabled: true
]
>>>
```
## Register Click Event
The click event will automatically perform click operations when a matching selector appears on the interface, such as automatically bypassing software pop-ups like user agreements, update prompts, etc., or automatically clicking on certain specific interfaces.
```{attention}
Before enabling the watcher or a monitoring event, any changes on the interface that match the monitoring conditions will not be automatically processed.
```
```python
d.register_click_target_selector_watcher("ClickAcceptWhenShowAggrement", [Selector(textContains="用户协议")], Selector(textContains="同意", clickable=True))
```
```{hint}
The second parameter supports multiple Selectors, meaning event processing will occur only when all Selector conditions are met.
```
After registering the above event, when an element containing the text "用户协议" appears on the interface, the "同意" (Agree) button will be automatically clicked.
## Register Key Press Event
The key press event will automatically perform key operations when a matching selector appears on the interface, such as automatic back press, which can automatically avoid entering certain interfaces. Of course, the same operation can also be achieved using click events.
```{attention}
Before enabling the watcher or a monitoring event, any changes on the interface that match the monitoring conditions will not be automatically processed.
```
```python
d.register_press_key_watcher("PressBackWhenHomePageShows", [Selector(textContains="个人中心")], Keys.KEY_HOME)
```
```{hint}
The second parameter supports multiple Selectors, meaning event processing will occur only when all Selector conditions are met.
```
After registering the above event, when an element containing the text "个人中心" appears on the interface, the phone's HOME key will be automatically pressed.
## Register Count Event
A count event increments the counter by 1 when an operation matching certain selectors appears on the interface. When important information flashes on the screen, you can use this feature to count whether certain interfaces have appeared or the number of times they appear.
```{attention}
Before enabling the watcher or a monitoring event, any changes on the interface that match the monitoring conditions will not be automatically processed.
```
```python
d.register_none_op_watcher("RecordElementAppearTimes", [Selector(textContains="好的")])
```
```{hint}
The second parameter supports multiple Selectors, meaning event processing will occur only when all Selector conditions are met.
```
The above event records the number of times an interface containing "好的" appears. Use the following interface to get the trigger count of the count event.
```python
d.get_watcher_triggered_count("RecordElementAppearTimes")
```
## Enable Event
After an event is registered, it needs to be enabled to be added to the interface watcher loop; otherwise, it will not be applied.
```{attention}
Before enabling the watcher or a monitoring event, any changes on the interface that match the monitoring conditions will not be automatically processed.
```
```python
d.set_watcher_enabled("RecordElementAppearTimes", True)
```
## Disable Event
You can call the following interface to make the interface watcher stop monitoring a certain event.
```{attention}
Before enabling the watcher or a monitoring event, any changes on the interface that match the monitoring conditions will not be automatically processed.
```
```python
d.set_watcher_enabled("RecordElementAppearTimes", False)
```
## Remove Event
Completely remove an event from the watcher; it will also be deleted from registered events.
```python
d.remove_watcher(name)
```
## Get Whether Event is Enabled
Call the following interface to check whether the event is properly enabled, to prevent cases where it is registered but not enabled.
```python
d.get_watcher_enabled(name)
```
## Advanced Selector
In addition to simple selectors, you can also use more complex selectors for element matching. The following example shows how to write a complex selector.
```python
d.register_press_key_watcher("PressBackWhenHomePageShows", [Selector(resourceId="com.android.market:id/v_app_item").child(index=3).sibling(index=2).child(index=2).child()], Keys.KEY_HOME)
```
## Example Complete Code
The following is an example code block that enables the watcher loop and registers three types of watcher events. When text containing "用户协议" appears on the interface, automatically click "同意"; when text containing "个人中心" appears, immediately return to the desktop; when text containing "好的" appears, record the number of times that interface appears.
```{attention}
Before enabling the watcher or a monitoring event, any changes on the interface that match the monitoring conditions will not be automatically processed.
```
```python
d.remove_all_watchers()
d.set_watcher_loop_enabled(True)
d.register_click_target_selector_watcher("ClickAcceptWhenShowAggrement", [Selector(textContains="用户协议")],
Selector(textContains="同意", clickable=True))
d.register_press_key_watcher("PressBackWhenHomePageShows", [Selector(textContains="个人中心")], Keys.KEY_HOME)
d.register_none_op_watcher("RecordElementAppearTimes", [Selector(textContains="好的")])
d.set_watcher_enabled("ClickAcceptWhenShowAggrement", True)
d.set_watcher_enabled("PressBackWhenHomePageShows", True)
d.set_watcher_enabled("RecordElementAppearTimes", True)
```
--- END OF ui-watcher.md ---
--- DOCUMENT: virtual-display.md ---
--- SOURCE: https://device-farm.com/docs/content/en/virtual-display.md ---
# Virtual Screen
A virtual screen refers to creating one or more virtual displays on your phone, essentially an isolated background screen. Apps and automation scripts running on the background screen do not affect the device's main screen. This means that while using automation scripts to automatically reply to WeChat messages on a background screen, you can continue using the main screen to scroll through TikTok videos. Our WebUI also supports multiple screen displays, allowing you to select and view or operate on other screens via the remote desktop.
## Create a Virtual Screen
It's very easy to use. You can create a virtual screen with the following call, which will create a virtual display with the same resolution and DPI as the current screen.
```python
vd = d.create_virtual_display()
```
Of course, you can also specify the virtual screen's resolution and DPI yourself.
```python
vd = d.create_virtual_display(width=1080, height=1920, densityDpi=480)
```
## Launch an App on a Virtual Screen
By default, no app is running on the newly created virtual screen, so it appears as a black screen. Therefore, you need to launch an app on the virtual screen first. You can start an app on the virtual screen using the following method.
```python
app = vd.application("com.android.settings")
app.start()
```
```{tip}
As you can see, it works much like the standard `d.xxx` usage pattern. You can even switch your existing code from `d.xx` to `vd.xx` with minimal changes, allowing your script to seamlessly transition to the virtual screen.
```
## Automation on a Virtual Screen
You can directly refer to other automation interface sections such as [Basic Automation](./ui-basics.md), [Advanced UI](./ui-advanced.md), etc. The usage is exactly the same, and you only need to make minor adjustments to your existing code to fully migrate virtual screen automation capabilities into your scripts. Refer to the code example below – simply replace the original `d` instance with the one created by `create_virtual_display`.
```{attention}
The `vd` instance generated by the virtual screen only includes automation and related actions. Methods that produce global effects, such as setting proxies, still need to be accessed via the original `d` instance. For interfaces that belong to the automation category but would produce global effects, a WARNING message will be output.
```
```python
# Replace the original d instance with a virtual screen instance
_d = Device("example.local")
d = _d.create_virtual_display()
# Original logic code
app = d.application("com.android.settings")
app.start()
...
d(textContains="WLAN").click()
...
# The watcher defined here will only affect the virtual screen
d.set_watcher_loop_enabled(True)
d.register_none_op_watcher("RecordElementAppearTimes", [Selector(textContains="WLAN")])
d.set_watcher_enabled("RecordElementAppearTimes", True)
# ...
d.screenshot(100).save("screenshot.jpeg")
# ...
# Release the virtual screen
d.release_virtual_display()
```
You can also use a `with` context manager, which will automatically destroy related resources after the code block ends.
```python
with d.create_virtual_display() as vd:
do_someting(vd)
```
--- END OF virtual-display.md ---
--- DOCUMENT: ocr.md ---
--- SOURCE: https://device-farm.com/docs/content/en/ocr.md ---
# Text Recognition
This section introduces how to use OCR to assist with interface operations. In applications such as games, conventional interface selectors may not work; in such cases, the OCR approach can be used. The OCR recognition method only supports operations like checking for element existence, clicking, and taking screenshots. Supported OCR backends include paddleocr, easyocr, and custom HTTP API interfaces.
## Setting the OCR Backend
Before using the OCR recognition method, you must set the OCR backend and **install the required dependencies in advance** on your own.
```{attention}
If it is a cluster scenario, i.e., controlling multiple devices on the same computer, you must encapsulate it as an OCR HTTP interface yourself. Using paddleocr or easyocr directly will consume a large amount of local memory or computing resources, because each process will load the models repeatedly.
```
Using paddleocr as the backend, set the screenshot quality for recognition to 80 and enable GPU acceleration.
```python
d.setup_ocr_backend("paddleocr", quality=80, use_gpu=True, drop_score=0.85, use_space_char=True)
```
Using easyocr as the backend, set the screenshot quality for recognition to 80, and recognize simplified Chinese and English.
```python
d.setup_ocr_backend("easyocr", ["ch_sim", "en"], quality=80)
```
Extra parameters in `setup_ocr_backend` should be the arguments used when initializing the respective instance. If you are unsure about the spelling of the above parameters, refer to the official instantiation examples below and compare them with the code above.
```python
paddleocr.PaddleOCR(use_gpu=True, drop_score=0.85, use_space_char=True)
easyocr.Reader(["ch_sim", "en"])
```
A custom OCR backend is mainly used for scenarios with many devices or when the local machine lacks GPU acceleration. You can deploy the recognition functionality as an HTTP service and use the custom backend to request remote recognition. You need to inherit and implement `CustomOcrBackend`, formatting the recognition results according to the required format. You can also find the definition of the response format in our provided [paddle_ocr_http_backend.py](https://github.com/firerpa/lamda/blob/HEAD/tools/paddle_ocr_http_api.py), make minor modifications, and directly deploy that service.
```python
class HttpOcrBackend(CustomOcrBackend):
def __init__(self, url, auth):
self.auth = auth
self.url = url
def ocr(self, image: bytes):
r = requests.post(self.url, headers={"X-Auth": self.auth},
data=image)
return r.json()
```
Then set the OCR recognition backend to the custom service class.
```python
d.setup_ocr_backend(HttpOcrBackend, "http://server/ocr", "Secret")
```
## OCR Selectors
Currently, the OCR recognition selectors support the following types.
### text
Matches the complete text.
```python
element = d.ocr(text="我的")
```
### textContains
Partial text containment match.
```python
element = d.ocr(textContains="我的")
```
### textMatches
Text regular expression match.
```python
element = d.ocr(textMatches=".*?我的")
```
## OCR Operations
Currently, OCR recognition selectors support the following related operations.
### click
Click the selected element.
```python
element.click()
```
### click_exists
Click if the element exists.
```python
element.click_exists()
```
### exists
Check if the element exists.
```python
element.exists()
```
### screenshot
Take a screenshot of the matched element.
```python
element.screenshot(100).save("element.png")
```
### info
Get the matched OCR information.
```python
element.info()
```
```{tip}
If OCR still cannot solve your problem, you can also try using the image feature matching interface for image matching.
```
--- END OF ocr.md ---
--- DOCUMENT: image-match.md ---
--- SOURCE: https://device-farm.com/docs/content/en/image-match.md ---
# Image Matching
This section introduces how to use template matching to locate and click elements. FIRERPA supports both template matching and feature point matching (SIFT). Before you begin, you need to understand some basics about image matching. Image matching is divided into template matching and feature point matching. Template matching is suitable for devices with the same resolution and is more efficient. Feature point matching is suitable for screens with different resolutions, but some thresholds may need fine-tuning according to the screen size of different devices.
```{hint}
All matching processes are performed on the FIRERPA server and do not consume local resources. However, due to mobile device performance limitations, the efficiency is lower. If performance is a concern, you can capture the screenshot locally and perform matching on your own machine.
```
## Matching Interface
The image matching interface is a relatively complex interface, but in most cases, you only need to adjust the `threshold` and `distance` parameters. You can find the meanings of each parameter in the parameter explanation below. The main form of the interface is as follows, by default performing full-screen matching using the template matching method. This interface is mainly used to save your host's computing resources. If you have requirements for performance or recognition results, you can also use the screenshot interface to capture and match images yourself.
```python
d.find_similar_image(data, threshold=0.0, distance=250, scale=1.0, area=FindImageArea.FIA_WHOLE_SCREEN, method=FindImageMethod.FIM_TEMPLATE)
```
| Field | Description |
| ----------- | ----------- |
| `data` | Byte data of the image to be matched (screenshot crop) |
| `threshold` | Discard threshold (similarity) |
| `distance` | Maximum feature point distance (feature matching only) |
| `scale` | Scale matching (improves performance, not applicable to template matching) |
| `area` | Matching area (restricting area improves performance) |
| `method` | Matching method |
## Matching Method
The matching method supports template matching and feature point matching. Template matching is suitable for cases with obvious texture and fixed target shapes, but is not robust to rotation, scale changes, and illumination variations. Feature point matching detects image feature points such as corners and edges, describes the features, and then matches them between two images. It is more robust to rotation, scale changes, and illumination variations, making it suitable for complex scene matching tasks.
| Matching Method | Description |
| ----------- | ----------- |
| `FindImageMethod.FIM_TEMPLATE` | Template matching |
| `FindImageMethod.FIM_FEATURE` | Feature point matching |
## Matching Area
The matching area is mainly used to balance mobile device performance. In most cases, the items to be matched are in a fixed area of the screen. You can reduce the image size by specifying the area where they are located, thus reducing the computational load during matching.
| Matching Area | Description |
| ----------- | ----------- |
| `FindImageArea.FIA_WHOLE_SCREEN` | Full screen matching |
| `FindImageArea.FIA_LEFT` | Left half screen matching |
| `FindImageArea.FIA_TOP_LEFT` | Top-left corner screen matching |
| `FindImageArea.FIA_TOP` | Top half screen matching |
| `FindImageArea.FIA_TOP_RIGHT` | Top-right corner screen matching |
| `FindImageArea.FIA_RIGHT` | Right half screen matching |
| `FindImageArea.FIA_BOTTOM_RIGHT` | Bottom-right corner screen matching |
| `FindImageArea.FIA_BOTTOM` | Bottom half screen matching |
| `FindImageArea.FIA_BOTTOM_LEFT` | Bottom-left corner screen matching |
--- END OF image-match.md ---
--- DOCUMENT: multi-touch.md ---
--- SOURCE: https://device-farm.com/docs/content/en/multi-touch.md ---
# Multi-finger Touch
In addition to regular automation, we also provide an advanced multi-finger touch interface, which supports hardware trajectory recording, replay, construction, saving, and other functions, allowing you to implement complex finger operations.
```{attention}
The multi-finger touch interface requires your client to be upgraded to at least version 9.22. For optimal compatibility, we recommend upgrading the server as well.
```
To quickly get started, we recommend enabling Developer Options - Input - Show taps and Pointer location (if not enabled, you may not see any feedback). The following example code shows how to draw an X symbol at the top-left corner of the screen.
```python
touch = d.touch()
finger0 = touch.contact(0)
finger1 = touch.contact(1)
# --- 1. Press at start points ---
finger0.down(150, 320)
finger1.down(450, 320)
touch.wait(20)
# --- 2. Move along trajectory (10 coordinate points) ---
finger0.move(180, 350)
finger1.move(420, 350)
touch.wait(20)
finger0.move(210, 380)
finger1.move(390, 380)
touch.wait(20)
finger0.move(240, 410)
finger1.move(360, 410)
touch.wait(20)
finger0.move(270, 440)
finger1.move(330, 440)
touch.wait(20)
finger0.move(300, 470) # Crossing center point
finger1.move(300, 470)
touch.wait(20)
finger0.move(330, 500)
finger1.move(270, 500)
touch.wait(20)
finger0.move(360, 530)
finger1.move(240, 530)
touch.wait(20)
finger0.move(390, 560)
finger1.move(210, 560)
touch.wait(20)
finger0.move(420, 590)
finger1.move(180, 590)
touch.wait(20)
finger0.move(450, 620) # End point
finger1.move(150, 620)
touch.wait(20)
# --- 3. Lift fingers ---
finger0.up()
finger1.up()
# Perform the operation
touch.perform()
```
## Recording Trajectories
To make trajectory construction easier, we also provide a recording function. This function requires you to operate on a physical device.
```python
# Get a multi-touch session
touch = d.touch()
# After calling record(), it will wait for you to operate on the physical device screen within the timeout (120 seconds).
# When all fingers are lifted, recording ends and returns.
touch.record()
# You can save the trajectory as a binary file
touch.save("/sdcard/touch.bin")
# You can also directly replay the trajectory
touch.perform()
```
## Saving Trajectories
To avoid reconstructing trajectories through code each time, we provide a trajectory saving function to persist trajectories to files.
```python
# Get a multi-touch session
touch = d.touch()
# You can save the trajectory you just built for later loading
touch.save("/sdcard/track0.bin")
```
## Loading Trajectories
You can directly load a trajectory from a file on disk into the session.
```python
# Get a multi-touch session
touch = d.touch()
# Load trajectory from file
touch.load("/sdcard/track0.bin")
```
## Constructing Trajectories
In addition to constructing trajectories through recording, you can also manually construct touch trajectories via code. Below we introduce manual construction and other available functions. A valid touch operation consists of three event types: press, move, and release; you can set pressure parameters for events such as press and move.
```python
# Get a multi-touch session
touch = d.touch()
# Get two (or more) fingertip inputs
finger0 = touch.contact(0)
finger1 = touch.contact(1)
# Finger 0 presses at coordinate (150,320)
# Parameter z represents finger pressure, valid values 1-255, default 128
finger0.down(150, 320, z=128)
# Finger 0 moves from (150,320) to (180,350)
finger0.move(180, 350, z=128)
# Wait 20 milliseconds (equivalent to finger0.wait(20))
touch.wait(20)
# Finger 0 lifts up
finger0.up()
# Clear all trajectories
touch.reset()
# Perform the operation (wait parameter indicates whether to wait for operation completion)
touch.perform(wait=True)
```
--- END OF multi-touch.md ---
--- DOCUMENT: app-ops.md ---
--- SOURCE: https://device-farm.com/docs/content/en/app-ops.md ---
# Application Operations
In this chapter, you will learn how to install, start, and close apps, grant or revoke app permissions, disable or enable apps, and replay **any** Activity (schemes, Activities) including unexported ones.
## Installing Apps
You need to prepare an APK file for installation. Single Android APK and Split APK (e.g., xapk) are supported.
### Single APK
For a single APK file, use the following procedure:
```python
>>> session = d.create_install_session()
>>> session.write("/path/to/file.apk")
session: 2033102247
name: "base.apk"
writtenBytes: 5130176
totalBytes: 5130176
>>> session.commit()
session: 2033102247
package: "com.example.apk"
legacyStatus: 1
statusMessage: "INSTALL_SUCCEEDED: Session installed"
finished: true
committed: true
progress: 1
success: true
>>>
```
### Split APK (e.g., XAPK)
For multi‑APK apps, you may need to first extract the bundle. After extraction, you will typically see a file set like the following:
```bash
➜ apk_temp/ $ ls -l
total 350720
-rw-rw-r-- 1 root root 86M 1 26 1970 com.some.package.apk
-rw-rw-r-- 1 root root 772K 1 26 1970 config.ar.apk
-rw-rw-r-- 1 root root 68M 1 26 1970 config.arm64_v8a.apk
-rw-rw-r-- 1 root root 676K 1 26 1970 config.de.apk
-rw-rw-r-- 1 root root 478K 1 26 1970 config.en.apk
```
Then install them as follows. Usually you need to install the base APK, language packs, and native library APKs.
```python
>>> session = d.create_install_session()
>>> session.write("com.example.app.apk", name="com.example.app.apk")
session: 1137194135
name: "com.example.app.apk"
writtenBytes: 89661970
totalBytes: 89661970
>>> session.write("config.de.apk", name="config.de.apk")
session: 1137194135
name: "config.de.apk"
writtenBytes: 692633
totalBytes: 90354603
>>> session.write("config.arm64_v8a.apk", name="config.arm64_v8a.apk")
session: 1137194135
name: "config.arm64_v8a.apk"
writtenBytes: 70978318
totalBytes: 161332921
>>> session.write("config.xxhdpi.apk", name="config.xxhdpi.apk")
session: 1137194135
name: "config.xxhdpi.apk"
writtenBytes: 6266292
totalBytes: 167599213
>>> session.commit()
session: 1137194135
package: "com.example.app"
legacyStatus: 1
statusMessage: "INSTALL_SUCCEEDED: Session installed"
finished: true
committed: true
progress: 0.900000036
success: true
>>>
```
If any error occurs during installation, an exception will be raised directly, for example when no base APK is provided for a split APK install:
```python
>>> session = d.create_install_session()
>>> session.write("config.de.apk", name="config.de.apk")
session: 1658145271
name: "config.de.apk"
writtenBytes: 692633
totalBytes: 692633
>>> session.commit()
Traceback (most recent call last):
File "", line 1, in
....
raise self.remote_exception(exception)
lamda.exceptions.InstallPackageFailed: INSTALL_FAILED_INVALID_APK: Full install must include a base package
```
### Session Parameters
Use specific session creation parameters to enable overwrite install, downgrade install, auto-grant permissions, installation to a specific user, etc.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `user` | `int` | `0` | Install to the specified user ID |
| `package` | `str` | `None` | Package name to be installed; may be omitted |
| `request_downgrade` | `bool` | `False` | Whether to allow downgrade installation |
| `grant_runtime_permissions` | `bool` | `False` | Whether to grant runtime permissions |
| `installer_package_name` | `str` | `None` | Custom installer name (e.g., `com.android.vending`) |
| `dont_kill_app` | `bool` | `False` | Do not kill the app during installation |
| `replace_existing` | `bool` | `True` | Replace an already installed app (in root mode always `True`, even if set to `False`) |
| `allow_test` | `bool` | `False` | Whether to allow installing test apps |
You can use these parameters when creating a session as shown below:
```python
session = d.create_install_session(request_downgrade=True)
```
### Other Cases
Some systems may intercept the installation process or pop up app‑scanning dialogs such as Google Play Protect. In such cases you may want to automate the handling to complete the installation. You can do that.
`commit` provides a background installation mode, where you can continue without waiting and manually query the installation status.
```python
>>> session = d.create_install_session()
>>> session.write("/path/to/file.apk")
session: 1079135981
name: "base.apk"
writtenBytes: 5130176
totalBytes: 5130176
>>> session.commit(wait=False)
session: 1079135981
status: -2147483648
legacyStatus: -2147483648
statusMessage: "Commit sent"
committed: true
progress: 0.8
>>> session.status()
session: 1079135981
package: "com.example.app"
status: -2147483648
legacyStatus: -2147483648
statusMessage: "Commit sent"
committed: true
progress: 0.900000036
>>> session.status()
session: 1079135981
package: "com.example.app"
legacyStatus: 1
statusMessage: "INSTALL_SUCCEEDED: Session installed"
finished: true
committed: true
progress: 1
success: true
```
This way, after installation starts, you can use other code to handle dialogs that appear during installation. Using a [Watcher](./ui-watcher.md) for automatic clicking is also a feasible approach.
## List Installed Apps
Get information about all apps installed on the device.
```python
d.enumerate_installed_apps()
```
```python
>>> d.enumerate_installed_apps()
[packageName: "com.android.uwb.resources"
label: "System UWB Resources"
uid: 10110
enabled: true
system: true
versionName: "T-initial"
, packageName: "com.android.adservices.api"
label: "Android System"
uid: 10105
enabled: true
system: true
versionName: "14"
...
```
## List Running Apps
Get information about currently running processes on the system.
```python
d.enumerate_running_processes()
```
```python
>>> d.enumerate_running_processes()
[packages: "com.android.launcher3"
processName: "com.android.launcher3"
uid: 10084
pid: 2360
label: "Quickstep"
, packages: "com.google.android.gms"
processName: "com.google.android.gms.persistent"
uid: 10123
pid: 2765
label: "Google Play services"
, packages: "com.instagram.android"
processName: "com.instagram.android"
uid: 10150
pid: 5529
label: "Instagram"
...
```
```python
>>> result = d.enumerate_running_processes()
>>> print(result[0].processName)
com.android.launcher3
```
## Get App by Name
Get an app instance by its common name (without knowing the package ID).
```python
app = d.get_application_by_name("微信")
```
## Get App by Package Name
Get an app instance using its package ID.
```python
app = d.application("com.tencent.mm")
```
## Get Foreground App
Get the app instance currently running in the foreground.
```python
app = d.current_application()
```
## Get Multi‑Profile App
Get an instance of an app installed under a separate user profile (typically distinguished by `user` and with uid 999).
```python
app = d.application("com.my.app", user=999)
```
## Start App
Start this app.
```python
app.start()
```
## Stop App
Force‑stop this app.
```python
app.stop()
```
## Check Foreground Status
Check whether the app is currently in the foreground.
```python
app.is_foreground()
```
## Get App Information
Retrieve information about the app, such as version, etc.
```python
app.info()
```
```python
>>> app.info()
packageName: "com.android.settings"
uid: 1000
enabled: true
processName: "com.android.settings"
sourceDir: "/system/product/priv-app/Settings/Settings.apk"
dataDir: "/data/user_de/0/com.android.settings"
firstInstallTime: 1230739200000
lastUpdateTime: 1230768000000
versionCode: 1276
versionName: "10"
```
```python
>>> result = app.info()
>>> print(result.processName)
'com.android.settings'
```
## Check if Installed
Check whether the app is already installed on the device.
```python
app.is_installed()
```
## Uninstall App
Uninstall the app from the device.
```python
app.uninstall()
```
## Launch an Activity
You can replay system‑level Activities to start any Activity of any application. Available parameters are shown below. Note that extras support only `boolean`, `int`, `short`, `long`, `double`, `float`, and `string` types.
For the definition of flags, refer to the documentation [developer.android.com/reference/android/content/Intent](https://developer.android.com/reference/android/content/Intent#FLAG_ACTIVITY_BROUGHT_TO_FRONT).
```python
from lamda.const import *
d.start_activity(action="*", category="*", component="*", extras={"boolean": False, "int": 1, "string": "abc", "float": 1.123}, flags=FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_CLEAR_TASK, data="*", debug=False)
```
Now, using the “get recent activities” API as an example, you can replay the last system Activity directly with:
```python
activity = d.get_last_activities(count=5)[-1]
d.start_activity(**activity)
```
If you want to launch an activity in a multi‑profile app, the following call will replay the Activity into the user 999 profile:
```python
d.start_activity(**activity, user=999)
```
Some examples for reference: the following call will dial the 10000 customer service number.
```python
d.start_activity(action="android.intent.action.CALL", data="tel:10000")
```
The following call will launch the Settings app, which is essentially equivalent to starting the app directly.
```python
d.start_activity(action="android.intent.action.MAIN", category="android.intent.category.LAUNCHER", component="com.android.settings/.Settings")
```
The next call launches the Settings app in debug mode. If you have ever seen `Waiting for debugger`, this may be useful to you. Of course, your device or app must be debuggable. The only difference from the previous call is the extra `debug` parameter.
```python
d.start_activity(action="android.intent.action.MAIN", category="android.intent.category.LAUNCHER", component="com.android.settings/.Settings", debug=True)
```
This call will take you directly to the certificate settings page.
```python
d.start_activity(action="com.android.settings.TRUSTED_CREDENTIALS")
```
## List App Permissions
This API lists all permission names declared by the app.
```python
app.permissions()
```
```python
>>> app.permissions()
['android.permission.REQUEST_NETWORK_SCORES', 'android.permission.WRITE_MEDIA_STORAGE', 'android.permission.WRITE_EXTERNAL_STORAGE', 'android.permission.READ_EXTERNAL_STORAGE', 'android.permission.WRITE_SETTINGS',...]
```
## Grant App Permission
This API grants a system permission to the app. You should use it before the app is started; granting permissions while the app is running and requesting them will not automatically take effect.
```python
from lamda.const import *
app.grant(PERMISSION_READ_PHONE_STATE, mode=GrantType.GRANT_ALLOW)
```
This is equivalent to providing the full permission string directly:
```python
app.grant("android.permission.READ_PHONE_STATE", mode=GrantType.GRANT_ALLOW)
```
The `mode` parameter of the `grant` method also supports `GrantType.GRANT_DENY`, which explicitly denies the permission, and `GrantType.GRANT_IGNORE`. `GRANT_IGNORE` is special: it grants the permission to the app but the app cannot actually use it; for example, if an app requests the camera and you use this parameter, the camera preview may remain black.
## Revoke App Permission
This API revokes a granted permission. You should also call it before the app starts.
```python
from lamda.const import *
app.revoke(PERMISSION_READ_PHONE_STATE)
```
## Check if Granted
This API checks whether a certain permission has been properly granted to the app.
```python
from lamda.const import *
app.is_permission_granted(PERMISSION_READ_PHONE_STATE)
```
## Clear App Cache
This API clears the app’s cache data, usually without adverse effects.
```python
app.clear_cache()
```
## Clear App Data
This API clears the app’s data. Note that this will erase all app data, causing account information etc. to be lost.
```python
app.reset()
```
## Get Launch Activity
You can query the launch Activity (entry Activity) of the app with this API.
```python
app.query_launch_activity()
```
```python
>>> app.query_launch_activity()
{'action': 'android.intent.action.MAIN', 'component': 'com.android.settings/com.android.settings.Settings', 'categories': ['android.intent.category.LAUNCHER']}
```
## Enable App
This API enables a previously disabled app. Once enabled, you can use the app normally again.
```python
app.enable()
```
## Disable App
This API disables an app. A disabled app will not appear in the app list and cannot be used until enabled. This can temporarily or permanently freeze the app; it cannot auto‑start. When many apps are installed on the device, you can appropriately disable those you currently do not need to reduce system resource usage.
```python
app.disable()
```
--- END OF app-ops.md ---
--- DOCUMENT: file-io.md ---
--- SOURCE: https://device-farm.com/docs/content/en/file-io.md ---
# File Read/Write
The file read/write interfaces allow you to easily upload or download files from a device, supporting large file transfers. You can use the relevant interfaces to download files to local storage or memory, or upload files from memory or local storage to the device. Additionally, it supports functions such as deleting files, modifying file permissions, and obtaining file information.
If the file does not exist or lacks permissions, the call will raise native Python exceptions, such as `OSError` or `FileNotFoundError`.
## Download File to Local
This interface can download a file from the device to local storage. In the example below, the content of the file `/verity_key` on the device is downloaded to a local file named `my_file.txt`. Note that the downloaded file will not retain the original permission information.
```python
d.download_file("/verity_key", "my_file.txt")
```
```python
>>> d.download_file("/adb_keys", "my_file.txt")
name: "adb_keys"
path: "/adb_keys"
st_mode: 33188
st_atime: 1230768000
st_mtime: 1230768000
st_ctime: 1230768000
st_size: 2202
```
```python
>>> result = d.download_file("/adb_keys", "my_file.txt")
>>> print(result.st_mtime)
1230768000
>>> os.chmod("my_file.txt", result.st_mode)
```
## Download File to Memory
This interface can download a file from the device into memory (via BytesIO).
```python
from io import BytesIO
fd = BytesIO()
d.download_fd("/verity_key", fd)
print(fd.getvalue())
```
## Download File to Descriptor
This interface can download a file from the device to a file descriptor. Note that the file must be opened in binary write mode (`wb`).
```python
fd = open("my_file.txt", "wb")
d.download_fd("/verity_key", fd)
```
## Upload File to Device
This interface can upload a local file to the device. The example below uploads the local file `测试文件.txt` to `/data/local/tmp/file.txt` on the device. Uploading a file also does not preserve the original permission information.
```python
d.upload_file("测试文件.txt", "/data/local/tmp/file.txt")
```
```python
>>> d.upload_file("file.txt", "/data/local/tmp/file.txt")
name: "file.txt"
path: "/data/local/tmp/file.txt"
st_mode: 33184
st_atime: 1230768000
st_mtime: 1230768000
st_ctime: 1230768000
```
```python
>>> result = d.upload_file("file.txt", "/data/local/tmp/file.txt")
>>> print(result.st_size)
0
```
## Upload File from Memory
The example below uploads the content of a file in memory via BytesIO to `/data/local/tmp/file.txt` on the device.
```python
from io import BytesIO
d.upload_fd(BytesIO(b"fileContent"), "/data/local/tmp/file.txt")
```
## Upload File from Descriptor
The example below uploads the content of the local file `myfile.txt` via a file descriptor to `/data/local/tmp/file.txt` on the device. Note that the local file must be opened in binary read mode (`rb`).
```python
fd = open("myfile.txt", "rb")
d.upload_fd(fd, "/data/local/tmp/file.txt")
```
## Delete Device File
This interface is used to delete a file on the device.
```python
d.delete_file("/data/local/tmp/file.txt")
```
```python
>>> d.delete_file("/data/local/tmp/file.txt")
True
```
```python
>>> d.delete_file("/adb_keys")
Traceback (most recent call last):
File "", line 1, in
OSError: [Errno 30] Read-only file system
```
## Modify File Permissions
This interface is used to modify file permissions on the device.
```python
d.file_chmod("/data/local/tmp/file.txt", mode=0o777)
```
```python
>>> d.file_chmod("/data/local/tmp/file.txt", mode=0o777)
name: "file.txt"
path: "/data/local/tmp/file.txt"
st_mode: 33279
st_atime: 1230768000
st_mtime: 1230768000
st_ctime: 1230768000
```
```python
>>> result = d.file_chmod("/data/local/tmp/file.txt", mode=0o777)
>>> print(oct(result.st_mode))
0o100777
```
## Get File Information
This interface is used to obtain information about a file on the device.
```python
d.file_stat("/data/local/tmp/file.txt")
```
```python
>>> d.file_stat("/data/local/tmp/file.txt")
name: "file.txt"
path: "/data/local/tmp/file.txt"
st_mode: 33279
st_atime: 1230768000
st_mtime: 1230768000
st_ctime: 1230768000
```
```python
>>> result = d.file_stat("/data/local/tmp/file.txt")
>>> print(result.name)
'file.txt'
```
--- END OF file-io.md ---
--- DOCUMENT: binary-patch.md ---
--- SOURCE: https://device-farm.com/docs/content/en/binary-patch.md ---
# Binary Patching
Binary patching is used to patch files or programs on devices. It employs hexadecimal wildcards for find and replace operations, supporting high/low nibble wildcards. For example, `??` represents any single byte, `B?` represents any byte with `B` as the high nibble (i.e., `B0`-`BF`), such as `BA`, `B1`, `B9`. The pattern `49 BA ?? ?C` will match any byte sequence in the file that conforms to the pattern. The following call will replace all byte sequences matching `AA BB CC D?` in `test.bin` with `AA BB CC DD`, and the interface returns the count of replacements and the offset of each replacement.
```python
d.hex_patch("AA BB CC D?", "AA BB CC DD", "/data/test.bin")
```
```python
>>> result = d.hex_patch("AA BB CC D?", "AA BB CC DD", "/data/test.bin")
>>> print(result.count)
1
>>> print(result.replaces[0].offset)
8123
```
```{note}
The demonstration shows only a four-byte match and replacement, but the interface actually supports multi-byte matching and replacement of arbitrary length, provided the match pattern contains at least two valid hex characters (i.e., at least one non-wildcard byte).
```
To limit the maximum number of replacements, use the `maxreplace` parameter; by default, all matches are replaced.
```python
d.hex_patch("AA BB CC D?", "AA BB CC DD", "/data/test.bin", maxreplace=2)
```
You can also enable dry-run mode with the `dryrun` parameter, which will only locate the matching positions in the file without performing actual replacements.
```python
d.hex_patch("AA BB ?? ??", "AA BB 00 00", "/data/test.bin", dryrun=True)
```
```{hint}
File paths support glob pattern matching. For example, `/data/app/*/test.bin` will match the `test.bin` file in any single-level subdirectory under `/data/app`.
```
--- END OF binary-patch.md ---
--- DOCUMENT: command-exec.md ---
--- SOURCE: https://device-farm.com/docs/content/en/command-exec.md ---
# Command Execution
This feature allows you to execute shell commands or complete shell scripts on the device. All commands run under the user account used to run the service. To support long-running scripts, this feature also supports scripts that run in the background without exiting.
## Foreground Command Execution
You can quickly execute a command or script as shown below. This operation blocks the current process, so your script or command should not run too long; it is suitable for scripts that take a short time (0–10 seconds).
```python
cmd = d.execute_script("whoami")
```
```python
>>> d.execute_script("notexist")
exitstatus: 127
stderr: "mksh: [2]: notexist: inaccessible or not found\n"
```
After the command finishes, it returns the exit status, standard output, and standard error.
```python
print(cmd.stdout)
print(cmd.stderr)
print(cmd.exitstatus)
```
```python
>>> result = d.execute_script("id")
>>> print(result.stdout)
b'uid=0(root) gid=0(root)\n'
>>> print(result.stderr)
b''
>>> print(result.exitstatus)
0
```
## Background Command Execution
Background command execution is suitable for long-running scripts. Given that users might write infinite loops or cause the script to produce infinite output that could fill up memory, the execution results of background scripts cannot be retrieved. If you need to obtain the script’s output, please redirect the output within the script.
The following example executes a long-running script `sleep` and returns an `sid` string as the ID of this background script.
```python
script = "sleep 100; exit 0;"
sid = d.execute_background_script(script)
```
If the script encounters unexpected circumstances, you can forcibly terminate the running background script.
```python
d.kill_background_script(sid)
```
Use `sid` to check whether the background script has finished running.
```python
d.is_background_script_finished(sid)
```
```python
>>> script = "sleep 100; exit 0;"
>>> sid = d.execute_background_script(script)
>>> print(sid)
ba06da93-c3aa-4457-b90e-247e42a16207
>>> d.is_background_script_finished(sid)
False
>>> d.kill_background_script(sid)
True
>>> d.is_background_script_finished(sid)
True
```
--- END OF command-exec.md ---
--- DOCUMENT: kv-store.md ---
--- SOURCE: https://device-farm.com/docs/content/en/kv-store.md ---
# Key-Value Store
The configuration store Storage is the built-in key-value store of FIRERPA with persistence. Even after FIRERPA or the device restarts, you can still read these variables on the next startup. This Storage allows you to persistently store information on the device for different client processes to read, enabling sharing of device configurations such as login accounts or other standalone information. You can even store encrypted configurations on the machine.
```{attention}
The total capacity of the built-in Storage is 128 MB, so do not use it to store large amounts of data. It does not support listing the container names or key names in the store. You must know the full container name and the key name in order to read a value from the container; otherwise, it will be permanently unreadable.
```
## Get Storage
First, obtain a Storage object so that you can perform subsequent operations.
```python
storage = d.stub("Storage")
```
## Clear Storage
Clear all information in the Storage, including containers. You can simply think of this as – formatting the Storage.
```python
storage.clear()
```
## Get Container
Obtain a key-value store container object. The storage is the store itself, and a container is a bucket within the store. All subsequent reads and writes operate on this bucket.
```python
container = storage.use("container_name")
```
## Get Encrypted Container
If you also need to store values securely – for example, when the device might be used by other people and you do not want your stored configurations to be read by others – you can use an encrypted container. Unlike the simple container retrieval, the encrypted container will encrypt the key-value pairs you set, and others will need a password or other verification to read them.
Our library comes with a built-in FernetCryptor encryptor/decryptor. You can also implement your own unique encryption algorithm following the FernetCryptor pattern, as long as you implement the `encrypt` and `decrypt` methods.
```python
from lamda.client import FernetCryptor
container = storage.use("container_name", cryptor=FernetCryptor,
key="this_is_password")
```
## Remove Container
Remove all key-value pairs stored in the container named `container_name`. This operation is equivalent to deleting the bucket.
```python
storage.remove("container_name")
```
## Write Key-Value to Container
Set the value of `key_name` to "value", where the value supports any msgpack-serializable variable.
```python
container.set("key_name", [1, 2, 3])
container.set("key_name", {"john": "due"})
container.set("key_name", b"value")
container.set("key_name", "value")
```
## Read Key-Value from Container
Get the value of `key_name`. If the key does not exist, `None` is returned.
```python
container.get("key_name")
```
## Get Time to Live
Get the time to live of `key_name`. `-2` means the key does not exist, `-1` means it never expires, and any other positive integer indicates the remaining seconds until the key expires.
```python
container.ttl("key_name")
```
## Set Time to Live
Set the time to live of a key-value pair. For example, the following call will set the key-value pair and cause it to be automatically deleted after 10 seconds.
```python
container.setex("key_name", "value", 10)
```
Alternatively, if you have already set this key before and now want to assign a time to live, you can use `expire`. After 60 seconds, `key_name` will be automatically deleted.
```python
container.expire("key_name", 60)
```
## Conditional Write
Set the key-value pair only if `key_name` does not exist. If the key already exists, no operation is performed.
```python
container.setnx("key_name", "value")
```
## Check Existence
Check whether `key_name` exists in the container.
```python
container.exists("key_name")
```
## Delete Key-Value from Container
Delete `key_name` and its value from the container.
```python
container.delete("key_name")
```
--- END OF kv-store.md ---
--- DOCUMENT: system-props.md ---
--- SOURCE: https://device-farm.com/docs/content/en/system-props.md ---
# System Properties
This interface is used to read or modify system properties, similar to the `getprop` and `setprop` commands. You might ask: why use this interface instead of the commands? What makes it unique?
Indeed, but you need to know that this interface can also directly write Android properties starting with `ro.`, which the system’s built-in commands cannot do.
## Getting a Property
Get the value of `ro.secure`. Of course, not just this property – all system properties can be read with this interface.
```python
d.getprop("ro.secure")
```
## Setting a Property
Set the value of `ro.secure` to `0`. You can set any property through this interface, including `ro.` read-only properties.
```python
d.setprop("ro.secure", "0")
```
--- END OF system-props.md ---
--- DOCUMENT: system-settings.md ---
--- SOURCE: https://device-farm.com/docs/content/en/system-settings.md ---
# System Settings
You can use FIRERPA’s Settings-related interfaces to configure certain Android system parameters or read system settings. This feature covers a wide range of actions, for example setting screen brightness, toggling developer mode, or even adjusting the ringtone volume. Here we only demonstrate a few simple examples.
## Calling the Interface
Before you begin, you need to obtain a Settings instance. You can call it as shown below.
```python
settings = d.stub("Settings")
```
Set the system screen brightness to manual mode, so that your device’s screen brightness will not change with ambient light, allowing you to set a fixed brightness value.
```python
settings.put_system("screen_brightness_mode", "0")
```
Get the current screen brightness, then lower it to 5.
```python
settings.get_system("screen_brightness")
settings.put_system("screen_brightness", "5")
```
Check whether developer mode is enabled, then disable it.
```python
settings.get_global("development_settings_enabled")
settings.put_global("development_settings_enabled", "0")
```
Check whether the screensaver is enabled, then disable it.
```python
settings.get_secure("screensaver_enabled")
settings.put_secure("screensaver_enabled", "0")
```
The following example sets the screen brightness to manual, brightness to 64, and keeps the screen on forever.
```python
settings.put_system("screen_brightness", "64")
settings.put_system("screen_brightness_mode", "1")
settings.put_system("screen_off_timeout", "604800000")
```
## Available Parameters
If you’re unsure about strings like `screen_brightness`, this section is for you. The parameters available for this interface depend entirely on what the system supports. Please note that some constants may be incompatible across different Android versions, and some manufacturers have their own custom variables. You can find the relevant available parameters in the Android official documentation linked below, and then use the above interfaces to configure system settings.
```{tip}
We list the officially defined available fields below, but the actual available fields in the system are far more than these. You can use the command `settings list [system|global|secure]` to see what settings are actually available in each category.
```
### Official Documentation
| Category | Link |
|----------|------|
| Global Settings, containing system-level preferences accessible to all users | https://developer.android.com/reference/android/provider/Settings.Global |
| System Settings, containing legacy system preferences primarily for ringtone, brightness, and other device-related configurations | https://developer.android.com/reference/android/provider/Settings.System |
| Secure Settings, containing settings related to privacy and security | https://developer.android.com/reference/android/provider/Settings.Secure |
### Global
| Field Name (lowercase) | Type | Description | Added in Version | Removed in Version |
|------------------------|------|-------------|------------------|-------------------|
| adb_enabled | int | Whether ADB (Android Debug Bridge) over USB is enabled. | API 17 | |
| airplane_mode_on | int | Whether airplane mode is turned on. | API 17 | |
| airplane_mode_radios | string | Comma-separated list of radios to disable when airplane mode is on. | API 17 | |
| always_finish_activities | int | If not 0, the activity manager will finish activities and processes as soon as they are no longer needed. If 0, the normal extended lifecycle is used. | API 17 | |
| animator_duration_scale | float | Scaling factor for Animator-based animations. | API 17 | |
| apply_ramping_ringer | int | Whether to apply ramping ringer to incoming calls. | API 29 | API 33 |
| auto_time | int | Whether to automatically set the device’s UTC system clock (e.g., using cellular signal like NITZ or other sources). | API 17 | |
| auto_time_zone | int | Whether to automatically set the device’s time zone system property (e.g., using MCC, NITZ, or other mechanisms like location). | API 17 | |
| bluetooth_on | int | Whether Bluetooth is enabled (0=disabled, 1=enabled). | API 17 | |
| boot_count | int | Number of times the device has booted since API level 24. | API 24 | |
| contact_metadata_sync_enabled | int | Whether contact metadata sync is enabled (1=enabled, 0=disabled). | API 24 | |
| data_roaming | int | Whether data roaming is enabled (0=no, 1=yes). | API 17 | |
| debug_app | string | Package name of the application to debug. | API 17 | |
| development_settings_enabled | int | Whether the user has enabled developer options (0=no, 1=yes). | API 17 | |
| device_name | string | The device name. | API 25 | |
| device_provisioned | int | Whether the device has been provisioned (0=no, 1=yes). | API 17 | |
| http_proxy | string | Hostname and port for the global HTTP proxy (separated by a colon). | API 17 | |
| install_non_market_apps | int | Whether installation of non-market applications is allowed. | API 17 | API 21 |
| mode_ringer | int | Ringer mode (internal use; changing this value will not change the actual ringer mode, see AudioManager). | API 17 | |
| network_preference | int | User preference for which network to use (only modifiable by the connectivity service). | API 17 | |
| radio_bluetooth | string | Constant for specifying the Bluetooth radio in airplane mode or satellite mode radio lists. | API 17 | |
| radio_cell | string | Constant for specifying the cellular radio in airplane mode radio lists. | API 17 | |
| radio_nfc | string | Constant for specifying the NFC radio in airplane mode or satellite mode radio lists. | API 17 | |
| radio_wifi | string | Constant for specifying the Wi‑Fi radio in airplane mode or satellite mode radio lists. | API 17 | |
| secure_frp_mode | int | Indicates whether the device is in restricted secure FRP (Factory Reset Protection) mode. | API 34 | |
| show_processes | int | Controls whether to show the process CPU usage meter. | API 17 | API 25 |
| stay_on_while_plugged_in | int | Whether to keep the device awake while plugged in (combinable values: AC, USB, wireless, dock charger). | API 17 | |
| transition_animation_scale | float | Scaling factor for activity transition animations. | API 17 | |
| usb_mass_storage_enabled | int | Whether USB mass storage is enabled. | API 17 | |
| use_google_mail | int | If this setting is set (any value), all references to Gmail on the device must be changed to Google Mail. | API 17 | |
| wait_for_debugger | int | If 1, when launching DEBUG_APP it will wait for the debugger to connect before executing user code; if 0, it starts normally. | API 17 | |
| wifi_device_owner_configs_lockdown | int | Controls whether Wi‑Fi configurations created by a device owner app should be locked down (only the device owner app can edit or delete them). | API 23 | |
| wifi_max_dhcp_retry_count | int | Maximum number of times to retry connecting to an access point after failing to obtain a DHCP IP address (N means a total of N+1 attempts). | API 17 | |
| wifi_mobile_data_transition_wakelock_timeout_ms | int | Maximum milliseconds to hold a wakelock while waiting for a mobile data connection after Wi‑Fi disconnects. | API 17 | |
| wifi_networks_available_notification_on | int | Whether to notify the user when open networks are available. | API 17 | API 26 |
| wifi_networks_available_repeat_delay | int | Delay (in seconds) before repeating the notification of available Wi‑Fi networks. | API 17 | API 30 |
| wifi_num_open_networks_kept | int | When the number of open networks exceeds this value, the least-recently-used excess networks will be removed. | API 17 | API 30 |
| wifi_on | int | Whether Wi‑Fi should be on (only modifiable by the Wi‑Fi service). | API 17 | |
| wifi_sleep_policy | int | Policy that determines when Wi‑Fi goes to sleep. | API 17 | API 30 |
| wifi_sleep_policy_default | int | Value for the default Wi‑Fi sleep policy (based on STAY_ON_WHILE_PLUGGED_IN). | API 17 | API 30 |
| wifi_sleep_policy_never | int | Value for never sleeping. | API 17 | API 30 |
| wifi_sleep_policy_never_while_plugged | int | Value for using the default policy on battery and never sleeping while plugged. | API 17 | API 30 |
| wifi_watchdog_on | int | Whether the Wi‑Fi watchdog is enabled. | API 17 | |
| window_animation_scale | float | Scaling factor for normal window animations. | API 17 | |
### System
| Field Name (lowercase) | Type | Description | Added in Version | Removed in Version |
|------------------------|------|-------------|------------------|-------------------|
| accelerometer_rotation | int | Controls whether the accelerometer is used for auto-rotation of the screen (0=disabled, 1=enabled) | API 3 | |
| alarm_alert | string | URI of the system default alarm alert sound | API 5 | |
| bluetooth_discoverability | int | Bluetooth discoverability setting (2=discoverable and connectable, 1=connectable but not discoverable, 0=neither) | API 1 | |
| bluetooth_discoverability_timeout | int | Timeout (in seconds) for the Bluetooth discoverable state | API 1 | |
| date_format | string | Date format (deprecated, no longer used) | API 1 | API 31 |
| dtmf_tone | int | Whether to play DTMF tones when dialing (1=yes, 0=no) | API 1 | |
| dtmf_tone_type | int | Type of DTMF tone for CDMA dialing (0=normal, 1=long) | API 23 | |
| end_button_behavior | int | Behavior when pressing the end call button during non-call (0=do nothing, 1=go to home screen, 2=sleep and lock, 3=go to home screen or sleep) | API 1 | |
| font_scale | float | Font scaling factor | API 1 | |
| haptic_feedback_enabled | int | Whether haptic feedback (vibration on tap) is enabled (1=enabled, 0=disabled) | API 3 | API 33 |
| mode_ringer_streams_affected | int | Bitmask of audio streams affected by the ringer mode | API 1 | |
| mute_streams_affected | int | Bitmask of audio streams affected by mute | API 1 | |
| notification_sound | string | URI of the system default notification sound | API 1 | |
| ringtone | string | URI of the system default ringtone | API 1 | |
| screen_brightness | int | Screen brightness value (1-255) | API 1 | |
| screen_brightness_mode | int | Brightness mode (0=manual, 1=automatic) | API 8 | |
| screen_off_timeout | int | Screen timeout before sleep (in milliseconds) | API 1 | |
| setup_wizard_has_run | int | Whether the setup wizard has run (non-zero means it has run) | API 1 | |
| show_gtalk_service_status | int | Whether to show GTalk service status | API 1 | |
| sound_effects_enabled | int | Whether sound effects (key clicks, etc.) are enabled (1=enabled) | API 1 | |
| text_auto_caps | int | Whether the text editor should auto-capitalize (1=on) | API 1 | |
| text_auto_punctuate | int | Whether the text editor should auto-punctuate (1=on) | API 1 | |
| text_auto_replace | int | Whether the text editor should auto-replace (1=on) | API 1 | |
| text_show_password | int | Whether the text editor should show password characters (1=show) | API 1 | |
| time_12_24 | string | Time display format ("12" or "24", region default if not set) | API 1 | |
| user_rotation | int | Default screen rotation angle (Surface rotation constant) | API 11 | |
| vibrate_on | int | Whether vibration is enabled (internal use, actual vibration controlled by AudioManager) | API 1 | |
| vibrate_when_ringing | int | Whether to vibrate when ringing (1=vibrate, 0=not vibrate) | API 23 | API 33 |
### Secure
| Field Name (lowercase) | Type | Description | Added in Version | Removed in Version |
|------------------------|------|-------------|------------------|-------------------|
| accessibility_display_inversion_enabled | int | Whether color inversion is enabled. | API 21 | |
| accessibility_enabled | int | Whether accessibility is enabled. | API 4 | |
| accessibility_speak_password | int | Whether to speak passwords in accessibility mode (deprecated, now controlled by individual accessibility services). | API 15 | API 26 |
| allowed_geolocation_origins | string | List of origins (space-separated) that the browser is allowed to access geolocation by default. | API 8 | |
| allow_mock_location | int | Whether to allow mock locations (deprecated, no longer used). | API 3 | API 23 |
| android_id | string | Unique Android ID of the device (64-bit hex string, behavior scoped since Android 8.0). | API 3 | |
| default_input_method | string | Component name (as a flattened string) of the current default input method. | API 3 | |
| enabled_accessibility_services | string | List of enabled accessibility services (string-encoded). | API 4 | |
| enabled_input_methods | string | List of enabled input methods (colon-separated, includes subtype information). | API 3 | |
| input_method_selector_visibility | int | Visibility state of the input method selector. | API 11 | |
| install_non_market_apps | int | Whether installation of non-market apps is allowed (1=allowed, 0=disallowed; on API 26+ use PackageManager.canRequestPackageInstalls()). | API 3 | API 26 |
| location_mode | int | Current location mode (integer value; on API 28+ prefer LocationManager.isLocationEnabled()). | API 19 | API 28 |
| lock_pattern_enabled | int | Whether the lock pattern is enabled (0=no, 1=yes; on API 23+ use KeyguardManager). | API 8 | API 23 |
| lock_pattern_visible | int | Whether the lock pattern lines are visible (0=no, 1=yes; on API 23+ reading throws SecurityException). | API 8 | API 23 |
| parental_control_enabled | int | Whether parental controls are enabled (no longer supported). | API 3 | |
| parental_control_last_update | string | Last update time of parental controls (no longer supported). | API 3 | |
| parental_control_redirect_url | string | Redirect URL for parental controls (no longer supported). | API 3 | |
| rtt_calling_mode | int | Whether RTT calling mode is enabled (0=off, 1=on). | API 28 | |
| secure_frp_mode | int | Whether the device is in restricted FRP (Factory Reset Protection) mode (0=no, 1=yes). | API 30 | API 34 |
| selected_input_method_subtype | string | ID of the selected input method subtype. | API 11 | |
| settings_classname | string | Class name of the Settings application. | API 3 | |
| skip_first_use_hints | int | Whether to skip first-use hints (0=show hints, 1=skip). | API 21 | |
| touch_exploration_enabled | int | Whether touch exploration (accessibility) is enabled. | API 14 | |
| tts_default_pitch | int | Default TTS pitch (100 = 1x). | API 4 | |
| tts_default_rate | int | Default TTS speech rate (100 = 1x). | API 4 | |
| tts_default_synth | string | Default TTS engine (package name). | API 4 | |
| tts_enabled_plugins | string | List of enabled TTS plugins (space-separated). | API 8 | |
--- END OF system-settings.md ---
--- DOCUMENT: selinux.md ---
--- SOURCE: https://device-farm.com/docs/content/en/selinux.md ---
# SELinux
This interface allows you to perform basic read and write operations on the system's SELinux rules, enabling you to set or bypass some system restrictions. The related interfaces may affect system functionality, so please use them with caution. You need to have a basic understanding of SELinux.
## Obtain an Operation Instance
Before starting, you need to obtain an SELinux instance.
```python
selinux = d.stub("SELinuxPolicy")
```
## Get SELinux Status
Use the following interface call to check whether SELinux rules have been enabled.
```python
selinux.is_enabled()
```
```python
>>> selinux.is_enabled()
True
```
Use the following interface call to get whether it is currently in enforcing state.
```python
selinux.get_enforce()
```
```python
>>> selinux.get_enforce()
1
```
## Set SELinux Status
Use the following interface call to set SELinux to enforcing or permissive.
```python
selinux.set_enforce(enable)
```
Use the following interface call to set to enforcing.
```python
>>> selinux.set_enforce(True)
1
```
Use the following interface call to set to permissive.
```python
>>> selinux.set_enforce(False)
0
```
## Create a New Domain
Use the following interface call to create a new domain in SELinux.
```python
selinux.create_domain(domain_name)
```
```python
>>> selinux.create_domain("hello_selinux")
True
```
## Set Domain to Permissive
Use the following interface call to set an SELinux domain to permissive state.
```python
selinux.permissive(domain_name)
```
```python
>>> selinux.permissive("untrusted_app")
True
```
## Set Domain to Enforcing
Use the following interface call to set an SELinux domain to enforcing state.
```python
selinux.enforce(domain_name)
```
```python
>>> selinux.enforce("untrusted_app")
True
```
## Refine Control Rules
Use the following interface calls to write more detailed allow and disallow rules.
```python
selinux.disallow(source, target, tclass, action)
```
```python
selinux.allow(source, target, tclass, action)
```
Among them, source and target respectively represent the source and target contexts, tclass represents the target class, action represents the specific operation. action only supports `*` (representing all) or a specific action. None of the parameters support multiple contexts or actions such as the standard rule format `{binder system_app}`. Here you can only provide a single name. Below is an example operation.
```python
>>> selinux.allow("hal_camera_default", "camera_vendor_data_file", "dir", "*")
```
The above rule means allowing `hal_camera_default` all operation permissions on the `camera_vendor_data_file` directory (`dir`). Similarly, changing to the `disallow` method would deny these permissions.
```python
>>> selinux.allow("hal_camera_default", "camera_vendor_data_file", "dir", "search")
```
You can also provide a refined action like `search` as shown above.
--- END OF selinux.md ---
--- DOCUMENT: device-status.md ---
--- SOURCE: https://device-farm.com/docs/content/en/device-status.md ---
# Device Status
We provide functions for reading device information, used to obtain the operational status of the device, including real-time information such as disk, battery, CPU, memory, and network. This allows you to understand the current state of the device in detail.
## Get Status Instance
Obtain a Status instance for subsequent operations.
```python
status = d.stub("Status")
```
## Get Boot Time
Get the boot time of the current device.
```python
status.get_boot_time()
```
```python
>>> status.get_boot_time()
1234567890
```
## Get Disk Usage
Get the disk usage of a specific mount directory.
```python
status.get_disk_usage(mountpoint="/data")
```
```python
>>> status.get_disk_usage(mountpoint="/data")
disk_total: 117153181696
disk_used: 8111099904
disk_free: 108907864064
disk_percent: 6.900000095367432
```
```python
>>> result = status.get_disk_usage(mountpoint="/data")
>>> print(result.disk_free)
108907864064
```
## Get Battery Info
Get the current battery level and temperature information of the device.
```python
status.get_battery_info()
```
```python
>>> status.get_battery_info()
batt_charging: True
batt_percent: 100
batt_temperature: 26.899999618530273
```
```python
>>> result = status.get_battery_info()
>>> print(result.batt_charging)
True
```
## Get CPU Usage
Get the current device's CPU and its usage.
```python
status.get_cpu_info()
```
```python
>>> status.get_cpu_info()
cpu_percent: 20.799999237060547
cpu_count: 8
cpu_freq_current: 823.2000122070312
cpu_freq_max: 1929.5999755859375
cpu_freq_min: 614.4000244140625
cpu_times_user: 13.699999809265137
cpu_times_system: 5.800000190734863
cpu_times_idle: 79.80000305175781
```
```python
>>> result = status.get_cpu_info()
>>> print(result.cpu_percent)
20.799999237060547
```
## Get Disk I/O (Overall)
Get the overall disk I/O status of the current device.
```python
status.get_overall_disk_io_info()
```
```python
>>> status.get_overall_disk_io_info()
disk_io_read_bytes: 11569016832
disk_io_read_count: 917667
disk_io_write_bytes: 6973407232
disk_io_write_count: 909946
disk_io_read_time: 364713
disk_io_write_time: 268013
disk_io_busy_time: 152621
```
```python
>>> result = status.get_overall_disk_io_info()
>>> print(result.disk_io_write_bytes)
6973407232
```
## Get Disk I/O (userdata)
Get the disk I/O status of the current device's userdata partition.
```python
status.get_userdata_disk_io_info()
```
```python
>>> status.get_userdata_disk_io_info()
disk_io_read_bytes: 2899529728
disk_io_read_count: 115970
disk_io_write_bytes: 1815506944
disk_io_write_count: 45254
disk_io_read_time: 152239
disk_io_write_time: 120825
disk_io_busy_time: 49127
```
```python
>>> result = status.get_userdata_disk_io_info()
>>> print(result.disk_io_read_bytes)
2899529728
```
## Get Network Traffic (Overall)
Get the overall network traffic statistics of the current device.
```python
status.get_overall_net_io_info()
```
```python
>>> status.get_overall_net_io_info()
net_io_bytes_sent: 65296119
net_io_packets_sent: 78793
net_io_bytes_recv: 60046396
net_io_packets_recv: 80745
```
```python
>>> result = status.get_overall_net_io_info()
>>> print(result.net_io_bytes_recv)
60046396
```
## Get Network Traffic (Specific Interface)
Get the data transmission and reception statistics for a specific network interface of the current device.
```python
status.get_net_io_info("wlan0")
```
```python
>>> status.get_net_io_info("wlan0")
net_io_bytes_sent: 36896321
net_io_packets_sent: 59869
net_io_bytes_recv: 58846862
net_io_packets_recv: 66759
```
```python
>>> result = status.get_net_io_info("wlan0")
>>> print(result.net_io_bytes_recv)
58846862
```
## Get Memory Usage
Get the current memory usage of the device.
```python
status.get_mem_info()
```
```python
>>> status.get_mem_info()
mem_total: 7823970304
mem_available: 3208761344
mem_percent: 59.0
mem_used: 4327931904
mem_free: 298639360
mem_active: 3535876096
mem_inactive: 1634873344
mem_buffers: 4243456
mem_cached: 3193155584
mem_shared: 34979840
mem_slab: 426651648
```
```python
>>> result = status.get_mem_info()
>>> print(result.mem_total)
7823970304
```
--- END OF device-status.md ---
--- DOCUMENT: wifi.md ---
--- SOURCE: https://device-farm.com/docs/content/en/wifi.md ---
# WiFi
WiFi-related operation functions are experimental features. We only introduce some of the available functionalities that have been implemented. You can obtain the device's WiFi status, WiFi scan results, signal strength, and add BSSID to the blacklist through relevant interfaces.
## Get WiFi Instance
First, you need to obtain a WiFi function instance, which can be achieved as follows.
```python
wifi = d.stub("Wifi")
```
## Get WiFi Status
Get information such as WiFi's BSSID, SSID, IP, etc.
```python
wifi.status()
```
```python
>>> wifi.status()
id: "0"
address: "c1:c2:c3:c4:c5:c6"
bssid: "00:12:34:56:78:90"
freq: "2447"
group_cipher: "TKIP"
ip_address: "192.168.1.158"
key_mgmt: "WPA2-PSK"
mode: "station"
pairwise_cipher: "CCMP"
ssid: "TPLINK_AE86"
wifi_generation: "4"
wpa_state: "COMPLETED"
```
```python
>>> result = wifi.status()
>>> print(result.ssid)
TPLINK_AE86
```
## Add to WiFi Blacklist
Add a BSSID to the WiFi blacklist (after that, this WiFi will not appear in the WiFi list).
```python
wifi.blacklist_add("3c:06:aa:8a:55:66")
```
## Get WiFi Blacklist
Get all BSSIDs in the WiFi blacklist.
```python
wifi.blacklist_get_all()
```
```python
>>> wifi.blacklist_get_all()
['3c:06:aa:8a:55:66']
```
## Clear WiFi Blacklist
Clear all BSSIDs from the WiFi blacklist.
```python
wifi.blacklist_clear()
```
## Perform WiFi Scan
Perform a WiFi scan; calling this will attempt to scan nearby WiFi networks.
```python
wifi.scan()
```
## Get WiFi Scan Results
Calling this interface will return the scan results of surrounding WiFi networks.
```python
wifi.scan_results()
```
```python
>>> wifi.scan_results()
[id: "0"
bssid: "00:12:34:56:78:90"
ssid: "TPLINK_AE86"
freq: "2447"
noise: "-89"
level: "-62"
tsf: "0000001234567890"
flags: "[WPA-PSK-CCMP+TKIP][WPA2-PSK-CCMP+TKIP][WPS][ESS]"
, id: "6"
bssid: "00:12:34:56:78:90"
ssid: "MIFI-97A5"
freq: "2437"
noise: "-89"
level: "-59"
tsf: "0000001234567890"
flags: "[WPA2-PSK-CCMP][WPS][ESS]"
...
]
```
```python
>>> result = wifi.scan_results()
>>> print(result[0].bssid)
00:12:34:56:78:90
```
## Get WiFi Signal Strength
Call the following interface to get information such as WiFi signal strength, link speed, and frequency.
```python
wifi.signal_poll()
```
```python
>>> wifi.signal_poll()
RSSI: "-59"
LINKSPEED: "39"
NOISE: "9999"
FREQUENCY: "2447"
```
```python
>>> result = wifi.signal_poll()
>>> print(result.LINKSPEED)
39
```
## Get WiFi MAC
Get the MAC address of the current WiFi.
```python
wifi.get_mac_addr()
```
```python
>>> wifi.get_mac_addr()
'c1:c2:c3:c4:c5:c6'
```
--- END OF wifi.md ---
--- DOCUMENT: set-proxy.md ---
--- SOURCE: https://device-farm.com/docs/content/en/set-proxy.md ---
# Setting a Proxy
In this section you will learn how to set up an IP proxy for the current phone. FIRERPA supports setting HTTP/s, SOCKS5, SHADOWSOCKS, and other proxies for the current phone, allowing device traffic to be routed through the specified proxy. It supports proxying IPv6 traffic, and when multiple network interfaces are available, you can specify the outbound interface.
## Connecting to a Proxy
This interface has many parameters. Assuming you obtained a proxy from a provider like `http://1.x.x.x:8080`, just a few lines of code below will route device traffic through this proxy. You can continue to view the `Complete Parameters` section below to learn about the parameters you can use.
```python
profile = GproxyProfile()
profile.type = GproxyType.HTTP_CONNECT
profile.drop_udp = True
profile.host = "1.x.x.x"
profile.port = 8080
d.start_gproxy(profile)
```
## Proxy Type (type)
| Proxy Type | Description |
|---------------------------------|------------------------------|
| GproxyType.HTTP_CONNECT | HTTP |
| GproxyType.HTTPS_CONNECT | HTTPS (HTTP+TLS) |
| GproxyType.SOCKS5 | SOCKS5 |
| GproxyType.SHADOWSOCKS | Shadowsocks |
| GproxyType.HTTP_RELAY | Deprecated |
### Shadowsocks Encryption Parameters
The following list contains the supported Shadowsocks encryption types. Only the types in the list are supported; obfuscation parameters are not supported.
| Encryption Type | Name |
|-------------------|--------------------------------|
| AES | aes-128-cfb |
| AES | aes-192-cfb |
| AES | aes-256-cfb |
| AES | aes-128-ctr |
| AES | aes-192-ctr |
| AES | aes-256-ctr |
| CAMELLIA | camellia-128-cfb |
| CAMELLIA | camellia-192-cfb |
| CAMELLIA | camellia-256-cfb |
| DES | des-cfb |
| AES-AEAD | aes-128-gcm |
| AES-AEAD | aes-192-gcm |
| AES-AEAD | aes-256-gcm |
| AEAD | chacha20-ietf-poly1305 |
For Shadowsocks, use the following method to set the encryption method and password.
```python
profile.login = "chacha20-ietf-poly1305"
profile.password = "password"
```
## Disabling the Proxy
You can disable the proxy set by FIRERPA on the system using the following method. This interface is very simple and does not require any additional parameters.
```python
d.stop_gproxy()
```
## Complete Parameters
Below is the complete parameter configuration for the proxy interface. You can decide whether to use each parameter based on its description.
You can configure the proxy service type using the following parameter. For a SOCKS5 proxy, it would be `GproxyType.SOCKS5`.
```python
profile.type = GproxyType.HTTP_CONNECT
```
If you need to redirect DNS queries to 114, this parameter will forward all DNS queries from the system to this address. If you use it together with OpenVPN, do not set it to OpenVPN's internal DNS, otherwise it may cause a complete network disconnection. When this configuration is not used, the system default DNS is used.
```{attention}
If you set the dns_proxy parameter to proxy DNS queries, the DNS server you use must support TCP queries. Typically, commonly used DNS servers support TCP queries.
```
```python
profile.nameserver = "114.114.114.114"
```
Configuration of the proxy server IP and port number.
```python
profile.host = "proxy_server_address"
profile.port = proxy_server_port
```
If your proxy server requires login authentication, you can provide it through the following parameters. This depends on your proxy provider. For Shadowsocks type, login is the encryption method.
```python
profile.login = "proxy_server_login_username"
profile.password = "proxy_server_login_password"
```
Used to block UDP traffic in the system. Why block UDP traffic? Because most public proxy services today do not support proxying UDP traffic within the system. Of course, some SOCKS5 servers support proxying UDP, such as the self-built solution we provide; Shadowsocks usually also supports UDP. Therefore, disabling system UDP traffic is a good choice. This option is off by default.
```python
profile.drop_udp = False
```
Used to configure whether to bypass the local network. If set to `True`, traffic to router network segments such as 192.168.x.x, 10.x.x.x will not go through the proxy. The default is False. Note that if udp_proxy is enabled, this option has no effect on UDP traffic.
```python
profile.bypass_local_subnet = True
```
Used to configure whether to proxy UDP traffic. This requires that your proxy meets certain prerequisites: your proxy must be of type `GproxyType.SOCKS5` or `GproxyType.SHADOWSOCKS`, and your proxy server must have UDP proxy support enabled. You can refer to our documentation to set up a SOCKS5 UDP proxy server or build your own Shadowsocks server. The default is False. If you are using an HTTP proxy or the drop_udp option is True, this option will be ignored.
```python
profile.udp_proxy = False
```
Used to set whether to forward all DNS traffic through the proxy. When enabled, all DNS traffic on the device will go through the proxy, which can prevent DNS contamination. When using this option, you need to specify the `nameserver` parameter at the same time. Do not use this in packet capture scenarios, otherwise the capture software may not correctly handle DNS packets, causing a fake network outage.
```python
profile.dns_proxy = False
```
Set the network interface for outbound proxy traffic on the device. For example, if your device has a SIM card network (rmment_datax), WIFI network (wlan0), and a USB tethering network (eth0), since different networks have different priorities, the system may default to the SIM card network. If you want the proxy traffic to go out through the USB tethering, you can use this parameter to specify the outbound interface.
```python
profile.interface = "eth0"
```
You can use the following configuration to set a proxy for only a specific application in the system; traffic from other applications will not go through the proxy.
```python
# The following three methods to select the target application; choose one.
app = d.application("com.android.browser")
app = d.get_application_by_name("Browser")
app = d.application("com.android.browser", user=999) # Multi-user application
profile.application.set(app)
```
## Automatic Proxy Application
You can have FIRERPA automatically connect to a preset proxy server on startup, ensuring that your phone's traffic always goes through the proxy. Add the following configuration (modify it to your proxy information; configuration method see [Service Configuration](./properties.md)). Some fields not described here use the same names as described in `Complete Parameters`.
```ini
gproxy.enable=true ; Whether to enable the service true | false
gproxy.type=http-connect ; The proxy type can be either http-connect, https-connect, socks5, shadowsocks
gproxy.host=172.1.1.1 ; Proxy server address
gproxy.port=8080 ; Proxy server port
gproxy.password= ; Proxy server login password (leave empty for no authentication)
gproxy.login= ; Proxy server login username (leave empty for no authentication, crypto method if type is shadowsocks)
gproxy.nameserver= ; Custom DNS server
gproxy.drop_udp=false ; Whether to discard UDP packets.
gproxy.udp_proxy=false ; Whether to proxy UDP packets (requires proxy support, such as socks5, shadowsocks).
gproxy.bypass_local_subnet=true ; Do not proxy local area network traffic.
gproxy.dns_proxy=false ; Make DNS queries go through the proxy (requires a DNS server that supports UDP and TCP).
gproxy.uid= ; Only proxy for specified UIDs. (default global)
```
## Building a Proxy Service
FIRERPA provides an out-of-the-box SOCKS5 proxy service Docker that supports UDP in the tools directory. You can learn how to deploy your own proxy server in the related section [Deploy Socks5 Proxy](./deploy-proxy.md) of this documentation.
--- END OF set-proxy.md ---
--- DOCUMENT: virtual-network.md ---
--- SOURCE: https://device-farm.com/docs/content/en/virtual-network.md ---
# OpenVPN Control
You can connect the current device to the OpenVPN network by calling the API provided by FIRERPA. The built-in OpenVPN support in FIRERPA allows you to choose one of three login modes: certificate-based (CA/CERT/KEY), username/password (CA/user/password), or certificate + username/password (CA/CERT/KEY/user/password) — depending on your OpenVPN server configuration. It can coexist with the system proxy. Note that this functionality only includes the core features of OpenVPN; except for `DNS` configuration, other automatic configurations pushed by the server (such as PAC proxy, HTTP proxy settings, etc.) are not applied. To save you the hassle of setting up an OpenVPN server, we provide an out-of-the-box OpenVPN Docker image with built-in scripts that can generate the API call code and auto‑start configuration.
## Connecting to VPN
We recommend reading the [Deploy OpenVPN Service](./deploy-vpn.md) documentation first to understand how to automatically generate this connection configuration. Manual writing is error‑prone. The following only introduces the main API calls.
```python
profile = OpenVPNProfile()
# Paste the code automatically generated by the self-hosted server here
d.start_openvpn(profile)
```
## Disconnecting VPN
Disconnecting the VPN is also simple. Just run the following code to stop OpenVPN.
```python
d.stop_openvpn()
```
## Full Parameters
The complete list of VPN interface parameters and their meanings is provided below. We do not recommend writing these parameters manually; use the code generated by your self-hosted server.
Whether to enable global VPN. If enabled, all traffic on the system will exit through the VPN server.
```python
profile.all_traffic = False
```
The transport protocol used by the server. Choose either `OpenVPNProto.UDP` or `OpenVPNProto.TCP` depending on your server configuration.
```python
profile.proto = OpenVPNProto.UDP
```
Username and password required for OpenVPN username/password authentication.
```python
profile.login = "username"
profile.password = "password"
```
Use these two parameters to set the address and port of the OpenVPN server.
```python
profile.host = Server address
profile.port = Server port
```
Set the server-side channel encryption method. The interface supports `AES_128_GCM`, `AES_256_GCM`, `CHACHA20_POLY1305`, `AES_128_CBC`, and `AES_256_CBC`.
```python
profile.cipher = OpenVPNCipher.AES_256_GCM
```
Configure OpenVPN TLS authentication parameters. For more details, refer to the official documentation at [openvpn.net/community-resources/reference-manual-for-openvpn-2-5](https://openvpn.net/community-resources/reference-manual-for-openvpn-2-5/).
```python
profile.tls_encryption = OpenVPNEncryption.TLS_CRYPT
profile.tls_key_direction = OpenVPNKeyDirection.KEY_DIRECTION_NONE
profile.tls_key = "-----BEGIN OpenVPN Static key V1-----"
```
Configuration for the OpenVPN client certificate, client private key, and server certificate.
```python
profile.ca = "-----BEGIN CERTIFICATE-----"
profile.cert = "-----BEGIN CERTIFICATE-----"
profile.key = "-----BEGIN PRIVATE KEY-----"
```
## Auto-connect VPN
You can configure FIRERPA to automatically connect to the VPN on startup. Refer to [Service Configuration](./properties.md) for the configuration method. Due to the complexity of the settings, we still recommend against manual authoring. Please check the [Deploy OpenVPN Service](./deploy-vpn.md) documentation to learn how to generate and fill in the relevant configuration.
## Quick VPN Setup
Please refer to the [Deploy OpenVPN Service](./deploy-vpn.md) documentation for deployment and usage instructions.
--- END OF virtual-network.md ---
--- DOCUMENT: install-mitm-cert.md ---
--- SOURCE: https://device-farm.com/docs/content/en/install-mitm-cert.md ---
# Installing the Man-in-the-Middle Certificate
This chapter describes how to install a system-level root certificate on your Android system, with compatibility across all Android versions. You can use the interfaces in this chapter in conjunction with [Set Proxy](./set-proxy.md) to perform man-in-the-middle packet capture. Of course, you may also choose to use our packaged [startmitm.py](https://github.com/firerpa/lamda/blob/HEAD/tools/startmitm.py); we are merely introducing the usage of this feature here.
## Preparation
Ensure you have prepared a certificate provided by Fiddler or mitmproxy. For mitmproxy, the certificate you should use is `mitmproxy-ca-cert.pem`. For Fiddler, it may be in crt format; you can export this file in Fiddler and provide the file path directly as a parameter, with no need to worry about file names or format conversion. To avoid unnecessary time waste, `mitmproxy` is recommended here. If you are using `Charles` or similar tools, we cannot guarantee that you can complete the setup in one go, because such applications involve complex configuration items and you may need to understand various proxy types to correctly configure HTTPS man-in-the-middle. If you must use it, we suggest using Charles' SOCKS5 as the proxy protocol.
## Installing the Root Certificate
Install the man-in-the-middle system root certificate. `cert_path` is the path to the certificate file. Once installed, it persists permanently and does not need to be reinstalled after a reboot.
```python
d.install_ca_certificate(cert_path)
```
```{hint}
The certificate installation and uninstallation interface is universal. You can use it to install certificates required by **any application**, and you can use it to install certificates provided by Fiddler/Charles, simply by providing the file path.
```
## Uninstalling the Root Certificate
Remove a custom root certificate that has been installed on the device. Frequent installation and uninstallation is not recommended.
```python
d.uninstall_ca_certificate(cert_path)
```
The complete code for installing the mitmproxy man-in-the-middle system certificate is as follows:
```python
import os
# Build the path to the mitmproxy-ca-cert.pem file
HOME = os.path.expanduser("~")
cert_path = os.path.join(HOME, ".mitmproxy", "mitmproxy-ca-cert.pem")
# Taking mitmproxy as an example, use the following code to install the MITM certificate
d.install_ca_certificate(cert_path)
# Use the following code to uninstall the certificate
d.uninstall_ca_certificate(cert_path)
```
--- END OF install-mitm-cert.md ---
--- DOCUMENT: shutdown-reboot.md ---
--- SOURCE: https://device-farm.com/docs/content/en/shutdown-reboot.md ---
# Shutdown and Restart
This interface is used to control device shutdown and restart, or to exit the FIRERPA service itself.
```{attention}
FIRERPA is designed as a 24/7 background service; frequent startup or shutdown is not recommended.
```
## Shut Down System
This operation will power off the current device.
```python
d.shutdown()
```
## Restart System
This operation will restart the current device.
```python
d.reboot()
```
## Exit Service
This operation will cause the FIRERPA service itself to exit.
```python
d.exit()
```
If it is inconvenient to use the interface, you can execute the following command:
```bash
kill -SIGUSR2 $(cat ~/lamda.pid)
```
It may take more than ten seconds for the FIRERPA service to fully exit. Please do not execute this command consecutively multiple times.
--- END OF shutdown-reboot.md ---
--- DOCUMENT: service-logs.md ---
--- SOURCE: https://device-farm.com/docs/content/en/service-logs.md ---
# Service Logs
FIRERPA does not write any logs by default. If you need to view relevant runtime logs, you can use two methods to make the service write logs to a file. If you installed via Magisk module, you may not be able to set a log file by directly adding a command line parameter; please refer to [Service Configuration](./properties.md) on how to configure `logfile`.
For users who start the service manually, you can also directly add the parameter `--logfile=/data/local/tmp/server.log` after the launch.sh command. Of course, using the `logfile` configuration is also an option.
```bash
sh /path/to/server/bin/launch.sh --logfile=/data/local/tmp/server.log
```
Under normal circumstances, we do not recommend enabling logging, because service logs are written in append mode. In extreme cases, it may fill up your device's storage, so you may need to write additional scripting logic to automatically clean logs to prevent the above issue.
--- END OF service-logs.md ---
--- DOCUMENT: api-lock.md ---
--- SOURCE: https://device-farm.com/docs/content/en/api-lock.md ---
# Interface Locking
The interfaces in this chapter are used to lock all API interfaces, so that the interfaces can only be used by the current Device instance, preventing the API from being used by other users or processes. You can set a default lock duration, periodically refresh the lock (recommended to refresh periodically), or actively release the interface lock.
## Acquire Lock
Acquire the lock. This lock will be automatically released after 60 seconds. Once released, other clients can acquire the lock. You can change this duration, but if set too long, once the script exits abnormally, you will barely be able to connect to the device, and you may need to restart the device to reconnect. This interface is reentrant, and reentrant calls are equivalent to `_refresh_lock`.
```python
d._acquire_lock(leaseTime=60)
```
## Release Lock
Actively release the API lock, after which other clients will be able to acquire the lock.
```python
d._release_lock()
```
## Refresh Lock
Refresh the lock. Each call sets the lock expiration time to the specified leaseTime. Please call this interface periodically to maintain API locking. Similarly, if leaseTime is set too high, once the script exits abnormally, you will barely be able to connect to the device.
```python
d._refresh_lock(leaseTime=60)
```
--- END OF api-lock.md ---
--- DOCUMENT: frida-usage.md ---
--- SOURCE: https://device-farm.com/docs/content/en/frida-usage.md ---
# Using Frida
FIRERPA comes with the latest version of Frida built in, so you don't need to start frida-server yourself. Our built-in Frida already includes various open-source stealth patches and additional custom hiding features we've added — you don't need to worry about Frida being detected. If our built-in Frida is detected, it means most frida-server versions you can download are also detectable.
```{attention}
Starting from version 9.0, the built-in Frida 17.x requires you to bundle frida-java-bridge into your script yourself, otherwise you will encounter "Java not defined" related errors. This change is an official Frida update. According to the official migration guide, you need to create a Node.js project and import frida-java-bridge. For details, refer to: https://github.com/oleavr/frida-agent-example , or use the [pack_frida_script.py](https://github.com/firerpa/lamda/blob/HEAD/tools/pack_frida_script.py) we provide to package the JS script.
```
## Using via Code
You can obtain a connected Frida instance by using `d.frida`, without having to manually construct a connection using the Frida library.
```python
conn = d.frida
conn.enumerate_processes()
```
Of course, if you want to understand its underlying implementation, you can also refer to the following code.
```python
# Get dynamic token
token = d._get_session_token()
manager = frida.get_device_manager()
conn = manager.add_remote_device("192.168.0.2:65000", token=token)
conn.enumerate_processes()
```
## Using via Command Line
If you need to use the Frida command line, we strongly recommend using the `frida`, `frida-trace`, `frida-ps` and other related commands via [Remote Desktop](./remote-desktop.md). In that environment, you don't need to do anything extra — just run `frida` directly without any additional connection parameters like `-U`, `-H`, etc., as the built-in environment already has everything configured by default.

If you have read this far, it means you still prefer to use the command on your computer. Now, you need to gather some necessary information based on how the service is installed/running. If you are using a [Server Certificate](./server-certificate.md), you need to have that certificate file ready, along with the target device's IP address and the FIRERPA service port (default 65000). Note, this port is not Frida's 27042 port.
Next, you need to use the API to obtain the current token, which is a fixed 16-character string, like `czvpyqg82dk0xrnj`. We know this might be a bit inconvenient, but your device's security is more important.
```python
token = d._get_session_token()
print(token)
```
Now you have a token from the above API, suppose it is `czvpyqg82dk0xrnj`. The next step is to build the Frida command. For all official Frida CLI tools, you simply need to add the parameters `-H 192.168.0.2:65000` and `--token xxxxxxxxxxxxxxxx`, for example:
```bash
frida -H 192.168.0.2:65000 -f com.android.settings --token xxxxxxxxxxxxxxxx
```
If your FIRERPA server is started with a service certificate, you also need to add the `--certificate` parameter to the command.
```bash
frida -H 192.168.0.2:65000 -f com.android.settings --certificate /path/to/lamda.pem --token xxxxxxxxxxxxxxxx
```
You may have noticed the main differences: `-U` becomes `-H` (because we are connecting over the network, not USB), and we add the `--token` parameter; if the server is using a service certificate, an additional `--certificate` parameter is required. This is to ensure your device's security and prevent unauthorized access.
## Using via Command Line (objection)
For other tools like objection, similar parameters are usually provided, but most unofficial tools have not fully added them. Currently, we have only patched objection, without affecting its original functionality. However, since objection has not been updated for a long time, we have not pushed this patch upstream. You can download the [objection-1.11.0-command-patch.diff](https://github.com/firerpa/lamda/blob/8.0/tools/objection-1.11.0-command-patch.diff) we provide and apply it to your installed objection code directory (you can find the installation path with the `pip3 show objection` command).
After that, you can use it as follows. You will see that only an additional `--token` parameter is needed.
```bash
objection -N -h 192.168.0.2 -p 65000 --token xxxxxxxxxxxxxxxx explore
```
Or, if the server was started with a service certificate, you must also add `--certificate` in the same way.
```bash
objection -N -h 192.168.0.2 -p 65000 --certificate /path/to/lamda.pem --token xxxxxxxxxxxxxxxx explore
```
## Exposing Application Interfaces
This section has been moved to the [Frida Export Interfaces](./frida-export.md) chapter.
--- END OF frida-usage.md ---
--- DOCUMENT: frida-export.md ---
--- SOURCE: https://device-farm.com/docs/content/en/frida-export.md ---
# Frida Export Interface
You can use this feature to call methods exported via Frida RPC as extensions of the FIRERPA interface. By writing your own functional Hook scripts, you can achieve ultimate control over the app, signature exporting, and more. Familiarity with writing Frida scripts is required.
```{attention}
Starting from version 9.0, the built-in Frida 17.x requires you to bundle the frida-java-bridge into your script yourself. Otherwise, errors related to `Java not defined` will occur. This change is an official Frida modification. According to the official change notes, you need to create a Node.js project and introduce frida-java-bridge. For details, refer to: https://github.com/oleavr/frida-agent-example , or use our provided [pack_frida_script.py](https://github.com/firerpa/lamda/blob/HEAD/tools/pack_frida_script.py) to package your js script.
```
## Writing Export Scripts
You need to write scripts in a specific format. Exported function names must follow naming conventions: camelCase must be used with the first letter lowercase. For acronyms such as HTTP, function names must not be in all uppercase. For example, `sendHTTPRequest` should be written as `sendHttpRequest` in the export script. Below is an approximate structure that a script should follow.
```js
Java.perform(function() {
const String = Java.use("java.lang.String")
rpc.exports.exampleFunc1 = function (a, b) {
return performRpcJVMCall(function() {
return String.$new("Execute on JVM thread:" + a + b).toString()
})
}
rpc.exports.exampleFunc2 = function (a, b) {
return performRpcJVMCallOnMain(function() {
return String.$new("Execute on Main UI thread:" + a + b).toString()
})
}
rpc.exports.exampleFunc3 = function (a, b) {
return performRpcCall(function() {
return a + b
})
})
```
In the above sample script, you can see three ways of defining functions. Among them, the `return performRpc` pattern code blocks must be used to ensure your values are correctly returned. The meanings of these three different `performRpc` call blocks are as follows:
```js
return performRpcJVMCall(function() {
// Execute on JVM thread
})
```
The `performRpcJVMCall` block represents executing your code within the JVM. You can use JVM-related functionality inside the block, such as `Java.use` or other operations involving the app's Java layer.
```js
return performRpcJVMCallOnMain(function() {
// Execute on UI thread
})
```
The `performRpcJVMCallOnMain` block represents executing your code on the JVM’s main UI thread. For operations involving the UI or the main thread, you need to execute them within this block to succeed. Also, make sure your code does not block the main thread, otherwise it may cause the app to become unresponsive or even crash.
```js
return performRpcCall(function() {
// Execute Normal JS code
})
```
The `performRpcCall` block can only be used to execute basic JavaScript code; you cannot use Java layer logic here.
```{attention}
You may still need to use Java.perform to wrap the overall code block to ensure Java logic executes properly.
```
Now, you understand the basic scripting logic. You can adapt your code into this format. If you just want to test, you can also directly copy this code to experience it. Below we will continue the explanation based on this Hook code.
## Injecting Export Scripts
The method of injecting export scripts here is completely identical to that in the `Persistent Frida Script` chapter.
```python
app = d.application("com.android.settings")
app.attach_script(script, runtime=ScriptRuntime.RUNTIME_QJS, standup=5)
```
After calling the injection interface, the script is injected. You can check whether the script is alive using the following call.
```python
app.is_script_alive()
```
## Calling Exported Methods
The way to call exported methods in the script is very similar to how you natively use the FIRERPA interface.
```python
app = d.application("com.android.settings")
app.exampleFunc1("FIRE", "RPA")
```
For apps with multiple instances, you need to get the multi-instance application instance:
```python
app = d.application("com.android.settings", user=UID)
app.exampleFunc1("FIRE", "RPA")
```
These two calling methods are equivalent. You will notice that `Func1` in the method name becomes `_func1`; both forms are acceptable.
```python
app.example_func1("FIRE", "RPA")
```
```python
>>> app.example_func1("FIRE", "RPA")
'Hello World:FIRERPA'
>>> app.example_func2("FRI", "DA")
'Hello World:FRIDA'
>>> app.example_func3("FIRE", "RPA")
'FIRERPA'
```
## Calling Exported Methods (HTTP Interface)
In addition to client-side calls, we also support calling via HTTP, which can be used in the following form.
You can use jsonrpclib for calls as shown below; alternatively, if you are familiar with the JSON-RPC 2.0 protocol, you can write your own code to send requests—this is the most standard protocol implementation with comprehensive documentation.
```python
import jsonrpclib
server = jsonrpclib.Server('http://192.168.0.2:65000/script/com.android.settings/0')
server.example_func1("FIRE", "RPA")
```
Of course, you can still use the previous calling protocol, which is relatively simple but not as standardized.
```python
import json
import requests
url = "http://192.168.0.2:65000/script/com.android.settings/exampleFunc1?user=0"
res = requests.post(url, data={"args": json.dumps(["FIRE", "RPA"])})
print(res.status_code, res.json()["result"])
```
The query parameter `user` in the link indicates the app's UID, defaulting to 0. For multi-instance apps, you must specify the UID.
The above code calls the script export interface via HTTP. `com.android.settings` is the app package name, and `exampleFunc1` is the exported function name. The request parameter `args` must be serialized using `json.dumps`. The parameter list supports multiple arguments, determined by the number of function parameters; providing an empty list `[]` means the exported function takes no parameters.
If FIRERPA has interface certificates enabled, you need to use HTTPS for access and provide the certificate password.
```python
headers = {"X-Token": "Certificate password"}
res = requests.post(url, data={"args": json.dumps(["LAM", "DA"])}, headers=headers, verify=False)
print(res.status_code, res.json()["result"])
```
## HTTP Status Codes
Calling via the HTTP interface returns specific status codes, which can be used for status determination.
| Status Code | Description |
| ----------- | ----------- |
| 200 | Everything is normal |
| 410 | Script not injected or not installed |
| 500 | Script or parameter error |
| 400 | Parameter error |
## Troubleshooting
If the interface hangs or times out when calling via API or HTTP, it is highly likely that the app is in the background and forced into sleep by the system. Therefore, you need to ensure the app is always running in the foreground.
--- END OF frida-export.md ---
--- DOCUMENT: frida-report.md ---
--- SOURCE: https://device-farm.com/docs/content/en/frida-report.md ---
# Frida Data Reporting
The data reporting functionality using Frida is based on the persistence feature. You can use your Frida scripts to automatically intercept method call data and report it via our specific method. The data reporting feature allows you to easily send the data intercepted by your script directly to Redis, MQTT, or an external HTTP interface, and you can receive the reported data content through Redis, MQTT, or an HTTP interface. To maximize network efficiency, data compression (zlib) is also supported before reporting.
```{attention}
Since version 9.0, the built-in Frida 17.x requires you to bundle frida-java-bridge into your script yourself; otherwise, errors related to Java not defined will occur. This change is from Frida official. According to the official change instructions, you need to create a Node.js project and introduce frida-java-bridge. For details, refer to: https://github.com/oleavr/frida-agent-example , or use the [pack_frida_script.py](https://github.com/firerpa/lamda/blob/HEAD/tools/pack_frida_script.py) we provide to bundle the JS script.
```
## Writing a Reporting Script
Usually, Frida scripts have functions like `send` and `log` to send data to the outside, but for FIRERPA, you need to use a specific method to send data out. The following uses our template code for OkHttp traffic interception as a demonstration. It is just a demo script and may not work properly for you. The script below is not much different from regular scripts, the only difference being the use of an `emit` method, which is a built-in method of FIRERPA. You can use it to conveniently and systematically submit data to the outside.
```js
Java.perform(function() {
Java.use("com.android.okhttp.internal.http.HttpEngine").getResponse.implementation = function() {
var response = this.getResponse()
var data = {}
data["url"] = response._request.value._url.value._url.value
data["body"] = response.body().string()
emit("report_data", JSON.stringify(data))
return response
}
})
```
The submission method has two parameters: `emit(name, content)`. `name` represents the type of data; if your reporting destination is set to Redis, this name represents the Redis queue name. You should describe it as accurately as possible in English, e.g., `product_info`. `content` represents the data content, and the type supports only strings and byte arrays. In the example, we converted it to a string for submission.
Now you understand the format and calling requirements for script writing, i.e., how to submit Hooked data to the outside. You also need to continue reading below to learn how to configure the **data reporting destination**.
## Data Reporting Destination
The data reporting destination indicates where the data emitted in your script should be sent. Supported destination types include HTTP interfaces, Redis queues, and MQTT. There are some differences among them.
In general, if you don't need to care about information such as data sources, i.e., you don't need to match **data** with **source device**, we recommend using Redis queues. Otherwise, you should use HTTP or MQTT (v5), because these protocols can carry more metadata, allowing you to perform more precise device matching.
### Reporting Metadata
For data reported to HTTP interfaces and MQTT destinations, in addition to the original report data, you can also obtain metadata about the device and script at the protocol layer. The metadata carried in the protocol is shown in the table below.
| Field | Description |
|------------|--------------------------------------------------|
| application| App package name (e.g., com.android.settings) |
| device | Device ID (e.g., 67b2a3d7-5004-ea2a-0d44-194de6ede8de) |
| encode | Data encoding (none | zlib) |
| name | Data name (e.g., report_data) |
| script | Script ID (e.g., 7c52530d) |
| sequence | Reporting sequence (e.g., 30) |
| timestamp | Reporting time (e.g., 1740023596914) |
| user | Multi-app user ID (e.g., 0) |
```{attention}
Due to the nature of the Redis protocol, data reported to a Redis destination does not include any of the above metadata.
```
`device` is the unique device ID; you can find it in the remote desktop information bar. Usually, this ID is unique and fixed. You can use it to mark the device and establish a correspondence. `encode` is the data encoding; supported encodings are `none` and `zlib`. If the encoding is zlib, you need to decompress the data body using zlib. `name` marks the type of this reported data; it is also the first parameter when you call the `emit` method. `sequence` represents the index of the reported data, starting from 0 and incrementing with each report. You can use this field to sort the data or check for report loss.
### Additional Parameters in the Reporting Link
You can specify some dynamic id parameters in the reporting link using the variable placeholder format `${name}` inserted into specific parts of the link. For example, `http://192.168.1.2/report/${device_id}`. The supported variables are shown in the table below.
| Name | Description |
| ------------- | ----------- |
| device_id | Unique device ID |
| device_id_short | Unique device ID (short device ID encoded in BASE62) |
| android_id | Android ID |
| serialno | ro.serialno |
### Reporting to HTTP
You need to write an HTTP service to receive the reported data yourself. The HTTP reporting interface must implement the POST method. FIRERPA will report data to the interface via POST, and will encode each field of the metadata as HTTP query parameters, which you can extract and process. The data body is carried in the POST request body. FIRERPA will automatically retry up to 3 times upon receiving 502, 503, or 504 status codes. If your backend correctly receives and processes the reported data, it should return plain text `OK` or `SUCCESS` with a `200` status code to indicate successful processing.
```{attention}
HTTP requests are multi-threaded; the messages received by the backend may not follow the reporting sequence (sequence).
```
#### Example Reporting Link
```txt
http://192.168.1.2/report/${device_id}?serialno=${serialno}
```
If standard HTTP protocol authentication is required:
```txt
http://user:password@192.168.1.2/report/${device_id}?serialno=${serialno}
```
### Reporting to MQTT
When reporting to MQTT, the metadata can be extracted from `UserProperty`. It supports TLS, username/password, and one-way certificate verification (server certificate verification).
#### Example Reporting Link
```txt
mqtt://test.mosquitto.org:1883/script/${device_id}/report
```
MQTT with password authentication:
```txt
mqtt://rw:readwrite@test.mosquitto.org:1884/script/${device_id}/report
```
Requiring server certificate authentication:
```txt
mqtts://test.mosquitto.org:8883/script/${device_id}/report?verify=true&ca=LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVBekNDQXV1Z0F3SUJBZ0lVQlkxaGxDR3ZkajROaEJYa1ovdUxVWk5JTEF3d0RRWUpLb1pJaHZjTkFRRUwKQlFBd2daQXhDekFKQmdOVkJBWVRBa2RDTVJjd0ZRWURWUVFJREE1VmJtbDBaV1FnUzJsdVoyUnZiVEVPTUF3RwpBMVVFQnd3RlJHVnlZbmt4RWpBUUJnTlZCQW9NQ1UxdmMzRjFhWFIwYnpFTE1Ba0dBMVVFQ3d3Q1EwRXhGakFVCkJnTlZCQU1NRFcxdmMzRjFhWFIwYnk1dmNtY3hIekFkQmdrcWhraUc5dzBCQ1FFV0VISnZaMlZ5UUdGMFkyaHYKYnk1dmNtY3dIaGNOTWpBd05qQTVNVEV3TmpNNVdoY05NekF3TmpBM01URXdOak01V2pDQmtERUxNQWtHQTFVRQpCaE1DUjBJeEZ6QVZCZ05WQkFnTURsVnVhWFJsWkNCTGFXNW5aRzl0TVE0d0RBWURWUVFIREFWRVpYSmllVEVTCk1CQUdBMVVFQ2d3SlRXOXpjWFZwZEhSdk1Rc3dDUVlEVlFRTERBSkRRVEVXTUJRR0ExVUVBd3dOYlc5emNYVnAKZEhSdkxtOXlaekVmTUIwR0NTcUdTSWIzRFFFSkFSWVFjbTluWlhKQVlYUmphRzl2TG05eVp6Q0NBU0l3RFFZSgpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFNRTBIS21JemZUT3drS0xUM1RISGUrT2JkaXphbVBnClVabUQ2NFRmM3pKZE5lWUdZbjRDRVhieVA2ZnkzdFdjOFMyYm9XNmR6ckg4U2RGZjl1bzMyMEdKQTlCN1UxRlcKVGUzeGRhL0xtM0pGZmFIamtXdzdqQndjYXVRWmpwR0lOSGFwSFJscGlDWnNxdUF0aE9neFc5U2dEZ1lsR3pFQQpzMDZwa0VGaU13K3FEZkxvL3N4RktCNnZRbEZla01lQ3ltakxDYk53UEp5cXloRm1QV3dpby9QRE1ydUJUelBICjNjaW9CbnJKV0tYYzNPalhkTEdGSk9majdwUDBqL2RyMkxINzJlU3Z2M1BRUUZsOTBDWlBGaHJDVWNSSFNTeG8KRTZ5akdPZG56N2Y2UHZlTElCNTc0a1FPUnd0OGVQbjB5aWRyVEMxaWN0aWtFRDNuSFloTVVPVUNBd0VBQWFOVApNRkV3SFFZRFZSME9CQllFRlBWVjZ4QlVGUGlHS0R5bzVWMytIYmg0TjlZU01COEdBMVVkSXdRWU1CYUFGUFZWCjZ4QlVGUGlHS0R5bzVWMytIYmg0TjlZU01BOEdBMVVkRXdFQi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUwKQlFBRGdnRUJBR2E5a1MyMU43MFRoTTYvSGo5RDdtYlZ4S0xCalZXZTJUUHNHZmJsM3JFRGZaK09LUloyajZBQwo2cjdqYjRUWk8zZHpGMnA2ZGdicmxVNzFZLzRLMFRkeklqUmozY1EzS1NtNDFKdlVRMGhaL2MwNGlHRGcveFdmCitwcDU4bmZQQVl3dWVycnVQTldtbFN0V0FYZjBVVHFSdGc0aFFEV0J1VUZESlR1V3V1QnZFWHVkejc0ZWgvd0sKc013ZnUxSEZ2ank1WjBpTURVOFBVRGVwalZvbE9DdWU5YXNobFM0RUI1SUVDZFNSMlRJdG5BSWlJd2lteDgzOQpMZFVkUnVkYWZNdTVUNVhtYTE4Mk9DMC91L3hSbEVtK3R2S0dHbWZGY04wcGlxVmw4T3JTUEJnSWxiKzFJS0pFCm0vWHJpV3IvQ3E0aC9KZkI3TlRzZXpWc2xna0Jhb1U9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
```
In the above link, the relevant data will be sent to the topic `script/${device_id}/report`. You can subscribe to these messages using something like `mosquitto_sub -L mqtt://test.mosquitto.org:1883/script/+/report`.
### Reporting to Redis
Redis reporting is relatively simple. Because it does not carry any metadata, you cannot directly distinguish the data source. You may need to achieve this by dynamically modifying the injected script. For Redis reporting, FIRERPA will directly push the data body into the queue via `LPUSH`. For example, in the example script above, the reported data will be pushed into the `report_data` queue.
```{attention}
Only standalone Redis service is supported; Redis cluster is not supported. Except for the password field, do not add variable placeholders in the Redis link. The link is in the format supported by the standard Redis library; modifying other parts may cause parsing errors.
```
#### Example Reporting Link
```txt
redis://1.2.3.4/0
```
Redis requiring password authentication:
```txt
redis://:password@1.2.3.4/0
```
TLS + password authentication Redis:
```txt
rediss://:password@1.2.3.4/0?ssl_ca_data=LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURUVENDQWpXZ0F3SUJBZ0lVUFIvcmcxK0x2aU5tYzNsc0...
```
## Injecting the Reporting Script
Of course, obtaining the app instance is the first step. You can obtain an `app` variable through the above calls, which represents the application instance you need to inject. You then need to use it to perform subsequent injection or detachment operations.
```python
app = d.application("com.android.settings")
```
Inject the above reporting script into the application. When data is intercepted, it will be submitted to the Redis `report_data` queue. In the example, your device needs direct access to the relevant service on 192.168.1.10; otherwise, it will not receive the data.
```python
app.attach_script(script, emit="redis://192.168.1.10/0")
```
Calling it this way will submit your data to an HTTP interface instead of Redis (HTTPS supported).
```python
app.attach_script(script, emit="http://192.168.1.10/dataReport")
```
When the data you report is large, enabling compression can significantly improve network transmission throughput. You can use the `encode` parameter to enable report data compression and need to decompress the data on the receiving end.
```python
app.attach_script(..., encode=DataEncode.DATA_ENCODE_ZLIB)
```
## Decompressing Reported Data
By default, reported data is not compressed. If you have enabled reporting compression, you also need to decompress the reported data on the receiving end using zlib. You can conveniently decompress the reported data using the standard Python library `zlib`'s `decompress` method.
```python
zlib.decompress(data)
```
## Removing the Reporting Script
Removing the reporting script is straightforward, just like the usage in the persistence script.
```python
app.detach_script()
```
--- END OF frida-report.md ---
--- DOCUMENT: frida-persist.md ---
--- SOURCE: https://device-farm.com/docs/content/en/frida-persist.md ---
# Persisting Frida Scripts
FIRERPA provides the ability to persist Frida scripts. You can conveniently inject scripts through relevant interfaces, and FIRERPA's script manager will automatically manage the injected scripts. Even if your script exits abnormally, or the app process exits, FIRERPA will automatically re-inject the script the next time your app is opened.
## Installing Scripts
Install the script to the target application. After installation, the script will be injected into the application immediately. The installed script automatically enters the script manager, which continuously monitors and re-injects the script when its state requires it.
```{attention}
The script manager allows only one script to be injected per application at a time; you cannot inject multiple scripts into the same application simultaneously.
```
```python
app = d.application("com.android.settings")
app.attach_script(script, runtime=ScriptRuntime.RUNTIME_QJS, standup=5)
```
The `script` parameter is the content of the Frida script you want to inject (supports bytecode), the `runtime` parameter specifies the corresponding runtime environment (default `qjs`), and the `standup` parameter indicates a delayed injection time, meaning injection occurs only 5 seconds after the application process starts (timing begins from process creation). The minimum value for this parameter is 1 second, maximum 300 seconds, to avoid injecting too early into the process and causing crashes or other race condition issues. In spawn mode, this parameter should always be set to 0.
This interface also supports injection in spawn mode, but note that using spawn mode may interrupt your UI operation flow, because once you use spawn mode, in the event of an injection script error or the app exiting, the mode automatically restarts the application, thus interfering with your interface operations. If you need to use spawn mode, please use the following parameters.
```python
app = d.application("com.android.settings")
app.attach_script(script, runtime=ScriptRuntime.RUNTIME_QJS, spawn=True, standup=0)
```
Note that spawn mode differs from normal mode: In normal mode, if the application exits for some reason, normal mode waits for the application to start before injecting, and will not launch the application on its own, so you may need to start the application manually or through code to continue injection. In spawn mode, however, even if the application exits, it will automatically restart the application and perform injection.
## Uninstalling
Remove the Frida script that has been installed in the application; the script will also be unloaded from the application process. At the same time, FIRERPA's script manager will no longer monitor the script's running state, nor will it re-inject after a script error.
```python
app = d.application("com.android.settings")
app.detach_script()
```
## Check if Installed
Determine whether a script is installed for the application. You can use the return value to decide whether to reinstall.
```python
app = d.application("com.android.settings")
app.is_attached_script()
```
## Check if Injected
Check whether the installed script is currently injected into the application process. Because even after installation, if the application is not running or the script has errors, the script may not be injected into the process. You can use the return value to determine whether to start the relevant application or check the injected script for syntax errors.
```python
app = d.application("com.android.settings")
app.is_script_alive()
```
## View Script Logs
Console logs such as `console.log` from your script, as well as script error messages, can be viewed, but you need to configure logging when FIRERPA starts. Please refer to the [View Logs](./service-logs.md) section to learn how to set up log files. Assume you have set the log file to `/data/local/tmp/server.log`. When you need to view script logs, you can run the following command, which filters out all log information from injected scripts. You can also continuously trace logs using commands like `tail -f`.
```bash
grep SCRIPT /data/local/tmp/server.log
```
## Offline Persistence
Offline persistence means you can place Frida scripts as configuration files on the phone, and FIRERPA will automatically load your Frida scripts after startup. You do not need to use the above API interfaces for injection, cancellation, etc.; simply write the script file in a specific format and place it in a fixed directory. This feature monitors the directory in real time, loading, unloading, and updating scripts dynamically. Directly editing files in the script directory also takes effect in real time. Below is a simple YAML configuration example; the script content corresponding to this configuration is `console.log("Hello From Yaml Script")`.
```yaml
enable: true
application: "com.android.settings"
version: "2.10"
user: 0
runtime: "qjs"
script: !!binary "Y29uc29sZS5sb2coIkhlbGxvIEZyb20gWWFtbCBTY3JpcHQiKQ=="
emit: "http://myserver/reportData"
encode: "none"
standup: 10
spawn: false
```
```{hint}
The above is a complete example of an offline script. After writing, save the configuration as `{file_name}.yaml` and place it in the `~/modules/script` folder on the device; the system will automatically load your configuration. The system automatically detects changes in the `~/modules/script` directory; if you update or delete a YAML configuration, the system will accordingly update or cancel the script injection automatically.
```
### Field Descriptions
| Field | Description |
|-------------|-----------------------------------------------------------------------------|
| enable | Whether to enable this script |
| application | The application ID to inject the script into (e.g., com.android.settings) |
| version | Supported application version for the injected script (`"N/A"` means any version) |
| user | For multi-user apps, specify the corresponding user ID (usually 999) |
| script | Script content (base64 encoded), supports text or binary format (please write according to the template) |
| runtime | Script runtime (qjs, v8) |
| standup | Delay injection time (calculated from process start) |
| spawn | Use spawn mode (if enabled, standup is ignored) |
| encode | Encoding method for data reporting (zlib or none) |
| emit | Target URL for data reporting |
For more on the emit data reporting feature, please refer to the [Using Frida to Report Data](./frida-report.md) section.
--- END OF frida-persist.md ---
--- DOCUMENT: script-encrypt.md ---
--- SOURCE: https://device-farm.com/docs/content/en/script-encrypt.md ---
# Script Encryption
In this chapter, you will learn how to encrypt modules or scripts that run in the built-in Python environment of FIRERPA. This chapter does not cover encrypting scripts on PC, but rather introduces encryption for modules in directories such as `~/modules/task`, `~/modules/extension`, and for scripts executed using the built-in Python command. We provide tools to help you quickly complete encryption.
Script encryption uses Cython to convert Python code into C language, then compiles it into `.so` files, which has minimal impact on performance. However, we recommend encrypting only the core logic rather than all script files; also, do not encrypt files from third-party libraries, as such operations are futile. Always follow the principle of "only encrypt your own logic."
## Building the Tool Image
This tool heavily depends on Docker, so please ensure Docker is installed before proceeding. Now clone the repository `https://github.com/firerpa/compiler` and build the image according to the documentation or using the following command:
```bash
docker build -t compiler .
```
## Encrypting Script Code
Use the following command to map the source code into the container and compile. Supported architectures (arch) include `arm`, `arm64`, `x86`, `x86_64`, corresponding to different architectures of the FIRERPA server. Files compiled for different architectures are not compatible with each other.
```bash
docker run -it --rm -v /source/dir:/data compiler:latest compile.sh --arch arm64 /data/my_important_script.py
```
Under normal circumstances, after running the command, you will get a file named `my_important_script.cpython-39.so` in the same directory as the script. Do not rename this file; if you need to use a different name, directly modify the original script's filename. When distributing the script, you can delete the corresponding `.py` source file and place the `.so` file in the appropriate directory.
```{attention}
If your code is not a FIRERPA module (for example, an extension or task) and consists of only a single Python file, the Python interpreter cannot directly run the .so file. In this case, you need to write an entry .py script to import and launch the encrypted .so file. If the code itself is a module, simply put the .so file into the corresponding directory and restart the service.
```
--- END OF script-encrypt.md ---
--- DOCUMENT: ida-debug.md ---
--- SOURCE: https://device-farm.com/docs/content/en/ida-debug.md ---
# IDA Debugging
This chapter will introduce how to use FIRERPA and its related features and scripts to quickly debug Android applications. Our demonstration environment is Android 10, the host system is Ubuntu 22.04, with IDA 7.5 (Wine), ADB, JDB, Python, and other tools installed. It is recommended that your IDA version is not lower than 7.5, and install the latest version of the FIRERPA server.

## Set to Debuggable Mode
If your system is already in debug mode, this step is not needed. Otherwise, open the FIRERPA remote desktop and enter `setdebuggable` in the terminal. After executing this command, your phone will undergo a soft reboot, but don't worry; it will fully recover in about two minutes. At this point, your phone has entered debuggable mode.

## Install IDA Debugging Service
Before starting debugging, you also need to upload the IDA Android debugging server to the device. In the `dbgsrv` folder under the IDA installation directory, you can find `android_server` and `android_server64`. If the target app uses 32-bit dynamic libraries, use `android_server`; otherwise use `android_server64`. Drag and drop the required file to upload or send it to the Android device via `adb push`, and grant executable permissions:
```bash
chmod 755 android_server*
```
After granting executable permissions, start the android_server service. You can run the executable via adb (root privileges required) or the remote desktop terminal. For Android 10 and later, you need to set the `IDA_LIBC_PATH` environment variable beforehand:
```bash
IDA_LIBC_PATH=/apex/com.android.runtime/lib64/bionic/libc.so ./android_server64 -kk
```
```{note}
If the debug target is a 32-bit application, please change IDA_LIBC_PATH to /apex/com.android.runtime/lib/bionic/libc.so and use the 32-bit android_server debugging service.
```
## Forward IDA Debugging Service
For scenarios like debugging that require high real-time performance and responsiveness, it is recommended to use a USB connection. Ensure the device is connected to the current computer via USB and ADB authorization is completed, then execute the following command:
```bash
adb forward tcp:23946 tcp:23946
```
```{note}
23946 is the default listening port of IDA android_server. If you modified the port when starting, specify the actual port number when forwarding.
```
## Start Application Debugging
We have provided an encapsulated script that can automatically complete most of the subsequent operations. You just need to follow the script's output prompts. You can find the `ida.py` script in the project's `tools` directory and run it with the following command:
```bash
python3 ida.py -d 192.168.0.2 -a com.android.settings
```

Where `-d` parameter is the device IP, i.e., the IP of the device running the FIRERPA service; `-a` parameter is the package name of the target application. After executing the command, the script will prompt you to attach the debugger to the application process in IDA.



Everything is ready. Return to the ida.py command line interface, press Enter, and then you can continue debugging.
--- END OF ida-debug.md ---
--- DOCUMENT: tools-prepare.md ---
--- SOURCE: https://device-farm.com/docs/content/en/tools-prepare.md ---
# Tool Preparation
These tools are designed to work on Linux/macOS systems under normal circumstances. They encapsulate commonly used personal functions and have not been specifically tested for Windows compatibility, but this does not mean FIRERPA does not support Windows. If you are using Windows, scripts ending with `.sh` may not work correctly. Before starting, please ensure that the FIRERPA server on your device is already running. Some command documentation may be updated at any time; to avoid version issues, please make sure you have installed the latest version of FIRERPA, its Python library, and dependency libraries on your phone before proceeding. Some functions require ADB, so please ensure that the installed ADB is the latest version.
## Clone the Tool Code
First, execute the following command to download the scripts and code of the related tools. We assume you have Git installed.
```bash
git clone https://github.com/firerpa/lamda.git
```
```{tip}
If Git is not installed, please download and extract this file: https://github.com/firerpa/lamda/archive/refs/heads/master.zip
```
## Install Related Dependencies
Enter the `tools` directory in the tool source code and install the required Python dependencies.
```bash
pip3 install -r requirements.txt
```
```{attention}
Our dependency file may not be updated in a timely manner. If you are prompted that a dependency is not installed during use, please handle it yourself.
```
## Environment Setup
Before using the tools, you need to set environment variables according to the server's startup parameters; otherwise, the related tools cannot automatically match the port, certificate, and other information you have modified. If you have enabled a service certificate on the server side, you need to set the certificate path before use.
```bash
# For Linux/macOS
export CERTIFICATE=/path/to/lamda.pem
# For Windows (paths must not contain spaces)
set CERTIFICATE=C:\Users\path\to\lamda.pem
```
Alternatively, if you have modified the default port (65000) of the FIRERPA service, you also need to set the port environment variable before use.
```bash
# For Linux/macOS
export PORT=8123
# For Windows (paths must not contain spaces)
set PORT=8123
```
--- END OF tools-prepare.md ---
--- DOCUMENT: ssh.md ---
--- SOURCE: https://device-farm.com/docs/content/en/ssh.md ---
# SSH and SCP
Through the `ssh.sh` and `scp.sh` scripts provided by FIRERPA, you can SSH into the device's shell terminal or copy files between your PC and the device.
## Prerequisites
Before using, ensure the FIRERPA server is running properly on the device and that you have completed the [tool preparation](./tools-prepare.md) on your PC (cloning the lamda repository, installing dependencies, setting `CERTIFICATE`/`PORT`, etc.). If the server has enabled [server certificate](./server-certificate.md), you also need to set the `CERTIFICATE` environment variable to the PEM file path.
## SSH Connection
Replace `192.168.1.2` with your device IP:
```bash
bash ssh.sh 192.168.1.2
```
```{note}
The built-in SSH service starts with FIRERPA by default. It can be controlled via sshd.enable; see [Service Configuration](./properties.md) for details.
```
## SCP File Transfer
Copy the `/sdcard/DCIM` directory from `192.168.1.2` to the current directory:
```bash
bash scp.sh 192.168.1.2:/sdcard/DCIM .
```
Copy the local directory `test/` to the `/sdcard/` directory on device `192.168.1.2`:
```bash
bash scp.sh test/ 192.168.1.2:/sdcard/
```
```{tip}
You can also use the Python API in [File I/O](./file-io.md) to upload or download files within your scripts.
```
--- END OF ssh.md ---
--- DOCUMENT: one-click-capture.md ---
--- SOURCE: https://device-farm.com/docs/content/en/one-click-capture.md ---
# One‑Click Man‑in‑the‑Middle Packet Capture
The one‑click man‑in‑the‑middle packet capture feature can automatically enable global MITM interception on the device. Compared with other common tools, you don’t need to worry about [installing the MITM certificate](./install-mitm-cert.md) or [setting up a proxy](./set-proxy.md), nor about differences in method due to various system versions. It adapts to multiple Android versions, and we’ve taken care of everything for you. It also supports real‑time packet modification and interception.
Additionally, the success rate is higher than with typical software. You can intercept the app’s HTTP/HTTPS traffic. The script automatically enables and disables the MITM interception; after exiting the script, the device and network immediately return to their original state without any extra configuration. The script also supports **international** MITM capture and can specify an upstream proxy, making it easy to capture traffic from overseas apps.
```{attention}
Regardless of which operating system you are using, to ensure success on the first attempt, temporarily and completely disable your **network firewall** before starting packet capture.
```
## Preparation
First, make sure that the computer and the device are on the same network segment or are connected via USB. In the following steps we assume that the IP of the mobile device running FIRERPA is 192.168.0.2. Next, confirm that you have correctly completed [tool preparation](./tools-prepare.md) on the PC, and verify that `mitmproxy` is installed and working (you can run `mitmdump` in the terminal to check).
If you don’t have a Python environment and are not familiar with cloning and dependency management, you can download [startmitm.exe](https://github.com/firerpa/lamda/releases/latest/download/startmitm.exe) from our release page. This is a packaged version of `startmitm.py` that allows you to perform packet capture without installing Python. It is a console program, and its command arguments are identical to those of `startmitm.py` (this file may be flagged by antivirus software; decide for yourself whether to use it).
## Basic Packet Capture
```{hint}
The app you are capturing may use SSL PINNING (certificate pinning) or the QUIC protocol, either of which can affect packet capture results. We will automatically force QUIC downgrade, but due to the fallback handshake time, the app may briefly freeze; please wait a moment. If the app uses certificate pinning, direct packet capture is not possible; you will need to combine reverse engineering and write a Frida script to bypass the SSL PINNING logic.
```
You can quickly start packet capture with the following command
```bash
python3 -u startmitm.py 192.168.0.2
```
You only need to press `Ctrl+C` once to exit packet capture.
## Shared Packet Capture
If you want to analyze traffic together with colleagues on the same local network, run the following command, then have them visit `http://:7890` in their browser to see the same capture interface as yours and analyze together easily.
```bash
python3 -u startmitm.py 192.168.0.2 --web-port 7890 --web-host 0.0.0.0
```
## Specific Application
If you only need to intercept traffic from a specific application rather than system‑wide traffic, append `:package_name` to the IP address. The following example intercepts only the traffic of `com.some.package`.
```bash
python3 -u startmitm.py 192.168.0.2:com.some.package
```
## Real‑Time Modification
You can modify the app’s request and response data in real time. This feature is implemented through mitmproxy’s event hooks. You need to write a hook script using mitmproxy’s API. For how to write hook scripts, refer to the mitmproxy official documentation [docs.mitmproxy.org/stable/addons-examples](https://docs.mitmproxy.org/stable/addons-examples/) and the example code [mitmproxy/examples/contrib](https://github.com/mitmproxy/mitmproxy/tree/9.0.0/examples/contrib).
Assuming your hook script is named `http_flow_hook.py`, apply it with the following command.
```bash
python3 -u startmitm.py 192.168.0.2 -s http_flow_hook.py
```
## Different Networks
If your phone and computer are not on the same network but you can physically access the device, you can also perform packet capture via USB ADB. Make sure the device is connected to the computer via USB ADB and properly authorized.
If the computer is connected to only one ADB device, run the following command; `localhost` means capture is performed for that ADB device.
```bash
python3 -u startmitm.py localhost
```
If multiple ADB devices are connected, specify the device serial number, which can be obtained from the output of `adb devices`.
```bash
python3 -u startmitm.py localhost --serial bfde362
```
If your network environment is even more restrictive – for example, the phone and computer are neither on the same network nor physically accessible, but you can still reach the FIRERPA port – packet capture is still possible. A common scenario: forwarding FIRERPA through its built‑in frp service to a remote server, where only one port between you and FIRERPA can be directly connected and other ports cannot communicate. The phone cannot access any port on the local machine, and the local machine can only access the phone’s FIRERPA port. In such a case, use the following method.
First, use `adb_pubkey.py` or manually call the API to install your local ADB public key onto the device. After installation, execute connect, where `x.x.x.x` is the IP address that can connect to the device’s port 65000. Modify it according to the actual situation.
```bash
adb connect x.x.x.x:65000
```
Then run the following command
```bash
python3 -u startmitm.py localhost
```
If there are multiple ADB devices, you can also do like this, where the `--serial` parameter is the ADB connect address (e.g., `x.x.x.x:65000`), not the device serial number.
```bash
python3 -u startmitm.py localhost --serial x.x.x.x:65000
```
## Upstream Proxy / International Packet Capture
startmitm itself launches mitmproxy as the proxy service. By default, all traffic is sent from the local network interface by mitmproxy. If you want traffic to go through an upstream proxy instead of directly from the local machine, you can specify an upstream proxy, but only upstream proxies using the **HTTP** protocol are supported. At the same time, you need to specify an upstream DNS that supports TCP queries to avoid DNS pollution.
```bash
python3 -u startmitm.py 192.168.0.2 --upstream http://127.0.0.1:7890 --proxy-dns 8.8.8.8
```
If the upstream HTTP proxy requires authentication, replace `USER:PASSWORD` with the correct credentials.
```bash
python3 -u startmitm.py 192.168.0.2 --upstream http://USER:PASSWORD@x.x.x.x:8080 --proxy-dns 8.8.8.8
```
In this way, the device’s traffic will be sent through the upstream proxy, and all DNS queries from the device will be forwarded through the proxy to 8.8.8.8.
--- END OF one-click-capture.md ---
--- DOCUMENT: deploy-vpn.md ---
--- SOURCE: https://device-farm.com/docs/content/en/deploy-vpn.md ---
# Deploying VPN Service
This image only ensures basic functionality. If you are capable of configuring it yourself, it is recommended to build your own setup or refer to this image’s implementation. Before use, you should have basic knowledge of Linux and Docker. This image has been tested on Debian 9. This article describes how to use it on Debian 9, and it generally applies to other systems like Ubuntu. The default service port is 1190/UDP; make sure this rule is allowed in your firewall.
## Preparations
You also need to make the following changes on the server and run these commands:
```bash
echo net.ipv4.ip_forward=1 >> /etc/sysctl.conf
sysctl -p
```
If your server **has `ufw` installed**, add the following content at the beginning of the `/etc/ufw/before.rules` file. Modify `eth0` and the subnet according to your actual server interface and configuration. Note that it should be added before the `*filter` rules (if any).
```bash
*nat
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -s 172.27.27.0/24 -o eth0 -j MASQUERADE
COMMIT
```
Change the following setting in `/etc/default/ufw` to ACCEPT.
```bash
DEFAULT_FORWARD_POLICY="ACCEPT"
```
Finally, reload the ufw rules with the following command:
```bash
ufw reload
```
If your server **does not have `ufw` installed**, ensure that iptables' FORWARD policy is ACCEPT and run the following command:
```{attention}
You may need to re-apply this rule after server reboot; installing `ufw` is recommended.
```
```bash
iptables -P FORWARD ACCEPT
```
## Initial Configuration
Now create a directory to store the OpenVPN service configuration.
```bash
mkdir -p ~/lamda-openvpn-server
```
Next, run the following command to initialize the OpenVPN service:
```bash
docker run -it --rm --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn ovpn-server-new
```
Wait for the command to finish. Now you can see the service configuration file `config.ovpn` in the `~/lamda-openvpn-server` directory. You can edit this file; it is recommended to change only the following fields:
```ini
# VPN subnet and netmask
server 172.27.27.0 255.255.255.0
# VPN service port
port 1190
# If VPN clients need to access a specific network interface on the server, you can add a route.
# Note: In this case the client can only access the host's IP within that subnet,
# full subnet access requires additional setup.
push "route 192.168.68.0 255.255.255.0"
# Change 114.114.114.114 to your desired DNS server address
push "dhcp-option DNS 114.114.114.114"
```
## Creating Client Connection Credentials
After editing, create a client. Replace `myname` with any unique name you prefer.
```bash
docker run -it --rm --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn ovpn-client-new myname
```
Once created, obtain the login credentials for this client with the following commands:
```bash
# Note: The IP in the configuration is automatically obtained from the current public IP;
# if it is incorrect, modify it manually.
#
# Generate an ovpn configuration and redirect it to a file named myname.ovpn. This file can be used with clients like OpenVPN Connect.
docker run -it --rm --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn ovpn-client-profile ovpn myname >myname.ovpn
# Generate an OpenVPNProfile for use with lamda, which can be used directly in lamda.
# The output contains openvpn.* related settings; refer to the service configuration chapter for how to fill them in to achieve automatic VPN connection.
docker run -it --rm --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn ovpn-client-profile lamda myname
```
## Revoking Client Credentials
If you need to revoke a client credential, run the following command. You may need to restart the OpenVPN service afterward.
```bash
docker run -it --rm --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn ovpn-client-revoke myname
```
## Starting the OpenVPN Service
Now you can start the OpenVPN service in the foreground to view client connection logs and troubleshoot errors directly.
```bash
docker run -it --rm --name openvpn-server --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn run
```
After confirming everything works, it is recommended to run the service in the background. Use the following command to start it in the background:
```bash
docker run -d --rm --name openvpn-server --privileged --net host -v ~/lamda-openvpn-server:/etc/openvpn rev1si0n/openvpn run
```
## Reference Documentation
Basic documentation: https://openvpn.net/community-resources/reference-manual-for-openvpn-2-4/
Routing setup: https://community.openvpn.net/openvpn/wiki/BridgingAndRouting
--- END OF deploy-vpn.md ---
--- DOCUMENT: deploy-proxy.md ---
--- SOURCE: https://device-farm.com/docs/content/en/deploy-proxy.md ---
# Deploying Proxy Service
Here we provide two methods for installing a SOCKS5 proxy. To avoid issues, please read the relevant descriptions carefully.
## If You Need to Proxy UDP Traffic
Due to the nature of SOCKS5 UDP proxying, many unexpected issues can arise, and the situation becomes increasingly complex. If you are certain that you need UDP, you must follow these rules: First, your host system must be Linux, and the firewall must open UDP ports `50000-55000`; second, your server's network must not be NAT (FULL NAT is also unacceptable, many cloud servers are currently in NAT mode).
```{tip}
Why must the host be Linux? Because on other systems, Docker may not properly map such a wide range of ports, and the Docker network mode cannot be set to host mode on other systems.
```
If you are unsure whether your server's network is NAT, use commands such as `ifconfig` to get the IP address of the default network interface, and then `ping` this address from the machine where you intend to use the proxy. If the ping fails, your server is likely in a NAT network. Of course, even if some conditions are not met, it does not mean UDP is completely unusable; you can still set up gost yourself, and the specific method will be introduced at the end of the document.
```{attention}
Due to the nature of SOCKS5 UDP proxying, if you determine that you need UDP, you must configure the firewall to allow UDP communication on all ports.
```
## If You Don't Need to Proxy UDP Traffic
If you do not need to proxy UDP, everything is simple. Execute the following command to start a SOCKS5 service.
```bash
docker run -it --rm -p 1080:1080 --name socks -e LOGIN=username -e PASSWORD=passwd rev1si0n/socks5
```
## Specifying an Outbound Network Interface
If your server or computer has multiple network interfaces with Internet access, for example, a home computer connected to two networks via Ethernet cable and Wi‑Fi, it may have two network interfaces such as `wlan0` and `eth0`. When you want to specify which network interface the proxy uses for outbound traffic, for instance, to go out through `eth0`, use the following command to start the SOCKS5 service.
```{attention}
Your computer or server must be running Linux; other systems do not support specifying the outbound network interface.
```
```bash
docker run -it --rm --net host --name socks -e LOGIN=username -e PASSWORD=passwd -e DEV=eth0 rev1si0n/socks5
```
If you have met all prerequisites for using UDP, just use the following command.
```bash
docker run -it --rm --net host --name socks -e LOGIN=username -e PASSWORD=passwd rev1si0n/socks5
```
## Using gost Service
If you want to use UDP but cannot meet the conditions mentioned above, or you don't need to specify an outbound interface, or you prefer not to install Docker, you can try gost.
Download the executable archive for your system from [github.com/ginuerzh/gost/releases/](https://github.com/ginuerzh/gost/releases).
```bash
gost -L=socks5://username:passwd@:1080
```
```{hint}
After completing all deployment steps, please refer to the [Set Proxy](./set-proxy.md) chapter to learn how to configure the network proxy for your device.
```
--- END OF deploy-proxy.md ---
--- DOCUMENT: deploy-forward.md ---
--- SOURCE: https://device-farm.com/docs/content/en/deploy-forward.md ---
# Deploying Forwarding Service
This type of port forwarding program is recommended only for Linux-based systems. Before starting, first open the firewall for port 6009/tcp and modify the configuration according to your needs. Now adjust the following template configuration as appropriate and save it as the `~/frps.ini` file on the server.
```{danger}
Forwarding to the public internet is extremely dangerous. Do not be complacent and assume that the IP and port are very hidden. Be sure to enable certificate verification for the service!
```
```ini
[common]
bind_addr = 0.0.0.0
bind_port = 6009
token = lamda
use_encryption = true
tls_enable = true
allow_ports = 2000-5000
authentication_timeout = 60
proxy_bind_addr = 127.0.0.1
max_pool_count = 15
```
## Starting the FRPS Service
Execute the following script to download and start the frps server.
```bash
export VERSION=0.52.0
export PLAT=linux_amd64
export DOWNLOADURL=https://github.com/fatedier/frp/releases/download/v${VERSION}/frp_${VERSION}_${PLAT}.tar.gz
wget ${DOWNLOADURL} -O - | tar -xz
cd frp_${VERSION}_${PLAT}
./frps -c ~/frps.ini
```
## Configuring FIRERPA
After the service starts, fill in the following configuration (for configuration methods, see [Service Configuration](./properties.md)). The service will automatically forward its own service port through this server.
```{danger}
Forwarding to the public internet is extremely dangerous. Do not be complacent and assume that the IP and port are very hidden. Be sure to enable certificate verification for the service!
```
```ini
[fwd]
fwd.host=Your server IP
fwd.port=6009
fwd.rport=0
fwd.token=lamda
fwd.protocol=tcp
fwd.enable=true
```
```{tip}
The distributed deployment chapter also has relevant deployment tutorials for your reference.
```
--- END OF deploy-forward.md ---
--- DOCUMENT: distributed-deployment.md ---
--- SOURCE: https://device-farm.com/docs/content/en/distributed-deployment.md ---
# Distributed Deployment
Sometimes you may encounter this situation: your phone is at home while you are elsewhere. How can you use it? You can use the built-in frp or OpenVPN client in FIRERPA to achieve distributed deployment, allowing you to connect to the distributed device from any location. Both solutions require you to have a public server, and you need to configure the relevant server-side software to function properly.
```{danger}
The operations in this chapter may expose your phone to the public network, increasing security risks. Please read carefully.
```
In this chapter, we will not cover basic operations such as opening firewall ports; we assume you are familiar with and have completed the required settings, and we assume you have some understanding of frp and OpenVPN. If you choose to deploy on your own, we strongly recommend using the OpenVPN networking approach.
## Via the StarLink Platform (Hub)
If you need to manage devices uniformly through the FIRERPA StarLink platform, please refer to the [FIRERPA StarLink Platform](./starlink.md) chapter. You can also self-deploy our open-source version [firerpa/hub](https://github.com/firerpa/hub) + [firerpa/hub-bridge](https://github.com/firerpa/hub-bridge) to achieve P2P access between devices. For specific instructions, please consult the project documentation.
## Via frp Forwarding
For frp server deployment and device-side `fwd.*` configuration, please refer to [Deploy Forwarding Service](./deploy-forward.md). After configuration, the forwarded ports are by default bound to `127.0.0.1` on the frps server, suitable for accessing the device locally on the public server.
```{danger}
Exposing forwarding to the public network is extremely dangerous. Do not take chances, do not assume that IP addresses and ports are very secret. Be sure to enable certificate verification for the service!
```
If you wish to access the device directly from anywhere on the public network, we still do not recommend exposing ports via frp; we recommend using OpenVPN to place the device and your computer in the same network segment. If you indeed need to expose a public port via frp, first ensure that FIRERPA uses a service certificate, and change the frps `proxy_bind_addr` to `0.0.0.0`.
## Connecting to a Device via frp Forwarding
The following demonstrates how to connect to the device on a public server where frps is deployed. Assuming `fwd.rport` is `12345`:
```python
from lamda.client import *
d = Device("127.0.0.1", port=12345)
```
You can also open `http://127.0.0.1:12345` or `https://127.0.0.1:12345` in a browser on the server to access the device's remote desktop.
## Networking via OpenVPN
Please refer to [Deploy OpenVPN Service](./deploy-vpn.md) to learn how to connect the device to the OpenVPN network. After networking, add your personal computer to the same OpenVPN network, and then you can directly access the device using its private IP address. The client can be downloaded from [OpenVPN Connect](https://openvpn.net/client/).
--- END OF distributed-deployment.md ---
--- DOCUMENT: starlink.md ---
--- SOURCE: https://device-farm.com/docs/content/en/starlink.md ---
# FIRERPA Starlink Platform
The Starlink Platform is a management system for distributed Android devices and automated task execution, with **completely localized data**, covering core scenarios such as **device access**, **device monitoring**, **script development**, **task orchestration**, **model configuration**, and **user management**. It is suitable for remote device operations, batch automation execution, and AI/RPA scripting, supporting cross-network device interconnection, unified access of local and remote devices, and P2P access.
It supports managing large-scale device nodes, writing, versioning, and debugging automation scripts, dynamically deploying and executing scripts on devices, tracking task execution status, results, and failure reasons, configuring AI models to facilitate Prompt/hybrid script execution, and managing platform users.
## Main Features
### Device Management
Starlink provides comprehensive device management capabilities, including paginated retrieval, sorting, and batch selection of device lists. It supports viewing devices by group and identifying statuses such as online, offline, and busy. Device access supports direct, P2P, and forwarding modes to adapt to different network environments. The platform also displays device information such as brand, model, architecture, service version, registration time, heartbeat time, battery level, and supports user binding management in device details.
### Real-time Monitoring
The platform provides real-time device monitoring capabilities, supporting device overview, group filtering, and status filtering, along with real-time device screen preview. The device details page enables continuous monitoring of real-time and historical metrics such as CPU, memory, network, threads, file descriptors, and crash counts. Combined with charting capabilities, it helps users continuously observe device status changes, suitable for remote inspections and anomaly troubleshooting.
### Batch Operations
In batch operation scenarios, Starlink supports pushing files to multiple devices simultaneously, providing upload progress feedback and a failure retry mechanism. It also supports pulling files from multiple devices and packaging them into a single download. Additionally, the platform supports batch execution of Shell commands, allowing per-device viewing of output results, error messages, and exit codes, facilitating centralized operation and batch processing.
### Task Orchestration
The platform includes built-in task orchestration and execution capabilities, supporting paginated viewing, searching, status filtering, time filtering, and sorting in the task list, allowing intuitive observation of task statuses such as running, paused, and completed. The system also provides task overview statistics, including running, succeeded, failed, timed out counts, and trend data. When creating a task, you can configure parameters such as script version, target group, execution mode, priority, timeout, and retries, and support various scheduling modes such as loop, count, per-device count, deadline, and Crontab.
### Task Tracking
In the task details, users can view basic task information, script version, execution configuration, parameter sources, and time information. They can also track task dispatching, success, failure, timeout, and binding status from the device dimension. The platform also provides execution instance details for viewing execution status, duration, exceptions, traceback, results, and resource information, and supports run control and version switching for tasks.
### Script Center
The Script Center is designed for script development, maintenance, and collaboration scenarios, supporting script list searching, type filtering, time filtering, and batch deletion. It supports two types of content: code scripts and Prompt/hybrid scripts. The platform allows sharing scripts with other users, and the script detail page integrates the Monaco Editor, providing a relatively complete online development experience. It also supports browsing script version history and Diff comparison, facilitating continuous iteration and change tracking.
### Online Debugging
Starlink provides built-in online debugging capabilities, allowing direct entry into debug mode from the script page, and initiating debug runs after selecting a target device and model. During debugging, it supports control operations such as Step In, Step Over, Step Out, Continue, and Quit, and allows viewing source code breakpoints, call stack, local variables, and debug output. It also supports executing expressions in the debug console. Combined with real-time screen mirroring, users can observe the script execution process in correlation with the device screen.
### Model Management
The platform provides AI model management capabilities, supporting maintenance of configuration such as model name, endpoint, model name, and key, along with a visual mode switch. Users can also configure parameters such as visual scaling, maximum tokens, temperature, and step delay to better adapt the model to Prompt/hybrid scripts and AI-driven task execution requirements.
## Installing the Platform
Our service ecosystem is fully based on Docker, so Docker must be installed on your computer or server. For a smooth process, we recommend using Linux-based systems such as Ubuntu, Debian, etc., rather than Windows/Mac.
First, you need to clone our configuration files. You can execute the following command:
```bash
git clone https://github.com/firerpa/starlink.git
```
```{tip}
If Git is not installed, please download and extract this file: https://github.com/firerpa/starlink/archive/refs/heads/master.zip
```
After cloning, enter the directory. The directory contains a hidden file `.env.example`, which you need to rename to `.env` and adjust the configuration items accordingly. Modify the configuration items based on your actual needs; some configurations, once set, cannot be changed later.
```{attention}
You need to correctly open ports `7123`, `8883`, `8000`, and `65000` (default) in the firewall. If you have modified these ports in the configuration, open the corresponding ports.
```
```ini
DOMAIN=starlink.local
# The following variables must not be modified after the first initialization.
API_PORT=65000
WEB_PORT=8000
FWD_PORT=7123
# Set default passwords for related databases (can be set randomly)
REDIS_PASSWORD=9XdTBBn11m5ugEGUavmR4C
RABBITMQ_PASSWORD=6LVc53IVJcRpiDsY0CyLcN
POSTGRES_PASSWORD=P5FinxzLZToVzAaAIZvsIw
# Set the physical network subnet, gateway, and interface name where the current server is located.
# If running on a public network, you do not need to configure the following three items, and remove all lan-macvlan sections in starlink.yaml
LOCAL_LAN_SUBNET=192.168.50.0/24
LOCAL_LAN_GATEWAY=192.168.50.1
LOCAL_LAN_INTERFACE=enp7s0
# Set the local IP of the current service, depending on whether your host supports docker lan-macvlan mode and whether you need to access devices on the local network
# You need to use an unused IP within the current LAN here. If you do not need to access local devices or the lan-macvlan mode is not supported, set it to 127.0.0.1. This item cannot be empty.
LOCAL_LAN_IP=192.168.50.200
# Set the public IP of the current server. If you do not intend to deploy on a public network, set it to the same value as LOCAL_LAN_IP; otherwise, set the correct public server address (can be obtained via the command curl -4 ip.sb)
PUBLIC_IP=192.168.50.200
# Deploy https://github.com/firerpa/hub-bridge to obtain the following parameters
# P2P mode allows you to access external network devices while running on an internal network. If you do not plan to use P2P mode, just keep the current commented-out state.
#TOP_ENDPOINT=http://x.x.x.x:9000
#TOP_CLIENT_KEY=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq...QIDAQAB
#TOP_SECRET=49c98730-....-bb0c9f22a310
```
```{hint}
For deployments in Mainland China, you may not be able to pull images from Docker Hub normally. You need to bypass this restriction on your own.
```
After configuration is complete, execute the following command in the current directory:
```bash
docker compose -f starlink.yaml up --force-recreate -d
```
After a short wait, you can access the backend in your browser via `https://{LOCAL_LAN_IP}:{WEB_PORT}`. In the case of public network deployment, replace `LOCAL_LAN_IP` with `PUBLIC_IP`. Use the default username `admin` and password `starlink` to log in to the backend. After logging in, please change the default password promptly.
## Manual Service
If these steps still seem quite complex to you, you can also opt for a paid installation service. Scan the QR code on WeChat to select the required number of installations. We will provide appropriate remote installation services based on your needs, saving you all the hassle.
On the **redirected page** after paying for the order, long-press the QR code to add our manual service WeChat (or add our company WeChat in the [Technical Support](./tech-support.md) section), and attach the order-generated service voucher or order screenshot. We will serve you wholeheartedly.
--- END OF starlink.md ---
--- DOCUMENT: model-extension.md ---
--- SOURCE: https://device-farm.com/docs/content/en/model-extension.md ---
# MCP / Agent Integration
This article introduces how to integrate FIRERPA with large models (based on MCP or commands). FIRERPA has implemented the MCP server protocol and OpenAI's native tool call functionality at the underlying level, allowing you to write your own MCP plugins and serve them via the standard port 65000, or inherit the Agent class to achieve fully automated tool calls. We also provide a built-in automated MCP service.
## Built-in Agent Command
With the built-in `agent` command, you can quickly complete fully colloquial tasks through large models, supporting any service provider compatible with OpenAI API + tool call or self-built services. Combined with the built-in crontab, you can schedule colloquial tasks to run periodically.
```{hint}
The agent command must be used within the built-in terminal, and you need to provide a valid API and key. The performance of different large models may vary, please choose the one that suits you best. We recommend Gemini, OpenAI, DeepSeek, GLM, etc.
```
| Parameter Name | Type | Required | Default Value | Description |
| :--- | :--- | :--- | :--- | :--- |
| `--api` | string (str) | Yes | - | API endpoint |
| `--model` | string (str) | Yes | - | Model name |
| `--temperature` | float (float) | No | `0.2` | Model sampling temperature |
| `--key` | string (str) | Yes | - | API key for authentication |
| `--vision` | boolean (bool) | No | `False` | Whether to enable vision mode |
| `--imsize` | integer (int) | No | `1000` | Image size in vision mode |
| `--prompt` | string (str) | Yes | - | Instruction executed by the agent |
| `--max-tokens` | integer (int) | No | `16384` | Maximum number of tokens to generate |
| `--step-delay` | float (float) | No | `0.0` | Delay between steps |
```{attention}
Note that the --api parameter's address is the full address, not the base_url. Usually you need to append /chat/completions after the base_url to form the complete API address.
```
After preparing the required information, you can enter the following command in the remote desktop terminal to let the AI automatically operate your device.
```bash
agent --api https://generativelanguage.googleapis.com/v1beta/openai/chat/completions --key YOUR_API_KEY --model gemini-2.5-flash --prompt "Open the Settings app, package name com.android.settings, find network settings, and turn on airplane mode"
```
If your task prompt is too long, you can also provide the model prompt via a file.
```bash
agent --api https://generativelanguage.googleapis.com/v1beta/openai/chat/completions --key YOUR_API_KEY --model gemini-2.5-flash --prompt /path/to/prompt.txt
```
## Claude & Cursor Integration (MCP)
This section introduces how to integrate FIRERPA's MCP functionality into large model clients. We take Claude and Cursor as examples for illustration; you can also use it in any other place that supports the MCP protocol.
```{note}
FIRERPA's built-in MCP service supports tool calls, resource reads, prompts, progress notifications, and logging.
```
### Using the Official Extension
For **Claude**, you need to first locate the Claude settings page and follow the diagram. Then, according to the prompts, edit Claude's `claude_desktop_config.json` configuration file and write the following MCP JSON service configuration.
```json
{"mcpServers": {"firerpa": {"command": "npx", "args": ["-y", "supergateway", "--streamableHttp", "http://192.168.0.2:65000/mcp/"]}}}
```
For **Cursor**, you need to open Cursor Settings, follow the diagram, and enter the following configuration.
```json
{"mcpServers": {"firerpa": {"url": "http://192.168.0.2:65000/mcp/"}}}
```
```{attention}
Please replace the address in the configuration with your own device's IP address.
```
### Writing MCP Extensions
We provide an example MCP plugin, which you can download from examples/user-home/modules/extension/firerpa.py. You can refer to its implementation to write your own or extend its functionality. After downloading the extension script, upload it to the device's `~/modules/extension` directory via remote desktop or manual push, and restart the FIRERPA service. FIRERPA's MCP extensions are deployed as Python scripts in the device's `~/modules/extension` directory and are automatically loaded after the service restarts.
--- END OF model-extension.md ---
--- DOCUMENT: capability-integration.md ---
--- SOURCE: https://device-farm.com/docs/content/en/capability-integration.md ---
# Capability Integration
This section introduces how to integrate FIRERPA Remote Desktop and its related capabilities, which can be integrated into front-end pages for operation, display, and other functions. Detailed descriptions are not provided here; you can refer to the APIFOX documentation for viewing and testing. However, some prerequisite settings and basic information need to be introduced first.
```{hint}
The following content requires you to have a basic understanding of WebSocket, Canvas drawing, H.264, etc.
```
## Prerequisite Setup
To facilitate your interface testing, please ensure that the device is connected to the current computer via USB, and make sure that the device has not enabled login certificate verification (HTTPS). After completing these two steps, you also need to complete the relevant settings on the APIFOX platform. Since WS interfaces are involved, you need to install the APIFOX desktop client (not the web version) and import the relevant project into the client. Please explore the installation and import methods on your own.
## Real-time Video
Real-time video is transmitted via WebSocket and supports two formats: MJPEG (Motion JPEG) and H.264 NALU. MJPEG is the simplest to use; it essentially transmits continuous screenshots of the device screen, and when the transmission speed is fast enough, it forms a dynamic real-time screen. You only need to draw each frame message received from the WebSocket onto the screen in JPEG format. The other format, H.264, requires more foundational knowledge, as you need to perform decoding before rendering to the canvas. You can search for or integrate an existing H.264 decoder to handle the decoding and drawing.
Compared with MJPEG, H.264 can reduce bandwidth usage by at least half and is faster, but it is not suitable for all devices. Some devices may have poor H.264 encoding performance, in which case MJPEG should be used. Of course, MJPEG also has its disadvantages: since each frame is a complete image, it demands higher bandwidth.
## Real-time Touch
Real-time touch is also transmitted via WebSocket, and there is nothing special about it. You just need to send three types of operations – "press", "move", and "release" – in a specific format. You can trigger these through web page events such as `mousedown` and `mouseup`, and the main data transmitted are the event type and coordinates. The only thing to note is that you need to perform coordinate conversion based on the canvas and the actual screen size, calculating the actual screen coordinates corresponding to the user's operation coordinates on the canvas.
## Key Operations
Key operations are relatively simple; you just need to send a POST request to the relevant interface in a specific format. Key operations support controlling the device's navigation keys and performing standard English text input.
## Command Terminal
The command terminal communicates via WebSocket, and you need to use technologies such as xterm.js for integration. You just need to format the input and output into a specific format according to the API documentation and send them, or request xterm output.
## Real-time Commands
The real-time command interface is used to send automation or control commands to the device. For the specific request format and WebSocket/HTTP paths, please refer to the APIFOX project documentation. When integrating, it is recommended to use it together with "Real-time Video" and "Real-time Touch" to ensure the temporal consistency of the screen and operations.
--- END OF capability-integration.md ---
--- DOCUMENT: virtual-debian.md ---
--- SOURCE: https://device-farm.com/docs/content/en/virtual-debian.md ---
# Virtual Debian Environment
You can create a complete Debian environment that runs within Android through the FIRERPA add-on module, similar to what `Termux` or `androdeb` provide. You can use `apt` to install software and compile code in this environment, and you can also compile and use BPF-related programs here. It is a near-complete Linux environment running on an Android device.
```{hint}
This Debian environment includes only essential packages, you need to use apt to install common commands like git, python3 yourself.
```
## Installing the Environment
You can find `lamda-mod-debian-arm64-v8a.tar.gz` on the project's release page (please download the package matching your device's architecture). After downloading, open Remote Desktop, drag and drop the downloaded file to upload it to the device (we assume you haven't changed the Remote Desktop file upload directory), then perform the following installation steps. This installation does not need to be repeated.
```{hint}
Files uploaded through Remote Desktop are placed in the ~/uploads directory by default.
```
```bash
tar -C ~/modules -xzf ~/uploads/lamda-mod-debian-arm64-v8a.tar.gz
```
After executing the command, the installation is complete. Next, we'll explain how to enter the environment.
## Entering the Environment
After installing the file system of the environment, we can execute a command to enter the virtual Debian environment. The Debian environment has been installed to `~/modules/debian`. You can enter the Debian interactive terminal with the following command:
```bash
debian /bin/bash
```
If you only need to execute a single command, such as the `id` command, you can use it like this:
```bash
debian /bin/bash -c id
```
```{attention}
Only one instance can enter the virtual environment at a time. If you keep the session alive after running `debian /bin/bash`, attempting to run the command again in another terminal will return an error. Please exit the previous debian session before trying again.
```
## Advanced Usage
We will introduce a simple advanced usage. Once done, you can run an SSH service or Python scripts in the environment, free from the session limit. First, execute the following command to enter the virtual environment.
```bash
debian /bin/bash
```
Now you should be inside the virtual environment. Follow along and execute the commands below.
```bash
apt update
apt install -y openssh-server procps python3 python3-pip python3-dev
echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config
echo 'StrictModes no' >> /etc/ssh/sshd_config
mkdir -p /run/sshd
# change root password
echo root:lamda|chpasswd
# exit the debian environment
exit
```
Now it's time to execute the command to start our SSH server.
```bash
debian /usr/sbin/sshd -D -e
```
The above command will occupy the current terminal (hang). To avoid this, you can also use the built-in scheduled tasks to make the SSH service start automatically with the FIRERPA service. Next, execute `crontab -e` and add the following rule, then restart the FIRERPA service or device. For documentation on scheduled tasks, you can also refer to the [Scheduled Tasks](./cron-jobs.md) section.
```bash
@reboot debian /usr/sbin/sshd -D -e >~/sshd.log 2>&1
```
Now, obtain the device's IP address, then execute the following command on your computer and enter the password `lamda` to log in.
```bash
ssh root@192.168.x.x
```
You can unleash your creativity and explore endless possibilities. It is a small Linux server.
--- END OF virtual-debian.md ---
--- DOCUMENT: cron-jobs.md ---
--- SOURCE: https://device-farm.com/docs/content/en/cron-jobs.md ---
# Scheduled Tasks
Scheduled tasks are used to periodically execute scripts or other tasks. These scheduled tasks follow the standard Linux Crontab syntax; except for some possible differences in commands, the syntax is identical. All rules will be executed as the user running the service. You need to be able to write basic Crontab rules; if you are unfamiliar, please study them on your own first.
```{attention}
Due to Android's sleep mechanisms, scheduled tasks may not run at your expected time after the screen is turned off. You may need to set the device to stay awake for scheduled tasks to execute at your intended times.
```
You need to use this feature within FIRERPA's built-in remote desktop terminal, built-in ADB terminal, or SSH terminal. After entering the terminal, execute the command `crontab -e` to enter the editing interface, press the `i` key to enter insert mode, and then write the relevant rules. Then press the `Esc` key, followed by `Shift` + `:` (to type a colon), type `wq` and press Enter to save. Some example rules are as follows.
```ini
@reboot echo Executes when the framework starts
0 */1 * * * echo Executes every hour
* * * * * echo Executes every minute
0 8 * * * echo Executes at 8:00 every day
```
If you want to save the output of a scheduled task, simply use standard input/output redirection:
```ini
* * * * * echo hello >/data/local/tmp/script.log 2>&1
```
It is recommended to use an online Crontab validation website to verify the accuracy of your rules.
--- END OF cron-jobs.md ---
--- DOCUMENT: changelog.md ---
--- SOURCE: https://device-farm.com/docs/content/en/changelog.md ---
# Version History
## Version 10.4
```ini
* Added sqlalchemy and paramiko
* Human-friendly query semantics for child, sibling, and index access
* Added fuser, killall, pstree, and run-parts commands
* Other minor bug fixes
```
## Version 10.2
```ini
* Added support for Android 17
* Added support for Android 16KB page size
* Upgraded runtime core to Python 3.12
* Added APK/XAPK installation API
* Fixed various issues
```
## Version 10.0
```ini
* Support for background virtual screen automation
* Gproxy proxy supports specifying outbound network interfaces
* Support for non-root execution mode (adb shell)
* Optimized real-time screen mirroring smoothness and audio
* Support for WebRTC screen mirroring
* WebUI supports dark mode
* Support configuring properties via WebUI
* File upload supports streaming, with a maximum file size of 2GB
* Redesigned built-in tunnel2 proxy functionality
* Optimized built-in AI token consumption
* Optimized event system
* Optimized service stability, etc.
```
## Version 9.25
```ini
* Fixed semaphore (Semaphore) issue
* Resolved MCP protocol compatibility issues
* Remote desktop supports multi-user mode
* Added standardized touch simulation API
* Android 16 compatibility fixes
```
## Version 9.20
```ini
* Top Bridge now uniformly uses SAPI request mode.
* MQTT connections added max_inflight_messages and session_expiry_interval parameters.
* Added control tasks such as system reboot, file download, directory execution, software update.
* Added OpenAI task executor supporting semantic task execution.
* Built-in MCP extension (API path /mcp/)
* Updated built-in Frida version.
Major Changes
=================================================================
Version 9.20 includes some major changes, only affecting users who use hub and hub-bridge. The main issue is that the response data format from hub-bridge has been adjusted (this does not affect the underlying database).
This will cause versions prior to 9.20 to be unable to connect to the new version of hub-bridge correctly. However, the new version of lamda-server can still be compatible with older versions of hub-bridge.
Solution 1: Use the new versions of hub and hub-bridge (v3, supporting simultaneous connection to local and remote devices), but all devices must be upgraded to version 9.20 or higher.
Solution 2: Continue using the old versions of hub and hub-bridge, which can normally connect to all related versions of 9.20 and earlier.
=================================================================
```
## Version 9.9
```ini
* Logic adjustments related to task and event system
```
## Version 9.8
```ini
* Added mmkv read/write library for Python
* Automatically exit Top Bridge after device removal from hub and restart
* Improved stealth performance
```
## Version 9.5
```ini
* Fixed functional anomalies on some models
* Fixed frida illegal instruction issue
```
## Version 9.4
```ini
* Support terminating all running tasks
* Optimized memory usage
```
## Version 9.3
```ini
* Other optimizations and fixes
```
## Version 9.2
```ini
* Fixed stack overflow issue on some devices
* Other optimizations and fixes
```
## Version 9.0
```ini
* Optimized service network performance
* MCP transport protocol switched to streamable-http with notification and progress support
* Proxy supports https and shadowsocks protocols
* Proxy supports IPv6 and UDP protocols
* P2P Bridge support (peer-to-peer interconnection)
* Built-in distributed task system
* Other optimizations and fixes
```
## Version 8.45
```ini
* Updated frida version
* Other compatibility fixes
```
## Version 8.44
```ini
* Optimized low-level Python compatibility
* Added WAV audio playback interface
* Default not using H.264 screen casting
```
## Version 8.40
```ini
* Fixed incomplete /data mounting issue
* Improved enhanced automation stability
```
## Version 8.38
```ini
* Enhanced automation features
* Fixed compatibility for some Samsung models
* Optimized audio real-time performance
```
## Version 8.35
```ini
* Remote desktop supports real-time audio streaming
* Fixed hex_patch segmentation fault
```
## Version 8.30
```ini
* Added binary patching interface
* Support using child, sibling in Selector
* Support viewing interface XML tree layout in remote desktop
* Updated frida to fix some issues
```
## Version 8.28
```ini
* Fixed install_local_file
* Fixed frida reporting ID increment
* Improved built-in TF inference performance
* Updated some third-party modules
```
## Version 8.25
```ini
* Added hexedit command
* Completely fixed Permission Loophole (maybe)
* Added on-device AI framework (tflite-runtime)
* Updated Frida to improve stealthiness
```
## Version 8.22
```ini
* Use completely new SQLite
* Remote desktop inspection displays current coordinates and RGB
* Added plugin setup logic
```
## Version 8.20
```ini
* Added official MCP plugin
* Optimized frida compatibility
* Optimized MCP protocol implementation
* Fixed self-recovery logic
```
## Version 8.18
```ini
* Reverted buggy Frida
* MCP, HTTP extension plugin support
```
## Version 8.15
```ini
* Fixed service unavailability issue
* Support calling exported scripts using JSON-RPC
* Fixed SSH user directory
* Updated some submodules
```
## Version 8.12
```ini
* Fixed touch anomaly
* Added some utility scripts
* Enhanced stability
```
## Version 8.10
```ini
* Optimized self-recovery logic
* Optimized touch compatibility
```
## Version 8.9
```ini
* Fixed parsing error
```
## Version 8.8
```ini
* Frida data reporting supports AMQP
* Fixed certificate issue caused by upstream library changes in cert.py
* Fixed resource release issue on service restart
```
## Version 8.5
```ini
* Optimized clipboard sharing logic
* Added Frida script crash logs
* Now supports Android 15
```
## Version 8.0
```ini
* API fully supports multi-instance apps
* Remote desktop supports clipboard sharing
* Added fixed configuration for some models unable to open apps
* Added YAML frida script persistence
* Fixed compatibility for low-version systems such as 6.0
* Fixed automation-related functional anomalies on high-version systems
* Removed/renamed some methods
* Updated underlying implementation
```
## Version 7.90
```ini
* Persistent scripts support spawn mode
* Support persistent script output logging
* Fixed dump_window_hierarchy
* Fixed logic error in frida instance retrieval
```
## Version 7.85
```ini
* Support mDNS broadcast service
* Support enumerating all elements selected by selector
* Client added automatic retry mechanism
* Fixed Bound comparison logic error
* Allow loading certificates from remote
```
## Version 7.80
```ini
* Optimized real-time screen casting smoothness
* Added persistent Hook script support
* Added Hook RPC support
* Added data reporting support
```
## Version 7.76
```ini
* Fixed tool version dependency
* Fixed Python version matching issue
* Updated some submodules
```
## Version 7.75
```ini
* Added OCR recognition interface
* Added get_application_by_name
* Updated some submodules and dependency versions
```
## Version 7.73
```ini
* Fixed white screen issue for some apps
```
## Version 7.72
```ini
* Updated some submodules
* Fixed known issues
```
## Version 7.71
```ini
* Fixed Permission Loophole #95
* Fixed enumerate_all_pkg_names
```
## Version 7.70
```ini
* Updated some submodules
* Fixed known issues
```
## Version 7.68
```ini
* Optimized H.264 real-time screen
```
## Version 7.67
```ini
* Removed some invalid program logic
* Fixed excessive auto-recovery time issue
* Other optimizations and fixes
```
## Version 7.65
```ini
* Removed IDA related tools and interfaces
* Fixed startup failure in some cases
* Other optimizations and fixes
* Added enhanced stealth mode
```
## Version 7.60
```ini
* Optimized image search speed
* Support region screenshot in remote desktop
* Fixed some issues with remote desktop
```
## Version 7.57
```ini
* Added feature and template image search client interface
* Other optimizations and fixes
```
## Version 7.55
```ini
* Fixed abnormal screen rotation display issue
* Fixed remote desktop initial connection disconnection issue
* Fixed element existence determination
* Added Meta key definition
* Other optimizations and fixes
```
## Version 7.52
```ini
* Fixed compatibility with Magisk version of Lightning
* Fixed issue of service not exiting normally
```
## Version 7.50
```ini
* Completely fixed Nox emulator compatibility issue
* Fixed zombie processes caused by logic errors
* New networking subscription service, networking without FRP or OpenVPN
* Fixed system multi-resolution issue
* Optimized system certificate injection logic for Android 13, 14
* Added support for multi-instance apps (user)
* OpenVPN now supports IPv6
```
## Version 7.30
```ini
* Fixed Lightning/Nox compatibility issues
* Some minor adjustments
```
## Version 7.28
```ini
* Added show_toast interface
* Built-in proxy now supports proxying DNS traffic
* startmitm now supports DNS through upstream proxy
* Fixed Android 10+ Frida spawn
```
## Version 7.25
```ini
* Fixed scheduled task execution failure
* Fixed launch failure from Termux
* Updated built-in Frida version
```
## Version 7.22
```ini
* Automatically synchronize system time
* Updated some built-in modules
* Some minor fixes
```
## Version 7.20
```ini
* Reduced detectability
* Optimized locking mechanism, can lock all API resources
* Fixed emulator compatibility
* Other minor modifications and fixes
```
## Version 7.15
```ini
* Support Android 14 (SDK 34)
* Fixed issue with registering monitor exceptions
* Improved remote desktop compatibility (theoretically supports all devices)
* Fixed scroll_from_bottom_to_top_to_end exception. Thanks to ThanhPham
* Fixed drag_to, long_click code errors
* Built-in OpenVPN supports userpass login
* Remote desktop supports setting up to 60 frames
* Updated DISCLAIMER.TXT
* Other minor modifications and fixes
```
## Version 5.6
```ini
* Fixed incomplete layout export issue. Thanks to ThanhPham
```
## Version 5.5
```ini
* Fixed adb push file corruption issue
* Added install_local_file interface
* Code structure optimization
```
## Version 5.3
```ini
* Support custom remote desktop login password after using certificates
* Fixed issue with some devices not supporting port reuse. Thanks to alex han
* Fixed some issues with Magisk installation script
* Fixed compatibility issues with Debian launcher
```
## Version 5.2
```ini
* Fixed issue where Selector containing False value was invalid. Thanks to ThanhPham
* When using FIRERPA, other accessibility services can be used simultaneously (only for Android >= 8.0)
```
## Version 5.0
```ini
Version 5.0 does not have a major essential difference from 3.0; it fixes a rather serious vulnerability and some minor issues. This vulnerability could under arbitrary circumstances lead to device intrusion. Some adjustments have also been made to internal service permissions, minimizing the risk of intrusion by unprivileged system users. The client now supports Python 3.11.
Note: Clients for 5.0 and 3.0 are not fully compatible, please update accordingly.
* Fixed a series of issues caused by login certificate
* Fixed Magisk module configuration reading strategy
* Remote desktop, RPC now fully support TLS
* Built-in Debian module can start Debian subsystem
* Remote desktop bug fixes and simple layout adjustments
* Internal service permissions and related directory adjustments
* Improved server stability
* Adjusted service installation method
* Proxy service nameserver supports specifying port
* Added internal storage (memory configuration) read/write interface
* and a series of updates and fixes
```
## Version 3.157
```ini
* UI inspection element highlighting
* Support system crash counting
```
## Version 3.155
```ini
* Support tab key to traverse UI elements
* Support typing English characters in remote desktop
* Support remote desktop touch
```
## Version 3.153
```ini
* Fixed screenshot failure in some scenarios
* Minor changes
```
## Version 3.152
```ini
* Minor UI style adjustments
```
## Version 3.151
```ini
* Fixed high-DPI screen casting stretching issue #41
```
## Version 3.150
```ini
* Modified logic when reloading scheduled tasks
* Fixed scapy routing issue
* Compatible with some Xiaomi devices
* Fixed Android 11 API compatibility issue (Thanks to Kate Swan)
* Support using 4G as proxy while connected to Wi-Fi
* Added some UI controls
```
## Version 3.138
```ini
* Fixed gRPC dependency issue
* Get system last toast
```
## Version 3.135
```ini
* Fixed remote desktop loading issue
* Completely fixed race condition issue in protocol
* Fixed Windows Python 3.10 compatibility issue
* Allow HTTP interface cross-site calls
* Added some missing modules
* Remote desktop added service status indicator
* Remote desktop responsive layout
* Pre-release next version
```
## Version 3.123
```ini
* Fixed incomplete retrieval of recent activities
```
## Version 3.120-1
```ini
* FIRERPA now supports itself as a proxy
* Added interface to get system recent Activity
* Fixed a race condition in protocol (maybe)
* Added some commands, removed SQLite db view
* Experimental H.264 screen casting
```
## Version 3.108
```ini
* Optimized network disconnection handling logic
* Added Redroid (remote android) support
* Partially compatible with uiautomator2
* Support folder upload
```
## Version 3.102
```ini
* Fixed file descriptor leak issue
* Support loading service configuration from remote file server
* Now uploaded armeabi-v7a server
* Fixed root certificate installation failure under Magisk
* Fixed configuration parsing error
* Minor UI adjustments
```
## Version 3.98
```ini
* Added crontab, vi commands
```
## Version 3.95
```ini
* Fixed existing issues in build process
* Minor changes
```
## Version 3.93
```ini
* Added Android constant definitions
```
## Version 3.90
```ini
* Removed unused libraries, reduced size
* Removed command line history feature incompatible with macOS in client
* Updated DISCLAIMER.TXT
* Updated some dependency library versions
```
## Version 3.83
```ini
* Support WSA #24 @aimardcr
* Fixed black screen on note7pro MIUI10 @yu1chaofan
* Minor changes
```
## Version 3.80
```ini
* Fixed SSH disconnection issue
* Reduced package size
```
## Version 3.78
```ini
* Fixed #21 @yu1chaofan
* Updated frida-server
```
## Version 3.76
```ini
* Default built-in shell uses bash
* Fixed remote desktop touch unusable after network disconnection
* Fixed OpenVPN zombie process issue
* startmitm.py supports specifying ADB serial number
* Support Magisk auto-start
```
## Version 3.0.59
```ini
* All interface prompts use English
* Fixed an unauthenticated Web interface issue
* Fixed old version compatibility
```
## Version 3.0.55
```ini
* Fixed crash caused by wide character request headers
* Merged mitmweb into startmitm process
* Minor Docker image modification
* Support layout inspection
```
## Version 3.0.50
```ini
* Support child, sibling selectors
```
## Version 3.0.48
```ini
* Installation-free Windows startmitm command
* Support upload/download files to/from memory
* Added screenshot() alias
```
## Version 3.0.47
```ini
* Simplified globalmitm, support HTTP, SOCKS5 proxy
* Enhanced WebView node finding
```
## Version 3.0.46
```ini
* Support two-finger zoom
* Simplified startmitm DNS man-in-the-middle operation
```
## Version 3.0.45
```ini
* Support custom server port (--port)
* Fixed directory index hanging when detecting file type
* globalmitm checks DNS service availability
* Fixed startmitm.py obtaining wrong network interface under multiple networks
* Client communication no longer automatically uses system proxy
```
## Version 3.0.35
```ini
* Improved built-in ADB performance
* OpenVPN service supports auth parameter (default SHA1)
* Fixed scrcpy anomaly when using built-in ADB
```
--- END OF changelog.md ---
--- DOCUMENT: tech-support.md ---
--- SOURCE: https://device-farm.com/docs/content/en/tech-support.md ---
# Technical Support
```{attention}
Before contacting support, please ensure that you have read the relevant documentation completely and in order. **Usually, we will answer any reasonable questions you may have.** If you have not followed the steps described in the documentation or have overlooked the prompts, we will ignore your inquiry. Thank you for your understanding. The FAQ section includes common issues and troubleshooting methods. Please check the [FAQ](./faq.md) section first to see if your problem is addressed there.
```
## Usage Help
If you have any questions during use, you can reach us through the following contact methods: QQ group 909327183, Telegram group [t.me/lamda_dev](https://t.me/lamda_dev).
## Other Support
If you require other types of support, including but not limited to private deployment, technical implementation solutions, consulting, or source code and other enterprise-level technical support, please contact [t.me/devicefarmdev](https://t.me/devicefarmdev).
## Issues and Suggestions
You can submit issues or suggestions on the project's [issues](https://github.com/firerpa/lamda/issues/new) page. For non-public suggestions, you can send them to our [email](mailto:lamda.dev@gmail.com). Your suggestions are our motivation for improvement, and we look forward to hearing from you.
--- END OF tech-support.md ---
--- DOCUMENT: privacy.md ---
--- SOURCE: https://device-farm.com/docs/content/en/privacy.md ---
# Privacy Statement
We deeply understand the importance of privacy to you and are committed to strictly complying with and protecting the security of your information. The purpose of this statement is to transparently and openly explain that, to ensure the normal operation of the service, we will collect limited, non-personally identifiable device information under the principle of minimization. **We do not collect any of your personal privacy information, such as contacts, SMS, geolocation, photo album contents, or any irrelevant data.** All related information is communicated through our domains device-farm.com, device-farm.net, and lamda.run, along with their subdomains, and will not use any other domain or establish any data connection unrelated to the following purposes.
The information we collect is only used for the technical purposes explicitly listed below, and the relevant information does not possess direct or indirect identifiability.
## Information Collection and Use
The information we collect is limited to data necessary for service authorization and maintenance. All fields and their purposes are shown below.
### 1. Authorization Verification
This process runs each time the service starts to verify the legitimacy of a commercial license; it is a necessary prerequisite for service operation.
| Collected Field | Field Description | Purpose |
| :--- | :--- | :--- |
| `device_id` | Internal device identifier | Binds the license to a specific device |
| `license` | License information | Verifies license status and validity |
| `abi` | System architecture (e.g., arm64-v8a) | Ensures service compatibility with the device architecture |
| `device` | Device brand / model | Used for troubleshooting and compatibility analysis |
| `platform` | Device platform (e.g., Android) | Basic environment identifier |
| `android_id` | System Android ID | Auxiliary device identifier for license binding and statistics |
| `serialno` | Device serial number | Auxiliary device identifier for precise license binding |
| `sdk` | Android system version number (API Level) | Ensures service compatibility with the system version |
| `version` | Current service version number | Used for license logic and version matching |
### 2. Version Check
This feature is disabled by default and requires you to manually enable it in the configuration. When enabled, it checks every 48 hours whether a new version is available.
| Collected Field | Field Description | Purpose |
| :--- | :--- | :--- |
| `device_id` | Internal device identifier | Counts user distribution across versions |
| `commit` | Service version Commit ID | Precisely compares the current version with the latest version |
| `channel` | Update channel (e.g., stable / beta) | Provides update packages for the corresponding channel |
| `date` | Service version release date | Determines whether a version is newer or older |
| `abi` | System architecture | Provides update packages for the corresponding architecture |
| `device` | Device brand / model | Compatibility analysis and troubleshooting |
| `platform` | Device platform | Basic environment identifier |
| `android_id` | System Android ID | Auxiliary statistics on update coverage |
| `sdk` | Android system version number | Ensures the update package is compatible with the system |
| `version` | Current service version number | Version comparison |
| `up` | Service startup time | Calculates uptime for auxiliary judgment |
### 3. System Notification
Used to receive remote notification messages from other platforms (e.g., displayed on a remote desktop); synchronizes every 24 hours.
| Collected Field | Field Description | Purpose |
| :--- | :--- | :--- |
| `device_id` | Internal device identifier | Identifies the target device to receive the notification |
| `version` | Service version number | Ensures notification format compatibility |
| `abi` | System architecture | Auxiliary field |
| `sdk` | Android system version number | Auxiliary field |
## Data Storage and Security
1. **Network transmission**: All transmitted data is encrypted with strong encryption to prevent interception or tampering during transit.
2. **Service processing**: The data we receive is used for real-time logical processing (such as license verification, version comparison, notification checking).
3. **Data retention**: We will store information such as your device model and system version. This information is stored anonymously.
## Information Sharing and Disclosure
We **will never** sell, trade, or otherwise transfer any of your information (including the device information described above) to any third party. Information may be disclosed only in the following extremely limited circumstances:
* **Compliance with the law**: When required by laws, regulations, subpoenas, or court orders.
* **Protection of rights**: When necessary to enforce our terms of service or to protect the rights, property, and safety of ourselves, other users, or the public.
## Your Rights and Control
If you do not wish for us to collect this device information, we respect your choice:
* **Offline authorization**: Using offline authorization means none of the information above will be sent to our servers.
* **Ceasing use**: If you do not want us to collect any of the above information and refuse offline authorization, the only option is to stop using this service, because once the information necessary for authorization is missing, the service cannot run.
## Contact Us
If you have any questions, comments, or concerns regarding this privacy statement, please [contact us](/docs/zh/tech-support).
--- END OF privacy.md ---
--- DOCUMENT: disclaimer.md ---
--- SOURCE: https://device-farm.com/docs/content/en/disclaimer.md ---
# Disclaimer
If you wish to obtain this service, you (hereinafter referred to as the "User") must agree to all terms of this agreement and complete the entire application process according to the prompts on the page. You can find DISCLAIMER.TXT in the source code or release package, or refer to the copy below.
To download and use the LAMDA software (hereinafter referred to as "this Service") developed by firerpa (address: github.com/firerpa, email: lamda.devel@gmail.com, hereinafter referred to as the "Developer"), you must carefully read and agree to all terms of this agreement. Please ensure that you fully understand and agree to the following before downloading, installing, or using this software.
You have no right to download, install, or use this software and its related services until you have fully read and accepted the terms of this agreement. Any action to download, install, or use this software will be deemed as your acknowledgment that you have read and agreed to all terms of this agreement and are willing to be bound by them.
Risk Notice:
This service requires the device to acquire root and shell permissions to run, and the default communication protocol and related certificate files are open information, which may increase the risk of your device being compromised.
This service may have unknown logical errors that could lead to potential risks such as data loss, system crashes, etc. It is the user's decision whether to download and use this service.
1. The purpose of this service is to improve the work efficiency of security analysis and testing personnel, for use in application behavior analysis, application compliance analysis, etc. The provided tools are all designed for legitimate and compliant APP testing analysis and Mock scenarios.
This service itself does not provide any functionality to invade, modify, or capture other applications' memory and network data. It integrates services provided by various open-source frameworks for users to choose from, facilitating the work of security analysts and reducing their repetitive labor and management costs.
This service is not for profit. Users can obtain it by downloading according to their own needs, and no fees are charged during the download and use process.
2. This service respects and protects users' personal privacy and will not steal any information from user devices. The startup of this service and the operations of reading, storing, and transmitting device data are all controlled by the user.
3. Users must use this service on virtual devices or dedicated devices that contain no private data. When using this service, users must comply with the laws and regulations of the People's Republic of China or the country/region to which the user belongs.
It must not be used for any illegal purposes, nor for any behavior detrimental to others.
4. Users may only use this service for legitimate learning, research, or legally authorized application analysis, testing, and other behaviors. If a user, during the use of this software service, violates the above principles and causes losses to third parties, all responsibility shall be borne solely by the user.
5. The Developer assumes no legal liability for any accidents, negligence, breach of contract, defamation, copyright or intellectual property infringement, and resulting losses (including but not limited to direct, indirect, incidental, or derivative losses) incurred by any unit or individual as a result of downloading and using this service.
6. You may use this service for commercial purposes, but only when extending derivative functionality or product development through the functions, interfaces, or related services provided by this service. You agree not to use this service, its related services, or interfaces for any behavior that violates local laws and regulations or engages in acts that harm the interests of others.
7. The user clearly understands and agrees to all the contents listed in the terms of this agreement, and bears all risks and consequences that may arise during the use of this service. The Developer assumes no legal liability.
8. The Developer has the right to unilaterally change the terms and appendixes of this statement at any time, or to suspend or terminate part or all of this service, and to announce such changes via message push, web announcements, etc., which shall automatically take effect upon publication without separate notice. If you continue to use this service after the announcement of changes to this statement, it indicates that you have fully read, understood, and accepted the revised statement.
9. If any part of this statement is held invalid or unenforceable, that part shall be amended in a manner consistent with relevant laws to reflect the Developer's original intent as closely as possible, and the remaining parts shall remain in full force and effect. The invalidity or unenforceability of any part does not constitute a waiver of the Developer's right to enforce other parts or this statement.
10. Reserved Rights: All other rights not expressly granted are reserved by the Developer.
Please confirm that you have read and accepted all terms of this agreement; otherwise, you are not authorized to download, install, or use this software and related services.
--- END OF disclaimer.md ---
# SECTION: SOURCE CODE & PROTOS
--- MODULE: lamda | FILE: __init__.py ---
```python
# Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
#
# Distributed under MIT license.
# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
__version__ = "10.4"
```
--- MODULE: lamda.client | FILE: client.py ---
```python
# Copyright 2022 rev1si0n (https://github.com/rev1si0n). All rights reserved.
#
# Distributed under MIT license.
# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
import os
import io
import re
import sys
import copy
import time
import uuid
import json
import base64
import posixpath
import hashlib
import platform
import warnings
import builtins
import logging
import msgpack
# fix protobuf>=4.0/win32, #10158
if sys.platform == "win32":
os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python"
import grpc
import pem as Pem
import collections.abc
# fix pyreadline, py310, Windows
collections.Callable = collections.abc.Callable
from urllib.parse import quote
from collections import defaultdict
from cryptography.fernet import Fernet
from os.path import basename, dirname, expanduser, join as joinpath
from google.protobuf.json_format import MessageToDict, MessageToJson
from grpc_interceptor import ClientInterceptor
from google.protobuf.message import Message
from asn1crypto import pem, x509
try:
import frida
_frida_dma = frida.get_device_manager()
except (ImportError, AttributeError):
_frida_dma = None
from . import __version__
from . types import AttributeDict, BytesIO
from . exceptions import (UnHandledException, DuplicateEntryError,
InvalidArgumentError, UiObjectNotFoundException,
IllegalStateException, InvalidOperationError)
from . import exceptions
handler = logging.StreamHandler()
logger = logging.getLogger("lamda.client")
formatter = logging.Formatter("%(asctime)s %(process)d %(levelname)7s@%(module)s:%(funcName)s - %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)
sys.path.append(joinpath(dirname(__file__)))
sys.path.append(joinpath(dirname(__file__), "rpc"))
# use native resolver to support mDNS
os.environ["GRPC_DNS_RESOLVER"] = "native"
protos, services = grpc.protos_and_services("services.proto")
__all__ = [
"Corner",
"Direction",
"GproxyType",
"GrantType",
"Group",
"CustomOcrBackend",
"OcrEngine",
"Key",
"Keys",
"KeyCode",
"KeyCodes",
"MetaKeyCode",
"MetaKeyCodes",
"BaseCryptor",
"FernetCryptor",
"OpenVPNAuth",
"OpenVPNEncryption",
"OpenVPNKeyDirection",
"FindImageMethod",
"FindImageArea",
"ToastDuration",
"OpenVPNCipher",
"OpenVPNProto",
"Orientation",
"OpenVPNProfile",
"GproxyProfile",
"TouchBuilder",
"ScriptRuntime",
"DataEncode",
"ImePolicy",
"AudioStreamType",
"PlayAudioProfile",
"ApplicationInfo",
"Selector",
"TouchWait",
"TouchMove",
"TouchDown",
"TouchUp",
"TouchAction",
"TouchSequence",
"Point",
"Bound",
"load_proto",
"to_dict",
"Device",
"logger",
]
def getXY(p):
return p.x, p.y
def checkArgumentTyp(a, types):
if not isinstance(a, types):
raise InvalidArgumentError(a)
def touchSequenceSave(s, fpath):
return BytesIO(s.SerializeToString()).save(fpath)
def touchSequenceLoad(s, fpath):
return s.FromString(BytesIO.load(fpath).getvalue())
def touchSequenceIndexer(s, index):
return s.sequence[index]
def touchSequenceIter(s):
yield from s.sequence
def touchSequenceAppendAction(s, **kwargs):
action = TouchAction(**kwargs)
s.sequence.append(action)
def touchSequenceAppendDown(s, **kwargs):
touchSequenceAppendAction(s, down=TouchDown(**kwargs))
def touchSequenceAppendMove(s, **kwargs):
touchSequenceAppendAction(s, move=TouchMove(**kwargs))
def touchSequenceAppendWait(s, **kwargs):
touchSequenceAppendAction(s, wait=TouchWait(**kwargs))
def touchSequenceAppendUp(s, **kwargs):
touchSequenceAppendAction(s, up=TouchUp(**kwargs))
def touchActionRealAction(a):
return getattr(a, a.type)
def touchActionType(a):
return a.WhichOneof("action")
def touchMoveShiftX(a, offset):
a.x = a.x + offset
return a.x
def touchMoveShiftY(a, offset):
a.y = a.y + offset
return a.y
def touchWaitShift(w, offset):
w.wait = w.wait + offset
return w.wait
def applicationInfoSet(application, app):
application.CopyFrom(app.info())
def height(b):
return b.bottom - b.top
def width(b):
return b.right - b.left
def center(b):
x = int(b.left + (b.right - b.left)/2)
y = int(b.top + (b.bottom - b.top)/2)
return Point(x=x, y=y)
def contain(a, b):
return all([b.top >= a.top,
b.left >= a.left,
b.bottom <= a.bottom,
b.right <= a.right])
def equal(a, b):
if not isinstance(b, protos.Bound):
return False
return all([b.top == a.top,
b.left == a.left,
b.bottom == a.bottom,
b.right == a.right])
def corner(b, position):
ca, cb = position.split("-")
return Point(x=getattr(b, cb),
y=getattr(b, ca))
# enum types
Corner = protos.Corner
Direction = protos.Direction
GproxyType = protos.GproxyType
GrantType = protos.GrantType
ScriptRuntime = protos.ScriptRuntime
DataEncode = protos.DataEncode
ImePolicy = protos.ImePolicy
Group = protos.Group
Key = protos.Key
Keys = protos.Key # make an alias
KeyCode = protos.KeyCode
KeyCodes = protos.KeyCode # make an alias
MetaKeyCode = protos.MetaKeyCode
MetaKeyCodes = protos.MetaKeyCode # make an alias
OpenVPNAuth = protos.OpenVPNAuth
OpenVPNEncryption = protos.OpenVPNEncryption
OpenVPNKeyDirection = protos.OpenVPNKeyDirection
OpenVPNCipher = protos.OpenVPNCipher
OpenVPNProto = protos.OpenVPNProto
ToastDuration = protos.ToastDuration
Orientation = protos.Orientation
AudioStreamType = protos.AudioStreamType
PlayAudioProfile = protos.PlayAudioRequest
# proxy request alias
OpenVPNProfile = protos.OpenVPNConfigRequest
GproxyProfile = protos.GproxyConfigRequest
# multitouch
TouchMove = protos.TouchMove
TouchWait = protos.TouchWait
TouchDown = protos.TouchDown
TouchUp = protos.TouchUp
TouchSequence = protos.TouchSequence
TouchAction = protos.TouchAction
ApplicationInfo = protos.ApplicationInfo
# uiautomator types
_Selector = protos.Selector
Bound = protos.Bound
Point = protos.Point
Point.getXY = getXY
ApplicationInfo.set = applicationInfoSet
TouchWait.shift = touchWaitShift
TouchMove.shiftX = touchMoveShiftX
TouchMove.shiftY = touchMoveShiftY
TouchDown.shiftX = touchMoveShiftX
TouchDown.shiftY = touchMoveShiftY
TouchAction.type = property(touchActionType)
TouchAction.action = property(touchActionRealAction)
TouchSequence.load = classmethod(touchSequenceLoad)
TouchSequence.save = touchSequenceSave
TouchSequence.appendAction = touchSequenceAppendAction
TouchSequence.appendDown = touchSequenceAppendDown
TouchSequence.appendMove = touchSequenceAppendMove
TouchSequence.appendWait = touchSequenceAppendWait
TouchSequence.appendUp = touchSequenceAppendUp
TouchSequence.__getitem__ = touchSequenceIndexer
TouchSequence.__iter__ = touchSequenceIter
HookRpcRequest = protos.HookRpcRequest
HookRpcResponse = protos.HookRpcResponse
Bound.width = property(width)
Bound.height = property(height)
FindImageMethod = protos.FindImageMethod
FindImageArea = protos.FindImageArea
Bound.center = center
Bound.corner = corner
Bound.__contains__ = contain
Bound.__eq__ = equal
def load_proto(name):
"""Load related proto files from the package."""
return grpc.protos_and_services(name)
def to_dict(prot):
"""Convert a proto response to a dict."""
r = MessageToJson(prot, preserving_proto_field_name=True)
return json.loads(r)
def Selector(**kwargs):
""" Selector wrapper """
fields = set(kwargs.pop("fields", []))
fields.update(kwargs.keys())
sel = _Selector(**kwargs, fields=fields)
return sel
def child_sibling(s, name, **selector):
s = copy.deepcopy(s)
s.childOrSibling.append(name)
s.childOrSiblingSelector.append(Selector(**selector))
return s
def child(s, **selector):
return child_sibling(s, "child", **selector)
def sibling(s, **selector):
return child_sibling(s, "sibling", **selector)
# bind Selector level child sibling
_Selector.child = child
_Selector.sibling = sibling
class CustomOcrBackend(object):
def __init__(self, *args, **kwargs):
raise NotImplementedError
def ocr(self, image):
raise NotImplementedError
class BaseCryptor(object):
def encrypt(self, data):
return data
def decrypt(self, data):
return data
class BaseServiceStub(object):
def __init__(self, stub):
self.stub = stub
class FernetCryptor(BaseCryptor):
def __init__(self, key=None):
key = self._get_key(key)
self.encoder = Fernet(key)
def encrypt(self, data):
return self.encoder.encrypt(data)
def decrypt(self, data):
return self.encoder.decrypt(data)
def _get_key(self, key):
key = (key or "").encode()
key = hashlib.sha256(key).digest()
key = base64.b64encode(key)
return key
class TouchBuilder(object):
def __init__(self):
self.s = TouchSequence()
def down(self, x, y, z=128, contact=0):
self.s.appendDown(tid=contact, x=x, y=y,
pressure=z)
return self
def move(self, x, y, z=128, contact=0):
self.s.appendMove(tid=contact, x=x, y=y,
pressure=z)
return self
def up(self, contact=0):
self.s.appendUp(tid=contact)
return self
def wait(self, mills):
self.s.appendWait(wait=mills)
return self
def build(self):
sequence = TouchSequence()
sequence.CopyFrom(self.s)
return sequence
class MultiTouchContact:
def __init__(self, builder, track):
self.builder = builder
self.track = track
def down(self, x, y, z=128):
self.builder.down(x, y, z=z, contact=self.track)
return self
def move(self, x, y, z=128):
self.builder.move(x, y, z=z, contact=self.track)
return self
def wait(self, mills):
self.builder.wait(mills)
return self
def up(self):
self.builder.up(contact=self.track)
return self
class MultiTouchOpStub:
def __init__(self, caller, track=0,
builder=None):
self.stub = caller.stub
self.builder = builder or TouchBuilder()
self.track = track
def contact(self, id):
return MultiTouchContact(self.builder, id)
def wait(self, mills):
self.builder.wait(mills)
def reset(self):
self.builder.s.ClearField("sequence")
def record(self):
ts = self.stub.recordTouch(protos.Empty())
self.builder.s.CopyFrom(ts)
def load(self, fpath):
ts = self.builder.s.load(fpath)
self.builder.s.CopyFrom(ts)
def save(self, fpath):
return self.builder.s.save(fpath)
def perform(self, wait=True):
tas = self.builder.build()
req = protos.PerformTouchRequest(sequence=tas, wait=wait)
r = self.stub.performTouch(req)
return r.value
class ClientLoggingInterceptor(ClientInterceptor):
def truncate_string(self, s):
return "{:.1024}...".format(s) if len(s) > 1024 else s
def intercept(self, function, request, details):
"""
Log API calls and arguments.
"""
displayable = isinstance(request, Message)
args = MessageToDict(request) if displayable else "-"
args = json.dumps(args, ensure_ascii=False, separators=(",", ":"))
args = self.truncate_string(args)
logger.debug("rpc {} {}".format(details.method, args))
res = function(request, details)
return res
class ClientSessionMetadataInterceptor(ClientInterceptor):
def __init__(self, session):
super(ClientSessionMetadataInterceptor, self).__init__()
self.session = session
def intercept(self, function, request, details):
metadata = {}
metadata["version"] = __version__
default = (self.session, platform.node())
session, name = self.session() if callable(self.session) else default
metadata["instance"] = session
metadata["hostname"] = quote(name)
details = details._replace(metadata=metadata.items())
return function(request, details)
class GrpcRemoteExceptionInterceptor(ClientInterceptor):
def intercept(self, function, request, details):
"""
Handle remote call errors and raise local exceptions.
"""
res = function(request, details)
self.raise_remote_exception(res)
return res
def remote_exception(self, exception):
exc = json.loads(exception)
name, args = exc["name"], exc["args"]
default = lambda *p: UnHandledException(name, *p)
clazz = getattr(builtins, name, default)
clazz = getattr(exceptions, name, clazz)
return clazz(*args)
def raise_remote_exception(self, res):
metadata = dict(res.initial_metadata() or [])
exception = metadata.get("exception", None)
if exception != None:
raise self.remote_exception(exception)
class ObjectUiAutomatorOpStub:
def __init__(self, caller, selector, display):
"""
UiAutomator sub-interface that behaves like an instance.
"""
self.display = display
self._selector = selector
self.selector = Selector(**selector)
self.stub = caller.stub
self.caller = caller
def __str__(self):
selector = ", ".join(["{}={}".format(k, v) \
for k, v in self._selector.items()])
return "Object@{}: {}".format(self.display, selector)
__repr__ = __str__
def child(self, **selector):
"""
Match child nodes within the selector.
"""
selector = self.selector.child(**selector)
s = MessageToDict(selector, preserving_proto_field_name=True)
return self.__class__(self.caller, s, self.display)
def sibling(self, **selector):
"""
Match sibling nodes of the selector.
"""
selector = self.selector.sibling(**selector)
s = MessageToDict(selector, preserving_proto_field_name=True)
return self.__class__(self.caller, s, self.display)
def take_screenshot(self, quality=100):
"""
Screenshot the selected element.
"""
req = protos.SelectorTakeScreenshotRequest(display=self.display,
selector=self.selector,
quality=quality)
r = self.stub.selectorTakeScreenshot(req)
return BytesIO(r.value)
def screenshot(self, quality=100):
return self.take_screenshot(quality=quality)
def get_text(self):
"""
Get text from the selected input field.
"""
req = protos.SelectorOnlyRequest(display=self.display,
selector=self.selector)
r = self.stub.selectorGetText(req)
return r.value
def clear_text_field(self):
"""
Clear text in the selected input field.
"""
req = protos.SelectorOnlyRequest(display=self.display,
selector=self.selector)
r = self.stub.selectorClearTextField(req)
return r.value
def set_text(self, text):
"""
Fill text into the selected input field.
"""
req = protos.SelectorSetTextRequest(display=self.display,
selector=self.selector,
text=text)
r = self.stub.selectorSetText(req)
return r.value
def click(self, corner=Corner.COR_CENTER):
"""
Click the selected widget.
"""
req = protos.SelectorClickRequest(display=self.display,
selector=self.selector,
corner=corner)
r = self.stub.selectorClick(req)
return r.value
def click_exists(self, corner=Corner.COR_CENTER):
"""
Click the selected widget without raising if missing.
"""
req = protos.SelectorClickRequest(display=self.display,
selector=self.selector,
corner=corner)
r = self.stub.selectorClickExists(req)
return r.value
def long_click(self, corner=Corner.COR_CENTER, timeout=0):
"""
Long-click the selected widget.
"""
req = protos.SelectorClickRequest(display=self.display,
selector=self.selector,
corner=corner,
timeout=timeout)
r = self.stub.selectorLongClick(req)
return r.value
def exists(self):
"""
Check whether the selected widget exists.
"""
req = protos.SelectorOnlyRequest(display=self.display,
selector=self.selector)
r = self.stub.selectorExists(req)
return r.value
def info(self):
"""
Get info for the selected widget.
"""
req = protos.SelectorOnlyRequest(display=self.display,
selector=self.selector)
return self.stub.selectorObjInfo(req)
def _chain(self, **kwargs):
selector = copy.deepcopy(self._selector)
child_sibling = selector.get("childOrSiblingSelector")
target = child_sibling[-1] if child_sibling else selector
target.update(**kwargs)
fields = set(target.pop("fields", []))
fields.update(target.keys())
target["fields"] = fields
return self.caller(**selector)
def text(self, txt):
return self._chain(text=txt)
def resourceId(self, name):
return self._chain(resourceId=name)
def description(self, desc):
return self._chain(description=desc)
def packageName(self, name):
return self._chain(packageName=name)
def className(self, name):
return self._chain(className=name)
def textContains(self, needle):
return self._chain(textContains=needle)
def descriptionContains(self, needle):
return self._chain(descriptionContains=needle)
def textStartsWith(self, needle):
return self._chain(textStartsWith=needle)
def descriptionStartsWith(self, needle):
return self._chain(descriptionStartsWith=needle)
def textMatches(self, match):
return self._chain(textMatches=match)
def descriptionMatches(self, match):
return self._chain(descriptionMatches=match)
def resourceIdMatches(self, match):
return self._chain(resourceIdMatches=match)
def packageNameMatches(self, match):
return self._chain(packageNameMatches=match)
def classNameMatches(self, match):
return self._chain(classNameMatches=match)
def checkable(self, value):
return self._chain(checkable=value)
def clickable(self, value):
return self._chain(clickable=value)
def focusable(self, value):
return self._chain(focusable=value)
def scrollable(self, value):
return self._chain(scrollable=value)
def longClickable(self, value):
return self._chain(longClickable=value)
def enabled(self, value):
return self._chain(enabled=value)
def checked(self, value):
return self._chain(checked=value)
def focused(self, value):
return self._chain(focused=value)
def selected(self, value):
return self._chain(selected=value)
def resultIndex(self, idx):
return self._chain(resultIndex=idx)
def instance(self, idx):
return self._chain(instance=idx)
def index(self, idx):
return self._chain(index=idx)
def __iter__(self):
"""
Iterate over all elements matching the selector.
"""
yield from [self.resultIndex(i) for i in \
range(self.count())]
def get(self, idx):
"""
Get the Nth matching result of the selector.
"""
return self.resultIndex(idx)
def count(self):
"""
Get the number of selected widgets.
"""
req = protos.SelectorOnlyRequest(display=self.display,
selector=self.selector)
r = self.stub.selectorCount(req)
return r.value
def _set_target_Point(self, req, target):
req.point.CopyFrom(target)
def _set_target_Selector(self, req, target):
req.target.CopyFrom(target)
def drag_to(self, target, step=32):
"""
Drag the selected widget to another selector or point.
"""
checkArgumentTyp(target, (Point, _Selector))
func = "_set_target_{}".format(target.DESCRIPTOR.name)
req = protos.SelectorDragToRequest(display=self.display,
selector=self.selector,
step=step)
getattr(self, func)(req, target)
r = self.stub.selectorDragTo(req)
return r.value
def wait_for_exists(self, timeout):
"""
Wait for the selected widget to appear.
"""
req = protos.SelectorWaitRequest(display=self.display,
selector=self.selector,
timeout=timeout)
r = self.stub.selectorWaitForExists(req)
return r.value
def wait_until_gone(self, timeout):
"""
Wait for the selected widget to disappear.
"""
req = protos.SelectorWaitRequest(display=self.display,
selector=self.selector,
timeout=timeout)
r = self.stub.selectorWaitUntilGone(req)
return r.value
def swipe(self, direction=Direction.DIR_UP, step=32):
"""
Swipe on the selected element.
"""
req = protos.SelectorSwipeRequest(display=self.display,
selector=self.selector,
direction=direction,
step=step)
r = self.stub.selectorSwipe(req)
return r.value
def pinch_in(self, percent, step=16):
"""
Pinch in.
"""
req = protos.SelectorPinchRequest(display=self.display,
selector=self.selector,
percent=percent, step=step)
r = self.stub.selectorPinchIn(req)
return r.value
def pinch_out(self, percent, step=16):
"""
Pinch out.
"""
req = protos.SelectorPinchRequest(display=self.display,
selector=self.selector,
percent=percent, step=step)
r = self.stub.selectorPinchOut(req)
return r.value
def scroll_to(self, target, is_vertical=True):
"""
Scroll a scrollable view until the target selector matches.
"""
checkArgumentTyp(target, _Selector)
req = protos.SelectorScrollRequest(display=self.display,
selector=self.selector,
vertical=is_vertical,
target=target)
r = self.stub.selectorScrollTo(req)
return r.value
def _fling_forward(self, is_vertical=True):
req = protos.SelectorFlingRequest(display=self.display,
selector=self.selector,
vertical=is_vertical)
r = self.stub.selectorFlingForward(req)
return r.value
def _fling_backward(self, is_vertical=True):
req = protos.SelectorFlingRequest(display=self.display,
selector=self.selector,
vertical=is_vertical)
r = self.stub.selectorFlingBackward(req)
return r.value
def _fling_to_end(self, max_swipes, is_vertical=True):
req = protos.SelectorFlingRequest(display=self.display,
selector=self.selector,
maxSwipes=max_swipes,
vertical=is_vertical)
r = self.stub.selectorFlingToEnd(req)
return r.value
def _fling_to_beginning(self, max_swipes, is_vertical=True):
req = protos.SelectorFlingRequest(display=self.display,
selector=self.selector,
maxSwipes=max_swipes,
vertical=is_vertical)
r = self.stub.selectorFlingToBeginning(req)
return r.value
def fling_from_top_to_bottom(self):
"""
Perform one top-to-bottom reading swipe on the selected element.
"""
return self._fling_backward(is_vertical=True)
def fling_from_bottom_to_top(self):
"""
Perform one bottom-to-top reading swipe on the selected element.
"""
return self._fling_forward(is_vertical=True)
def fling_from_left_to_right(self):
"""
Perform one left-to-right reading swipe on the selected element.
"""
return self._fling_backward(is_vertical=False)
def fling_from_right_to_left(self):
"""
Perform one right-to-left reading swipe on the selected element.
"""
return self._fling_forward(is_vertical=False)
def fling_from_top_to_bottom_to_end(self, max_swipes):
"""
Swipe top-to-bottom until scrolling stops or max_swipes is reached.
"""
return self._fling_to_beginning(max_swipes, is_vertical=True)
def fling_from_bottom_to_top_to_end(self, max_swipes):
"""
Swipe bottom-to-top until scrolling stops or max_swipes is reached.
"""
return self._fling_to_end(max_swipes, is_vertical=True)
def fling_from_left_to_right_to_end(self, max_swipes):
"""
Swipe left-to-right until scrolling stops or max_swipes is reached.
"""
return self._fling_to_beginning(max_swipes, is_vertical=False)
def fling_from_right_to_left_to_end(self, max_swipes):
"""
Swipe right-to-left until scrolling stops or max_swipes is reached.
"""
return self._fling_to_end(max_swipes, is_vertical=False)
def _scroll_forward(self, step, is_vertical=True):
req = protos.SelectorScrollRequest(display=self.display,
selector=self.selector,
vertical=is_vertical,
step=step)
r = self.stub.selectorScrollForward(req)
return r.value
def _scroll_backward(self, step, is_vertical=True):
req = protos.SelectorScrollRequest(display=self.display,
selector=self.selector,
vertical=is_vertical,
step=step)
r = self.stub.selectorScrollBackward(req)
return r.value
def _scroll_to_end(self, max_swipes, step, is_vertical=True):
req = protos.SelectorScrollRequest(display=self.display,
selector=self.selector,
maxSwipes=max_swipes,
vertical=is_vertical,
step=step)
r = self.stub.selectorScrollToEnd(req)
return r.value
def _scroll_to_beginning(self, max_swipes, step, is_vertical=True):
req = protos.SelectorScrollRequest(display=self.display,
selector=self.selector,
maxSwipes=max_swipes,
vertical=is_vertical,
step=step)
r = self.stub.selectorScrollToBeginning(req)
return r.value
def scroll_from_top_to_bottom(self, step):
"""
Perform a normal top-to-bottom swipe on the selected element.
"""
return self._scroll_backward(step, is_vertical=True)
def scroll_from_bottom_to_top(self, step):
"""
Perform a normal bottom-to-top swipe on the selected element.
"""
return self._scroll_forward(step, is_vertical=True)
def scroll_from_left_to_right(self, step):
"""
Perform a normal left-to-right swipe on the selected element.
"""
return self._scroll_backward(step, is_vertical=False)
def scroll_from_right_to_left(self, step):
"""
Perform a normal right-to-left swipe on the selected element.
"""
return self._scroll_forward(step, is_vertical=False)
def scroll_from_top_to_bottom_to_end(self, max_swipes, step):
"""
Repeat normal top-to-bottom swipes until scrolling stops or max_swipes is reached.
"""
return self._scroll_to_beginning(max_swipes, step, is_vertical=True)
def scroll_from_bottom_to_top_to_end(self, max_swipes, step):
"""
Repeat normal bottom-to-top swipes until scrolling stops or max_swipes is reached.
"""
return self._scroll_to_end(max_swipes, step, is_vertical=True)
def scroll_from_left_to_right_to_end(self, max_swipes, step):
"""
Repeat normal left-to-right swipes until scrolling stops or max_swipes is reached.
"""
return self._scroll_to_beginning(max_swipes, step, is_vertical=False)
def scroll_from_right_to_left_to_end(self, max_swipes, step):
"""
Repeat normal right-to-left swipes until scrolling stops or max_swipes is reached.
"""
return self._scroll_to_end(max_swipes, step, is_vertical=False)
class UiAutomatorStub(BaseServiceStub):
def __init__(self, *args, display=0, **kwargs):
self.display = display
super(UiAutomatorStub, self).__init__(*args, **kwargs)
def device_info(self):
"""
Get device and display info.
"""
r = self.stub.deviceInfo(protos.Empty())
return r
def set_watcher_loop_enabled(self, enabled):
"""
Enable or disable watcher UI checks on the device.
"""
req = protos.WatcherControlRequest(display=self.display,
enable=enabled)
r = self.stub.setWatcherLoopEnabled(req)
return r.value
def get_watcher_loop_enabled(self):
"""
Check whether watcher UI checks are enabled.
"""
req = protos.WatcherControlRequest(display=self.display)
r = self.stub.getWatcherLoopEnabled(req)
return r.value
def get_watcher_triggered_count(self, name):
"""
Get how many times this watcher was triggered.
"""
req = protos.WatcherControlRequest(display=self.display,
name=name)
r = self.stub.getWatcherTriggeredCount(req)
return r.value
def reset_watcher_triggered_count(self, name):
"""
Reset this watcher's trigger count to 0.
"""
req = protos.WatcherControlRequest(display=self.display,
name=name)
r = self.stub.resetWatcherTriggeredCount(req)
return r.value
def get_enabled_watchers(self):
"""
Get enabled watchers applied on the system.
"""
req = protos.WatcherControlRequest(display=self.display)
return self.stub.getEnabledWatchers(req).watchers
def get_watchers(self):
"""
Get registered watchers applied on the system.
"""
req = protos.WatcherControlRequest(display=self.display)
return self.stub.getWatchers(req).watchers
def remove_all_watchers(self):
req = protos.WatcherControlRequest(display=self.display)
r = self.stub.removeAllWatchers(req)
return r.value
def register_click_target_selector_watcher(self, name, conditions,
target):
"""
Register a watcher that clicks a selector when matched.
"""
req = protos.WatcherControlRequest(display=self.display,
name=name, selectors=conditions,
target=target)
r = self.stub.registerClickUiObjectWatcher(req)
return r.value
def register_press_key_watcher(self, name, conditions, key):
"""
Register a watcher that presses a key when matched.
"""
req = protos.WatcherControlRequest(display=self.display,
name=name, selectors=conditions,
key=key)
r = self.stub.registerPressKeysWatcher(req)
return r.value
def register_none_op_watcher(self, name, conditions):
"""
Register a watcher that does nothing when matched.
"""
req = protos.WatcherControlRequest(display=self.display,
name=name, selectors=conditions)
r = self.stub.registerNoneOpWatcher(req)
return r.value
def set_watcher_enabled(self, name, enable):
"""
Enable or disable this watcher.
"""
req = protos.WatcherControlRequest(display=self.display,
name=name, enable=enable)
r = self.stub.setWatcherEnable(req)
return r.value
def get_watcher_enabled(self, name):
"""
Check whether this watcher is enabled.
"""
req = protos.WatcherControlRequest(display=self.display,
name=name)
r = self.stub.getWatcherEnable(req)
return r.value
def get_last_toast(self):
"""
Get the last toast message.
"""
r = self.stub.getLastToast(protos.Empty())
return r
def remove_watcher(self, name):
"""
Remove a watcher.
"""
req = protos.WatcherControlRequest(display=self.display,
name=name)
r = self.stub.removeWatcher(req)
return r.value
def long_click(self, point, timeout=0):
req = protos.ClickPointRequest(display=self.display,
point=point,
timeout=timeout)
r = self.stub.pointLongClick(req)
return r.value
def click(self, point):
"""
Click a point on the screen.
"""
req = protos.ClickPointRequest(display=self.display,
point=point)
r = self.stub.click(req)
return r.value
def drag(self, A, B, step=32):
"""
Drag from point A to point B.
"""
req = protos.DragPointRequest(display=self.display,
A=A, B=B, step=step)
r = self.stub.drag(req)
return r.value
def swipe(self, A, B, step=32):
"""
Swipe from point A to point B.
"""
req = protos.SwipePointRequest(display=self.display,
A=A, B=B, step=step)
r = self.stub.swipe(req)
return r.value
def swipe_points(self, *points, step=32):
"""
Swipe across a sequence of points.
"""
req = protos.SwipePointsRequest(display=self.display,
points=points, step=step)
r = self.stub.swipePoints(req)
return r.value
def open_notification(self):
"""
Open the notification shade.
"""
r = self.stub.openNotification(protos.Empty())
return r.value
def open_quick_settings(self):
"""
Open the quick settings shade.
"""
r = self.stub.openQuickSettings(protos.Empty())
return r.value
def wake_up(self):
"""
Wake the device.
"""
r = self.stub.wakeUp(protos.Empty())
return r.value
def sleep(self):
"""
Sleep the device.
"""
r = self.stub.sleep(protos.Empty())
return r.value
def is_screen_on(self):
"""
Check whether the device is awake.
"""
r = self.stub.isScreenOn(protos.Empty())
return r.value
def is_screen_locked(self):
"""
Check whether the screen is locked.
"""
r = self.stub.isScreenLocked(protos.Empty())
return r.value
def set_clipboard(self, text):
"""
Set clipboard text.
"""
req = protos.ClipboardRequest(ID=str(uuid.uuid4()), value=text)
r = self.stub.setClipboard(req)
return r.value
def get_clipboard(self):
"""
Get clipboard text before Android 10.
"""
r = self.stub.getClipboard(protos.Empty())
return r.value
def _set_target_Area(self, req, area):
req.area = area
def _set_target_Bound(self, req, bound):
req.bound.CopyFrom(bound)
def find_similar_image(self, data, threshold=0.0, distance=250,
scale=1.0, area=FindImageArea.FIA_WHOLE_SCREEN,
method=FindImageMethod.FIM_TEMPLATE):
"""
Find similar image positions on screen from a target image.
"""
req = protos.FindImageRequest()
checkArgumentTyp(area, (Bound, int))
name = getattr(getattr(area, "DESCRIPTOR", None),
"name", "Area")
func = "_set_target_{}".format(name)
getattr(self, func)(req, area)
req.method = method
req.display = self.display
req.distance = distance
req.threshold = threshold
req.scale = scale
req.partial = data
r = self.stub.findSimilarImage(req)
return r.bounds
def freeze_rotation(self, freeze=True):
"""
Lock screen rotation.
"""
req = protos.FreezeRotationRequest(freeze=freeze,
display=self.display)
r = self.stub.freezeRotation(req)
return r.value
def set_orientation(self, orien=Orientation.ORIEN_NATURE):
"""
Set screen rotation.
"""
req = protos.OrientationRequest(orientation=orien,
display=self.display)
r = self.stub.setOrientation(req)
return r.value
def press_key(self, key):
"""
Press a hardware key such as HOME, VOLUME, or BACK.
"""
req = protos.PressKeyRequest(display=self.display, key=key)
r = self.stub.pressKey(req)
return r.value
def press_keycode(self, code, meta=0):
"""
Press an undefined key by integer keycode.
ref: https://developer.android.com/reference/android/view/KeyEvent
"""
req = protos.PressKeyRequest(display=self.display,
code=code, meta=meta)
r = self.stub.pressKeyCode(req)
return r.value
def take_screenshot(self, quality, bound=None):
"""
Capture a full-screen screenshot.
"""
req = protos.TakeScreenshotRequest(display=self.display,
quality=quality,
bound=bound)
r = self.stub.takeScreenshot(req)
return BytesIO(r.value)
def screenshot(self, quality, bound=None):
return self.take_screenshot(quality, bound=bound)
def dump_window_hierarchy(self, compressed=False):
"""
Get the current UI layout XML.
"""
req = protos.DumpWindowHierarchyRequest(display=self.display,
compressed=compressed)
r = self.stub.dumpWindowHierarchy(req)
return BytesIO(r.value)
def wait_for_idle(self, timeout):
"""
Wait until the current screen is idle.
"""
r = self.stub.waitForIdle(protos.Integer(value=timeout))
return r.value
def __call__(self, **kwargs):
return ObjectUiAutomatorOpStub(self, kwargs,
self.display)
class VirtualDisplayStub(UiAutomatorStub):
def __init__(self, *args, display=0, device=None, **kwargs):
self._warning_global = True
super(VirtualDisplayStub, self).__init__(*args,
display=display, **kwargs)
self.device = device
def _create_virtual_display(self, width=None, height=None,
densityDpi=None, name=None):
default = self.stub.getDisplayInfo(protos.Integer(value=0))
req = protos.CreateVirtualDisplayRequest(densityDpi=densityDpi or default.densityDpi,
width=width or default.width,
height=height or default.height)
req.name = name or uuid.uuid4().hex[::6]
return self.stub.createVirtualDisplay(req).value
def _list_virtual_displays(self):
return self.stub.listAllDisplays(protos.Empty()).displays
def _release_virtual_display(self, display):
req = protos.Integer(value=display)
return self.stub.releaseVirtualDisplay(req).value
def disable_global_method_warning(self):
self._warning_global = False
# Application compat
def enumerate_installed_apps(self, user=0):
self._warning_global_use("enumerate_installed_apps")
return self.device.proxy("Application", display=self.display).enumerate_installed_apps(user=user)
def enumerate_running_processes(self):
self._warning_global_use("enumerate_running_processes")
return self.device.proxy("Application", display=self.display).enumerate_running_processes()
def current_application(self):
return self.device.proxy("Application", display=self.display).current_application()
def start_activity(self, **activity):
return self.device.proxy("Application", display=self.display).start_activity(**activity)
def get_application_by_name(self, name):
return self.device.proxy("Application", display=self.display).get_application_by_name(name)
def application(self, applicationId, user=0):
return self.device.proxy("Application", display=self.display)(
applicationId, user=user)
# Display misc
def release_virtual_display(self):
return self._release_virtual_display(self.display)
def get_display_info(self):
return self.stub.getDisplayInfo(protos.Integer(value=self.display))
def set_display_ime_policy(self, policy):
req = protos.ImePolicyRequest(display=self.display, policy=policy)
return self.stub.setDisplayImePolicy(req).value
def get_display_ime_policy(self):
req = protos.Integer(value=self.display)
return self.stub.getDisplayImePolicy(req).value
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
self._release_virtual_display(self.display)
def ocr(self, index=0, **kwargs):
return self.device.ocr(index=index, display=self.display,
**kwargs)
# Global-effect method overriding
def device_info(self):
self._warning_global_use("device_info")
return super(VirtualDisplayStub, self).device_info()
def get_last_toast(self):
self._warning_global_use("get_last_toast")
return super(VirtualDisplayStub, self).get_last_toast()
def open_notification(self):
self._warning_global_use("open_notification")
return super(VirtualDisplayStub, self).open_notification()
def open_quick_settings(self):
self._warning_global_use("open_quick_settings")
return super(VirtualDisplayStub, self).open_quick_settings()
def wake_up(self):
self._warning_global_use("wake_up")
return super(VirtualDisplayStub, self).wake_up()
def sleep(self):
self._warning_global_use("sleep")
return super(VirtualDisplayStub, self).sleep()
def is_screen_on(self):
self._warning_global_use("is_screen_on")
return super(VirtualDisplayStub, self).is_screen_on()
def is_screen_locked(self):
self._warning_global_use("is_screen_locked")
return super(VirtualDisplayStub, self).is_screen_locked()
def set_clipboard(self, text):
self._warning_global_use("set_clipboard")
return super(VirtualDisplayStub, self).set_clipboard(text)
def get_clipboard(self):
self._warning_global_use("get_clipboard")
return super(VirtualDisplayStub, self).get_clipboard()
def wait_for_idle(self, timeout):
self._warning_global_use("wait_for_idle")
return super(VirtualDisplayStub, self).wait_for_idle(timeout)
def _warning_global_use(self, method):
if self._warning_global:
logger.warning(f"Method '{method}' cannot be applied specifically to a virtual screen "
"as it has a global effect. Please use the corresponding global method, or call "\
"disable_global_method_warning() to suppress this warning.")
class AppScriptRpcInterface(object):
def __init__(self, stub, application,
name):
self.application = application
self.stub = stub
self.name = name
def __str__(self):
return "{}:Script:{}".format(self.application,
self.name)
__repr__ = __str__
def __call__(self, *args):
call_args = dict()
call_args["method"] = self.name
call_args["args"] = args
req = HookRpcRequest()
req.package = self.application.applicationId
req.user = self.application.user
req.callinfo = json.dumps(call_args)
result = self.stub.callScript(req)
data = json.loads(result.callresult)
return data
class ApplicationOpStub:
def __init__(self, stub, applicationId, user=0, display=0):
"""
Application sub-interface that behaves like an instance.
"""
self.user = user
self.display = display
self.applicationId = applicationId
self.stub = stub
def __str__(self):
return "Application:{}:{}@{}".format(self.applicationId,
self.user, self.display)
__repr__ = __str__
def is_foreground(self):
"""
Check whether the app is in the foreground.
"""
req = protos.ApplicationRequest(name=self.applicationId)
req.user = self.user
req.display = self.display
r = self.stub.isForeground(req)
return r.value
def permissions(self):
"""
Get all app permissions.
"""
req = protos.ApplicationRequest(name=self.applicationId)
req.user = self.user
r = self.stub.getPermissions(req)
return r.permissions
def grant(self, permission, mode=GrantType.GRANT_ALLOW):
"""
Grant a runtime permission to the app.
"""
req = protos.ApplicationRequest(name=self.applicationId,
permission=permission,
mode=mode)
req.user = self.user
r = self.stub.grantPermission(req)
return r.value
def revoke(self, permission):
"""
Revoke a runtime permission from the app.
"""
req = protos.ApplicationRequest(name=self.applicationId,
permission=permission)
req.user = self.user
r = self.stub.revokePermission(req)
return r.value
def query_launch_activity(self):
"""
Get launch activity info.
"""
req = protos.ApplicationRequest(name=self.applicationId)
req.user = self.user
r = self.stub.queryLaunchActivity(req)
return to_dict(r)
def is_permission_granted(self, permission):
"""
Check whether the app has a runtime permission.
"""
req = protos.ApplicationRequest(name=self.applicationId,
permission=permission)
req.user = self.user
r = self.stub.isPermissionGranted(req)
return r.value
def clear_cache(self):
"""
Clear app cache data.
"""
req = protos.ApplicationRequest(name=self.applicationId)
req.user = self.user
r = self.stub.deleteApplicationCache(req)
return r.value
def reset(self):
"""
Clear all app data.
"""
req = protos.ApplicationRequest(name=self.applicationId)
req.user = self.user
r = self.stub.resetApplicationData(req)
return r.value
def start(self):
"""
Start the app.
"""
req = protos.ApplicationRequest(name=self.applicationId)
req.user = self.user
req.display = self.display
r = self.stub.startApplication(req)
return r.value
def stop(self):
"""
Stop the app.
"""
req = protos.ApplicationRequest(name=self.applicationId)
req.user = self.user
r = self.stub.stopApplication(req)
return r.value
def info(self):
"""
Get app info.
"""
req = protos.ApplicationRequest(name=self.applicationId)
req.user = self.user
r = self.stub.applicationInfo(req)
return r
def uninstall(self):
"""
Uninstall the app (always returns true).
"""
req = protos.ApplicationRequest(name=self.applicationId)
req.user = self.user
r = self.stub.uninstallApplication(req)
return r.value
def enable(self):
"""
Enable the app.
"""
req = protos.ApplicationRequest(name=self.applicationId)
req.user = self.user
r = self.stub.enableApplication(req)
return r.value
def disable(self):
"""
Disable the app.
"""
req = protos.ApplicationRequest(name=self.applicationId)
req.user = self.user
r = self.stub.disableApplication(req)
return r.value
def is_installed(self):
"""
Check whether the app is installed.
"""
req = protos.ApplicationRequest(name=self.applicationId)
req.user = self.user
r = self.stub.isInstalled(req)
return r.value
def attach_script(self, script, runtime=ScriptRuntime.RUNTIME_QJS,
emit="",
process=None,
encode=DataEncode.DATA_ENCODE_NONE,
spawn=False,
standup=5):
"""
Inject a persistent hook script into the app.
"""
s = isinstance(script, str)
script = script.encode() if s else script
req = protos.HookRequest()
req.package = self.applicationId
req.script = script
req.runtime = runtime
req.standup = standup
req.spawn = spawn
req.destination = emit
req.encode = encode
req.user = self.user
req.process = process or ""
r = self.stub.attachScript(req)
return r.value
def detach_script(self):
"""
Remove the injected hook script.
"""
req = protos.HookRequest()
req.package = self.applicationId
req.user = self.user
r = self.stub.detachScript(req)
return r.value
def is_attached_script(self):
"""
Check whether a hook script is injected into this app.
"""
req = protos.HookRequest()
req.package = self.applicationId
req.user = self.user
r = self.stub.isScriptAttached(req)
return r.value
def is_script_alive(self):
"""
Check whether the hook script in this app is healthy.
"""
req = protos.HookRequest()
req.package = self.applicationId
req.user = self.user
r = self.stub.isScriptAlive(req)
return r.value
def __getattr__(self, name):
"""
Call an exported method from the injected hook script.
"""
return AppScriptRpcInterface(self.stub, self,
name)
class ApplicationInstallSession(object):
def __init__(self, device, session, tmpdir=None):
self.tmpdir = tmpdir or "/data/local/tmp"
self.stub = device.proxy("Application").stub
self.device = device
self.session = session
def _write(self, path, name=None, delete=False):
req = protos.InstallSessionWriteRequest(session=self.session,
path=path, name=name,
delete=delete)
return self.stub.installSessionWrite(req)
def write(self, path, name=None):
suffix = uuid.uuid4().hex[::4]
dest = posixpath.join(self.tmpdir, "{}_{}.apk".format(
self.session, suffix))
info = self.device.upload_file(path, dest)
self.device.file_chmod(info.path, mode=0o777)
return self._write(info.path, name, True)
def commit(self, wait=True, timeout=0):
req = protos.InstallSessionCommitRequest(session=self.session,
wait=wait, timeout=timeout)
return self.stub.installSessionCommit(req)
def abandon(self):
req = protos.InstallSessionAbandonRequest(session=self.session)
return self.stub.installSessionAbandon(req).value
def status(self):
req = protos.InstallSessionQueryRequest(session=self.session)
return self.stub.installSessionQuery(req)
class ApplicationStub(BaseServiceStub):
def __init__(self, *args, display=0, device=None, **kwargs):
super(ApplicationStub, self).__init__(*args, **kwargs)
self.display = display
self.device = device
def current_application(self):
"""
Get the current foreground app info.
"""
req = protos.Integer(value=self.display)
top = self.stub.currentApplication(req)
app = self.__call__(top.packageName, user=top.user)
app.activity = top.activity
return app
def get_application_by_name(self, name, user=0):
req = protos.String(value=name)
r = self.stub.getIdentifierByLabel(req)
app = self.__call__(r.value, user=user)
return app
def enumerate_running_processes(self):
"""
List all running Android app processes.
"""
r = self.stub.enumerateRunningProcesses(protos.Empty())
return r.processes
def enumerate_installed_apps(self, user=0):
"""
List application IDs of all installed apps.
"""
req = protos.Integer(value=user)
r = self.stub.enumerateInstalledApps(req)
return r.applications
def start_activity(self, **activity):
"""
Start an activity (always returns True).
"""
activity.setdefault("extras", {})
extras = activity.pop("extras")
req = protos.ApplicationActivityRequest(**activity)
req.extras.update(extras)
req.display = self.display
r = self.stub.startActivity(req)
return r.value
def _create_install_session(self, user=0, size_bytes=0, package=None,
installer_package_name=None, dont_kill_app=False,
replace_existing=True, allow_test=False, request_downgrade=False,
grant_runtime_permissions=False, tmpdir=None):
req = protos.InstallSessionCreateRequest(user=user)
req.sizeBytes = size_bytes
req.dontKillApp = dont_kill_app
req.requestDowngrade = request_downgrade
req.grantRuntimePermissions = grant_runtime_permissions
req.replaceExisting = replace_existing
req.allowTest = allow_test
req.installerPackageName = installer_package_name or ""
req.package = package or ""
info = self.stub.installSessionCreate(req)
params = dict(device=self.device, session=info.session,
tmpdir=tmpdir)
return ApplicationInstallSession(**params)
def __call__(self, applicationId, user=0):
return ApplicationOpStub(self.stub, applicationId,
user=user, display=self.display)
class StorageOpStub:
# Helpers for container value serialization.
def _decrypt(self, data):
return self.cryptor.decrypt(data)
def _encrypt(self, data):
return self.cryptor.encrypt(data)
def _unpack(self, value):
return msgpack.loads(self._decrypt(value))
def _pack(self, value):
return self._encrypt(msgpack.dumps(value))
# This interface may not be portable across languages.
def __init__(self, stub, name, cryptor=None):
self.cryptor = cryptor
self.name = name
self.stub = stub
def delete(self, key):
"""
Delete a key.
"""
req = protos.StorageRequest(key=key)
req.container = self.name
res = self.stub.delete(req)
return res.value
def exists(self, key):
"""
Check whether a key exists.
"""
req = protos.StorageRequest(key=key)
req.container = self.name
res = self.stub.exists(req)
return res.value
def get(self, key, default=None):
"""
Get the value for a key.
"""
req = protos.StorageRequest(key=key)
req.container = self.name
val = self.stub.get(req).value
res = self._unpack(val) if val else default
return res
def set(self, key, value):
"""
Set the value for a key.
"""
value = self._pack(value)
req = protos.StorageRequest(key=key, value=value)
req.container = self.name
res = self.stub.set(req)
return res.value
def setex(self, key, value, ttl):
"""
Set a key value and expire it after TTL seconds.
"""
value = self._pack(value)
req = protos.StorageRequest(key=key, value=value)
req.container = self.name
req.ttl = ttl
res = self.stub.setex(req)
return res.value
def setnx(self, key, value):
"""
Set the value for a key only if it does not exist.
"""
value = self._pack(value)
req = protos.StorageRequest(key=key, value=value)
req.container = self.name
res = self.stub.setnx(req)
return res.value
def expire(self, key, ttl):
"""
Set a key to expire after TTL seconds.
"""
req = protos.StorageRequest(key=key, ttl=ttl)
req.container = self.name
res = self.stub.expire(req)
return res.value
def ttl(self, key):
"""
Get the TTL for a key.
"""
req = protos.StorageRequest(key=key)
req.container = self.name
res = self.stub.ttl(req)
return res.value
class StorageStub(BaseServiceStub):
def clear(self):
"""
Delete all storage containers.
"""
r = self.stub.clearAll(protos.Empty())
return r.value
def use(self, name, cryptor=BaseCryptor, **kwargs):
"""
Use a storage container.
"""
return StorageOpStub(self.stub, name, cryptor(**kwargs))
def remove(self, name):
"""
Delete a storage container.
"""
req = protos.String(value=name)
r = self.stub.clearContainer(req)
return r.value
class UtilStub(BaseServiceStub):
def _get_file_content(self, certfile):
with open(certfile, "rb") as fd:
return fd.read()
def is_ca_certificate_installed(self, certfile):
"""
Install a system certificate for MITM.
"""
data = self._get_file_content(certfile)
req = protos.CertifiRequest(cert=data)
r = self.stub.isCACertificateInstalled(req)
return r.value
def install_ca_certificate(self, certfile):
"""
Install a system certificate for MITM.
"""
data = self._get_file_content(certfile)
req = protos.CertifiRequest(cert=data)
r = self.stub.installCACertificate(req)
return r.value
def uninstall_ca_certificate(self, certfile):
"""
Remove a system certificate used for MITM.
"""
data = self._get_file_content(certfile)
req = protos.CertifiRequest(cert=data)
r = self.stub.uninstallCACertificate(req)
return r.value
def reboot(self):
"""
Reboot the host device.
"""
r = self.stub.reboot(protos.Empty())
return r.value
def shutdown(self):
"""
Shut down the host device.
"""
r = self.stub.shutdown(protos.Empty())
return r.value
def reload(self, clean=False):
"""
Reload the server running on the device.
"""
req = protos.Boolean(value=clean)
r = self.stub.reload(req)
return r.value
def exit(self):
"""
Exit the server running on the device.
"""
r = self.stub.exit(protos.Empty())
return r.value
def beep(self):
"""
Play a beep to help locate the device.
"""
r = self.stub.beepBeep(protos.Empty())
return r.value
def play_audio(self, file, type=AudioStreamType.AST_SYSTEM,
loop=1, interval=0):
"""
Play a WAV file.
"""
profile = PlayAudioProfile()
profile.file = file
profile.type = type
profile.loop = loop
profile.interval = interval
r = self.stub.playAudio(profile)
return r.value
def show_toast(self, text, duration=ToastDuration.TD_SHORT):
"""
Show a toast message at the bottom of the screen.
"""
req = protos.ShowToastRequest(text=text, duration=duration)
r = self.stub.showToast(req)
return r.value
def setprop(self, name, value):
"""
Set a system property, including read-only ro.xx values.
"""
req = protos.SetPropRequest(name=name, value=value)
r = self.stub.setProp(req)
return r.value
def getprop(self, name):
"""
Get a system property.
"""
req = protos.String(value=name)
r = self.stub.getProp(req)
return r.value
def server_info(self):
"""
Get server ID, version, and related info.
"""
r = self.stub.serverInfo(protos.Empty())
return r
def hex_patch(self, pattern, replacement, path,
maxreplace=-1,
dryrun=False):
"""
Replace bytes in a file on the device.
"""
req = protos.HexPatchRequest()
req.pattern = pattern
req.replacement = replacement
req.path = path
req.maxreplace = maxreplace
req.dryrun = dryrun
return self.stub.hexPatch(req)
class DebugStub(BaseServiceStub):
def _read_pubkey(self, pubkey):
with open(pubkey, "rb") as fd:
return fd.read()
def install_adb_pubkey(self, pubkey):
"""
Add a public key to the built-in adb service.
"""
req = protos.ADBDConfigRequest()
req.adb_pubkey = self._read_pubkey(pubkey)
r = self.stub.installADBPubKey(req)
return r.value
def uninstall_adb_pubkey(self, pubkey):
"""
Remove a public key from the built-in adb service.
"""
req = protos.ADBDConfigRequest()
req.adb_pubkey = self._read_pubkey(pubkey)
r = self.stub.uninstallADBPubKey(req)
return r.value
def is_android_debug_bridge_running(self):
"""
Check whether the remote adb daemon is running.
"""
r = self.stub.isAndroidDebugBridgeRunning(protos.Empty())
return r.value
def start_android_debug_bridge(self):
"""
Start the built-in adbd.
"""
r = self.stub.startAndroidDebugBridge(protos.Empty())
return r.value
def stop_android_debug_bridge(self):
"""
Stop the built-in adb daemon.
"""
r = self.stub.stopAndroidDebugBridge(protos.Empty())
return r.value
class SettingsStub(BaseServiceStub):
def _put(self, group, name, value):
req = protos.SettingsRequest(group=group, name=name,
value=value)
r = self.stub.putSettings(req)
return r.value
def _get(self, group, name):
req = protos.SettingsRequest(group=group,name=name)
r = self.stub.getSettings(req)
return r.value
def get_system(self, name):
"""
Equivalent to settings get system xxxx.
"""
return self._get(Group.GROUP_SYSTEM, name)
def put_system(self, name, value):
"""
Equivalent to settings put system xxxx xxxx.
"""
return self._put(Group.GROUP_SYSTEM, name, value)
def get_global(self, name):
"""
Equivalent to settings get global xxxx.
"""
return self._get(Group.GROUP_GLOBAL, name)
def put_global(self, name, value):
"""
Equivalent to settings put global xxxx xxxx.
"""
return self._put(Group.GROUP_GLOBAL, name, value)
def get_secure(self, name):
"""
Equivalent to settings get secure xxxx.
"""
return self._get(Group.GROUP_SECURE, name)
def put_secure(self, name, value):
"""
Equivalent to settings put secure xxxx xxxx.
"""
return self._put(Group.GROUP_SECURE, name, value)
class ShellStub(BaseServiceStub):
def execute_script(self, script, alias=None,
timeout=60):
"""
Run a script in the foreground.
"""
req = protos.ShellRequest(name=alias, script=script,
timeout=timeout)
r = self.stub.executeForeground(req)
return r
def execute_background_script(self, script, alias=None):
"""
Run a script in the background.
"""
req = protos.ShellRequest(name=alias, script=script)
r = self.stub.executeBackground(req)
return r.tid
def is_background_script_finished(self, tid):
"""
Check whether the background script has finished.
"""
req = protos.ShellTask(tid=tid)
r = self.stub.isBackgroundFinished(req)
return r.value
def kill_background_script(self, tid):
"""
Force-stop the background script.
"""
req = protos.ShellTask(tid=tid)
r = self.stub.killBackground(req)
return r.value
class StatusStub(BaseServiceStub):
def get_boot_time(self):
"""
Get the device boot time as a Unix timestamp.
"""
r = self.stub.getBootTime(protos.Empty())
return r.value
def get_disk_usage(self, mountpoint="/data"):
"""
Get partition usage stats.
"""
req = protos.String(value=mountpoint)
r = self.stub.getDiskUsage(req)
return r
def get_battery_info(self):
"""
Get battery info.
"""
r = self.stub.getBatteryInfo(protos.Empty())
return r
def get_cpu_info(self):
"""
Get CPU usage and related stats.
"""
r = self.stub.getCpuInfo(protos.Empty())
return r
def get_overall_disk_io_info(self):
"""
Get global disk I/O stats.
"""
r = self.stub.getOverallDiskIOInfo(protos.Empty())
return r
def get_overall_net_io_info(self):
"""
Get global network traffic stats.
"""
r = self.stub.getOverallNetIOInfo(protos.Empty())
return r
def get_userdata_disk_io_info(self):
"""
Get user-data disk I/O stats.
"""
r = self.stub.getUserDataDiskIOInfo(protos.Empty())
return r
def get_net_io_info(self, interface):
"""
Get network traffic stats for a specific interface.
"""
req = protos.String(value=interface)
r = self.stub.getNetIOInfo(req)
return r
def get_mem_info(self):
"""
Get memory stats.
"""
r = self.stub.getMemInfo(protos.Empty())
return r
class ProxyStub(BaseServiceStub):
def is_openvpn_running(self):
"""
Check whether OPENVPN is running.
"""
r = self.stub.isOpenVPNRunning(protos.Empty())
return r.value
def is_gproxy_running(self):
"""
Check whether GPROXY is running.
"""
r = self.stub.isGproxyRunning(protos.Empty())
return r.value
def start_openvpn(self, profile):
"""
Start OPENVPN.
"""
checkArgumentTyp(profile, OpenVPNProfile)
r = self.stub.startOpenVPN(profile)
return r.value
def start_gproxy(self, profile):
"""
Start GPROXY.
"""
checkArgumentTyp(profile, GproxyProfile)
r = self.stub.startGproxy(profile)
return r.value
def stop_openvpn(self):
"""
Stop OPENVPN.
"""
r = self.stub.stopOpenVPN(protos.Empty())
return r.value
def stop_gproxy(self):
"""
Stop GPROXY.
"""
r = self.stub.stopGproxy(protos.Empty())
return r.value
class SelinuxPolicyStub(BaseServiceStub):
def allow(self, source, target, tclass, action):
"""
selinux allow
"""
req = protos.SelinuxPolicyRequest(source=source, target=target,
tclass=tclass, action=action)
r = self.stub.policySetAllow(req)
return r.value
def disallow(self, source, target, tclass, action):
"""
selinux disallow
"""
req = protos.SelinuxPolicyRequest(source=source, target=target,
tclass=tclass, action=action)
r = self.stub.policySetDisallow(req)
return r.value
def get_enforce(self):
"""
Get the current SELinux enforce state.
"""
r = self.stub.getEnforce(protos.Empty())
return r.value
def set_enforce(self, enforced=True):
"""
Set the current SELinux enforce state.
"""
req = protos.Boolean(value=enforced)
r = self.stub.setEnforce(req)
return r.value
def enabled(self):
"""
Check whether SELinux is enabled on the device.
"""
r = self.stub.isEnabled(protos.Empty())
return r.value
def enforce(self, name):
"""
Set a domain to enforce.
"""
req = protos.String(value=name)
r = self.stub.policySetEnforce(req)
return r.value
def permissive(self, name):
"""
Set a domain to permissive.
"""
req = protos.String(value=name)
r = self.stub.policySetPermissive(req)
return r.value
def create_domain(self, name):
"""
Create a new SELinux domain.
"""
req = protos.String(value=name)
r = self.stub.policyCreateDomain(req)
return r.value
class FileStub(BaseServiceStub):
def _fd_stream_read(self, fd, chunksize):
for chunk in iter(lambda: fd.read(chunksize), bytes()):
yield chunk
def _fd_streaming_send(self, fd, dest, chunksize):
yield protos.FileRequest(path=dest)
for chunk in self._fd_stream_read(fd, chunksize):
yield protos.FileRequest(payload=chunk)
def _fd_streaming_recv(self, fd, iterator):
for chunk in iterator:
fd.write(chunk.payload)
def download_fd(self, fpath, fd):
"""
Download a file from the device to a file descriptor.
"""
req = protos.FileRequest(path=fpath)
iterator = self.stub.downloadFile(req)
self._fd_streaming_recv(fd, iterator)
st = self.file_stat(fpath)
return st
def upload_fd(self, fd, dest):
"""
Upload a file descriptor to the device.
"""
chunksize = 1024*1024*1
streaming = self._fd_streaming_send(fd, dest,
chunksize)
self.stub.uploadFile(streaming)
st = self.file_stat(dest)
return st
def download_file(self, fpath, dest):
"""
Download a file from the device to local storage.
"""
with io.open(dest, mode="wb") as fd:
return self.download_fd(fpath, fd)
def upload_file(self, fpath, dest):
"""
Upload a local file to the device.
"""
with io.open(fpath, mode="rb") as fd:
return self.upload_fd(fd, dest)
def delete_file(self, fpath):
"""
Delete a file on the device.
"""
req = protos.FileRequest(path=fpath)
r = self.stub.deleteFile(req)
return r.value
def file_chmod(self, fpath, mode=0o644):
"""
Change file permissions on the device.
"""
req = protos.FileRequest(path=fpath, mode=mode)
r = self.stub.fileChmod(req)
return r
def file_stat(self, fpath):
"""
Get file info on the device.
"""
req = protos.FileRequest(path=fpath)
r = self.stub.fileStat(req)
return r
class LockStub(BaseServiceStub):
def acquire_lock(self, leaseTime=60):
"""
Acquire the device control lock and raise if it is busy.
"""
req = protos.Integer(value=leaseTime)
r = self.stub.acquireLock(req)
return r.value
def get_session_token(self):
"""
Get the current session token.
"""
r = self.stub.getSessionToken(protos.Empty())
return r.value
def refresh_lock(self, leaseTime=60):
"""
Refresh the device control lock within 60 seconds to keep the session.
"""
req = protos.Integer(value=leaseTime)
r = self.stub.refreshLock(req)
return r.value
def release_lock(self):
"""
Release the device control lock.
"""
r = self.stub.releaseLock(protos.Empty())
return r.value
class WifiStub(BaseServiceStub):
def status(self):
"""
Get info for the currently connected Wi-Fi.
"""
r = self.stub.status(protos.Empty())
return r
def blacklist_add(self, bssid):
"""
Add a BSSID to the Wi-Fi blacklist.
"""
r = self.stub.blacklistAdd(protos.String(value=bssid))
return r.value
def blacklist_clear(self):
"""
Clear the Wi-Fi BSSID blacklist.
"""
r = self.stub.blacklistClear(protos.Empty())
return r.value
def blacklist_get_all(self):
"""
Get all blacklisted Wi-Fi BSSIDs.
"""
r = self.stub.blacklistAll(protos.Empty())
return r.bssids
def scan(self):
"""
Request a nearby Wi-Fi scan.
"""
r = self.stub.scan(protos.Empty())
return r.value
def scan_results(self):
"""
Get scanned nearby Wi-Fi networks.
"""
r = self.stub.scanResults(protos.Empty())
return r.stations
def get_mac_addr(self):
"""
Get the current Wi-Fi MAC address.
"""
r = self.stub.getMacAddr(protos.Empty())
return r.value
def signal_poll(self):
"""
Get current Wi-Fi signal info.
"""
r = self.stub.signalPoll(protos.Empty())
return r
def list_networks(self):
"""
List previously connected Wi-Fi networks.
"""
r = self.stub.listNetworks(protos.Empty())
return r.networks
def select_network(self, networkId):
raise NotImplementedError
def enable_network(self, networkId):
raise NotImplementedError
def disable_network(self, networkId):
raise NotImplementedError
def add_network(self):
raise NotImplementedError
def remove_network(self, networkId):
raise NotImplementedError
def set_network_config(self, networkId, name, value):
raise NotImplementedError
def get_network_config(self, networkId, name):
raise NotImplementedError
def disconnect(self):
"""
Disconnect Wi-Fi.
"""
r = self.stub.disconnect(protos.Empty())
return r.value
def reconnect(self):
"""
Reconnect Wi-Fi.
"""
r = self.stub.reconnect(protos.Empty())
return r.value
def set_config(self, name, value):
raise NotImplementedError
def set_auto_connect(self, auto=True):
raise NotImplementedError
def save_config(self):
raise NotImplementedError
class OcrOperator(object):
def __init__(self, device, elements=None,
display=0,
**kwargs):
self.elements = elements
self.index = kwargs.pop("index", 0)
self.func, self.rule = kwargs.popitem()
self.match = getattr(self, self.func)
self.automator = device.proxy("UiAutomator",
display=display)
def text(self, item):
return self.rule == item["text"]
def textMatches(self, item):
return bool(re.match(self.rule, item["text"],
re.DOTALL))
def textContains(self, item):
return self.rule in item["text"]
def find_target_item(self):
m = [e for e in self.elements \
if self.match(e)]
o = (m and len(m) > self.index) != True
return None if o else m[self.index]
def find_item_or_throw(self):
item = self.find_target_item()
msg = "OcrSelector[{}={}]".format(self.func, self.rule)
item or self.throw(UiObjectNotFoundException, msg)
return item
def find_cb(self, func, ret, *args):
item = self.find_target_item()
return func(item, *args) if item else ret
def find_or_throw_cb(self, func, *args):
item = self.find_item_or_throw()
return func(item, *args)
def throw(self, exception, *args):
raise exception(*args)
def _screenshot(self, item, quality):
return self.automator.screenshot(quality, bound=item["bound"])
def _click(self, item):
point = item["bound"].center()
return self.automator.click(point)
def __str__(self):
return "Ocr: {}={}".format(self.func, self.rule)
__repr__ = __str__
def exists(self):
"""
OCR: check whether the element exists.
"""
return bool(self.find_target_item())
def click(self):
"""
OCR: click the element or raise if missing.
"""
return self.find_or_throw_cb(self._click)
def click_exists(self):
"""
OCR: click the element without raising if missing.
"""
return self.find_cb(self._click, False)
def screenshot(self, quality=100):
"""
OCR: screenshot the element.
"""
return self.find_or_throw_cb(self._screenshot,
quality)
def take_screenshot(self, quality=100):
"""
OCR: screenshot the element.
"""
return self.screenshot(quality)
def info(self):
"""
OCR: get info for the matched element.
"""
item = self.find_item_or_throw()
return item
class OcrEngine(object):
def __init__(self, service, *args,
**kwargs):
args = list(args)
if type(service) == type:
args.insert(0, service)
service = "custom"
func = getattr(self, "init_{}".format(service))
func(*args, **kwargs)
def init_paddleocr(self, *args, **kwargs):
from paddleocr import PaddleOCR
self._service = PaddleOCR(*args, **kwargs)
self._ocr = self.ocr_paddleocr
def init_easyocr(self, *args, **kwargs):
from easyocr import Reader
self._service = Reader(*args, **kwargs)
self._ocr = self.ocr_easyocr
def init_custom(self, service, *args, **kwargs):
self._service = service(*args, **kwargs)
self._ocr = self.ocr_custom
def ocr_custom(self, image):
result = self._service.ocr(image)
return result
def ocr_paddleocr(self, image):
r = self._service.ocr(image)
n = bool(r and r[0] and type(r[0][-1])==float)
result = (r if n else r[0]) or []
output = [[n[0], n[1][0], n[1][1]] for n in result]
return output
def ocr_easyocr(self, image):
result = self._service.readtext(image)
return result
def ocr(self, screenshot):
img = screenshot.getvalue()
result = self._ocr(img) or []
output = [self.format(*n) for n in result]
return output
def format(self, box, text, confidence):
bound = Bound()
bound.left = int(min(p[0] for p in box))
bound.top = int(min(p[1] for p in box))
bound.bottom = int(max(p[1] for p in box))
bound.right = int(max(p[0] for p in box))
info = dict(text=text, confidence=confidence,
bound=bound)
return info
class Device(object):
def __init__(self, host, port=65000,
certificate=None,
session=None):
self.certificate = certificate
self.server = "{0}:{1}".format(host, port)
policy = dict()
policy["maxAttempts"] = 5
policy["retryableStatusCodes"] = ["UNAVAILABLE"]
policy["backoffMultiplier"] = 2
policy["initialBackoff"] = "0.5s"
policy["maxBackoff"] = "15s"
config = json.dumps(dict(methodConfig=[{"name": [{}],
"retryPolicy": policy,}]))
option = dict()
option["grpc.max_send_message_length"] = 64*1024*1024
option["grpc.max_receive_message_length"] = 128*1024*1024
option["grpc.keepalive_time_ms"] = 60*1000
option["grpc.keepalive_timeout_ms"] = 20*1000
option["grpc.keepalive_permit_without_calls"] = True
option["grpc.max_pings_without_data"] = 0
option["grpc.service_config"] = config
option["grpc.enable_http_proxy"] = 0
if certificate is not None:
with open(certificate, "rb") as fd:
key, crt, ca = self._parse_certdata(fd.read())
creds = grpc.ssl_channel_credentials(root_certificates=ca,
certificate_chain=crt,
private_key=key)
self._chan = grpc.secure_channel(self.server, creds,
options=(("grpc.ssl_target_name_override",
self._parse_cname(crt)),
*tuple(option.items()),))
else:
self._chan = grpc.insecure_channel(self.server,
options=(*tuple(option.items()),)
)
session = session or uuid.uuid4().hex
interceptors = [ClientSessionMetadataInterceptor(session),
GrpcRemoteExceptionInterceptor(),
ClientLoggingInterceptor()]
self._ocr = None
self._ocr_img_quality = 75
self.channel = grpc.intercept_channel(self._chan,
*interceptors)
self.session = session
@property
def frida(self):
if _frida_dma is None:
raise ModuleNotFoundError("frida")
try:
device = _frida_dma.get_device_matching(
lambda d: d.name==self.server)
# make a call to check server connectivity
device.query_system_parameters()
return device
except:
""" No-op """
kwargs = {}
if self.certificate is not None:
kwargs["certificate"] = self.certificate
if self._get_session_token():
kwargs["token"] = self._get_session_token()
try:
_frida_dma.remove_remote_device(self.server)
except frida.InvalidArgumentError:
""" No-op """
device = _frida_dma.add_remote_device(self.server,
**kwargs)
return device
def __str__(self):
return "Device@{}".format(self.server)
__repr__ = __str__
def _parse_certdata(self, data):
key, crt, ca = Pem.parse(data)
ca = ca.as_bytes()
crt = crt.as_bytes()
key = key.as_bytes()
return key, crt, ca
def _parse_cname(self, crt):
_, _, der = pem.unarmor(crt)
subject = x509.Certificate.load(der).subject
return subject.native["common_name"]
def stub(self, module):
return self.proxy(module)
def proxy(self, module, clazz=None, **kwargs):
this = sys.modules[__name__]
stub = getattr(services, "{0}Stub".format(module))(self.channel)
wrap = getattr(this, "{0}Stub".format(clazz or module))
return wrap(stub, **kwargs)
# Shortcut: File
def download_fd(self, fpath, fd):
return self.stub("File").download_fd(fpath, fd)
def upload_fd(self, fd, dest):
return self.stub("File").upload_fd(fd, dest)
def download_file(self, fpath, dest):
return self.stub("File").download_file(fpath, dest)
def upload_file(self, fpath, dest):
return self.stub("File").upload_file(fpath, dest)
def delete_file(self, fpath):
return self.stub("File").delete_file(fpath)
def file_chmod(self, fpath, mode=0o644):
return self.stub("File").file_chmod(fpath, mode=mode)
def file_stat(self, fpath):
return self.stub("File").file_stat(fpath)
# Shortcut: Application
def create_install_session(self, user=0, size_bytes=0, package=None,
installer_package_name=None, dont_kill_app=False,
replace_existing=True, allow_test=False, request_downgrade=False,
grant_runtime_permissions=False, tmpdir=None):
kwargs = dict(user=user)
kwargs["size_bytes"] = size_bytes
kwargs["package"] = package
kwargs["request_downgrade"] = request_downgrade
kwargs["grant_runtime_permissions"] = grant_runtime_permissions
kwargs["installer_package_name"] = installer_package_name
kwargs["dont_kill_app"] = dont_kill_app
kwargs["replace_existing"] = replace_existing
kwargs["allow_test"] = allow_test
kwargs["tmpdir"] = tmpdir
proxy = self.proxy("Application", device=self)
return proxy._create_install_session(**kwargs)
def current_application(self):
return self.stub("Application").current_application()
def enumerate_installed_apps(self, user=0):
return self.stub("Application").enumerate_installed_apps(user=user)
def enumerate_running_processes(self):
return self.stub("Application").enumerate_running_processes()
def start_activity(self, **activity):
return self.stub("Application").start_activity(**activity)
def get_application_by_name(self, name):
return self.stub("Application").get_application_by_name(name)
def application(self, applicationId, user=0):
return self.stub("Application")(applicationId, user=user)
# Shortcut: Util
def touch(self):
return MultiTouchOpStub(self.stub("Util"))
def show_toast(self, text, duration=ToastDuration.TD_SHORT):
return self.stub("Util").show_toast(text, duration=duration)
def is_ca_certificate_installed(self, certdata):
return self.stub("Util").is_ca_certificate_installed(certdata)
def uninstall_ca_certificate(self, certfile):
return self.stub("Util").uninstall_ca_certificate(certfile)
def install_ca_certificate(self, certfile):
return self.stub("Util").install_ca_certificate(certfile)
def reboot(self):
return self.stub("Util").reboot()
def shutdown(self):
return self.stub("Util").shutdown()
def exit(self):
return self.stub("Util").exit()
def reload(self, clean=False):
return self.stub("Util").reload(clean)
def beep(self):
return self.stub("Util").beep()
def play_audio(self, file, type=AudioStreamType.AST_SYSTEM,
loop=1, interval=0):
return self.stub("Util").play_audio(file, type=type, loop=loop,
interval=interval)
def setprop(self, name, value):
return self.stub("Util").setprop(name, value)
def getprop(self, name):
return self.stub("Util").getprop(name)
def hex_patch(self, pattern, replacement, path,
maxreplace=-1, dryrun=False):
return self.stub("Util").hex_patch(pattern, replacement, path,
maxreplace=maxreplace,
dryrun=dryrun)
# Shortcut: Debug
def install_adb_pubkey(self, pubkey):
return self.stub("Debug").install_adb_pubkey(pubkey)
def uninstall_adb_pubkey(self, pubkey):
return self.stub("Debug").uninstall_adb_pubkey(pubkey)
def start_android_debug_bridge(self):
return self.stub("Debug").start_android_debug_bridge()
def is_android_debug_bridge_running(self):
return self.stub("Debug").is_android_debug_bridge_running()
def stop_android_debug_bridge(self):
return self.stub("Debug").stop_android_debug_bridge()
# Shortcut: Proxy
def is_openvpn_running(self):
return self.stub("Proxy").is_openvpn_running()
def is_gproxy_running(self):
return self.stub("Proxy").is_gproxy_running()
def start_openvpn(self, profile):
return self.stub("Proxy").start_openvpn(profile)
def start_gproxy(self, profile):
return self.stub("Proxy").start_gproxy(profile)
def stop_openvpn(self):
return self.stub("Proxy").stop_openvpn()
def stop_gproxy(self):
return self.stub("Proxy").stop_gproxy()
# Virtual Display
def get_virtual_display_by_id(self, display):
return self.proxy("UiAutomator", clazz="VirtualDisplay",
display=display, device=self)
def create_virtual_display(self, width=None, height=None, densityDpi=None, name=None):
display = self.proxy("UiAutomator", clazz="VirtualDisplay")._create_virtual_display(
width, height, densityDpi, name=name)
return self.get_virtual_display_by_id(display)
# Shortcut: Shell
def execute_script(self, script, alias=None, timeout=60):
return self.stub("Shell").execute_script(script, alias=alias,
timeout=timeout)
def execute_background_script(self, script, alias=None):
return self.stub("Shell").execute_background_script(script, alias=alias)
def is_background_script_finished(self, tid):
return self.stub("Shell").is_background_script_finished(tid)
def kill_background_script(self, tid):
return self.stub("Shell").kill_background_script(tid)
# Shortcut: UiAutomator
def click(self, point):
return self.stub("UiAutomator").click(point)
def long_click(self, point, timeout=0):
return self.stub("UiAutomator").long_click(point, timeout=timeout)
def drag(self, A, B, step=32):
return self.stub("UiAutomator").drag(A, B, step=step)
def swipe(self, A, B, step=32):
return self.stub("UiAutomator").swipe(A, B, step=step)
def swipe_points(self, *points, step=32):
return self.stub("UiAutomator").swipe_points(*points, step=step)
def open_notification(self):
return self.stub("UiAutomator").open_notification()
def open_quick_settings(self):
return self.stub("UiAutomator").open_quick_settings()
def wake_up(self):
return self.stub("UiAutomator").wake_up()
def sleep(self):
return self.stub("UiAutomator").sleep()
def is_screen_on(self):
return self.stub("UiAutomator").is_screen_on()
def is_screen_locked(self):
return self.stub("UiAutomator").is_screen_locked()
def set_clipboard(self, text):
return self.stub("UiAutomator").set_clipboard(text)
def get_clipboard(self):
return self.stub("UiAutomator").get_clipboard()
def freeze_rotation(self, freeze=True):
return self.stub("UiAutomator").freeze_rotation(freeze=freeze)
def set_orientation(self, orien=Orientation.ORIEN_NATURE):
return self.stub("UiAutomator").set_orientation(orien)
def press_key(self, key):
return self.stub("UiAutomator").press_key(key)
def press_keycode(self, code, meta=0):
return self.stub("UiAutomator").press_keycode(code, meta)
def take_screenshot(self, quality=100, bound=None):
return self.stub("UiAutomator").take_screenshot(quality, bound=bound)
def screenshot(self, quality=100, bound=None):
return self.stub("UiAutomator").screenshot(quality, bound=bound)
def dump_window_hierarchy(self, compressed=False):
return self.stub("UiAutomator").dump_window_hierarchy(compressed=compressed)
def wait_for_idle(self, timeout):
return self.stub("UiAutomator").wait_for_idle(timeout)
def get_last_toast(self):
return self.stub("UiAutomator").get_last_toast()
def find_similar_image(self, data, threshold=0.0, distance=250,
scale=1.0, area=FindImageArea.FIA_WHOLE_SCREEN,
method=FindImageMethod.FIM_TEMPLATE):
return self.stub("UiAutomator").find_similar_image(data, threshold=threshold,
distance=distance, scale=scale,
area=area, method=method)
# watcher
def remove_all_watchers(self):
return self.stub("UiAutomator").remove_all_watchers()
def set_watcher_loop_enabled(self, enabled):
return self.stub("UiAutomator").set_watcher_loop_enabled(enabled)
def get_watcher_loop_enabled(self):
return self.stub("UiAutomator").get_watcher_loop_enabled()
def get_watcher_triggered_count(self, name):
return self.stub("UiAutomator").get_watcher_triggered_count(name)
def reset_watcher_triggered_count(self, name):
return self.stub("UiAutomator").reset_watcher_triggered_count(name)
def get_enabled_watchers(self):
return self.stub("UiAutomator").get_enabled_watchers()
def get_watchers(self):
return self.stub("UiAutomator").get_watchers()
def register_click_target_selector_watcher(self, name, conditions,
target):
return self.stub("UiAutomator").register_click_target_selector_watcher(
name, conditions, target
)
def register_press_key_watcher(self, name, conditions, key):
return self.stub("UiAutomator").register_press_key_watcher(
name, conditions, key
)
def register_none_op_watcher(self, name, conditions):
return self.stub("UiAutomator").register_none_op_watcher(
name, conditions
)
def set_watcher_enabled(self, name, enable):
return self.stub("UiAutomator").set_watcher_enabled(name, enable)
def get_watcher_enabled(self, name):
return self.stub("UiAutomator").get_watcher_enabled(name)
def remove_watcher(self, name):
return self.stub("UiAutomator").remove_watcher(name)
def device_info(self):
return self.stub("UiAutomator").device_info()
def server_info(self):
return self.stub("Util").server_info()
def __call__(self, **kwargs):
return self.stub("UiAutomator")(**kwargs)
# OCR extension.
def ocr(self, index=0, display=0, **kwargs):
if not isinstance(self._ocr, OcrEngine):
raise IllegalStateException("Ocr engine is not setted up")
if any(r not in ["text", "textContains", "textMatches"] \
for r in kwargs.keys()):
raise InvalidArgumentError("Only text* matches are supported")
if len(kwargs) != 1:
raise InvalidArgumentError("Only or at least one rule can be used")
image = self.proxy("UiAutomator", display=display).screenshot(
self._ocr_img_quality)
return OcrOperator(self,
elements=self._ocr.ocr(image),
index=index,
display=display,
**kwargs
)
def setup_ocr_backend(self, service, *args, quality=75,
**kwargs):
self._ocr_img_quality = quality
self._ocr = OcrEngine(service, *args,
**kwargs)
# Logging control.
def set_debug_log_enabled(self, enable):
level = logging.DEBUG if enable else logging.WARN
logger.setLevel(level)
return enable
# Lock API.
def _get_session_token(self):
return self.stub("Lock").get_session_token()
def _acquire_lock(self, leaseTime=60):
return self.stub("Lock").acquire_lock(leaseTime)
def _refresh_lock(self, leaseTime=60):
return self.stub("Lock").refresh_lock(leaseTime)
def _release_lock(self):
return self.stub("Lock").release_lock()
def __enter__(self):
self._acquire_lock(leaseTime=sys.maxsize)
return self
def __exit__(self, type, value, traceback):
self._release_lock()
if __name__ == "__main__":
import code
import readline
import rlcompleter
import argparse
parser = argparse.ArgumentParser()
crt = os.environ.get("CERTIFICATE", None)
port = int(os.environ.get("PORT", 65000))
parser.add_argument("-device", type=str, default="127.0.0.1",
help="service ip address")
parser.add_argument("-port", type=int, default=port,
help="service port")
parser.add_argument("-cert", type=str, default=crt,
help="ssl cert")
args = parser.parse_args()
readline.parse_and_bind("tab: complete")
d = Device(args.device, port=args.port,
certificate=args.cert)
code.interact(local=globals())
```
--- MODULE: lamda.const | FILE: const.py ---
```python
# Copyright 2022 rev1si0n (https://github.com/rev1si0n). All rights reserved.
#
# Distributed under MIT license.
# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
# Android runtime permissions
PERMISSION_READ_SMS = "android.permission.READ_SMS"
PERMISSION_READ_CALENDAR = "android.permission.READ_CALENDAR"
PERMISSION_READ_CALL_LOG = "android.permission.READ_CALL_LOG"
PERMISSION_ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION"
PERMISSION_ANSWER_PHONE_CALLS = "android.permission.ANSWER_PHONE_CALLS"
PERMISSION_RECEIVE_WAP_PUSH = "android.permission.RECEIVE_WAP_PUSH"
PERMISSION_BODY_SENSORS = "android.permission.BODY_SENSORS"
PERMISSION_READ_PHONE_NUMBERS = "android.permission.READ_PHONE_NUMBERS"
PERMISSION_RECEIVE_MMS = "android.permission.RECEIVE_MMS"
PERMISSION_RECEIVE_SMS = "android.permission.RECEIVE_SMS"
PERMISSION_READ_EXTERNAL_STORAGE = "android.permission.READ_EXTERNAL_STORAGE"
PERMISSION_ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION"
PERMISSION_READ_PHONE_STATE = "android.permission.READ_PHONE_STATE"
PERMISSION_SEND_SMS = "android.permission.SEND_SMS"
PERMISSION_CALL_PHONE = "android.permission.CALL_PHONE"
PERMISSION_WRITE_CONTACTS = "android.permission.WRITE_CONTACTS"
PERMISSION_ACCEPT_HANDOVER = "android.permission.ACCEPT_HANDOVER"
PERMISSION_CAMERA = "android.permission.CAMERA"
PERMISSION_WRITE_CALENDAR = "android.permission.WRITE_CALENDAR"
PERMISSION_WRITE_CALL_LOG = "android.permission.WRITE_CALL_LOG"
PERMISSION_USE_SIP = "android.permission.USE_SIP"
PERMISSION_PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS"
PERMISSION_READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS"
PERMISSION_GET_ACCOUNTS = "android.permission.GET_ACCOUNTS"
PERMISSION_WRITE_EXTERNAL_STORAGE = "android.permission.WRITE_EXTERNAL_STORAGE"
PERMISSION_ACTIVITY_RECOGNITION = "android.permission.ACTIVITY_RECOGNITION"
PERMISSION_RECORD_AUDIO = "android.permission.RECORD_AUDIO"
PERMISSION_READ_CONTACTS = "android.permission.READ_CONTACTS"
PERMISSION_ACCESS_BACKGROUND_LOCATION = "android.permission.ACCESS_BACKGROUND_LOCATION"
PERMISSION_ACCESS_MEDIA_LOCATION = "android.permission.ACCESS_MEDIA_LOCATION"
# Android activity flags
FLAG_ACTIVITY_BROUGHT_TO_FRONT = 0x00400000
FLAG_ACTIVITY_CLEAR_TASK = 0x00008000
FLAG_ACTIVITY_CLEAR_TOP = 0x04000000
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS = 0x00800000
FLAG_ACTIVITY_FORWARD_RESULT = 0x02000000
FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY = 0x00100000
FLAG_ACTIVITY_LAUNCH_ADJACENT = 0x00001000
FLAG_ACTIVITY_MATCH_EXTERNAL = 0x00000800
FLAG_ACTIVITY_MULTIPLE_TASK = 0x08000000
FLAG_ACTIVITY_NEW_DOCUMENT = 0x00080000
FLAG_ACTIVITY_NEW_TASK = 0x10000000
FLAG_ACTIVITY_NO_ANIMATION = 0x00010000
FLAG_ACTIVITY_NO_HISTORY = 0x40000000
FLAG_ACTIVITY_NO_USER_ACTION = 0x00040000
FLAG_ACTIVITY_PREVIOUS_IS_TOP = 0x01000000
FLAG_ACTIVITY_REORDER_TO_FRONT = 0x00020000
FLAG_ACTIVITY_REQUIRE_DEFAULT = 0x00000200
FLAG_ACTIVITY_REQUIRE_NON_BROWSER = 0x00000400
FLAG_ACTIVITY_RESET_TASK_IF_NEEDED = 0x00200000
FLAG_ACTIVITY_RETAIN_IN_RECENTS = 0x00002000
FLAG_ACTIVITY_SINGLE_TOP = 0x20000000
FLAG_ACTIVITY_TASK_ON_HOME = 0x00004000
```
--- MODULE: lamda.exceptions | FILE: exceptions.py ---
```python
# Copyright 2022 rev1si0n (https://github.com/rev1si0n). All rights reserved.
#
# Distributed under MIT license.
# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
class CompatibilityException(Exception):
""" Exception """
class DeadSystemException(Exception):
""" Exception """
class DeviceUnavailable(Exception):
""" Exception """
class DuplicateEntryError(Exception):
""" Exception """
class IllegalArgumentException(Exception):
""" Exception """
class IllegalStateException(Exception):
""" Exception """
class InstallPackageFailed(Exception):
""" Exception """
class InternalRpcException(Exception):
""" Exception """
class InvalidAndroidPackage(Exception):
""" Exception """
class InvalidArgumentError(Exception):
""" Exception """
class InvalidOperationError(Exception):
""" Exception """
class InvalidRootCertificate(Exception):
""" Exception """
class MethodNotFoundException(Exception):
""" Exception """
class NameNotFoundException(Exception):
""" Exception """
class NotImplementedException(Exception):
""" Exception """
class NullPointerException(Exception):
""" Exception """
class SecurityException(Exception):
""" Exception """
class ServiceUnavailable(Exception):
""" Exception """
class StaleObjectException(Exception):
""" Exception """
class StartupActivityNotFound(Exception):
""" Exception """
class StorageOutOfMemory(Exception):
""" Exception """
class UiAutomatorException(Exception):
""" Exception """
class UiObjectNotFoundException(Exception):
""" Exception """
class UnHandledException(Exception):
""" Exception """
class UnsupportedOperationException(Exception):
""" Exception """
```
--- MODULE: lamda.types | FILE: types.py ---
```python
# Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
#
# Distributed under MIT license.
# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
import io
import codecs
__all__ = ["AttributeDict", "BytesIO"]
class AttributeDict(dict):
def __getattr__(self, attr):
return self[attr]
def __setattr__(self, attr, value):
self[attr] = value
def remove(self, key):
key in self and self.pop(key)
class BytesIO(io.BytesIO):
@classmethod
def decode_from(cls, data, encoding):
return cls(codecs.decode(data, encoding))
def encode(self, encoding):
return codecs.encode(self.getvalue(), encoding)
def decode(self, encoding):
return codecs.decode(self.getvalue(), encoding)
def save(self, fpath):
with open(fpath, "wb") as fd:
return fd.write(self.getvalue())
@classmethod
def load(cls, fpath):
with open(fpath, "rb") as fd:
return cls(fd.read())
```
--- FILE: rpc/application.proto ---
```protobuf
// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
//
// Distributed under MIT license.
// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
syntax = "proto3";
package lamda.rpc;
import "google/protobuf/struct.proto";
enum GrantType {
GRANT_ALLOW = 0;
GRANT_DENY = 1;
GRANT_IGNORE = 2;
}
enum DataEncode {
DATA_ENCODE_NONE = 0;
DATA_ENCODE_ZLIB = 1;
}
enum ScriptRuntime {
RUNTIME_QJS = 0;
RUNTIME_V8 = 1;
}
message ApplicationRequest {
string name = 1;
string permission = 2;
GrantType mode = 3;
string path = 4;
uint32 user = 5;
uint32 display = 16;
}
message ApplicationActivityRequest {
string package = 1;
string action = 2;
string category = 3;
string component = 4;
google.protobuf.Struct extras = 5;
repeated string categories = 6;
int64 flags = 7;
bool debug = 8;
string data = 9;
uint32 user = 10;
uint32 display = 16;
}
message ApplicationActivityInfo {
string package = 1;
string action = 2;
string category = 3;
string component = 4;
google.protobuf.Struct extras = 5;
repeated string categories = 6;
int64 flags = 7;
bool debug = 8;
string data = 9;
uint32 user = 10;
}
message ApplicationPermissions {
repeated string permissions = 1;
}
message ApplicationInfo {
string packageName = 1;
uint32 uid = 2;
bool enabled = 3;
string processName = 4;
string sourceDir = 5;
string dataDir = 6;
uint32 baseRevisionCode = 7;
int64 firstInstallTime = 8;
int64 lastUpdateTime = 9;
uint32 versionCode = 10;
string versionName = 11;
string activity = 12;
uint32 user = 13;
string label = 14;
}
message ApplicationProcess {
repeated string packages = 1;
string processName = 2;
int64 uid = 3;
int64 pid = 4;
string label = 5;
}
message ApplicationProcesses {
repeated ApplicationProcess processes = 1;
}
message InstalledApplicationInfo {
string packageName = 1;
string label = 2;
int64 uid = 3;
int64 user = 4;
bool enabled = 5;
bool system = 6;
string versionName = 7;
}
message InstalledApplications {
repeated InstalledApplicationInfo applications = 1;
}
message HookRequest {
string package = 1;
bytes script = 2;
ScriptRuntime runtime = 3;
string destination = 4;
DataEncode encode = 5;
uint32 standup = 6;
bool spawn = 7;
uint32 user = 8;
string process = 9;
}
message HookRpcRequest {
string package = 1;
string callinfo = 2;
uint32 user = 3;
}
message HookRpcResponse {
string package = 1;
string callresult = 2;
}
message InstallSessionCreateRequest {
uint32 user = 1;
string installerPackageName = 2;
uint64 sizeBytes = 4;
string package = 5;
bool dontKillApp = 14;
bool replaceExisting = 15;
bool allowTest = 16;
bool requestDowngrade = 17;
bool grantRuntimePermissions=18;
}
message InstallSessionWriteRequest {
int32 session = 1;
string path = 2;
string name = 3;
bool delete = 10;
}
message InstallSessionAbandonRequest {
int32 session = 1;
}
message InstallSessionQueryRequest {
int32 session = 1;
}
message InstallSessionCommitRequest {
int32 session = 1;
bool wait = 2;
uint64 timeout = 3;
}
message InstallSessionCreateResponse {
uint32 user = 1;
int32 session = 2;
}
message InstallSessionWriteResponse {
int32 session = 1;
string name = 2;
uint64 writtenBytes = 3;
uint64 totalBytes = 4;
}
message InstallSessionStatusResponse {
uint32 user = 1;
int32 session = 2;
string package = 3;
int32 status = 4;
int32 legacyStatus = 5;
string statusMessage = 6;
bool finished = 7;
bool committed = 8;
bool abandoned = 9;
float progress = 10;
bool success = 11;
}
```
--- FILE: rpc/debug.proto ---
```protobuf
// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
//
// Distributed under MIT license.
// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
syntax = "proto3";
package lamda.rpc;
import "google/protobuf/struct.proto";
message ADBDConfigRequest {
string adb_pubkey = 1;
}
```
--- FILE: rpc/file.proto ---
```protobuf
// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
//
// Distributed under MIT license.
// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
syntax = "proto3";
package lamda.rpc;
message FileStat {
string name = 1;
string path = 2;
bool directory = 3;
int64 st_mode = 4;
int64 st_atime = 5;
int64 st_mtime = 6;
int64 st_ctime = 7;
int32 st_uid = 8;
int32 st_gid = 9;
int64 st_size = 10;
}
message FileRequest {
string path = 1;
uint64 mode = 2;
bytes payload = 3;
}
message FileDataResponse {
bytes payload = 1;
}
```
--- FILE: rpc/policy.proto ---
```protobuf
// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
//
// Distributed under MIT license.
// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
syntax = "proto3";
package lamda.rpc;
message SelinuxPolicyRequest {
string source = 1;
string target = 2;
string tclass = 3;
string action = 4;
}
```
--- FILE: rpc/proxy.proto ---
```protobuf
// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
//
// Distributed under MIT license.
// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
syntax = "proto3";
package lamda.rpc;
import "application.proto";
// Supported Shadowsocks Ciphers (no ref)
enum ShadowCipher {
SS_AES_128_CFB = 0;
SS_AES_192_CFB = 1;
SS_AES_256_CFB = 2;
SS_AES_128_CTR = 3;
SS_AES_192_CTR = 4;
SS_AES_256_CTR = 5;
SS_CAMELLIA_128_CFB = 6;
SS_CAMELLIA_192_CFB = 7;
SS_CAMELLIA_256_CFB = 8;
SS_DES_CFB = 9;
SS_AES_128_GCM = 20;
SS_AES_192_GCM = 21;
SS_AES_256_GCM = 22;
SS_CHACHA20_IETF_POLY1305 = 23;
}
enum GproxyType {
SOCKS5 = 0;
HTTP_CONNECT = 1;
HTTP_RELAY = 2;
HTTPS_CONNECT = 3;
SHADOWSOCKS = 4;
}
message GproxyConfigRequest {
ApplicationInfo application = 1;
GproxyType type = 2;
string nameserver = 3;
string login = 4;
string password = 5;
string host = 6;
uint32 port = 7;
bool bypass_local_subnet = 8;
bool drop_udp = 9;
bool udp_proxy = 10;
bool dns_proxy = 11;
string interface = 12;
}
enum OpenVPNProto {
TCP = 0;
UDP = 1;
}
enum OpenVPNAuth {
SHA1 = 0;
SHA224 = 1;
SHA256 = 2;
SHA384 = 3;
SHA512 = 4;
RIPEMD160 = 5;
RSA_SHA1 = 6;
RSA_SHA224 = 7;
RSA_SHA256 = 8;
RSA_SHA384 = 9;
RSA_SHA512 = 10;
RSA_RIPEMD160 = 11;
}
enum OpenVPNCipher {
AES_128_GCM = 0;
AES_256_GCM = 1;
CHACHA20_POLY1305= 2;
AES_128_CBC = 3;
AES_256_CBC = 4;
}
enum OpenVPNKeyDirection {
// because 0 is a default value
// so we use 1 as key-direction 0
KEY_DIRECTION_NONE = 0;
KEY_DIRECTION_0 = 1;
KEY_DIRECTION_1 = 2;
}
enum OpenVPNEncryption {
TLS_NONE = 0;
TLS_AUTH = 1;
TLS_CRYPT = 2;
TLS_CRYPT_V2 = 3;
}
message OpenVPNConfigRequest {
bool all_traffic = 1;
OpenVPNProto proto = 2;
string host = 3;
uint32 port = 4;
OpenVPNCipher cipher = 5;
string ca = 6;
string cert = 7;
string key = 8;
OpenVPNEncryption tls_encryption = 9;
OpenVPNKeyDirection tls_key_direction = 10;
string tls_key = 11;
OpenVPNAuth auth = 12;
string login = 13;
string password = 14;
}
```
--- FILE: rpc/settings.proto ---
```protobuf
// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
//
// Distributed under MIT license.
// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
syntax = "proto3";
package lamda.rpc;
enum Group {
GROUP_SYSTEM = 0;
GROUP_SECURE = 1;
GROUP_GLOBAL = 2;
}
message SettingsRequest {
Group group = 1;
string name = 2;
string value = 3;
}
```
--- FILE: rpc/shell.proto ---
```protobuf
// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
//
// Distributed under MIT license.
// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
syntax = "proto3";
package lamda.rpc;
message ShellRequest {
string tid = 1;
string name = 2;
string script = 3;
int32 timeout = 4;
}
message ShellResult {
int32 exitstatus = 1;
bytes stdout = 2;
bytes stderr = 3;
}
message ShellTask {
string tid = 1;
string name = 2;
int32 pid = 3;
}
```
--- FILE: rpc/status.proto ---
```protobuf
// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
//
// Distributed under MIT license.
// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
syntax = "proto3";
package lamda.rpc;
message BatteryInfo {
bool batt_charging = 1;
int32 batt_percent = 2;
float batt_temperature = 3;
}
message CpuInfo {
float cpu_percent = 1;
int32 cpu_count = 2;
float cpu_freq_current = 3;
float cpu_freq_max = 4;
float cpu_freq_min = 5;
float cpu_times_user = 6;
float cpu_times_system = 7;
float cpu_times_idle = 8;
}
message DiskUsage {
int64 disk_total = 1;
int64 disk_used = 2;
int64 disk_free = 3;
float disk_percent = 4;
}
message DiskIOInfo {
int64 disk_io_read_bytes = 1;
int64 disk_io_read_count = 2;
int64 disk_io_write_bytes = 3;
int64 disk_io_write_count = 4;
int64 disk_io_read_time = 5;
int64 disk_io_write_time = 6;
int64 disk_io_busy_time = 7;
}
message NetIOInfo {
int64 net_io_bytes_sent = 1;
int64 net_io_packets_sent = 2;
int64 net_io_bytes_recv = 3;
int64 net_io_packets_recv = 4;
int64 net_io_dropin = 5;
int64 net_io_dropout = 6;
int64 net_io_errin = 7;
int64 net_io_errout = 8;
}
message MemInfo {
int64 mem_total = 1;
int64 mem_available = 2;
float mem_percent = 3;
int64 mem_used = 4;
int64 mem_free = 5;
int64 mem_active = 6;
int64 mem_inactive = 7;
int64 mem_buffers = 8;
int64 mem_cached = 9;
int64 mem_shared = 10;
int64 mem_slab = 11;
}
```
--- FILE: rpc/storage.proto ---
```protobuf
// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
//
// Distributed under MIT license.
// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
syntax = "proto3";
package lamda.rpc;
message StorageRequest {
string container = 1;
string key = 2;
bytes value = 3;
uint32 ttl = 4;
}
```
--- FILE: rpc/types.proto ---
```protobuf
// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
//
// Distributed under MIT license.
// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
syntax = "proto3";
package lamda.rpc;
message Boolean {
bool value = 1;
}
message Integer {
int64 value = 1;
}
message String {
string value = 1;
}
message Bytes {
bytes value = 1;
}
message Empty {
}
```
--- FILE: rpc/uiautomator.proto ---
```protobuf
// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
//
// Distributed under MIT license.
// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
syntax = "proto3";
package lamda.rpc;
message Point {
uint32 x = 1;
uint32 y = 2;
}
message Bound {
uint32 top = 1;
uint32 left = 2;
uint32 bottom = 3;
uint32 right = 4;
}
enum Orientation {
ORIEN_NATURE = 0;
ORIEN_LEFT = 1;
ORIEN_UPSIDEDOWN = 2;
ORIEN_RIGHT = 3;
ORIEN_UNFREEZE = 4;
}
enum Key {
KEY_BACK = 0;
KEY_CAMERA = 1;
KEY_CENTER = 2;
KEY_DELETE = 3;
KEY_DOWN = 4;
KEY_ENTER = 5;
KEY_HOME = 6;
KEY_LEFT = 7;
KEY_MENU = 8;
KEY_POWER = 9;
KEY_RECENT = 10;
KEY_RIGHT = 11;
KEY_SEARCH = 12;
KEY_UP = 13;
KEY_VOLUME_DOWN = 14;
KEY_VOLUME_MUTE = 15;
KEY_VOLUME_UP = 16;
}
enum KeyCode {
KEYCODE_UNKNOWN = 0;
KEYCODE_SOFT_LEFT = 1;
KEYCODE_SOFT_RIGHT = 2;
KEYCODE_HOME = 3;
KEYCODE_BACK = 4;
KEYCODE_CALL = 5;
KEYCODE_ENDCALL = 6;
KEYCODE_0 = 7;
KEYCODE_1 = 8;
KEYCODE_2 = 9;
KEYCODE_3 = 10;
KEYCODE_4 = 11;
KEYCODE_5 = 12;
KEYCODE_6 = 13;
KEYCODE_7 = 14;
KEYCODE_8 = 15;
KEYCODE_9 = 16;
KEYCODE_STAR = 17;
KEYCODE_POUND = 18;
KEYCODE_DPAD_UP = 19;
KEYCODE_DPAD_DOWN = 20;
KEYCODE_DPAD_LEFT = 21;
KEYCODE_DPAD_RIGHT = 22;
KEYCODE_DPAD_CENTER = 23;
KEYCODE_VOLUME_UP = 24;
KEYCODE_VOLUME_DOWN = 25;
KEYCODE_POWER = 26;
KEYCODE_CAMERA = 27;
KEYCODE_CLEAR = 28;
KEYCODE_A = 29;
KEYCODE_B = 30;
KEYCODE_C = 31;
KEYCODE_D = 32;
KEYCODE_E = 33;
KEYCODE_F = 34;
KEYCODE_G = 35;
KEYCODE_H = 36;
KEYCODE_I = 37;
KEYCODE_J = 38;
KEYCODE_K = 39;
KEYCODE_L = 40;
KEYCODE_M = 41;
KEYCODE_N = 42;
KEYCODE_O = 43;
KEYCODE_P = 44;
KEYCODE_Q = 45;
KEYCODE_R = 46;
KEYCODE_S = 47;
KEYCODE_T = 48;
KEYCODE_U = 49;
KEYCODE_V = 50;
KEYCODE_W = 51;
KEYCODE_X = 52;
KEYCODE_Y = 53;
KEYCODE_Z = 54;
KEYCODE_COMMA = 55;
KEYCODE_PERIOD = 56;
KEYCODE_ALT_LEFT = 57;
KEYCODE_ALT_RIGHT = 58;
KEYCODE_SHIFT_LEFT = 59;
KEYCODE_SHIFT_RIGHT = 60;
KEYCODE_TAB = 61;
KEYCODE_SPACE = 62;
KEYCODE_SYM = 63;
KEYCODE_EXPLORER = 64;
KEYCODE_ENVELOPE = 65;
KEYCODE_ENTER = 66;
KEYCODE_DEL = 67;
KEYCODE_GRAVE = 68;
KEYCODE_MINUS = 69;
KEYCODE_EQUALS = 70;
KEYCODE_LEFT_BRACKET = 71;
KEYCODE_RIGHT_BRACKET = 72;
KEYCODE_BACKSLASH = 73;
KEYCODE_SEMICOLON = 74;
KEYCODE_APOSTROPHE = 75;
KEYCODE_SLASH = 76;
KEYCODE_AT = 77;
KEYCODE_NUM = 78;
KEYCODE_HEADSETHOOK = 79;
KEYCODE_FOCUS = 80;
KEYCODE_PLUS = 81;
KEYCODE_MENU = 82;
KEYCODE_NOTIFICATION = 83;
KEYCODE_SEARCH = 84;
KEYCODE_MEDIA_PLAY_PAUSE= 85;
KEYCODE_MEDIA_STOP = 86;
KEYCODE_MEDIA_NEXT = 87;
KEYCODE_MEDIA_PREVIOUS = 88;
KEYCODE_MEDIA_REWIND = 89;
KEYCODE_MEDIA_FAST_FORWARD = 90;
KEYCODE_MUTE = 91;
KEYCODE_PAGE_UP = 92;
KEYCODE_PAGE_DOWN = 93;
KEYCODE_PICTSYMBOLS = 94;
KEYCODE_SWITCH_CHARSET = 95;
KEYCODE_BUTTON_A = 96;
KEYCODE_BUTTON_B = 97;
KEYCODE_BUTTON_C = 98;
KEYCODE_BUTTON_X = 99;
KEYCODE_BUTTON_Y = 100;
KEYCODE_BUTTON_Z = 101;
KEYCODE_BUTTON_L1 = 102;
KEYCODE_BUTTON_R1 = 103;
KEYCODE_BUTTON_L2 = 104;
KEYCODE_BUTTON_R2 = 105;
KEYCODE_BUTTON_THUMBL = 106;
KEYCODE_BUTTON_THUMBR = 107;
KEYCODE_BUTTON_START = 108;
KEYCODE_BUTTON_SELECT = 109;
KEYCODE_BUTTON_MODE = 110;
KEYCODE_ESCAPE = 111;
KEYCODE_FORWARD_DEL = 112;
KEYCODE_CTRL_LEFT = 113;
KEYCODE_CTRL_RIGHT = 114;
KEYCODE_CAPS_LOCK = 115;
KEYCODE_SCROLL_LOCK = 116;
KEYCODE_META_LEFT = 117;
KEYCODE_META_RIGHT = 118;
KEYCODE_FUNCTION = 119;
KEYCODE_SYSRQ = 120;
KEYCODE_BREAK = 121;
KEYCODE_MOVE_HOME = 122;
KEYCODE_MOVE_END = 123;
KEYCODE_INSERT = 124;
KEYCODE_FORWARD = 125;
KEYCODE_MEDIA_PLAY = 126;
KEYCODE_MEDIA_PAUSE = 127;
KEYCODE_MEDIA_CLOSE = 128;
KEYCODE_MEDIA_EJECT = 129;
KEYCODE_MEDIA_RECORD = 130;
KEYCODE_F1 = 131;
KEYCODE_F2 = 132;
KEYCODE_F3 = 133;
KEYCODE_F4 = 134;
KEYCODE_F5 = 135;
KEYCODE_F6 = 136;
KEYCODE_F7 = 137;
KEYCODE_F8 = 138;
KEYCODE_F9 = 139;
KEYCODE_F10 = 140;
KEYCODE_F11 = 141;
KEYCODE_F12 = 142;
KEYCODE_NUM_LOCK = 143;
KEYCODE_NUMPAD_0 = 144;
KEYCODE_NUMPAD_1 = 145;
KEYCODE_NUMPAD_2 = 146;
KEYCODE_NUMPAD_3 = 147;
KEYCODE_NUMPAD_4 = 148;
KEYCODE_NUMPAD_5 = 149;
KEYCODE_NUMPAD_6 = 150;
KEYCODE_NUMPAD_7 = 151;
KEYCODE_NUMPAD_8 = 152;
KEYCODE_NUMPAD_9 = 153;
KEYCODE_NUMPAD_DIVIDE = 154;
KEYCODE_NUMPAD_MULTIPLY = 155;
KEYCODE_NUMPAD_SUBTRACT = 156;
KEYCODE_NUMPAD_ADD = 157;
KEYCODE_NUMPAD_DOT = 158;
KEYCODE_NUMPAD_COMMA = 159;
KEYCODE_NUMPAD_ENTER = 160;
KEYCODE_NUMPAD_EQUALS = 161;
KEYCODE_NUMPAD_LEFT_PAREN = 162;
KEYCODE_NUMPAD_RIGHT_PAREN = 163;
KEYCODE_VOLUME_MUTE = 164;
KEYCODE_INFO = 165;
KEYCODE_CHANNEL_UP = 166;
KEYCODE_CHANNEL_DOWN = 167;
KEYCODE_ZOOM_IN = 168;
KEYCODE_ZOOM_OUT = 169;
KEYCODE_TV = 170;
KEYCODE_WINDOW = 171;
KEYCODE_GUIDE = 172;
KEYCODE_DVR = 173;
KEYCODE_BOOKMARK = 174;
KEYCODE_CAPTIONS = 175;
KEYCODE_SETTINGS = 176;
KEYCODE_TV_POWER = 177;
KEYCODE_TV_INPUT = 178;
KEYCODE_STB_POWER = 179;
KEYCODE_STB_INPUT = 180;
KEYCODE_AVR_POWER = 181;
KEYCODE_AVR_INPUT = 182;
KEYCODE_PROG_RED = 183;
KEYCODE_PROG_GREEN = 184;
KEYCODE_PROG_YELLOW = 185;
KEYCODE_PROG_BLUE = 186;
KEYCODE_APP_SWITCH = 187;
KEYCODE_BUTTON_1 = 188;
KEYCODE_BUTTON_2 = 189;
KEYCODE_BUTTON_3 = 190;
KEYCODE_BUTTON_4 = 191;
KEYCODE_BUTTON_5 = 192;
KEYCODE_BUTTON_6 = 193;
KEYCODE_BUTTON_7 = 194;
KEYCODE_BUTTON_8 = 195;
KEYCODE_BUTTON_9 = 196;
KEYCODE_BUTTON_10 = 197;
KEYCODE_BUTTON_11 = 198;
KEYCODE_BUTTON_12 = 199;
KEYCODE_BUTTON_13 = 200;
KEYCODE_BUTTON_14 = 201;
KEYCODE_BUTTON_15 = 202;
KEYCODE_BUTTON_16 = 203;
KEYCODE_LANGUAGE_SWITCH = 204;
KEYCODE_MANNER_MODE = 205;
KEYCODE_3D_MODE = 206;
KEYCODE_CONTACTS = 207;
KEYCODE_CALENDAR = 208;
KEYCODE_MUSIC = 209;
KEYCODE_CALCULATOR = 210;
KEYCODE_ZENKAKU_HANKAKU = 211;
KEYCODE_EISU = 212;
KEYCODE_MUHENKAN = 213;
KEYCODE_HENKAN = 214;
KEYCODE_KATAKANA_HIRAGANA = 215;
KEYCODE_YEN = 216;
KEYCODE_RO = 217;
KEYCODE_KANA = 218;
KEYCODE_ASSIST = 219;
KEYCODE_BRIGHTNESS_DOWN = 220;
KEYCODE_BRIGHTNESS_UP = 221;
KEYCODE_MEDIA_AUDIO_TRACK = 222;
KEYCODE_SLEEP = 223;
KEYCODE_WAKEUP = 224;
KEYCODE_PAIRING = 225;
KEYCODE_MEDIA_TOP_MENU = 226;
KEYCODE_11 = 227;
KEYCODE_12 = 228;
KEYCODE_LAST_CHANNEL = 229;
KEYCODE_TV_DATA_SERVICE = 230;
KEYCODE_VOICE_ASSIST = 231;
KEYCODE_TV_RADIO_SERVICE = 232;
KEYCODE_TV_TELETEXT = 233;
KEYCODE_TV_NUMBER_ENTRY = 234;
KEYCODE_TV_TERRESTRIAL_ANALOG = 235;
KEYCODE_TV_TERRESTRIAL_DIGITAL = 236;
KEYCODE_TV_SATELLITE = 237;
KEYCODE_TV_SATELLITE_BS = 238;
KEYCODE_TV_SATELLITE_CS = 239;
KEYCODE_TV_SATELLITE_SERVICE = 240;
KEYCODE_TV_NETWORK = 241;
KEYCODE_TV_ANTENNA_CABLE = 242;
KEYCODE_TV_INPUT_HDMI_1 = 243;
KEYCODE_TV_INPUT_HDMI_2 = 244;
KEYCODE_TV_INPUT_HDMI_3 = 245;
KEYCODE_TV_INPUT_HDMI_4 = 246;
KEYCODE_TV_INPUT_COMPOSITE_1 = 247;
KEYCODE_TV_INPUT_COMPOSITE_2 = 248;
KEYCODE_TV_INPUT_COMPONENT_1 = 249;
KEYCODE_TV_INPUT_COMPONENT_2 = 250;
KEYCODE_TV_INPUT_VGA_1 = 251;
KEYCODE_TV_AUDIO_DESCRIPTION = 252;
KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP = 253;
KEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN = 254;
KEYCODE_TV_ZOOM_MODE = 255;
KEYCODE_TV_CONTENTS_MENU = 256;
KEYCODE_TV_MEDIA_CONTEXT_MENU = 257;
KEYCODE_TV_TIMER_PROGRAMMING = 258;
KEYCODE_HELP = 259;
KEYCODE_NAVIGATE_PREVIOUS = 260;
KEYCODE_NAVIGATE_NEXT = 261;
KEYCODE_NAVIGATE_IN = 262;
KEYCODE_NAVIGATE_OUT = 263;
KEYCODE_STEM_PRIMARY = 264;
KEYCODE_STEM_1 = 265;
KEYCODE_STEM_2 = 266;
KEYCODE_STEM_3 = 267;
KEYCODE_DPAD_UP_LEFT = 268;
KEYCODE_DPAD_DOWN_LEFT = 269;
KEYCODE_DPAD_UP_RIGHT = 270;
KEYCODE_DPAD_DOWN_RIGHT = 271;
KEYCODE_MEDIA_SKIP_FORWARD = 272;
KEYCODE_MEDIA_SKIP_BACKWARD = 273;
KEYCODE_MEDIA_STEP_FORWARD = 274;
KEYCODE_MEDIA_STEP_BACKWARD = 275;
KEYCODE_SOFT_SLEEP = 276;
KEYCODE_CUT = 277;
KEYCODE_COPY = 278;
KEYCODE_PASTE = 279;
KEYCODE_SYSTEM_NAVIGATION_UP = 280;
KEYCODE_SYSTEM_NAVIGATION_DOWN = 281;
KEYCODE_SYSTEM_NAVIGATION_LEFT = 282;
KEYCODE_SYSTEM_NAVIGATION_RIGHT = 283;
KEYCODE_ALL_APPS = 284;
KEYCODE_REFRESH = 285;
KEYCODE_THUMBS_UP = 286;
KEYCODE_THUMBS_DOWN = 287;
KEYCODE_PROFILE_SWITCH = 288;
}
enum MetaKeyCode {
META_UNKNOWN = 0;
META_SHIFT_ON = 1;
META_ALT_ON = 2;
META_SYM_ON = 4;
META_FUNCTION_ON = 8;
META_ALT_LEFT_ON = 16;
META_ALT_RIGHT_ON = 32;
META_ALT_MASK = 50;
META_SHIFT_LEFT_ON = 64;
META_SHIFT_RIGHT_ON = 128;
META_SHIFT_MASK = 193;
META_CTRL_ON = 4096;
META_CTRL_LEFT_ON = 8192;
META_CTRL_RIGHT_ON = 16384;
META_CTRL_MASK = 28672;
META_META_ON = 65536;
META_META_LEFT_ON = 131072;
META_META_RIGHT_ON = 262144;
META_META_MASK = 458752;
META_CAPS_LOCK_ON = 1048576;
META_NUM_LOCK_ON = 2097152;
META_SCROLL_LOCK_ON = 4194304;
}
enum Corner {
COR_CENTER = 0;
COR_BOTTOMRIGHT = 1;
COR_TOPLEFT = 2;
}
enum Direction {
DIR_UP = 0;
DIR_LEFT = 1;
DIR_DOWN = 2;
DIR_RIGHT = 3;
}
enum ImePolicy {
IME_LOCAL = 0;
IME_FALLBACK_DISPLAY=1;
IME_HIDE = 2;
}
message ObjInfo {
Bound bounds = 1;
bool checkable = 2;
bool checked = 3;
uint32 childCount = 4;
string className = 5;
bool clickable = 6;
string contentDescription = 7;
bool enabled = 8;
bool focusable = 9;
bool focused = 10;
bool longClickable = 11;
string packageName = 12;
string resourceName = 13;
bool scrollable = 14;
bool selected = 15;
string text = 16;
Bound visibleBounds = 17;
}
message ToastInfo {
uint64 timestamp = 1;
string package = 2;
string message = 3;
}
message ObjInfoList {
repeated ObjInfo objects = 1;
}
message Selector {
uint32 mask = 1;
string text = 2;
string textContains = 3;
string textMatches = 4;
string textStartsWith = 5;
string className = 6;
string classNameMatches = 7;
string description = 8;
string descriptionContains = 9;
string descriptionMatches = 10;
string descriptionStartsWith = 11;
bool checkable = 12;
bool checked = 13;
bool clickable = 14;
bool longClickable = 15;
bool scrollable = 16;
bool enabled = 17;
bool focusable = 18;
bool focused = 19;
bool selected = 20;
string packageName = 21;
string packageNameMatches = 22;
string resourceId = 23;
string resourceIdMatches = 24;
uint32 index = 25;
uint32 instance = 26;
repeated string childOrSibling = 27;
repeated Selector childOrSiblingSelector = 28;
uint32 resultIndex = 49;
repeated string fields = 50;
}
message DeviceInfo {
string productName = 1;
uint32 sdkInt = 2;
uint32 displayHeight = 3;
uint32 displayRotation = 4;
uint32 displaySizeDpX = 5;
uint32 displaySizeDpY = 6;
uint32 displayWidth = 7;
bool screenLocked = 8;
bool screenOn = 9;
bool naturalOrientation = 10;
string currentPackageName = 11;
}
message SelectorTakeScreenshotRequest {
Selector selector = 1;
uint32 quality = 3;
uint32 display = 16;
}
message DumpWindowHierarchyRequest {
bool compressed = 1;
uint32 display = 16;
}
message SelectorSetTextRequest {
Selector selector = 1;
string text = 3;
uint32 display = 16;
}
message SelectorClickRequest {
Selector selector = 1;
Corner corner = 3;
uint32 timeout = 4;
uint32 display = 16;
}
message SelectorOnlyRequest {
Selector selector = 1;
uint32 display = 16;
}
message SelectorDragToRequest {
Selector selector = 1;
oneof _target {
Selector target = 3;
Point point = 4;
}
uint32 step = 5;
uint32 display = 16;
}
message SelectorWaitRequest {
Selector selector = 1;
uint32 timeout = 3;
uint32 display = 16;
}
message SelectorSwipeRequest {
Selector selector = 1;
Direction direction = 3;
uint32 step = 4;
uint32 display = 16;
}
message SelectorFlingRequest {
Selector selector = 1;
bool vertical = 3;
uint32 maxSwipes = 4;
uint32 display = 16;
}
message SelectorScrollRequest {
Selector selector = 1;
Selector target = 2;
bool vertical = 3;
uint32 maxSwipes = 4;
uint32 step = 5;
uint32 display = 16;
}
message SelectorPinchRequest {
Selector selector = 1;
uint32 percent = 3;
uint32 step = 4;
uint32 display = 16;
}
message ClickPointRequest {
Point point = 1;
uint32 timeout = 2;
uint32 display = 16;
}
message DragPointRequest {
Point A = 1;
Point B = 2;
uint32 step = 3;
uint32 display = 16;
}
message SwipePointRequest {
Point A = 1;
Point B = 2;
uint32 step = 3;
uint32 display = 16;
}
message SwipePointsRequest {
repeated Point points = 1;
uint32 step = 2;
uint32 display = 16;
}
message OrientationRequest {
Orientation orientation = 1;
uint32 display = 16;
}
message PressKeyRequest {
Key key = 1;
uint32 code = 2;
uint32 meta = 3;
uint32 display = 16;
}
message TakeScreenshotRequest {
Bound bound = 1;
uint32 quality = 2;
uint32 display = 16;
}
message ClipboardRequest {
string ID = 1;
string value = 2;
}
message WatcherInfo {
string name = 1;
bool enabled = 3;
}
message WatcherInfoList {
repeated WatcherInfo watchers = 1;
}
message WatcherControlRequest {
string name = 1;
repeated Selector selectors = 2;
oneof _target {
Selector target = 3;
Key key = 4;
}
bool enable = 5;
uint32 display = 16;
}
enum FindImageMethod {
FIM_TEMPLATE = 0;
FIM_FEATURE = 1;
}
enum FindImageArea {
FIA_WHOLE_SCREEN = 0;
FIA_LEFT = 1;
FIA_TOP_LEFT = 2;
FIA_TOP = 3;
FIA_TOP_RIGHT = 4;
FIA_RIGHT = 5;
FIA_BOTTOM_RIGHT = 6;
FIA_BOTTOM = 7;
FIA_BOTTOM_LEFT = 8;
}
message FindImageRequest {
bytes partial = 1;
FindImageMethod method = 2;
oneof _area {
FindImageArea area = 3;
Bound bound = 4;
}
uint32 distance = 5;
float threshold = 6;
float scale = 7;
uint32 display = 16;
}
message FindImageResponse {
repeated Bound bounds = 1;
}
message CreateVirtualDisplayRequest {
string name = 1;
uint32 width = 2;
uint32 height = 3;
uint32 densityDpi = 4;
}
message DisplayInfo {
string name = 1;
uint32 displayId = 2;
uint32 height = 4;
uint32 width = 5;
uint32 densityDpi = 8;
uint32 rotation = 9;
uint32 installOrientation=10;
bool virtual = 16;
}
message DisplayInfoList {
repeated DisplayInfo displays = 1;
}
message ImePolicyRequest {
ImePolicy policy = 1;
uint32 display = 16;
}
message FreezeRotationRequest {
bool freeze = 1;
uint32 display = 16;
}
```
--- FILE: rpc/util.proto ---
```protobuf
// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
//
// Distributed under MIT license.
// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
syntax = "proto3";
package lamda.rpc;
enum ToastDuration {
TD_SHORT = 0;
TD_LONG = 1;
}
enum AudioStreamType {
AST_ALARM = 0;
AST_MEDIA = 1;
AST_NOTIFICATION = 2;
AST_RING = 3;
AST_SYSTEM = 4;
AST_VOICE = 5;
}
message TouchDown {
int32 tid = 1;
int32 x = 2;
int32 y = 3;
int32 pressure = 4;
}
message TouchUp {
int32 tid = 1;
}
message TouchMove {
int32 tid = 1;
int32 x = 2;
int32 y = 3;
int32 pressure = 4;
}
message TouchWait {
uint32 wait = 1;
}
message TouchAction {
oneof action {
TouchDown down = 1;
TouchMove move = 2;
TouchWait wait = 3;
TouchUp up = 4;
}
}
message TouchSequence {
repeated TouchAction sequence = 1;
}
message PerformTouchRequest {
TouchSequence sequence = 1;
bool wait = 2;
}
message SetPropRequest {
string name = 1;
string value = 2;
}
message CertifiRequest {
bytes cert = 1;
}
message ShowToastRequest {
string text = 1;
ToastDuration duration = 2;
}
message ServerInfoResponse {
string uniqueId = 1;
string version = 2;
string architecture = 3;
uint64 uptime = 4;
bool secure = 5;
bool privileged = 6;
}
message HexPatchRequest {
string pattern = 1;
string replacement = 2;
string path = 3;
int32 maxreplace = 4;
bool dryrun = 5;
}
message HexPatchItem {
string path = 1;
int32 index = 2;
uint64 offset = 3;
}
message HexPatchResponse {
int32 count = 1;
repeated HexPatchItem replaces = 2;
}
message PlayAudioRequest {
string file = 1;
AudioStreamType type = 2;
int32 loop = 3;
int32 interval = 4;
}
```
--- FILE: rpc/wifi.proto ---
```protobuf
// Copyright 2022 rev1si0n (lamda.devel@gmail.com). All rights reserved.
//
// Distributed under MIT license.
// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
syntax = "proto3";
package lamda.rpc;
message WifiStatus {
string id = 1;
string address = 2;
string bssid = 3;
string freq = 4;
string group_cipher = 5;
string ip_address = 6;
string key_mgmt = 7;
string mode = 8;
string pairwise_cipher = 9;
string ssid = 10;
string wifi_generation = 11;
string wpa_state = 12;
}
message SignalPoll {
string RSSI = 1;
string LINKSPEED = 2;
string NOISE = 3;
string FREQUENCY = 4;
string WIDTH = 5;
string AVG_RSSI = 6;
string AVG_BEACON_RSSI = 7;
string CENTER_FRQ1 = 8;
}
message WifiInfo {
string id = 1;
string bssid = 2;
string ssid = 3;
string freq = 4;
string noise = 5;
string level = 6;
string tsf = 7;
string flags = 8;
}
message ScanResult {
repeated WifiInfo stations = 1;
}
message Network {
int32 nid = 1;
string bssid = 2;
string ssid = 3;
string flags = 4;
}
message NetworkList {
repeated Network networks = 1;
}
message NetworkConfig {
Network network = 1;
string name = 2;
string value = 3;
}
message WifiConfig {
string name = 1;
string value = 2;
}
message WifiBlacklist {
repeated string bssids = 1;
}
```