Hospital Android Adb
/install hospital-android-adb
Hospital Appointment Booking (医联 / Yilian)
Automate doctor appointment booking on the 医联 Android app (com.wonders.mobile.app.yilian). The app uses native Android views (no UC WebView issues). Use uiautomator2 for all interactions — Appium is NOT needed.
Before Any Action
import uiautomator2 as u2
d = u2.connect()
Kill 12306 first if running — both apps can't share the accessibility service:
import subprocess
subprocess.run(["adb", "shell", "am", "force-stop", "com.MobileTicket"])
Launch 医联:
subprocess.run(["adb", "shell", "monkey", "-p", "com.wonders.mobile.app.yilian",
"-c", "android.intent.category.LAUNCHER", "1"])
import time; time.sleep(5)
App Info
| Field | Value |
|---|---|
| Package | com.wonders.mobile.app.yilian |
| Activity chain | MainActivity → SearchActivity → HospitalHomePageActivity → DepartmentActivity → SpecialDiseaseActivity → SchedulingActivity → ReserveInfoActivity |
| UI type | Native Android (RecyclerView, LinearLayout, TextView) — no WebView issues |
| Input method | el.set_text("华山医院") via AccessibilityService — no IME needed |
⚠️ Key Technical Constraints
Chinese Text Input: Use set_text()
d.send_keys("华山医院") fails (clipboard permission denied). adb shell input text fails (NullPointerException on Chinese chars). Use set_text() instead:
# Focus the field, then set text via AccessibilityService ACTION_SET_TEXT
el = d(className='android.widget.EditText')
el.click() # focus first
time.sleep(0.5)
el.set_text("华山医院") # bypasses IME/clipboard entirely
This works because set_text() uses the Android AccessibilityService's ACTION_SET_TEXT action, which directly injects text into the focused EditText without needing an IME or clipboard permissions.
Element Clickability
Many department/button TextViews have clickable=false. Their parent container (LinearLayout/FrameLayout) has clickable=true. Always check parent containers by bounds containment:
for p in root.iter('node'):
if p.get('clickable') == 'true' and parent_bounds_contains(p, child_bounds):
# This is the actual clickable target
Schedule Data Loading Delay
The SchedulingActivity`` RecyclerView takes **2-10 seconds** to populate with available dates. After tapping a slot, poll the dump for date texts like "2026-06-01"` until they appear.
Screen Reading
Primary: uiautomator2 dump_hierarchy + XML parsing
xml = d.dump_hierarchy()
root = __import__('xml.etree.ElementTree', fromlist=['ElementTree']).fromstring(xml.encode())
Helper: find clickable elements with text
import re
for node in root.iter('node'):
text = node.get('text', '').strip()
bounds = node.get('bounds', '')
clickable = node.get('clickable')
if text and clickable == 'true':
nums = re.findall(r'\d+', bounds)
if len(nums) >= 4:
h = int(nums[3]) - int(nums[1])
if h > 20: # meaningful height
print(f"'{text}' at {bounds}")
Helper: find clickable parent by bounds
def find_clickable_parent(root, child_bounds):
child_nums = __import__('re').findall(r'\d+', child_bounds)
if len(child_nums) \x3C 4: return None
cx1, cy1, cx2, cy2 = int(child_nums[0]), int(child_nums[1]), int(child_nums[2]), int(child_nums[3])
for p in root.iter('node'):
if p.get('clickable') != 'true': continue
pn = __import__('re').findall(r'\d+', p.get('bounds', ''))
if len(pn) >= 4:
px1, py1, px2, py2 = int(pn[0]), int(pn[1]), int(pn[2]), int(pn[3])
if px1 \x3C= cx1 and py1 \x3C= cy1 and px2 >= cx2 and py2 >= cy2:
return p
return None
Fallback: Screenshot + OCR
d.screenshot('/tmp/screen.png')
import pytesseract
from PIL import Image
text = pytesseract.image_to_string(Image.open('/tmp/screen.png'),
lang='chi_sim', config='--psm 6')
Booking Flow
Step 1: Home Page — Search for Hospital
Home page UI:
- "搜医院、科室、医生" — search box (clickable) at top
- "当日挂号" — same-day appointments
- "预约挂号" — appointment booking label (NOT clickable)
d(text='搜医院、科室、医生').click()
time.sleep(2)
On the search page, type the hospital name:
el = d(className='android.widget.EditText')
el.click()
time.sleep(0.5)
el.set_text("华山医院") # set text via AccessibilityService
time.sleep(2) # wait for suggestions
After typing, the search results / suggestions appear. Tap the hospital card:
xml = d.dump_hierarchy()
if '华山医院' in xml:
# Tap at hospital card position
d.click(540, 1033) # center of hospital card
Step 2: Hospital Card → Tap to Enter
On the search result page, tap the hospital card to go to its detail page:
# Look for the hospital card in the results
for node in root.iter('node'):
if '华山医院' in node.get('text', ''):
parent = find_clickable_parent(root, node.get('bounds', ''))
# Tap parent's center
...
This navigates to HospitalHomePageActivity.
Step 3: Find "预约挂号" Icon
On the hospital detail page (HospitalHomePageActivity), there are icon buttons:
- 预约挂号 at column 2 (x: 270-540) in the first row
- 当日挂号 at column 3 (x: 540-810) in the first row
# The "预约挂号" icon is in the clickable container:
# Row 1: [0,1279][270,1522], [270,1279][540,1522], [540,1279][810,1522], [810,1279][1080,1522]
# Column 2 (270-540) = 预约挂号
d.click(405, 1400) # center of second icon
This navigates to DepartmentActivity.
Step 4: Select Appointment Type Tab + Department
On DepartmentActivity:
- Tabs at top: "专家门诊", "专病门诊", "普通门诊" (text, NOT clickable — tap their clickable parent)
- Department list shows available departments
First select the appointment type tab, then tap the target department:
# Tap "普通门诊" tab
d.click(900, 516) # center of third tab
# The department list refreshes. Now find "皮肤科":
xml = d.dump_hierarchy()
root = __import__('xml.etree.ElementTree', fromlist=['ElementTree']).fromstring(xml.encode())
for node in root.iter('node'):
if node.get('text', '').strip() == '皮肤科':
parent = find_clickable_parent(root, node.get('bounds', ''))
if parent:
nums = __import__('re').findall(r'\d+', parent.get('bounds', ''))
cx = (int(nums[0]) + int(nums[2])) // 2
cy = (int(nums[1]) + int(nums[3])) // 2
d.click(cx, cy)
This may navigate to SpecialDiseaseActivity (department detail).
Step 5: Select Appointment Entry
On SpecialDiseaseActivity, find the appointment type entry for the department:
# Look for entries like "普通门诊-[医联平台] ¥25" with "有号" (available)
for node in root.iter('node'):
t = node.get('text', '').strip()
if '有号' in t or '普通门诊' in t:
parent = find_clickable_parent(root, node.get('bounds', ''))
if parent:
# Tap to navigate to SchedulingActivity
Step 6: Select Date and Time Slot
On SchedulingActivity (activity == ".patient.ui.hospital.SchedulingActivity"):
The RecyclerView takes 2-10 seconds to populate. Poll for data:
# Wait for schedule data to load
for i in range(15):
time.sleep(1)
xml = d.dump_hierarchy()
if '2026-06-01' in xml: # target date appeared
break
# Target date will show as: "2026-06-01 星期一 上午"
# Find its clickable parent and tap
root = __import__('xml.etree.ElementTree', fromlist=['ElementTree']).fromstring(xml.encode())
for node in root.iter('node'):
if node.get('text', '').strip() == '2026-06-01 星期一 上午':
parent = find_clickable_parent(root, node.get('bounds', ''))
...
This navigates to ReserveInfoActivity.
Step 7: Confirm Appointment
On ReserveInfoActivity, verify all details:
- 医院: 华山医院
- 科室: 皮肤科
- 类型: 普通门诊
- 就诊日期: 2026-06-01 星期一 上午
- 就诊时段: 07:45-08:00 (changeable by tapping)
Patient selection: tap the patient name to select/deselect. Captcha section:
- "获取验证码" — tap to request SMS code
- "请输入验证码" — tap to enter code
- "确定预约" at bottom — tap to finalize
SMS verification code is required — inform the user to check their phone.
# Tap 确定预约 after captcha is entered
d.click(540, 2292)
Activity Flow Reference
| # | Activity | What You See |
|---|---|---|
| 1 | .patient.ui.MainActivity |
Home page: search box, 当日挂号 |
| 2 | .patient.ui.hospital.search.SearchActivity |
Hospital search results / suggestions |
| 3 | .patient.ui.hospital.HospitalHomePageActivity |
Hospital detail: 预约挂号 icon |
| 4 | .patient.ui.hospital.depart.DepartmentActivity |
Department list with tabs |
| 5 | .patient.ui.hospital.SpecialDiseaseActivity |
Department detail: appointment type entry |
| 6 | .patient.ui.hospital.SchedulingActivity |
Date/time slot picker (RecyclerView) |
| 7 | .patient.ui.hospital.ReserveInfoActivity |
Confirmation: details, captcha, 确定预约 |
Key Elements Reference
| Element | Where | Notes |
|---|---|---|
| 搜医院、科室、医生 | Home page | Clickable search box |
| 预约挂号 (icon) | Hospital page | 2nd icon in row 1 |
| 当日挂号 | Hospital page | 3rd icon in row 1 |
| 普通门诊 | Department page | Tab, 3rd position |
| 皮肤科 / other dept | Department page | In scroll list |
| 有号 | Various | Indicates availability |
| 确定预约 | Reserve page | Submit button |
| 获取验证码 | Reserve page | Request SMS |
| 请输入验证码 | Reserve page | Captcha input |
Common Pitfalls
- 12306 conflict: Can't run both apps' automation simultaneously. Force-stop 12306 first.
- Chinese text input: NOT supported via ADB. Use hot search suggestions or pre-existing text.
- Schedule loading delay: RecyclerView populates asynchronously. Poll for date text (up to 15s).
- Elements with clickable=false: TextViews may not be clickable — tap their parent container instead.
- ADB disconnects: USB can drop. Run
adb kill-server && adb start-serverto recover. - SMS captcha: The user must enter the verification code manually.
- Patient selection: Verify correct patient is checked before confirming.
Decision Tree
d.dump_hierarchy() + check activity:
├─ activity == "MainActivity" → HOME
│ → tap search box → tap hot suggestion
├─ activity == "SearchActivity" → SEARCH RESULTS
│ → tap hospital card
├─ activity == "HospitalHomePageActivity" → HOSPITAL PAGE
│ → tap 预约挂号 icon (2nd icon, 1st row)
├─ activity == "DepartmentActivity" → DEPARTMENTS
│ → select tab (普通门诊) → tap department name
├─ activity == "SpecialDiseaseActivity" → DEPT DETAIL
│ → tap available appointment entry (有号)
├─ activity == "SchedulingActivity" → DATE PICKER
│ → wait for data → tap target date/time slot
├─ activity == "ReserveInfoActivity" → CONFIRMATION
│ → verify details → get SMS captcha → tap 确定预约
└─ otherwise → unknown state, take screenshot
Files
- Uses
uiautomator2Python library only (no Appium) - No app-specific scripts needed
- 确保已安装 OpenClaw(本地或 Docker 部署)
- 在对话框中输入安装命令:
/install hospital-android-adb - 安装完成后,直接呼叫该 Skill 的名称或使用
/hospital-android-adb触发 - 根据 Skill 的参数说明提供必要输入,即可获得结构化输出
Hospital Android Adb 是什么?
Automate hospital appointment booking on the 医联 (Yilian) Android app using uiautomator2. Covers hospital search (with Chinese text input), department selecti... 它是一个面向 Claude Code / OpenClaw 的 AI Agent Skill 插件,目前累计下载 48 次。
如何安装 Hospital Android Adb?
在 OpenClaw 或 Claude Code 对话框中运行命令「/install hospital-android-adb」即可一键安装,无需额外配置。
Hospital Android Adb 是免费的吗?
是的,Hospital Android Adb 完全免费,采用 MIT-0 许可证,可自由下载、安装和使用。
Hospital Android Adb 支持哪些平台?
Hospital Android Adb 跨平台运行,可在任意部署了 OpenClaw / Claude Code 的环境中使用(cross-platform)。
谁开发了 Hospital Android Adb?
由 openlittlebear(@openlittlebear)开发并维护,当前版本 v1.0.0。