# 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.

Remote Desktop Demo

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.

MCP Demo

## 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.

Remote desktop example

```{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. ```

APP interface

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.

Remote desktop interface example

```{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. ![Remote Desktop](/assets/images/frida-remote-desktop.png) 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. ``` ![Select Element](/assets/images/auto-eyeselect.png) 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`. ![Inspect Element](/assets/images/inspect-demo.gif) ```{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.

Screen Coordinates

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.

Screen Area

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.

APP Configuration

### 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.

WebUI Configuration

### 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). ![Remote Desktop](/assets/images/remote-desktop.png) ## 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 Upload](/assets/images/file-upload.png) ## 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. ![File Download](/assets/images/file-download.png) ## 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. ![WebUI Settings](/assets/images/settings.png) | 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. ``` ![mdns-beacon](/assets/images/mdns-beacon.png) 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. ![Sample Element](/assets/images/auto-eyeselect.png) ```{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. ![Text Input](/assets/images/input-text.png) ```{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.

Example Layout

```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. ![Frida](/assets/images/frida-remote-desktop.png) 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. ![Dynamic Debugging](/assets/images/IDA.png) ## 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. ![Dynamic Debugging](/assets/images/debugger-setdebuggable.png) ## 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 ``` ![Dynamic Debugging](/assets/images/debugger-cmd.png) 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. ![Dynamic Debugging](/assets/images/debugger-attach.png) ![Dynamic Debugging](/assets/images/debugger-setup.png)

Dynamic Debugging

![Dynamic Debugging](/assets/images/debugger-start.png) 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.

Manual Service

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/"]}}} ```

example

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/"}}} ```

example

```{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; } ```