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
- Make sure OpenClaw is installed (local or Docker)
- Run the install command in chat:
/install hospital-android-adb - After installation, invoke the skill by name or use
/hospital-android-adb - Provide required inputs per the skill's parameter spec and get structured output
What is Hospital Android Adb?
Automate hospital appointment booking on the 医联 (Yilian) Android app using uiautomator2. Covers hospital search (with Chinese text input), department selecti... It is an AI Agent Skill for Claude Code / OpenClaw, with 48 downloads so far.
How do I install Hospital Android Adb?
Run "/install hospital-android-adb" in the OpenClaw or Claude Code chat to install it in one step — no extra setup required.
Is Hospital Android Adb free?
Yes, Hospital Android Adb is completely free, licensed under MIT-0. You can download, install and use it at no cost.
Which platforms does Hospital Android Adb support?
Hospital Android Adb is cross-platform and runs anywhere OpenClaw / Claude Code is available (cross-platform).
Who created Hospital Android Adb?
It is built and maintained by openlittlebear (@openlittlebear); the current version is v1.0.0.