autoglm-gui 0.2.0__py3-none-any.whl → 0.2.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {autoglm_gui-0.2.0.dist-info → autoglm_gui-0.2.3.dist-info}/METADATA +2 -3
- autoglm_gui-0.2.3.dist-info/RECORD +35 -0
- phone_agent/__init__.py +11 -0
- phone_agent/actions/__init__.py +5 -0
- phone_agent/actions/handler.py +307 -0
- phone_agent/adb/__init__.py +51 -0
- phone_agent/adb/connection.py +350 -0
- phone_agent/adb/device.py +224 -0
- phone_agent/adb/input.py +109 -0
- phone_agent/adb/screenshot.py +109 -0
- phone_agent/agent.py +253 -0
- phone_agent/config/__init__.py +35 -0
- phone_agent/config/apps.py +227 -0
- phone_agent/config/i18n.py +73 -0
- phone_agent/config/prompts.py +75 -0
- phone_agent/config/prompts_en.py +79 -0
- phone_agent/config/prompts_zh.py +77 -0
- phone_agent/model/__init__.py +5 -0
- phone_agent/model/client.py +190 -0
- scrcpy-server-v3.3.3 +0 -0
- autoglm_gui-0.2.0.dist-info/RECORD +0 -17
- {autoglm_gui-0.2.0.dist-info → autoglm_gui-0.2.3.dist-info}/WHEEL +0 -0
- {autoglm_gui-0.2.0.dist-info → autoglm_gui-0.2.3.dist-info}/entry_points.txt +0 -0
- {autoglm_gui-0.2.0.dist-info → autoglm_gui-0.2.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Screenshot utilities for capturing Android device screen."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
import tempfile
|
|
7
|
+
import uuid
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from io import BytesIO
|
|
10
|
+
from typing import Tuple
|
|
11
|
+
|
|
12
|
+
from PIL import Image
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class Screenshot:
|
|
17
|
+
"""Represents a captured screenshot."""
|
|
18
|
+
|
|
19
|
+
base64_data: str
|
|
20
|
+
width: int
|
|
21
|
+
height: int
|
|
22
|
+
is_sensitive: bool = False
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_screenshot(device_id: str | None = None, timeout: int = 10) -> Screenshot:
|
|
26
|
+
"""
|
|
27
|
+
Capture a screenshot from the connected Android device.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
device_id: Optional ADB device ID for multi-device setups.
|
|
31
|
+
timeout: Timeout in seconds for screenshot operations.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Screenshot object containing base64 data and dimensions.
|
|
35
|
+
|
|
36
|
+
Note:
|
|
37
|
+
If the screenshot fails (e.g., on sensitive screens like payment pages),
|
|
38
|
+
a black fallback image is returned with is_sensitive=True.
|
|
39
|
+
"""
|
|
40
|
+
temp_path = os.path.join(tempfile.gettempdir(), f"screenshot_{uuid.uuid4()}.png")
|
|
41
|
+
adb_prefix = _get_adb_prefix(device_id)
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
# Execute screenshot command
|
|
45
|
+
result = subprocess.run(
|
|
46
|
+
adb_prefix + ["shell", "screencap", "-p", "/sdcard/tmp.png"],
|
|
47
|
+
capture_output=True,
|
|
48
|
+
text=True,
|
|
49
|
+
timeout=timeout,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Check for screenshot failure (sensitive screen)
|
|
53
|
+
output = result.stdout + result.stderr
|
|
54
|
+
if "Status: -1" in output or "Failed" in output:
|
|
55
|
+
return _create_fallback_screenshot(is_sensitive=True)
|
|
56
|
+
|
|
57
|
+
# Pull screenshot to local temp path
|
|
58
|
+
subprocess.run(
|
|
59
|
+
adb_prefix + ["pull", "/sdcard/tmp.png", temp_path],
|
|
60
|
+
capture_output=True,
|
|
61
|
+
text=True,
|
|
62
|
+
timeout=5,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if not os.path.exists(temp_path):
|
|
66
|
+
return _create_fallback_screenshot(is_sensitive=False)
|
|
67
|
+
|
|
68
|
+
# Read and encode image
|
|
69
|
+
img = Image.open(temp_path)
|
|
70
|
+
width, height = img.size
|
|
71
|
+
|
|
72
|
+
buffered = BytesIO()
|
|
73
|
+
img.save(buffered, format="PNG")
|
|
74
|
+
base64_data = base64.b64encode(buffered.getvalue()).decode("utf-8")
|
|
75
|
+
|
|
76
|
+
# Cleanup
|
|
77
|
+
os.remove(temp_path)
|
|
78
|
+
|
|
79
|
+
return Screenshot(
|
|
80
|
+
base64_data=base64_data, width=width, height=height, is_sensitive=False
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
except Exception as e:
|
|
84
|
+
print(f"Screenshot error: {e}")
|
|
85
|
+
return _create_fallback_screenshot(is_sensitive=False)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _get_adb_prefix(device_id: str | None) -> list:
|
|
89
|
+
"""Get ADB command prefix with optional device specifier."""
|
|
90
|
+
if device_id:
|
|
91
|
+
return ["adb", "-s", device_id]
|
|
92
|
+
return ["adb"]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _create_fallback_screenshot(is_sensitive: bool) -> Screenshot:
|
|
96
|
+
"""Create a black fallback image when screenshot fails."""
|
|
97
|
+
default_width, default_height = 1080, 2400
|
|
98
|
+
|
|
99
|
+
black_img = Image.new("RGB", (default_width, default_height), color="black")
|
|
100
|
+
buffered = BytesIO()
|
|
101
|
+
black_img.save(buffered, format="PNG")
|
|
102
|
+
base64_data = base64.b64encode(buffered.getvalue()).decode("utf-8")
|
|
103
|
+
|
|
104
|
+
return Screenshot(
|
|
105
|
+
base64_data=base64_data,
|
|
106
|
+
width=default_width,
|
|
107
|
+
height=default_height,
|
|
108
|
+
is_sensitive=is_sensitive,
|
|
109
|
+
)
|
phone_agent/agent.py
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"""Main PhoneAgent class for orchestrating phone automation."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import traceback
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any, Callable
|
|
7
|
+
|
|
8
|
+
from phone_agent.actions import ActionHandler
|
|
9
|
+
from phone_agent.actions.handler import do, finish, parse_action
|
|
10
|
+
from phone_agent.adb import get_current_app, get_screenshot
|
|
11
|
+
from phone_agent.config import get_messages, get_system_prompt
|
|
12
|
+
from phone_agent.model import ModelClient, ModelConfig
|
|
13
|
+
from phone_agent.model.client import MessageBuilder
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class AgentConfig:
|
|
18
|
+
"""Configuration for the PhoneAgent."""
|
|
19
|
+
|
|
20
|
+
max_steps: int = 100
|
|
21
|
+
device_id: str | None = None
|
|
22
|
+
lang: str = "cn"
|
|
23
|
+
system_prompt: str | None = None
|
|
24
|
+
verbose: bool = True
|
|
25
|
+
|
|
26
|
+
def __post_init__(self):
|
|
27
|
+
if self.system_prompt is None:
|
|
28
|
+
self.system_prompt = get_system_prompt(self.lang)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class StepResult:
|
|
33
|
+
"""Result of a single agent step."""
|
|
34
|
+
|
|
35
|
+
success: bool
|
|
36
|
+
finished: bool
|
|
37
|
+
action: dict[str, Any] | None
|
|
38
|
+
thinking: str
|
|
39
|
+
message: str | None = None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class PhoneAgent:
|
|
43
|
+
"""
|
|
44
|
+
AI-powered agent for automating Android phone interactions.
|
|
45
|
+
|
|
46
|
+
The agent uses a vision-language model to understand screen content
|
|
47
|
+
and decide on actions to complete user tasks.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
model_config: Configuration for the AI model.
|
|
51
|
+
agent_config: Configuration for the agent behavior.
|
|
52
|
+
confirmation_callback: Optional callback for sensitive action confirmation.
|
|
53
|
+
takeover_callback: Optional callback for takeover requests.
|
|
54
|
+
|
|
55
|
+
Example:
|
|
56
|
+
>>> from phone_agent import PhoneAgent
|
|
57
|
+
>>> from phone_agent.model import ModelConfig
|
|
58
|
+
>>>
|
|
59
|
+
>>> model_config = ModelConfig(base_url="http://localhost:8000/v1")
|
|
60
|
+
>>> agent = PhoneAgent(model_config)
|
|
61
|
+
>>> agent.run("Open WeChat and send a message to John")
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
model_config: ModelConfig | None = None,
|
|
67
|
+
agent_config: AgentConfig | None = None,
|
|
68
|
+
confirmation_callback: Callable[[str], bool] | None = None,
|
|
69
|
+
takeover_callback: Callable[[str], None] | None = None,
|
|
70
|
+
):
|
|
71
|
+
self.model_config = model_config or ModelConfig()
|
|
72
|
+
self.agent_config = agent_config or AgentConfig()
|
|
73
|
+
|
|
74
|
+
self.model_client = ModelClient(self.model_config)
|
|
75
|
+
self.action_handler = ActionHandler(
|
|
76
|
+
device_id=self.agent_config.device_id,
|
|
77
|
+
confirmation_callback=confirmation_callback,
|
|
78
|
+
takeover_callback=takeover_callback,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
self._context: list[dict[str, Any]] = []
|
|
82
|
+
self._step_count = 0
|
|
83
|
+
|
|
84
|
+
def run(self, task: str) -> str:
|
|
85
|
+
"""
|
|
86
|
+
Run the agent to complete a task.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
task: Natural language description of the task.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Final message from the agent.
|
|
93
|
+
"""
|
|
94
|
+
self._context = []
|
|
95
|
+
self._step_count = 0
|
|
96
|
+
|
|
97
|
+
# First step with user prompt
|
|
98
|
+
result = self._execute_step(task, is_first=True)
|
|
99
|
+
|
|
100
|
+
if result.finished:
|
|
101
|
+
return result.message or "Task completed"
|
|
102
|
+
|
|
103
|
+
# Continue until finished or max steps reached
|
|
104
|
+
while self._step_count < self.agent_config.max_steps:
|
|
105
|
+
result = self._execute_step(is_first=False)
|
|
106
|
+
|
|
107
|
+
if result.finished:
|
|
108
|
+
return result.message or "Task completed"
|
|
109
|
+
|
|
110
|
+
return "Max steps reached"
|
|
111
|
+
|
|
112
|
+
def step(self, task: str | None = None) -> StepResult:
|
|
113
|
+
"""
|
|
114
|
+
Execute a single step of the agent.
|
|
115
|
+
|
|
116
|
+
Useful for manual control or debugging.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
task: Task description (only needed for first step).
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
StepResult with step details.
|
|
123
|
+
"""
|
|
124
|
+
is_first = len(self._context) == 0
|
|
125
|
+
|
|
126
|
+
if is_first and not task:
|
|
127
|
+
raise ValueError("Task is required for the first step")
|
|
128
|
+
|
|
129
|
+
return self._execute_step(task, is_first)
|
|
130
|
+
|
|
131
|
+
def reset(self) -> None:
|
|
132
|
+
"""Reset the agent state for a new task."""
|
|
133
|
+
self._context = []
|
|
134
|
+
self._step_count = 0
|
|
135
|
+
|
|
136
|
+
def _execute_step(
|
|
137
|
+
self, user_prompt: str | None = None, is_first: bool = False
|
|
138
|
+
) -> StepResult:
|
|
139
|
+
"""Execute a single step of the agent loop."""
|
|
140
|
+
self._step_count += 1
|
|
141
|
+
|
|
142
|
+
# Capture current screen state
|
|
143
|
+
screenshot = get_screenshot(self.agent_config.device_id)
|
|
144
|
+
current_app = get_current_app(self.agent_config.device_id)
|
|
145
|
+
|
|
146
|
+
# Build messages
|
|
147
|
+
if is_first:
|
|
148
|
+
self._context.append(
|
|
149
|
+
MessageBuilder.create_system_message(self.agent_config.system_prompt)
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
screen_info = MessageBuilder.build_screen_info(current_app)
|
|
153
|
+
text_content = f"{user_prompt}\n\n{screen_info}"
|
|
154
|
+
|
|
155
|
+
self._context.append(
|
|
156
|
+
MessageBuilder.create_user_message(
|
|
157
|
+
text=text_content, image_base64=screenshot.base64_data
|
|
158
|
+
)
|
|
159
|
+
)
|
|
160
|
+
else:
|
|
161
|
+
screen_info = MessageBuilder.build_screen_info(current_app)
|
|
162
|
+
text_content = f"** Screen Info **\n\n{screen_info}"
|
|
163
|
+
|
|
164
|
+
self._context.append(
|
|
165
|
+
MessageBuilder.create_user_message(
|
|
166
|
+
text=text_content, image_base64=screenshot.base64_data
|
|
167
|
+
)
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# Get model response
|
|
171
|
+
try:
|
|
172
|
+
response = self.model_client.request(self._context)
|
|
173
|
+
except Exception as e:
|
|
174
|
+
if self.agent_config.verbose:
|
|
175
|
+
traceback.print_exc()
|
|
176
|
+
return StepResult(
|
|
177
|
+
success=False,
|
|
178
|
+
finished=True,
|
|
179
|
+
action=None,
|
|
180
|
+
thinking="",
|
|
181
|
+
message=f"Model error: {e}",
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Parse action from response
|
|
185
|
+
try:
|
|
186
|
+
action = parse_action(response.action)
|
|
187
|
+
except ValueError:
|
|
188
|
+
if self.agent_config.verbose:
|
|
189
|
+
traceback.print_exc()
|
|
190
|
+
action = finish(message=response.action)
|
|
191
|
+
|
|
192
|
+
if self.agent_config.verbose:
|
|
193
|
+
# Print thinking process
|
|
194
|
+
msgs = get_messages(self.agent_config.lang)
|
|
195
|
+
print("\n" + "=" * 50)
|
|
196
|
+
print(f"💭 {msgs['thinking']}:")
|
|
197
|
+
print("-" * 50)
|
|
198
|
+
print(response.thinking)
|
|
199
|
+
print("-" * 50)
|
|
200
|
+
print(f"🎯 {msgs['action']}:")
|
|
201
|
+
print(json.dumps(action, ensure_ascii=False, indent=2))
|
|
202
|
+
print("=" * 50 + "\n")
|
|
203
|
+
|
|
204
|
+
# Remove image from context to save space
|
|
205
|
+
self._context[-1] = MessageBuilder.remove_images_from_message(self._context[-1])
|
|
206
|
+
|
|
207
|
+
# Execute action
|
|
208
|
+
try:
|
|
209
|
+
result = self.action_handler.execute(
|
|
210
|
+
action, screenshot.width, screenshot.height
|
|
211
|
+
)
|
|
212
|
+
except Exception as e:
|
|
213
|
+
if self.agent_config.verbose:
|
|
214
|
+
traceback.print_exc()
|
|
215
|
+
result = self.action_handler.execute(
|
|
216
|
+
finish(message=str(e)), screenshot.width, screenshot.height
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Add assistant response to context
|
|
220
|
+
self._context.append(
|
|
221
|
+
MessageBuilder.create_assistant_message(
|
|
222
|
+
f"<think>{response.thinking}</think><answer>{response.action}</answer>"
|
|
223
|
+
)
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# Check if finished
|
|
227
|
+
finished = action.get("_metadata") == "finish" or result.should_finish
|
|
228
|
+
|
|
229
|
+
if finished and self.agent_config.verbose:
|
|
230
|
+
msgs = get_messages(self.agent_config.lang)
|
|
231
|
+
print("\n" + "🎉 " + "=" * 48)
|
|
232
|
+
print(
|
|
233
|
+
f"✅ {msgs['task_completed']}: {result.message or action.get('message', msgs['done'])}"
|
|
234
|
+
)
|
|
235
|
+
print("=" * 50 + "\n")
|
|
236
|
+
|
|
237
|
+
return StepResult(
|
|
238
|
+
success=result.success,
|
|
239
|
+
finished=finished,
|
|
240
|
+
action=action,
|
|
241
|
+
thinking=response.thinking,
|
|
242
|
+
message=result.message or action.get("message"),
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def context(self) -> list[dict[str, Any]]:
|
|
247
|
+
"""Get the current conversation context."""
|
|
248
|
+
return self._context.copy()
|
|
249
|
+
|
|
250
|
+
@property
|
|
251
|
+
def step_count(self) -> int:
|
|
252
|
+
"""Get the current step count."""
|
|
253
|
+
return self._step_count
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Configuration module for Phone Agent."""
|
|
2
|
+
|
|
3
|
+
from phone_agent.config.apps import APP_PACKAGES
|
|
4
|
+
from phone_agent.config.i18n import get_message, get_messages
|
|
5
|
+
from phone_agent.config.prompts_en import SYSTEM_PROMPT as SYSTEM_PROMPT_EN
|
|
6
|
+
from phone_agent.config.prompts_zh import SYSTEM_PROMPT as SYSTEM_PROMPT_ZH
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_system_prompt(lang: str = "cn") -> str:
|
|
10
|
+
"""
|
|
11
|
+
Get system prompt by language.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
lang: Language code, 'cn' for Chinese, 'en' for English.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
System prompt string.
|
|
18
|
+
"""
|
|
19
|
+
if lang == "en":
|
|
20
|
+
return SYSTEM_PROMPT_EN
|
|
21
|
+
return SYSTEM_PROMPT_ZH
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Default to Chinese for backward compatibility
|
|
25
|
+
SYSTEM_PROMPT = SYSTEM_PROMPT_ZH
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"APP_PACKAGES",
|
|
29
|
+
"SYSTEM_PROMPT",
|
|
30
|
+
"SYSTEM_PROMPT_ZH",
|
|
31
|
+
"SYSTEM_PROMPT_EN",
|
|
32
|
+
"get_system_prompt",
|
|
33
|
+
"get_messages",
|
|
34
|
+
"get_message",
|
|
35
|
+
]
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""App name to package name mapping for supported applications."""
|
|
2
|
+
|
|
3
|
+
APP_PACKAGES: dict[str, str] = {
|
|
4
|
+
# Social & Messaging
|
|
5
|
+
"微信": "com.tencent.mm",
|
|
6
|
+
"QQ": "com.tencent.mobileqq",
|
|
7
|
+
"微博": "com.sina.weibo",
|
|
8
|
+
# E-commerce
|
|
9
|
+
"淘宝": "com.taobao.taobao",
|
|
10
|
+
"京东": "com.jingdong.app.mall",
|
|
11
|
+
"拼多多": "com.xunmeng.pinduoduo",
|
|
12
|
+
"淘宝闪购": "com.taobao.taobao",
|
|
13
|
+
"京东秒送": "com.jingdong.app.mall",
|
|
14
|
+
# Lifestyle & Social
|
|
15
|
+
"小红书": "com.xingin.xhs",
|
|
16
|
+
"豆瓣": "com.douban.frodo",
|
|
17
|
+
"知乎": "com.zhihu.android",
|
|
18
|
+
# Maps & Navigation
|
|
19
|
+
"高德地图": "com.autonavi.minimap",
|
|
20
|
+
"百度地图": "com.baidu.BaiduMap",
|
|
21
|
+
# Food & Services
|
|
22
|
+
"美团": "com.sankuai.meituan",
|
|
23
|
+
"大众点评": "com.dianping.v1",
|
|
24
|
+
"饿了么": "me.ele",
|
|
25
|
+
"肯德基": "com.yek.android.kfc.activitys",
|
|
26
|
+
# Travel
|
|
27
|
+
"携程": "ctrip.android.view",
|
|
28
|
+
"铁路12306": "com.MobileTicket",
|
|
29
|
+
"12306": "com.MobileTicket",
|
|
30
|
+
"去哪儿": "com.Qunar",
|
|
31
|
+
"去哪儿旅行": "com.Qunar",
|
|
32
|
+
"滴滴出行": "com.sdu.did.psnger",
|
|
33
|
+
# Video & Entertainment
|
|
34
|
+
"bilibili": "tv.danmaku.bili",
|
|
35
|
+
"抖音": "com.ss.android.ugc.aweme",
|
|
36
|
+
"快手": "com.smile.gifmaker",
|
|
37
|
+
"腾讯视频": "com.tencent.qqlive",
|
|
38
|
+
"爱奇艺": "com.qiyi.video",
|
|
39
|
+
"优酷视频": "com.youku.phone",
|
|
40
|
+
"芒果TV": "com.hunantv.imgo.activity",
|
|
41
|
+
"红果短剧": "com.phoenix.read",
|
|
42
|
+
# Music & Audio
|
|
43
|
+
"网易云音乐": "com.netease.cloudmusic",
|
|
44
|
+
"QQ音乐": "com.tencent.qqmusic",
|
|
45
|
+
"汽水音乐": "com.luna.music",
|
|
46
|
+
"喜马拉雅": "com.ximalaya.ting.android",
|
|
47
|
+
# Reading
|
|
48
|
+
"番茄小说": "com.dragon.read",
|
|
49
|
+
"番茄免费小说": "com.dragon.read",
|
|
50
|
+
"七猫免费小说": "com.kmxs.reader",
|
|
51
|
+
# Productivity
|
|
52
|
+
"飞书": "com.ss.android.lark",
|
|
53
|
+
"QQ邮箱": "com.tencent.androidqqmail",
|
|
54
|
+
# AI & Tools
|
|
55
|
+
"豆包": "com.larus.nova",
|
|
56
|
+
# Health & Fitness
|
|
57
|
+
"keep": "com.gotokeep.keep",
|
|
58
|
+
"美柚": "com.lingan.seeyou",
|
|
59
|
+
# News & Information
|
|
60
|
+
"腾讯新闻": "com.tencent.news",
|
|
61
|
+
"今日头条": "com.ss.android.article.news",
|
|
62
|
+
# Real Estate
|
|
63
|
+
"贝壳找房": "com.lianjia.beike",
|
|
64
|
+
"安居客": "com.anjuke.android.app",
|
|
65
|
+
# Finance
|
|
66
|
+
"同花顺": "com.hexin.plat.android",
|
|
67
|
+
# Games
|
|
68
|
+
"星穹铁道": "com.miHoYo.hkrpg",
|
|
69
|
+
"崩坏:星穹铁道": "com.miHoYo.hkrpg",
|
|
70
|
+
"恋与深空": "com.papegames.lysk.cn",
|
|
71
|
+
"AndroidSystemSettings": "com.android.settings",
|
|
72
|
+
"Android System Settings": "com.android.settings",
|
|
73
|
+
"Android System Settings": "com.android.settings",
|
|
74
|
+
"Android-System-Settings": "com.android.settings",
|
|
75
|
+
"Settings": "com.android.settings",
|
|
76
|
+
"AudioRecorder": "com.android.soundrecorder",
|
|
77
|
+
"audiorecorder": "com.android.soundrecorder",
|
|
78
|
+
"Bluecoins": "com.rammigsoftware.bluecoins",
|
|
79
|
+
"bluecoins": "com.rammigsoftware.bluecoins",
|
|
80
|
+
"Broccoli": "com.flauschcode.broccoli",
|
|
81
|
+
"broccoli": "com.flauschcode.broccoli",
|
|
82
|
+
"Booking.com": "com.booking",
|
|
83
|
+
"Booking": "com.booking",
|
|
84
|
+
"booking.com": "com.booking",
|
|
85
|
+
"booking": "com.booking",
|
|
86
|
+
"BOOKING.COM": "com.booking",
|
|
87
|
+
"Chrome": "com.android.chrome",
|
|
88
|
+
"chrome": "com.android.chrome",
|
|
89
|
+
"Google Chrome": "com.android.chrome",
|
|
90
|
+
"Clock": "com.android.deskclock",
|
|
91
|
+
"clock": "com.android.deskclock",
|
|
92
|
+
"Contacts": "com.android.contacts",
|
|
93
|
+
"contacts": "com.android.contacts",
|
|
94
|
+
"Duolingo": "com.duolingo",
|
|
95
|
+
"duolingo": "com.duolingo",
|
|
96
|
+
"Expedia": "com.expedia.bookings",
|
|
97
|
+
"expedia": "com.expedia.bookings",
|
|
98
|
+
"Files": "com.android.fileexplorer",
|
|
99
|
+
"files": "com.android.fileexplorer",
|
|
100
|
+
"File Manager": "com.android.fileexplorer",
|
|
101
|
+
"file manager": "com.android.fileexplorer",
|
|
102
|
+
"gmail": "com.google.android.gm",
|
|
103
|
+
"Gmail": "com.google.android.gm",
|
|
104
|
+
"GoogleMail": "com.google.android.gm",
|
|
105
|
+
"Google Mail": "com.google.android.gm",
|
|
106
|
+
"GoogleFiles": "com.google.android.apps.nbu.files",
|
|
107
|
+
"googlefiles": "com.google.android.apps.nbu.files",
|
|
108
|
+
"FilesbyGoogle": "com.google.android.apps.nbu.files",
|
|
109
|
+
"GoogleCalendar": "com.google.android.calendar",
|
|
110
|
+
"Google-Calendar": "com.google.android.calendar",
|
|
111
|
+
"Google Calendar": "com.google.android.calendar",
|
|
112
|
+
"google-calendar": "com.google.android.calendar",
|
|
113
|
+
"google calendar": "com.google.android.calendar",
|
|
114
|
+
"GoogleChat": "com.google.android.apps.dynamite",
|
|
115
|
+
"Google Chat": "com.google.android.apps.dynamite",
|
|
116
|
+
"Google-Chat": "com.google.android.apps.dynamite",
|
|
117
|
+
"GoogleClock": "com.google.android.deskclock",
|
|
118
|
+
"Google Clock": "com.google.android.deskclock",
|
|
119
|
+
"Google-Clock": "com.google.android.deskclock",
|
|
120
|
+
"GoogleContacts": "com.google.android.contacts",
|
|
121
|
+
"Google-Contacts": "com.google.android.contacts",
|
|
122
|
+
"Google Contacts": "com.google.android.contacts",
|
|
123
|
+
"google-contacts": "com.google.android.contacts",
|
|
124
|
+
"google contacts": "com.google.android.contacts",
|
|
125
|
+
"GoogleDocs": "com.google.android.apps.docs.editors.docs",
|
|
126
|
+
"Google Docs": "com.google.android.apps.docs.editors.docs",
|
|
127
|
+
"googledocs": "com.google.android.apps.docs.editors.docs",
|
|
128
|
+
"google docs": "com.google.android.apps.docs.editors.docs",
|
|
129
|
+
"Google Drive": "com.google.android.apps.docs",
|
|
130
|
+
"Google-Drive": "com.google.android.apps.docs",
|
|
131
|
+
"google drive": "com.google.android.apps.docs",
|
|
132
|
+
"google-drive": "com.google.android.apps.docs",
|
|
133
|
+
"GoogleDrive": "com.google.android.apps.docs",
|
|
134
|
+
"Googledrive": "com.google.android.apps.docs",
|
|
135
|
+
"googledrive": "com.google.android.apps.docs",
|
|
136
|
+
"GoogleFit": "com.google.android.apps.fitness",
|
|
137
|
+
"googlefit": "com.google.android.apps.fitness",
|
|
138
|
+
"GoogleKeep": "com.google.android.keep",
|
|
139
|
+
"googlekeep": "com.google.android.keep",
|
|
140
|
+
"GoogleMaps": "com.google.android.apps.maps",
|
|
141
|
+
"Google Maps": "com.google.android.apps.maps",
|
|
142
|
+
"googlemaps": "com.google.android.apps.maps",
|
|
143
|
+
"google maps": "com.google.android.apps.maps",
|
|
144
|
+
"Google Play Books": "com.google.android.apps.books",
|
|
145
|
+
"Google-Play-Books": "com.google.android.apps.books",
|
|
146
|
+
"google play books": "com.google.android.apps.books",
|
|
147
|
+
"google-play-books": "com.google.android.apps.books",
|
|
148
|
+
"GooglePlayBooks": "com.google.android.apps.books",
|
|
149
|
+
"googleplaybooks": "com.google.android.apps.books",
|
|
150
|
+
"GooglePlayStore": "com.android.vending",
|
|
151
|
+
"Google Play Store": "com.android.vending",
|
|
152
|
+
"Google-Play-Store": "com.android.vending",
|
|
153
|
+
"GoogleSlides": "com.google.android.apps.docs.editors.slides",
|
|
154
|
+
"Google Slides": "com.google.android.apps.docs.editors.slides",
|
|
155
|
+
"Google-Slides": "com.google.android.apps.docs.editors.slides",
|
|
156
|
+
"GoogleTasks": "com.google.android.apps.tasks",
|
|
157
|
+
"Google Tasks": "com.google.android.apps.tasks",
|
|
158
|
+
"Google-Tasks": "com.google.android.apps.tasks",
|
|
159
|
+
"Joplin": "net.cozic.joplin",
|
|
160
|
+
"joplin": "net.cozic.joplin",
|
|
161
|
+
"McDonald": "com.mcdonalds.app",
|
|
162
|
+
"mcdonald": "com.mcdonalds.app",
|
|
163
|
+
"Osmand": "net.osmand",
|
|
164
|
+
"osmand": "net.osmand",
|
|
165
|
+
"PiMusicPlayer": "com.Project100Pi.themusicplayer",
|
|
166
|
+
"pimusicplayer": "com.Project100Pi.themusicplayer",
|
|
167
|
+
"Quora": "com.quora.android",
|
|
168
|
+
"quora": "com.quora.android",
|
|
169
|
+
"Reddit": "com.reddit.frontpage",
|
|
170
|
+
"reddit": "com.reddit.frontpage",
|
|
171
|
+
"RetroMusic": "code.name.monkey.retromusic",
|
|
172
|
+
"retromusic": "code.name.monkey.retromusic",
|
|
173
|
+
"SimpleCalendarPro": "com.scientificcalculatorplus.simplecalculator.basiccalculator.mathcalc",
|
|
174
|
+
"SimpleSMSMessenger": "com.simplemobiletools.smsmessenger",
|
|
175
|
+
"Telegram": "org.telegram.messenger",
|
|
176
|
+
"temu": "com.einnovation.temu",
|
|
177
|
+
"Temu": "com.einnovation.temu",
|
|
178
|
+
"Tiktok": "com.zhiliaoapp.musically",
|
|
179
|
+
"tiktok": "com.zhiliaoapp.musically",
|
|
180
|
+
"Twitter": "com.twitter.android",
|
|
181
|
+
"twitter": "com.twitter.android",
|
|
182
|
+
"X": "com.twitter.android",
|
|
183
|
+
"VLC": "org.videolan.vlc",
|
|
184
|
+
"WeChat": "com.tencent.mm",
|
|
185
|
+
"wechat": "com.tencent.mm",
|
|
186
|
+
"Whatsapp": "com.whatsapp",
|
|
187
|
+
"WhatsApp": "com.whatsapp",
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def get_package_name(app_name: str) -> str | None:
|
|
192
|
+
"""
|
|
193
|
+
Get the package name for an app.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
app_name: The display name of the app.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
The Android package name, or None if not found.
|
|
200
|
+
"""
|
|
201
|
+
return APP_PACKAGES.get(app_name)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def get_app_name(package_name: str) -> str | None:
|
|
205
|
+
"""
|
|
206
|
+
Get the app name from a package name.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
package_name: The Android package name.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
The display name of the app, or None if not found.
|
|
213
|
+
"""
|
|
214
|
+
for name, package in APP_PACKAGES.items():
|
|
215
|
+
if package == package_name:
|
|
216
|
+
return name
|
|
217
|
+
return None
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def list_supported_apps() -> list[str]:
|
|
221
|
+
"""
|
|
222
|
+
Get a list of all supported app names.
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
List of app names.
|
|
226
|
+
"""
|
|
227
|
+
return list(APP_PACKAGES.keys())
|