Coverage for kea/input_event.py: 64%

652 statements  

« prev     ^ index     » next       coverage.py v7.6.9, created at 2024-12-22 16:05 +0800

1import json 

2import os 

3import random 

4import time 

5from abc import abstractmethod 

6 

7from . import utils 

8from .intent import Intent 

9 

10from typing import TYPE_CHECKING, Union 

11if TYPE_CHECKING: 

12 from .device import Device 

13 from .device_hm import DeviceHM 

14 

15POSSIBLE_KEYS = ["BACK", "MENU", "HOME"] 

16 

17# Unused currently, but should be useful. 

18POSSIBLE_BROADCASTS = [ 

19 "android.intent.action.AIRPLANE_MODE_CHANGED", 

20 "android.intent.action.BATTERY_CHANGED", 

21 "android.intent.action.BATTERY_LOW", 

22 "android.intent.action.BATTERY_OKAY", 

23 "android.intent.action.BOOT_COMPLETED", 

24 "android.intent.action.DATE_CHANGED", 

25 "android.intent.action.DEVICE_STORAGE_LOW", 

26 "android.intent.action.DEVICE_STORAGE_OK", 

27 "android.intent.action.INPUT_METHOD_CHANGED", 

28 "android.intent.action.INSTALL_PACKAGE", 

29 "android.intent.action.LOCALE_CHANGED", 

30 "android.intent.action.MEDIA_EJECT", 

31 "android.intent.action.MEDIA_MOUNTED", 

32 "android.intent.action.MEDIA_REMOVED", 

33 "android.intent.action.MEDIA_SHARED", 

34 "android.intent.action.MEDIA_UNMOUNTED", 

35 "android.intent.action.NEW_OUTGOING_CALL", 

36 "android.intent.action.OPEN_DOCUMENT", 

37 "android.intent.action.OPEN_DOCUMENT_TREE", 

38 "android.intent.action.PACKAGE_ADDED", 

39 "android.intent.action.PACKAGE_CHANGED", 

40 "android.intent.action.PACKAGE_DATA_CLEARED", 

41 "android.intent.action.PACKAGE_FIRST_LAUNCH", 

42 "android.intent.action.PACKAGE_FULLY_REMOVED", 

43 "android.intent.action.PACKAGE_INSTALL", 

44 "android.intent.action.PACKAGE_REMOVED", 

45 "android.intent.action.PACKAGE_REPLACED", 

46 "android.intent.action.PACKAGE_RESTARTED", 

47 "android.intent.action.PACKAGE_VERIFIED", 

48 "android.intent.action.PASTE", 

49 "android.intent.action.POWER_CONNECTED", 

50 "android.intent.action.POWER_DISCONNECTED", 

51 "android.intent.action.POWER_USAGE_SUMMARY", 

52 "android.intent.action.PROVIDER_CHANGED", 

53 "android.intent.action.QUICK_CLOCK", 

54 "android.intent.action.REBOOT", 

55 "android.intent.action.SCREEN_OFF", 

56 "android.intent.action.SCREEN_ON", 

57 "android.intent.action.SET_WALLPAPER", 

58 "android.intent.action.SHUTDOWN", 

59 "android.intent.action.TIMEZONE_CHANGED", 

60 "android.intent.action.TIME_CHANGED", 

61 "android.intent.action.TIME_TICK", 

62 "android.intent.action.UID_REMOVED", 

63 "android.intent.action.UNINSTALL_PACKAGE", 

64 "android.intent.action.USER_BACKGROUND", 

65 "android.intent.action.USER_FOREGROUND", 

66 "android.intent.action.USER_INITIALIZE", 

67 "android.intent.action.USER_PRESENT", 

68 "android.intent.action.VOICE_COMMAND", 

69 "android.intent.action.WALLPAPER_CHANGED", 

70 "android.intent.action.WEB_SEARCH", 

71] 

72 

73KEY_KeyEvent = "key" 

74KEY_ManualEvent = "manual" 

75KEY_SearchEvent = "search" 

76KEY_SetTextAndSearchEvent = "set_text_and_search" 

77KEY_ExitEvent = "exit" 

78KEY_FRESH_Reinstall_App_Event = "fresh_reinstall_app" 

79KEY_Kill_Restart_App_Event = "KillAndRestartAppEvent" 

80KEY_TouchEvent = "touch" 

81KEY_LongTouchEvent = "long_touch" 

82KEY_SwipeEvent = "swipe" 

83KEY_ScrollEvent = "scroll" 

84KEY_SetTextEvent = "set_text" 

85KEY_IntentEvent = "intent" 

86KEY_SpawnEvent = "spawn" 

87KEY_KillAppEvent = "kill_app" 

88KEY_RotateDeviceToLandscapeEvent = "rotate_device_right" 

89KEY_RotateDeviceToPortraitEvent = "rotate_device_neutral" 

90 

91class InvalidEventException(Exception): 

92 pass 

93 

94 

95class InputEvent(object): 

96 """ 

97 The base class of all events 

98 """ 

99 

100 def __init__(self): 

101 self.event_type = None 

102 self.log_lines = None 

103 

104 def to_dict(self): 

105 return self.__dict__ 

106 

107 def to_json(self): 

108 return json.dumps(self.to_dict()) 

109 

110 def __str__(self): 

111 return self.to_dict().__str__() 

112 

113 @abstractmethod 

114 def send(self, device:Union["Device", "DeviceHM"]): 

115 """ 

116 send this event to device 

117 :param device: Device 

118 :return: 

119 """ 

120 raise NotImplementedError 

121 

122 @staticmethod 

123 @abstractmethod 

124 def get_random_instance(device, app): 

125 """ 

126 get a random instance of event 

127 :param device: Device 

128 :param app: App 

129 """ 

130 raise NotImplementedError 

131 

132 @staticmethod 

133 def from_dict(event_dict): 

134 if not isinstance(event_dict, dict): 

135 return None 

136 if 'event_type' not in event_dict: 

137 return None 

138 event_type = event_dict['event_type'] 

139 if event_type == KEY_KeyEvent: 

140 return KeyEvent(event_dict=event_dict) 

141 elif event_type == KEY_TouchEvent: 

142 return TouchEvent(event_dict=event_dict) 

143 elif event_type == KEY_LongTouchEvent: 

144 return LongTouchEvent(event_dict=event_dict) 

145 elif event_type == KEY_SwipeEvent: 

146 return SwipeEvent(event_dict=event_dict) 

147 elif event_type == KEY_ScrollEvent: 

148 return ScrollEvent(event_dict=event_dict) 

149 elif event_type == KEY_SetTextEvent: 

150 return SetTextEvent(event_dict=event_dict) 

151 elif event_type == KEY_IntentEvent: 

152 return IntentEvent(event_dict=event_dict) 

153 elif event_type == KEY_ExitEvent: 

154 return ExitEvent(event_dict=event_dict) 

155 elif event_type == KEY_SpawnEvent: 

156 return SpawnEvent(event_dict=event_dict) 

157 

158 @abstractmethod 

159 def get_event_str(self, state): 

160 pass 

161 

162 def get_views(self): 

163 return [] 

164 

165 

166class EventLog(object): 

167 """ 

168 save an event to local file system 

169 """ 

170 

171 def __init__(self, device, app, event, profiling_method=None, tag=None): 

172 self.device = device 

173 self.app = app 

174 self.event = event 

175 if tag is None: 

176 from datetime import datetime 

177 

178 tag = datetime.now().strftime("%Y-%m-%d_%H%M%S") 

179 self.tag = tag 

180 

181 self.from_state = None 

182 self.to_state = None 

183 self.event_str = None 

184 

185 self.profiling_method = profiling_method 

186 self.trace_remote_file = "/data/local/tmp/event.trace" 

187 self.is_profiling = False 

188 self.profiling_pid = -1 

189 self.sampling = None 

190 # sampling feature was added in Android 5.0 (API level 21) 

191 if ( 

192 profiling_method is not None 

193 and str(profiling_method) != "full" 

194 and self.device.get_sdk_version() >= 21 

195 ): 

196 self.sampling = int(profiling_method) 

197 

198 def to_dict(self): 

199 return { 

200 "tag": self.tag, 

201 "event": self.event.to_dict(), 

202 "start_state": self.from_state.state_str, 

203 "stop_state": self.to_state.state_str, 

204 "event_str": self.event_str, 

205 } 

206 

207 def save2dir(self, output_dir=None): 

208 # Save event 

209 if output_dir is None: 

210 if self.device.output_dir is None: 

211 return 

212 else: 

213 output_dir = os.path.join(self.device.output_dir, "events") 

214 try: 

215 if not os.path.exists(output_dir): 

216 os.makedirs(output_dir) 

217 event_json_file_path = "%s/event_%s.json" % (output_dir, self.tag) 

218 event_json_file = open(event_json_file_path, "w") 

219 json.dump(self.to_dict(), event_json_file, indent=2) 

220 event_json_file.close() 

221 except Exception as e: 

222 self.device.logger.warning("Saving event to dir failed.") 

223 self.device.logger.warning(e) 

224 

225 def save_views(self, output_dir=None): 

226 # Save views 

227 views = self.event.get_views() 

228 if views: 

229 for view_dict in views: 

230 self.from_state.save_view_img( 

231 view_dict=view_dict, output_dir=output_dir 

232 ) 

233 

234 def is_start_event(self): 

235 if isinstance(self.event, IntentEvent): 

236 intent_cmd = self.event.intent 

237 if "start" in intent_cmd and self.app.get_package_name() in intent_cmd: 

238 return True 

239 return False 

240 

241 def start(self): 

242 """ 

243 start sending event 

244 """ 

245 self.from_state = self.device.get_current_state() 

246 self.start_profiling() 

247 self.event_str = self.event.get_event_str(self.from_state) 

248 print("Action: %s" % self.event_str) 

249 self.device.send_event(self.event) 

250 

251 def start_profiling(self): 

252 """ 

253 start profiling the current event 

254 @return: 

255 """ 

256 if self.profiling_method is None: 

257 return 

258 if self.is_profiling: 

259 return 

260 pid = self.device.get_app_pid(self.app) 

261 if pid is None: 

262 if self.is_start_event(): 

263 start_intent = self.app.get_start_with_profiling_intent( 

264 self.trace_remote_file, self.sampling 

265 ) 

266 self.event.intent = start_intent.get_cmd() 

267 self.is_profiling = True 

268 return 

269 if self.sampling is not None: 

270 self.device.adb.shell( 

271 [ 

272 "am", 

273 "profile", 

274 "start", 

275 "--sampling", 

276 str(self.sampling), 

277 str(pid), 

278 self.trace_remote_file, 

279 ] 

280 ) 

281 else: 

282 self.device.adb.shell( 

283 ["am", "profile", "start", str(pid), self.trace_remote_file] 

284 ) 

285 self.is_profiling = True 

286 self.profiling_pid = pid 

287 

288 def stop(self): 

289 """ 

290 finish sending event 

291 """ 

292 self.stop_profiling() 

293 self.to_state = self.device.get_current_state() 

294 self.save2dir() 

295 self.save_views() 

296 

297 def stop_profiling(self, output_dir=None): 

298 if self.profiling_method is None: 

299 return 

300 if not self.is_profiling: 

301 return 

302 try: 

303 if self.profiling_pid == -1: 

304 pid = self.device.get_app_pid(self.app) 

305 if pid is None: 

306 return 

307 self.profiling_pid = pid 

308 

309 self.device.adb.shell(["am", "profile", "stop", str(self.profiling_pid)]) 

310 if self.sampling is None: 

311 time.sleep(3) # guess this time can vary between machines 

312 

313 if output_dir is None: 

314 if self.device.output_dir is None: 

315 return 

316 else: 

317 output_dir = os.path.join(self.device.output_dir, "events") 

318 if not os.path.exists(output_dir): 

319 os.makedirs(output_dir) 

320 event_trace_local_path = "%s/event_trace_%s.trace" % (output_dir, self.tag) 

321 self.device.pull_file(self.trace_remote_file, event_trace_local_path) 

322 

323 except Exception as e: 

324 self.device.logger.warning("profiling event failed") 

325 self.device.logger.warning(e) 

326 

327 

328class ManualEvent(InputEvent): 

329 """ 

330 a manual event 

331 """ 

332 

333 def __init__(self, event_dict=None): 

334 super().__init__() 

335 self.event_type = KEY_ManualEvent 

336 self.time = time.time() 

337 if event_dict is not None: 

338 self.__dict__.update(event_dict) 

339 

340 @staticmethod 

341 def get_random_instance(device, app): 

342 return None 

343 

344 def send(self, device): 

345 # do nothing 

346 pass 

347 

348 def get_event_str(self, state): 

349 return "%s(time=%s)" % (self.__class__.__name__, self.time) 

350 

351 

352class ExitEvent(InputEvent): 

353 """ 

354 an event to stop testing 

355 """ 

356 

357 def __init__(self, event_dict=None): 

358 super().__init__() 

359 self.event_type = KEY_ExitEvent 

360 if event_dict is not None: 

361 self.__dict__.update(event_dict) 

362 

363 @staticmethod 

364 def get_random_instance(device, app): 

365 return None 

366 

367 def send(self, device): 

368 # device.disconnect() 

369 raise KeyboardInterrupt() 

370 

371 def get_event_str(self, state): 

372 return "%s()" % self.__class__.__name__ 

373 

374 def get_event_name(self): 

375 return "Exit" 

376 

377 

378class KillAppEvent(InputEvent): 

379 """ 

380 an event to stop testing 

381 """ 

382 

383 def __init__(self, app=None, event_dict=None): 

384 super().__init__() 

385 self.event_type = KEY_KillAppEvent 

386 self.stop_intent = None 

387 if app: 

388 self.stop_intent = app.get_stop_intent().get_cmd() 

389 elif event_dict is not None: 

390 self.__dict__.update(event_dict) 

391 

392 @staticmethod 

393 def get_random_instance(device, app): 

394 return None 

395 

396 def send(self, device): 

397 if self.stop_intent: 

398 device.send_intent(self.stop_intent) 

399 device.key_press('HOME') 

400 

401 def get_event_str(self, state): 

402 return "%s()" % self.__class__.__name__ 

403 

404 def get_event_name(self): 

405 return "KillApp" 

406 

407class KillAndRestartAppEvent(InputEvent): 

408 """ 

409 an event to kill and restart app 

410 """ 

411 

412 def __init__(self, app=None, event_dict=None): 

413 super().__init__() 

414 self.event_type = KEY_KillAppEvent 

415 self.stop_intent = None 

416 self.start_intent = None 

417 if app: 

418 self.stop_intent = app.get_stop_intent().get_cmd() 

419 self.start_intent = app.get_start_intent().get_cmd() 

420 elif event_dict is not None: 

421 self.__dict__.update(event_dict) 

422 

423 @staticmethod 

424 def get_random_instance(device, app): 

425 return None 

426 

427 def send(self, device): 

428 if self.stop_intent: 

429 device.send_intent(self.stop_intent) 

430 device.key_press('HOME') 

431 if self.start_intent: 

432 device.send_intent(self.start_intent) 

433 

434 def get_event_str(self, state): 

435 return "%s()" % self.__class__.__name__ 

436 

437 def get_event_name(self): 

438 return "KillAndRestartApp" 

439 

440 

441class RotateDevice(InputEvent): 

442 def __init__(self): 

443 super().__init__() 

444 

445 @staticmethod 

446 def get_random_instance(device, app): 

447 return None 

448 

449 def send(self, device): 

450 # do nothing 

451 pass 

452 

453 def get_event_str(self, state): 

454 return "%s()" % self.__class__.__name__ 

455 

456 def get_event_name(self): 

457 return "Rotate" 

458 

459class RotateDeviceToLandscapeEvent(RotateDevice): 

460 """ 

461 an event to rotate device 

462 """ 

463 def __init__(self): 

464 super().__init__() 

465 

466 def send(self, device): 

467 device.rotate_device_right() 

468 time.sleep(1) 

469 return True 

470 

471 def get_event_name(self): 

472 return "RotateRight" 

473 

474class RotateDeviceToPortraitEvent(RotateDevice): 

475 """ 

476 an event to rotate device 

477 """ 

478 def __init__(self): 

479 super().__init__() 

480 

481 def send(self, device): 

482 device.rotate_device_neutral() 

483 time.sleep(1) 

484 return True 

485 

486 def get_event_name(self): 

487 return "RotateNeutral" 

488 

489 

490class KeyEvent(InputEvent): 

491 """ 

492 a key pressing event 

493 """ 

494 

495 def __init__(self, name=None, event_dict=None): 

496 super().__init__() 

497 self.event_type = KEY_KeyEvent 

498 self.name = name 

499 if event_dict is not None: 

500 self.__dict__.update(event_dict) 

501 

502 @staticmethod 

503 def get_random_instance(device, app): 

504 key_name = random.choice(POSSIBLE_KEYS) 

505 return KeyEvent(key_name) 

506 

507 def send(self, device): 

508 device.key_press(self.name) 

509 return True 

510 

511 def get_event_str(self, state): 

512 return "%s(state=%s, name=%s)" % ( 

513 self.__class__.__name__, 

514 state.state_str, 

515 self.name, 

516 ) 

517 

518 def get_event_name(self): 

519 return self.name 

520 

521# restart event 

522class ReInstallAppEvent(InputEvent): 

523 """ 

524 an event to restart the app with a fresh state 

525 """ 

526 

527 def __init__(self, app=None, event_dict=None): 

528 super(ReInstallAppEvent, self).__init__() 

529 self.app = app 

530 self.event_type = KEY_FRESH_Reinstall_App_Event 

531 self.package = self.app.get_package_name() 

532 intent = self.app.get_start_intent() 

533 if isinstance(intent, Intent): 

534 self.intent = intent.get_cmd() 

535 elif isinstance(intent, str): 

536 self.intent = intent 

537 elif event_dict is not None: 

538 self.__dict__.update(event_dict) 

539 else: 

540 msg = "intent must be either an instance of Intent or a string." 

541 raise InvalidEventException(msg) 

542 

543 @staticmethod 

544 def get_random_instance(device, app): 

545 return None 

546 

547 def send(self, device): 

548 device.clear_data(self.package) 

549 device.uninstall_app(self.app) 

550 device.install_app(self.app) 

551 device.send_documents(self.app) 

552 # if "anki" in self.package: 

553 # device.mkdir("/storage/emulated/0/AnkiDroid/") 

554 # device.push_file(os.path.join("Document","collection.anki2"), "/storage/emulated/0/AnkiDroid/") 

555 device.send_intent(intent=self.intent) 

556 time.sleep(3) 

557 

558 def get_event_str(self, state, content_free=False): 

559 return "%s()" % self.__class__.__name__ 

560 

561 def get_event_name(self): 

562 return "ReInstallApp" 

563 

564class UIEvent(InputEvent): 

565 """ 

566 This class describes a UI event of app, such as touch, click, etc 

567 """ 

568 

569 def __init__(self): 

570 super().__init__() 

571 

572 def send(self, device): 

573 raise NotImplementedError 

574 

575 @staticmethod 

576 def get_random_instance(device, app): 

577 if not device.is_foreground(app): 

578 # if current app is in background, bring it to foreground 

579 component = app.get_package_name() 

580 if app.get_main_activity(): 

581 component += "/%s" % app.get_main_activity() 

582 return IntentEvent(Intent(suffix=component)) 

583 

584 else: 

585 choices = {TouchEvent: 6, LongTouchEvent: 2, SwipeEvent: 2} 

586 event_type = utils.weighted_choice(choices) 

587 return event_type.get_random_instance(device, app) 

588 

589 @staticmethod 

590 def get_xy(x, y, view): 

591 if x and y: 

592 return x, y 

593 if view: 

594 from .device_state import DeviceState 

595 

596 return DeviceState.get_view_center(view_dict=view) 

597 return x, y 

598 

599 @staticmethod 

600 def view_str(state, view): 

601 view_class = view['class'].split('.')[-1] 

602 view_text = ( 

603 view['text'].replace('\n', '\\n') if 'text' in view and view['text'] else '' 

604 ) 

605 view_text = view_text[:10] if len(view_text) > 10 else view_text 

606 view_short_sig = f'{state.activity_short_name}/{view_class}-{view_text}' 

607 return f"state={state.state_str}, view={view['view_str']}({view_short_sig})" 

608 

609 def set_view(self, view): 

610 self.view = view 

611 

612 def set_xy(self, x, y): 

613 self.x = x 

614 self.y = y 

615 

616class TouchEvent(UIEvent): 

617 """ 

618 a touch on screen 

619 """ 

620 

621 def __init__(self, x=None, y=None, view=None, event_dict=None): 

622 super().__init__() 

623 self.event_type = KEY_TouchEvent 

624 self.x = x 

625 self.y = y 

626 self.view = view 

627 if event_dict is not None: 

628 self.__dict__.update(event_dict) 

629 

630 @staticmethod 

631 def get_random_instance(device, app): 

632 x = random.uniform(0, device.get_width()) 

633 y = random.uniform(0, device.get_height()) 

634 return TouchEvent(x, y) 

635 

636 def send(self, device): 

637 x, y = UIEvent.get_xy(x=self.x, y=self.y, view=self.view) 

638 device.view_long_touch(x=x, y=y, duration=200) 

639 return True 

640 

641 def get_event_str(self, state): 

642 if self.view is not None: 

643 return f"{self.__class__.__name__}({UIEvent.view_str(state, self.view)})" 

644 elif self.x is not None and self.y is not None: 

645 return "%s(state=%s, x=%s, y=%s)" % ( 

646 self.__class__.__name__, 

647 state.state_str, 

648 self.x, 

649 self.y, 

650 ) 

651 else: 

652 msg = "Invalid %s!" % self.__class__.__name__ 

653 raise InvalidEventException(msg) 

654 

655 def get_views(self): 

656 return [self.view] if self.view else [] 

657 

658 def get_event_name(self): 

659 return "Click" 

660 

661class LongTouchEvent(UIEvent): 

662 """ 

663 a long touch on screen 

664 """ 

665 

666 def __init__(self, x=None, y=None, view=None, duration=2000, event_dict=None): 

667 super().__init__() 

668 self.event_type = KEY_LongTouchEvent 

669 self.x = x 

670 self.y = y 

671 self.view = view 

672 self.duration = duration 

673 if event_dict is not None: 

674 self.__dict__.update(event_dict) 

675 

676 @staticmethod 

677 def get_random_instance(device, app): 

678 x = random.uniform(0, device.get_width()) 

679 y = random.uniform(0, device.get_height()) 

680 return LongTouchEvent(x, y) 

681 

682 def send(self, device): 

683 x, y = UIEvent.get_xy(x=self.x, y=self.y, view=self.view) 

684 device.view_long_touch(x=x, y=y, duration=self.duration) 

685 return True 

686 

687 def get_event_str(self, state): 

688 if self.view is not None: 

689 return f"{self.__class__.__name__}({UIEvent.view_str(state, self.view)})" 

690 elif self.x is not None and self.y is not None: 

691 return "%s(state=%s, x=%s, y=%s)" % ( 

692 self.__class__.__name__, 

693 state.state_str, 

694 self.x, 

695 self.y, 

696 ) 

697 else: 

698 msg = "Invalid %s!" % self.__class__.__name__ 

699 raise InvalidEventException(msg) 

700 

701 def get_views(self): 

702 return [self.view] if self.view else [] 

703 

704 def get_event_name(self): 

705 return "LongClick" 

706 

707class SwipeEvent(UIEvent): 

708 """ 

709 a drag gesture on screen 

710 """ 

711 

712 def __init__( 

713 self, 

714 start_x=None, 

715 start_y=None, 

716 start_view=None, 

717 end_x=None, 

718 end_y=None, 

719 end_view=None, 

720 duration=1000, 

721 event_dict=None, 

722 ): 

723 super().__init__() 

724 self.event_type = KEY_SwipeEvent 

725 

726 self.start_x = start_x 

727 self.start_y = start_y 

728 self.start_view = start_view 

729 

730 self.end_x = end_x 

731 self.end_y = end_y 

732 self.end_view = end_view 

733 

734 self.duration = duration 

735 

736 if event_dict is not None: 

737 self.__dict__.update(event_dict) 

738 

739 @staticmethod 

740 def get_random_instance(device, app): 

741 start_x = random.uniform(0, device.get_width()) 

742 start_y = random.uniform(0, device.get_height()) 

743 end_x = random.uniform(0, device.get_width()) 

744 end_y = random.uniform(0, device.get_height()) 

745 return SwipeEvent(start_x=start_x, start_y=start_y, end_x=end_x, end_y=end_y) 

746 

747 def send(self, device): 

748 start_x, start_y = UIEvent.get_xy( 

749 x=self.start_x, y=self.start_y, view=self.start_view 

750 ) 

751 end_x, end_y = UIEvent.get_xy(x=self.end_x, y=self.end_y, view=self.end_view) 

752 device.view_drag((start_x, start_y), (end_x, end_y), self.duration) 

753 return True 

754 

755 def get_event_str(self, state): 

756 if self.start_view is not None: 

757 start_view_str = UIEvent.view_str(state, self.start_view) 

758 elif self.start_x is not None and self.start_y is not None: 

759 start_view_str = "state=%s, start_x=%s, start_y=%s" % ( 

760 state.state_str, 

761 self.start_x, 

762 self.start_y, 

763 ) 

764 else: 

765 msg = "Invalid %s!" % self.__class__.__name__ 

766 raise InvalidEventException(msg) 

767 

768 if self.end_view is not None: 

769 end_view_str = "end_view=%s" % self.end_view['view_str'] 

770 elif self.end_x is not None and self.end_y is not None: 

771 end_view_str = "end_x=%s, end_y=%s" % (self.end_x, self.end_y) 

772 else: 

773 msg = "Invalid %s!" % self.__class__.__name__ 

774 raise InvalidEventException(msg) 

775 

776 return "%s(%s, %s, duration=%s)" % ( 

777 self.__class__.__name__, 

778 start_view_str, 

779 end_view_str, 

780 self.duration, 

781 ) 

782 

783 def get_views(self): 

784 views = [] 

785 if self.start_view: 

786 views.append(self.start_view) 

787 if self.end_view: 

788 views.append(self.end_view) 

789 return views 

790 

791 def get_event_name(self): 

792 return self.get_event_str() 

793 

794class ScrollEvent(UIEvent): 

795 """ 

796 swipe gesture 

797 """ 

798 

799 def __init__(self, x=None, y=None, view=None, direction="DOWN", event_dict=None): 

800 super().__init__() 

801 self.event_type = KEY_ScrollEvent 

802 self.x = x 

803 self.y = y 

804 self.view = view 

805 self.direction = direction 

806 

807 if event_dict is not None: 

808 self.__dict__.update(event_dict) 

809 

810 @staticmethod 

811 def get_random_instance(device, app): 

812 x = random.uniform(0, device.get_width()) 

813 y = random.uniform(0, device.get_height()) 

814 direction = random.choice(["UP", "DOWN", "LEFT", "RIGHT"]) 

815 return ScrollEvent(x, y, direction) 

816 

817 def send(self, device): 

818 if self.view is not None: 

819 from .device_state import DeviceState 

820 

821 width = DeviceState.get_view_width(view_dict=self.view) 

822 height = DeviceState.get_view_height(view_dict=self.view) 

823 else: 

824 width = device.get_width() 

825 height = device.get_height() 

826 

827 x, y = UIEvent.get_xy(x=self.x, y=self.y, view=self.view) 

828 if not x or not y: 

829 # If no view and no coordinate specified, use the screen center coordinate 

830 x = width / 2 

831 y = height / 2 

832 

833 start_x, start_y = x, y 

834 end_x, end_y = x, y 

835 duration = 500 

836 

837 if self.direction == "UP": 

838 start_y -= height * 2 / 5 

839 end_y += height * 2 / 5 

840 elif self.direction == "DOWN": 

841 start_y += height * 2 / 5 

842 end_y -= height * 2 / 5 

843 elif self.direction == "LEFT": 

844 start_x -= width * 2 / 5 

845 end_x += width * 2 / 5 

846 elif self.direction == "RIGHT": 

847 start_x += width * 2 / 5 

848 end_x -= width * 2 / 5 

849 

850 device.view_drag((start_x, start_y), (end_x, end_y), duration) 

851 return True 

852 

853 def get_event_str(self, state): 

854 if self.view is not None: 

855 return f"{self.__class__.__name__}({UIEvent.view_str(state, self.view)}, direction={self.direction})" 

856 elif self.x is not None and self.y is not None: 

857 return "%s(state=%s, x=%s, y=%s, direction=%s)" % ( 

858 self.__class__.__name__, 

859 state.state_str, 

860 self.x, 

861 self.y, 

862 self.direction, 

863 ) 

864 else: 

865 return "%s(state=%s, direction=%s)" % ( 

866 self.__class__.__name__, 

867 state.state_str, 

868 self.direction, 

869 ) 

870 

871 def get_views(self): 

872 return [self.view] if self.view else [] 

873 

874 def get_event_name(self): 

875 return "Scroll "+self.direction 

876 

877class SetTextEvent(UIEvent): 

878 """ 

879 input text to target UI 

880 """ 

881 

882 @staticmethod 

883 def get_random_instance(device, app): 

884 pass 

885 

886 def __init__(self, x=None, y=None, view=None, text=None, event_dict=None): 

887 super().__init__() 

888 self.event_type = KEY_SetTextEvent 

889 self.x = x 

890 self.y = y 

891 self.view = view 

892 self.text = text 

893 if event_dict is not None: 

894 self.__dict__.update(event_dict) 

895 

896 def send(self, device:Union["Device", "DeviceHM"]): 

897 x, y = UIEvent.get_xy(x=self.x, y=self.y, view=self.view) 

898 touch_event = TouchEvent(x=x, y=y) 

899 touch_event.send(device) 

900 if device.is_harmonyos: 

901 try: 

902 self.clear_text(device) 

903 except: 

904 print( 

905 "Fail to clear text. Append instead." 

906 ) 

907 device.view_set_text(self.text) 

908 return True 

909 

910 def clear_text(self, device:Union["Device", "DeviceHM"]): 

911 kwargs = dict() 

912 if self.view["resource_id"]: 

913 kwargs["id"] = self.view["resource_id"] 

914 if self.view["class"]: 

915 kwargs["type"] = self.view["class"] 

916 if self.view["package"]: 

917 kwargs["bundleName"] = self.view["class"] 

918 device.hm2(**kwargs).clear_text() 

919 

920 def get_event_str(self, state): 

921 if self.view is not None: 

922 return f"{self.__class__.__name__}({UIEvent.view_str(state, self.view)}, text={self.text})" 

923 elif self.x is not None and self.y is not None: 

924 return "%s(state=%s, x=%s, y=%s, text=%s)" % ( 

925 self.__class__.__name__, 

926 state.state_str, 

927 self.x, 

928 self.y, 

929 self.text, 

930 ) 

931 else: 

932 msg = "Invalid %s!" % self.__class__.__name__ 

933 raise InvalidEventException(msg) 

934 

935 def get_views(self): 

936 return [self.view] if self.view else [] 

937 

938 def set_text(self, text): 

939 self.text = text 

940 

941 def get_event_name(self): 

942 return "SetText "+self.text 

943 

944class SearchEvent(InputEvent): 

945 """ 

946 use the uiautomator2's search method 

947 """ 

948 

949 def __init__(self, event_dict=None): 

950 super().__init__() 

951 self.event_type = KEY_SearchEvent 

952 self.time = time.time() 

953 if event_dict is not None: 

954 self.__dict__.update(event_dict) 

955 

956 @staticmethod 

957 def get_random_instance(device, app): 

958 return None 

959 

960 def send(self, device): 

961 d = device.u2 

962 d.send_action("search") 

963 

964 def get_event_str(self, state): 

965 return "%s(time=%s)" % (self.__class__.__name__, self.time) 

966 

967 def get_event_name(self): 

968 return "Search" 

969 

970class SetTextAndSearchEvent(InputEvent): 

971 """ 

972 use the uiautomator2's send_keys and search method 

973 """ 

974 

975 def __init__(self, text, event_dict=None): 

976 super().__init__() 

977 self.event_type = KEY_SetTextAndSearchEvent 

978 self.text = text 

979 if event_dict is not None: 

980 self.__dict__.update(event_dict) 

981 

982 @staticmethod 

983 def get_random_instance(device, app): 

984 return None 

985 

986 def send(self, device): 

987 d = device.u2 

988 d.send_keys(self.text) 

989 d.send_action("search") 

990 

991 def get_event_str(self, state): 

992 return "%s(text=%s)" % (self.__class__.__name__, self.text) 

993 

994 def get_event_name(self): 

995 return "SetTextAndSearch "+self.text 

996 

997class IntentEvent(InputEvent): 

998 """ 

999 An event describing an intent 

1000 """ 

1001 

1002 def __init__(self, intent=None, event_dict=None): 

1003 super().__init__() 

1004 self.event_type = KEY_IntentEvent 

1005 if event_dict is not None: 

1006 intent = event_dict['intent'] 

1007 if isinstance(intent, Intent): 

1008 self.intent = intent.get_cmd() 

1009 elif isinstance(intent, str): 

1010 self.intent = intent 

1011 else: 

1012 msg = "intent must be either an instance of Intent or a string." 

1013 raise InvalidEventException(msg) 

1014 if event_dict is not None: 

1015 self.__dict__.update(event_dict) 

1016 

1017 @staticmethod 

1018 def get_random_instance(device, app): 

1019 pass 

1020 

1021 def send(self, device): 

1022 device.send_intent(intent=self.intent) 

1023 time.sleep(2) 

1024 return True 

1025 

1026 def get_event_str(self, state): 

1027 return "%s(intent='%s')" % (self.__class__.__name__, self.intent) 

1028 

1029 def get_event_name(self): 

1030 if isinstance(self.intent, Intent): 

1031 return "%s" % self.intent.prefix 

1032 else: 

1033 return "%s" % self.intent.split(" ")[1] 

1034 

1035class SpawnEvent(InputEvent): 

1036 """ 

1037 An event to spawn then stop testing 

1038 """ 

1039 

1040 def __init__(self, event_dict=None): 

1041 super().__init__() 

1042 self.event_type = KEY_SpawnEvent 

1043 if event_dict is not None: 

1044 self.__dict__.update(event_dict) 

1045 

1046 @staticmethod 

1047 def get_random_instance(device, app): 

1048 return None 

1049 

1050 def send(self, device): 

1051 master = self.__dict__["master"] 

1052 # force touch the view 

1053 init_script = { 

1054 "views": { 

1055 "droid_master_view": { 

1056 "resource_id": self.__dict__["view"]["resource_id"], 

1057 "class": self.__dict__["view"]["class"], 

1058 } 

1059 }, 

1060 "states": {"droid_master_state": {"views": ["droid_master_view"]}}, 

1061 "operations": { 

1062 "droid_master_operation": [ 

1063 {"event_type": "touch", "target_view": "droid_master_view"} 

1064 ] 

1065 }, 

1066 "main": {"droid_master_state": ["droid_master_operation"]}, 

1067 } 

1068 init_script_json = json.dumps(init_script, indent=2) 

1069 import xmlrpc.client 

1070 

1071 proxy = xmlrpc.client.ServerProxy(master) 

1072 proxy.spawn(device.serial, init_script_json) 

1073 

1074 def get_event_str(self, state): 

1075 return "%s()" % self.__class__.__name__ 

1076 

1077 

1078EVENT_TYPES = { 

1079 KEY_KeyEvent: KeyEvent, 

1080 KEY_TouchEvent: TouchEvent, 

1081 KEY_LongTouchEvent: LongTouchEvent, 

1082 KEY_SwipeEvent: SwipeEvent, 

1083 KEY_ScrollEvent: ScrollEvent, 

1084 KEY_IntentEvent: IntentEvent, 

1085 KEY_SpawnEvent: SpawnEvent, 

1086}