1
1
# -*- coding: utf-8 -*-
2
2
3
- import atexit
4
3
import time
5
4
import uuid
6
5
from typing import Type , Any , Tuple , Dict , Union , List
11
10
except ImportError :
12
11
from cached_property import cached_property
13
12
14
- from . import logger
15
13
from .exception import DeviceNotFoundError
16
14
from ._client import HMClient
17
15
from ._uiobject import UiObject
18
16
from .hdc import list_devices
17
+ from ._toast import ToastWatcher
19
18
from .proto import HypiumResponse , KeyCode , Point , DisplayRotation , DeviceInfo
20
19
21
20
@@ -24,25 +23,30 @@ class Driver:
24
23
25
24
def __init__ (self , serial : str ):
26
25
self .serial = serial
27
- if not self ._check_serial ():
26
+ if not self ._is_device_online ():
28
27
raise DeviceNotFoundError (f"Device [{ self .serial } ] not found" )
29
28
30
29
self ._client = HMClient (self .serial )
31
30
self ._this_driver = self ._client ._hdriver .value # "Driver#0"
32
31
self .hdc = self ._client .hdc
33
32
34
33
def __new__ (cls : Type [Any ], serial : str ) -> Any :
34
+ """
35
+ Ensure that only one instance of Driver exists per device serial number.
36
+ """
35
37
if serial not in cls ._instance :
36
38
cls ._instance [serial ] = super ().__new__ (cls )
37
39
return cls ._instance [serial ]
38
40
39
41
def __call__ (self , ** kwargs ) -> UiObject :
42
+
40
43
return UiObject (self ._client , ** kwargs )
41
44
42
45
def __del__ (self ):
43
- self ._client .release ()
46
+ if hasattr (self , '_client' ) and self ._client :
47
+ self ._client .release ()
44
48
45
- def _check_serial (self ):
49
+ def _is_device_online (self ):
46
50
_serials = list_devices ()
47
51
return True if self .serial in _serials else False
48
52
@@ -59,6 +63,9 @@ def stop_app(self, package_name: str):
59
63
self .hdc .stop_app (package_name )
60
64
61
65
def clear_app (self , package_name : str ):
66
+ """
67
+ Clear the application's cache and data.
68
+ """
62
69
self .hdc .shell (f"bm clean -n { package_name } -c" ) # clear cache
63
70
self .hdc .shell (f"bm clean -n { package_name } -d" ) # clear data
64
71
@@ -96,21 +103,30 @@ def unlock(self):
96
103
self .hdc .swipe (0.5 * w , 0.8 * h , 0.5 * w , 0.2 * h )
97
104
time .sleep (.5 )
98
105
106
+ def _invoke (self , api : str , args : List = []) -> HypiumResponse :
107
+ return self ._client .invoke (api , this = self ._this_driver , args = args )
108
+
99
109
@cached_property
100
110
def display_size (self ) -> Tuple [int , int ]:
101
111
api = "Driver.getDisplaySize"
102
- resp : HypiumResponse = self ._client . invoke (api , self . _this_driver )
112
+ resp : HypiumResponse = self ._invoke (api )
103
113
w , h = resp .result .get ("x" ), resp .result .get ("y" )
104
114
return w , h
105
115
106
116
@cached_property
107
117
def display_rotation (self ) -> DisplayRotation :
108
118
api = "Driver.getDisplayRotation"
109
- value = self ._client . invoke (api , self . _this_driver ).result
119
+ value = self ._invoke (api ).result
110
120
return DisplayRotation .from_value (value )
111
121
112
122
@cached_property
113
123
def device_info (self ) -> DeviceInfo :
124
+ """
125
+ Get detailed information about the device.
126
+
127
+ Returns:
128
+ DeviceInfo: An object containing various properties of the device.
129
+ """
114
130
hdc = self .hdc
115
131
return DeviceInfo (
116
132
productName = hdc .product_name (),
@@ -123,16 +139,43 @@ def device_info(self) -> DeviceInfo:
123
139
displayRotation = self .display_rotation
124
140
)
125
141
142
+ @cached_property
143
+ def toast_watcher (self ):
144
+ return ToastWatcher (self )
145
+
126
146
def open_url (self , url : str ):
127
147
self .hdc .shell (f"aa start -U { url } " )
128
148
129
149
def pull_file (self , rpath : str , lpath : str ):
150
+ """
151
+ Pull a file from the device to the local machine.
152
+
153
+ Args:
154
+ rpath (str): The remote path of the file on the device.
155
+ lpath (str): The local path where the file should be saved.
156
+ """
130
157
self .hdc .recv_file (rpath , lpath )
131
158
132
159
def push_file (self , lpath : str , rpath : str ):
160
+ """
161
+ Push a file from the local machine to the device.
162
+
163
+ Args:
164
+ lpath (str): The local path of the file.
165
+ rpath (str): The remote path where the file should be saved on the device.
166
+ """
133
167
self .hdc .send_file (lpath , rpath )
134
168
135
169
def screenshot (self , path : str ) -> str :
170
+ """
171
+ Take a screenshot of the device display.
172
+
173
+ Args:
174
+ path (str): The local path to save the screenshot.
175
+
176
+ Returns:
177
+ str: The path where the screenshot is saved.
178
+ """
136
179
_uuid = uuid .uuid4 ().hex
137
180
_tmp_path = f"/data/local/tmp/_tmp_{ _uuid } .jpeg"
138
181
self .shell (f"snapshot_display -f { _tmp_path } " )
@@ -145,7 +188,14 @@ def shell(self, cmd):
145
188
146
189
def _to_abs_pos (self , x : Union [int , float ], y : Union [int , float ]) -> Point :
147
190
"""
148
- returns a function which can convert percent size to abs size
191
+ Convert percentages to absolute screen coordinates.
192
+
193
+ Args:
194
+ x (Union[int, float]): X coordinate as a percentage or absolute value.
195
+ y (Union[int, float]): Y coordinate as a percentage or absolute value.
196
+
197
+ Returns:
198
+ Point: A Point object with absolute screen coordinates.
149
199
"""
150
200
assert x >= 0
151
201
assert y >= 0
@@ -163,21 +213,28 @@ def click(self, x: Union[int, float], y: Union[int, float]):
163
213
# self.hdc.tap(point.x, point.y)
164
214
point = self ._to_abs_pos (x , y )
165
215
api = "Driver.click"
166
- self ._client . invoke (api , self . _this_driver , args = [point .x , point .y ])
216
+ self ._invoke (api , args = [point .x , point .y ])
167
217
168
218
def double_click (self , x : Union [int , float ], y : Union [int , float ]):
169
219
point = self ._to_abs_pos (x , y )
170
220
api = "Driver.doubleClick"
171
- self ._client . invoke (api , self . _this_driver , args = [point .x , point .y ])
221
+ self ._invoke (api , args = [point .x , point .y ])
172
222
173
223
def long_click (self , x : Union [int , float ], y : Union [int , float ]):
174
224
point = self ._to_abs_pos (x , y )
175
225
api = "Driver.longClick"
176
- self ._client . invoke (api , self . _this_driver , args = [point .x , point .y ])
226
+ self ._invoke (api , args = [point .x , point .y ])
177
227
178
228
def swipe (self , x1 , y1 , x2 , y2 , speed = 1000 ):
179
229
"""
180
- speed为滑动速率, 范围:200-40000, 不在范围内设为默认值为600, 单位: 像素点/秒
230
+ Perform a swipe action on the device screen.
231
+
232
+ Args:
233
+ x1 (float): The start X coordinate as a percentage or absolute value.
234
+ y1 (float): The start Y coordinate as a percentage or absolute value.
235
+ x2 (float): The end X coordinate as a percentage or absolute value.
236
+ y2 (float): The end Y coordinate as a percentage or absolute value.
237
+ speed (int, optional): The swipe speed in pixels per second. Default is 1000. Range: 200-40000. If not within the range, set to default value of 600.
181
238
"""
182
239
point1 = self ._to_abs_pos (x1 , y1 )
183
240
point2 = self ._to_abs_pos (x2 , y2 )
@@ -187,3 +244,12 @@ def swipe(self, x1, y1, x2, y2, speed=1000):
187
244
def input_text (self , x , y , text : str ):
188
245
point = self ._to_abs_pos (x , y )
189
246
self .hdc .input_text (point .x , point .y , text )
247
+
248
+ def dump_hierarchy (self ) -> Dict :
249
+ """
250
+ Dump the UI hierarchy of the device screen.
251
+
252
+ Returns:
253
+ Dict: The dumped UI hierarchy as a dictionary.
254
+ """
255
+ return self .hdc .dump_hierarchy ()
0 commit comments