1#!/usr/bin/env python3 2# coding=utf-8 3 4# 5# Copyright (c) 2022 Huawei Device Co., Ltd. 6# Licensed under the Apache License, Version 2.0 (the "License"); 7# you may not use this file except in compliance with the License. 8# You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, 14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15# See the License for the specific language governing permissions and 16# limitations under the License. 17# 18import threading 19 20from xdevice import ManagerType 21from xdevice import Plugin 22from xdevice import get_plugin 23from xdevice import IDeviceManager 24from xdevice import IFilter 25from xdevice import platform_logger 26from xdevice import ParamError 27from xdevice import ConfigConst 28from xdevice import HdcCommandRejectedException 29from xdevice import ShellCommandUnresponsiveException 30from xdevice import DeviceConnectorType 31from xdevice import DeviceEvent 32from xdevice import TestDeviceState 33from xdevice import DeviceState 34from xdevice import handle_allocation_event 35from xdevice import DeviceAllocationState 36from xdevice import DeviceStateMonitor 37from xdevice import convert_serial 38from xdevice import check_mode_in_sys 39from xdevice import DeviceNode 40from xdevice import DeviceSelector 41 42from ohos.environment.dmlib import DeviceConnector 43from ohos.environment.dmlib import HDC_NAME 44from ohos.environment.dmlib import HDC_STD_NAME 45from ohos.config.config_manager import OHOSUserConfigManager 46 47__all__ = ["ManagerDevice"] 48 49LOG = platform_logger("ManagerDevice") 50 51 52@Plugin(type=Plugin.MANAGER, id=ManagerType.device) 53class ManagerDevice(IDeviceManager, IFilter): 54 """ 55 Class representing device manager 56 managing the set of available devices for testing 57 """ 58 59 instance = None 60 61 def __new__(cls): 62 """ 63 Singleton instance 64 """ 65 if cls.instance is None: 66 cls.instance = object.__new__(cls) 67 return cls.instance 68 69 def __init__(self): 70 self.devices_list = [] 71 self.global_device_filter = [] 72 self.lock_con = threading.Condition() 73 self.list_con = threading.Condition() 74 self.device_connectors = [] 75 self.usb_type = "usb-hdc" 76 self.managed_device_listener = None 77 self.support_labels = ["phone", "watch", "car", "tv", "tablet", "ivi", "2in1"] 78 self.support_types = ["device"] 79 self.wait_times = 0 80 self.__device_alias = {} 81 82 def init_environment(self, environment: str = "", user_config_file: str = "") -> bool: 83 return self._start_device_monitor(environment, user_config_file) 84 85 def env_stop(self): 86 self._stop_device_monitor() 87 88 def _start_device_monitor(self, environment: str = "", user_config_file: str = "") -> bool: 89 self.managed_device_listener = ManagedDeviceListener(self) 90 ohos_manager = OHOSUserConfigManager( 91 config_file=user_config_file, env=environment) 92 devices = ohos_manager.get_devices("environment/device") 93 if devices: 94 self.__set_device_alias(devices) 95 try: 96 for device in devices: 97 device_connector = DeviceConnector( 98 device.get("ip"), device.get("port"), device.get("usb_type")) 99 self.device_connectors.append(device_connector) 100 self.global_device_filter.extend( 101 ohos_manager.get_sn_list(device.get("sn"))) 102 device_connector.add_device_change_listener( 103 self.managed_device_listener) 104 device_connector.start() 105 except (ParamError, FileNotFoundError) as error: 106 self.env_stop() 107 for device in devices: 108 LOG.debug("Start %s error: %s" % ( 109 device.get("usb_type"), error)) 110 device_connector = DeviceConnector( 111 device.get("ip"), device.get("port"), 112 DeviceConnectorType.hdc) 113 self.device_connectors.append(device_connector) 114 device_connector.add_device_change_listener(self.managed_device_listener) 115 device_connector.start() 116 return True 117 else: 118 LOG.error("OHOS Manager device is not supported, please check config user_config.xml", error_no="00108") 119 return False 120 121 def _stop_device_monitor(self): 122 for device_connector in self.device_connectors: 123 device_connector.monitor_lock.acquire(1) 124 device_connector.remove_device_change_listener(self.managed_device_listener) 125 device_connector.terminate() 126 device_connector.monitor_lock.release() 127 128 def apply_device(self, device_option, timeout=3): 129 cnt = 0 130 max_reply_apply_time = 3 131 while cnt <= max_reply_apply_time: 132 LOG.debug("Apply device: apply lock con lock") 133 self.lock_con.acquire() 134 try: 135 device = self.allocate_device_option(device_option) 136 if device or cnt == max_reply_apply_time: 137 return device 138 LOG.debug("Wait for available device founded") 139 self.lock_con.wait(cnt * 2 + 1) 140 cnt += 1 141 finally: 142 LOG.debug("Apply device: release lock con lock") 143 self.lock_con.release() 144 145 def allocate_device_option(self, device_option): 146 """ 147 Request a device for testing that meets certain criteria. 148 """ 149 150 LOG.debug("Allocate device option: apply list con lock") 151 if not self.list_con.acquire(timeout=5): 152 LOG.debug("Allocate device option: list con wait timeout") 153 return None 154 try: 155 allocated_device = None 156 LOG.debug("Require device label is: %s" % device_option.label) 157 for device in self.devices_list: 158 if device_option.matches(device): 159 self.handle_device_event(device, 160 DeviceEvent.ALLOCATE_REQUEST) 161 LOG.debug("Allocate device sn: %s, type: %s" % ( 162 device.__get_serial__(), device.__class__)) 163 return device 164 return allocated_device 165 166 finally: 167 LOG.debug("Allocate device option: release list con lock") 168 self.list_con.release() 169 170 def release_device(self, device): 171 LOG.debug("Release device: apply list con lock") 172 self.list_con.acquire() 173 try: 174 if device.test_device_state == TestDeviceState.ONLINE: 175 self.handle_device_event(device, DeviceEvent.FREE_AVAILABLE) 176 else: 177 self.handle_device_event(device, DeviceEvent.FREE_UNAVAILABLE) 178 LOG.debug("Free device sn: %s, type: %s" % ( 179 device.__get_serial__(), device.__class__.__name__)) 180 finally: 181 LOG.debug("Release_device: release list con lock") 182 self.list_con.release() 183 184 def lock_device(self, device): 185 LOG.debug("Apply device: apply list con lock") 186 self.list_con.acquire() 187 try: 188 if device.test_device_state == TestDeviceState.ONLINE: 189 self.handle_device_event(device, DeviceEvent.ALLOCATE_REQUEST) 190 191 LOG.debug("Lock device sn: %s, type: %s" % ( 192 device.__get_serial__(), device.__class__.__name__)) 193 194 finally: 195 LOG.debug("Lock_device: release list con lock") 196 self.list_con.release() 197 198 def reset_device(self, device): 199 if device and hasattr(device, "reset"): 200 device.reset() 201 202 def find_device(self, idevice): 203 LOG.debug("Find device: apply list con lock") 204 self.list_con.acquire() 205 try: 206 for device in self.devices_list: 207 if device == idevice: 208 return device 209 return None 210 finally: 211 LOG.debug("Find device: release list con lock") 212 self.list_con.release() 213 214 def append_device_by_sort(self, device_instance): 215 LOG.debug("Append device: apply list con lock") 216 self.list_con.acquire() 217 try: 218 if (not self.global_device_filter or 219 not self.devices_list or 220 device_instance.device_sn not in self.global_device_filter): 221 self.devices_list.append(device_instance) 222 else: 223 device_dict = dict(zip( 224 self.global_device_filter, 225 list(range(1, len(self.global_device_filter) + 1)))) 226 for index in range(len(self.devices_list)): 227 if self.devices_list[index].device_sn not in \ 228 self.global_device_filter: 229 self.devices_list.insert(index, device_instance) 230 break 231 if device_dict[device_instance.device_sn] < \ 232 device_dict[self.devices_list[index].device_sn]: 233 self.devices_list.insert(index, device_instance) 234 break 235 else: 236 self.devices_list.append(device_instance) 237 finally: 238 LOG.debug("Append device: release list con lock") 239 self.list_con.release() 240 241 def find_or_create(self, idevice): 242 try: 243 device = self.find_device(idevice) 244 if device is None: 245 device = get_plugin( 246 plugin_type=Plugin.DEVICE, 247 plugin_id=idevice.device_os_type)[0] 248 device_sn = idevice.device_sn 249 device_instance = device.__class__() 250 device_instance.__set_serial__(device_sn) 251 device_instance.device_id = self.__device_alias.get(device_sn, "") 252 device_instance.host = idevice.host 253 device_instance.port = idevice.port 254 device_instance.usb_type = self.usb_type 255 LOG.debug("Create device(%s) host is %s, " 256 "port is %s, device sn is %s, usb type is %s" % 257 (device_instance, device_instance.host, 258 device_instance.port, device_instance.device_sn, 259 device_instance.usb_type)) 260 device_instance.device_state = idevice.device_state 261 device_instance.test_device_state = \ 262 TestDeviceState.get_test_device_state( 263 device_instance.device_state) 264 device_instance.device_state_monitor = \ 265 DeviceStateMonitor(device_instance) 266 if idevice.device_state == DeviceState.ONLINE or \ 267 idevice.device_state == DeviceState.CONNECTED: 268 device_instance.get_device_type() 269 self.append_device_by_sort(device_instance) 270 device = device_instance 271 else: 272 LOG.debug("Find device(%s), host is %s, " 273 "port is %s, device sn is %s, usb type is %s" % 274 (device, device.host, device.port, device.device_sn, 275 device.usb_type)) 276 return device 277 except (HdcCommandRejectedException, ShellCommandUnresponsiveException) as hcr_error: 278 LOG.debug("%s occurs error. Reason:%s" % 279 (idevice.device_sn, hcr_error)) 280 return None 281 282 def remove(self, idevice): 283 LOG.debug("Remove: apply list con lock") 284 self.list_con.acquire() 285 try: 286 self.devices_list.remove(idevice) 287 finally: 288 LOG.debug("Remove: release list con lock") 289 self.list_con.release() 290 291 def handle_device_event(self, device, event): 292 state_changed = None 293 old_state = device.device_allocation_state 294 new_state = handle_allocation_event(old_state, event) 295 296 if new_state == DeviceAllocationState.checking_availability: 297 if self.global_device_filter and \ 298 device.device_sn not in self.global_device_filter: 299 event = DeviceEvent.AVAILABLE_CHECK_IGNORED 300 else: 301 event = DeviceEvent.AVAILABLE_CHECK_PASSED 302 new_state = handle_allocation_event(new_state, event) 303 304 if old_state != new_state: 305 state_changed = True 306 device.device_allocation_state = new_state 307 308 if state_changed is True and \ 309 new_state == DeviceAllocationState.available: 310 # notify_device_state_change 311 LOG.debug("Handle device event apply lock con") 312 self.lock_con.acquire() 313 LOG.debug("Find available device") 314 self.lock_con.notify_all() 315 LOG.debug("Handle device event release lock con") 316 self.lock_con.release() 317 318 if device.device_allocation_state == \ 319 DeviceAllocationState.unknown: 320 self.remove(device) 321 return 322 323 def launch_emulator(self): 324 pass 325 326 def kill_emulator(self): 327 pass 328 329 def list_devices(self): 330 for device_connector in self.device_connectors: 331 device_connector.monitor_lock.acquire(1) 332 try: 333 if check_mode_in_sys(ConfigConst.app_test): 334 return self.get_device_info() 335 else: 336 print("OHOS devices:") 337 print("{0:<20}{1:<16}{2:<16}{3:<16}{4:<16}{5:<16}{6:<16}".format( 338 "Serial", "OsType", "State", "Allocation", "Product", "host", 339 "port")) 340 for device in self.devices_list: 341 print( 342 "{0:<20}{1:<16}{2:<16}{3:<16}{4:<16}{5:<16}{6:<16}".format( 343 convert_serial(device.device_sn), 344 device.device_os_type, 345 device.test_device_state.value, 346 device.device_allocation_state, 347 device.label if device.label else 'None', 348 device.host, device.port)) 349 return "" 350 finally: 351 for device_connector in self.device_connectors: 352 device_connector.monitor_lock.release() 353 354 def get_device_info(self): 355 devices_info = [] 356 for device in self.devices_list: 357 result = device.get_device_params() 358 devices_info.append(result) 359 return devices_info 360 361 def __set_device_alias(self, devices): 362 for device in devices: 363 sn, alias = device.get("sn"), device.get("alias") 364 if sn and alias: 365 self.__device_alias.update({sn: alias}) 366 367 def __filter_selector__(self, selector): 368 if isinstance(selector, DeviceSelector): 369 return True 370 return False 371 372 def __filter_xml_node__(self, node): 373 if isinstance(node, DeviceNode): 374 if HDC_NAME in node.get_connectors() or \ 375 HDC_STD_NAME in node.get_connectors(): 376 return True 377 return False 378 379 380class ManagedDeviceListener(object): 381 """ 382 A class to listen for and act on device presence updates from ddmlib 383 """ 384 385 def __init__(self, manager): 386 self.manager = manager 387 388 def device_changed(self, idevice): 389 test_device = self.manager.find_or_create(idevice) 390 if test_device is None: 391 return 392 new_state = TestDeviceState.get_test_device_state(idevice.device_state) 393 test_device.test_device_state = new_state 394 if hasattr(test_device, "_is_root"): 395 test_device._is_root = None 396 if new_state == TestDeviceState.ONLINE: 397 self.manager.handle_device_event(test_device, 398 DeviceEvent.STATE_CHANGE_ONLINE) 399 LOG.debug("Set device %s %s to true" % ( 400 convert_serial(test_device.device_sn), ConfigConst.recover_state)) 401 test_device.set_recover_state(True) 402 elif new_state == TestDeviceState.NOT_AVAILABLE: 403 self.manager.handle_device_event(test_device, 404 DeviceEvent.STATE_CHANGE_OFFLINE) 405 if hasattr(test_device, "call_proxy_listener"): 406 test_device.call_proxy_listener() 407 test_device.device_state_monitor.set_state( 408 test_device.test_device_state) 409 LOG.debug("Device changed to %s: %s %s %s %s" % ( 410 new_state, convert_serial(idevice.device_sn), 411 idevice.device_os_type, idevice.host, idevice.port)) 412 413 def device_connected(self, idevice): 414 test_device = self.manager.find_or_create(idevice) 415 if test_device is None: 416 return 417 418 new_state = TestDeviceState.get_test_device_state(idevice.device_state) 419 test_device.test_device_state = new_state 420 if test_device.test_device_state == TestDeviceState.ONLINE: 421 self.manager.handle_device_event(test_device, 422 DeviceEvent.CONNECTED_ONLINE) 423 elif new_state == TestDeviceState.NOT_AVAILABLE: 424 self.manager.handle_device_event(test_device, 425 DeviceEvent.CONNECTED_OFFLINE) 426 test_device.device_state_monitor.set_state( 427 test_device.test_device_state) 428 LOG.debug("Device connected: {} {} {} {}, state: {}".format( 429 convert_serial(idevice.device_sn), idevice.device_os_type, 430 idevice.host, idevice.port, test_device.test_device_state)) 431 LOG.debug("Set device %s %s to true" % ( 432 convert_serial(idevice.device_sn), ConfigConst.recover_state)) 433 test_device.set_recover_state(True) 434 435 def device_disconnected(self, disconnected_device): 436 test_device = self.manager.find_device(disconnected_device) 437 if test_device is not None: 438 test_device.test_device_state = TestDeviceState.NOT_AVAILABLE 439 self.manager.handle_device_event(test_device, 440 DeviceEvent.DISCONNECTED) 441 test_device.device_state_monitor.set_state( 442 TestDeviceState.NOT_AVAILABLE) 443 LOG.debug("Device disconnected: %s %s %s %s" % ( 444 convert_serial(disconnected_device.device_sn), 445 disconnected_device.device_os_type, 446 disconnected_device.host, disconnected_device.port)) 447