11# -*- coding: utf-8 -*-
22
3- import atexit
43import time
54import uuid
65from typing import Type , Any , Tuple , Dict , Union , List
1110except ImportError :
1211 from cached_property import cached_property
1312
14- from . import logger
1513from .exception import DeviceNotFoundError
1614from ._client import HMClient
1715from ._uiobject import UiObject
1816from .hdc import list_devices
17+ from ._toast import ToastWatcher
1918from .proto import HypiumResponse , KeyCode , Point , DisplayRotation , DeviceInfo
2019
2120
@@ -24,25 +23,30 @@ class Driver:
2423
2524 def __init__ (self , serial : str ):
2625 self .serial = serial
27- if not self ._check_serial ():
26+ if not self ._is_device_online ():
2827 raise DeviceNotFoundError (f"Device [{ self .serial } ] not found" )
2928
3029 self ._client = HMClient (self .serial )
3130 self ._this_driver = self ._client ._hdriver .value # "Driver#0"
3231 self .hdc = self ._client .hdc
3332
3433 def __new__ (cls : Type [Any ], serial : str ) -> Any :
34+ """
35+ Ensure that only one instance of Driver exists per device serial number.
36+ """
3537 if serial not in cls ._instance :
3638 cls ._instance [serial ] = super ().__new__ (cls )
3739 return cls ._instance [serial ]
3840
3941 def __call__ (self , ** kwargs ) -> UiObject :
42+
4043 return UiObject (self ._client , ** kwargs )
4144
4245 def __del__ (self ):
43- self ._client .release ()
46+ if hasattr (self , '_client' ) and self ._client :
47+ self ._client .release ()
4448
45- def _check_serial (self ):
49+ def _is_device_online (self ):
4650 _serials = list_devices ()
4751 return True if self .serial in _serials else False
4852
@@ -59,6 +63,9 @@ def stop_app(self, package_name: str):
5963 self .hdc .stop_app (package_name )
6064
6165 def clear_app (self , package_name : str ):
66+ """
67+ Clear the application's cache and data.
68+ """
6269 self .hdc .shell (f"bm clean -n { package_name } -c" ) # clear cache
6370 self .hdc .shell (f"bm clean -n { package_name } -d" ) # clear data
6471
@@ -96,21 +103,30 @@ def unlock(self):
96103 self .hdc .swipe (0.5 * w , 0.8 * h , 0.5 * w , 0.2 * h )
97104 time .sleep (.5 )
98105
106+ def _invoke (self , api : str , args : List = []) -> HypiumResponse :
107+ return self ._client .invoke (api , this = self ._this_driver , args = args )
108+
99109 @cached_property
100110 def display_size (self ) -> Tuple [int , int ]:
101111 api = "Driver.getDisplaySize"
102- resp : HypiumResponse = self ._client . invoke (api , self . _this_driver )
112+ resp : HypiumResponse = self ._invoke (api )
103113 w , h = resp .result .get ("x" ), resp .result .get ("y" )
104114 return w , h
105115
106116 @cached_property
107117 def display_rotation (self ) -> DisplayRotation :
108118 api = "Driver.getDisplayRotation"
109- value = self ._client . invoke (api , self . _this_driver ).result
119+ value = self ._invoke (api ).result
110120 return DisplayRotation .from_value (value )
111121
112122 @cached_property
113123 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+ """
114130 hdc = self .hdc
115131 return DeviceInfo (
116132 productName = hdc .product_name (),
@@ -123,16 +139,43 @@ def device_info(self) -> DeviceInfo:
123139 displayRotation = self .display_rotation
124140 )
125141
142+ @cached_property
143+ def toast_watcher (self ):
144+ return ToastWatcher (self )
145+
126146 def open_url (self , url : str ):
127147 self .hdc .shell (f"aa start -U { url } " )
128148
129149 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+ """
130157 self .hdc .recv_file (rpath , lpath )
131158
132159 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+ """
133167 self .hdc .send_file (lpath , rpath )
134168
135169 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+ """
136179 _uuid = uuid .uuid4 ().hex
137180 _tmp_path = f"/data/local/tmp/_tmp_{ _uuid } .jpeg"
138181 self .shell (f"snapshot_display -f { _tmp_path } " )
@@ -145,7 +188,14 @@ def shell(self, cmd):
145188
146189 def _to_abs_pos (self , x : Union [int , float ], y : Union [int , float ]) -> Point :
147190 """
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.
149199 """
150200 assert x >= 0
151201 assert y >= 0
@@ -163,21 +213,28 @@ def click(self, x: Union[int, float], y: Union[int, float]):
163213 # self.hdc.tap(point.x, point.y)
164214 point = self ._to_abs_pos (x , y )
165215 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 ])
167217
168218 def double_click (self , x : Union [int , float ], y : Union [int , float ]):
169219 point = self ._to_abs_pos (x , y )
170220 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 ])
172222
173223 def long_click (self , x : Union [int , float ], y : Union [int , float ]):
174224 point = self ._to_abs_pos (x , y )
175225 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 ])
177227
178228 def swipe (self , x1 , y1 , x2 , y2 , speed = 1000 ):
179229 """
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.
181238 """
182239 point1 = self ._to_abs_pos (x1 , y1 )
183240 point2 = self ._to_abs_pos (x2 , y2 )
@@ -187,3 +244,12 @@ def swipe(self, x1, y1, x2, y2, speed=1000):
187244 def input_text (self , x , y , text : str ):
188245 point = self ._to_abs_pos (x , y )
189246 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