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
« 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
7from . import utils
8from .intent import Intent
10from typing import TYPE_CHECKING, Union
11if TYPE_CHECKING:
12 from .device import Device
13 from .device_hm import DeviceHM
15POSSIBLE_KEYS = ["BACK", "MENU", "HOME"]
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]
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"
91class InvalidEventException(Exception):
92 pass
95class InputEvent(object):
96 """
97 The base class of all events
98 """
100 def __init__(self):
101 self.event_type = None
102 self.log_lines = None
104 def to_dict(self):
105 return self.__dict__
107 def to_json(self):
108 return json.dumps(self.to_dict())
110 def __str__(self):
111 return self.to_dict().__str__()
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
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
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)
158 @abstractmethod
159 def get_event_str(self, state):
160 pass
162 def get_views(self):
163 return []
166class EventLog(object):
167 """
168 save an event to local file system
169 """
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
178 tag = datetime.now().strftime("%Y-%m-%d_%H%M%S")
179 self.tag = tag
181 self.from_state = None
182 self.to_state = None
183 self.event_str = None
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)
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 }
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)
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 )
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
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)
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
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()
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
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
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)
323 except Exception as e:
324 self.device.logger.warning("profiling event failed")
325 self.device.logger.warning(e)
328class ManualEvent(InputEvent):
329 """
330 a manual event
331 """
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)
340 @staticmethod
341 def get_random_instance(device, app):
342 return None
344 def send(self, device):
345 # do nothing
346 pass
348 def get_event_str(self, state):
349 return "%s(time=%s)" % (self.__class__.__name__, self.time)
352class ExitEvent(InputEvent):
353 """
354 an event to stop testing
355 """
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)
363 @staticmethod
364 def get_random_instance(device, app):
365 return None
367 def send(self, device):
368 # device.disconnect()
369 raise KeyboardInterrupt()
371 def get_event_str(self, state):
372 return "%s()" % self.__class__.__name__
374 def get_event_name(self):
375 return "Exit"
378class KillAppEvent(InputEvent):
379 """
380 an event to stop testing
381 """
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)
392 @staticmethod
393 def get_random_instance(device, app):
394 return None
396 def send(self, device):
397 if self.stop_intent:
398 device.send_intent(self.stop_intent)
399 device.key_press('HOME')
401 def get_event_str(self, state):
402 return "%s()" % self.__class__.__name__
404 def get_event_name(self):
405 return "KillApp"
407class KillAndRestartAppEvent(InputEvent):
408 """
409 an event to kill and restart app
410 """
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)
423 @staticmethod
424 def get_random_instance(device, app):
425 return None
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)
434 def get_event_str(self, state):
435 return "%s()" % self.__class__.__name__
437 def get_event_name(self):
438 return "KillAndRestartApp"
441class RotateDevice(InputEvent):
442 def __init__(self):
443 super().__init__()
445 @staticmethod
446 def get_random_instance(device, app):
447 return None
449 def send(self, device):
450 # do nothing
451 pass
453 def get_event_str(self, state):
454 return "%s()" % self.__class__.__name__
456 def get_event_name(self):
457 return "Rotate"
459class RotateDeviceToLandscapeEvent(RotateDevice):
460 """
461 an event to rotate device
462 """
463 def __init__(self):
464 super().__init__()
466 def send(self, device):
467 device.rotate_device_right()
468 time.sleep(1)
469 return True
471 def get_event_name(self):
472 return "RotateRight"
474class RotateDeviceToPortraitEvent(RotateDevice):
475 """
476 an event to rotate device
477 """
478 def __init__(self):
479 super().__init__()
481 def send(self, device):
482 device.rotate_device_neutral()
483 time.sleep(1)
484 return True
486 def get_event_name(self):
487 return "RotateNeutral"
490class KeyEvent(InputEvent):
491 """
492 a key pressing event
493 """
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)
502 @staticmethod
503 def get_random_instance(device, app):
504 key_name = random.choice(POSSIBLE_KEYS)
505 return KeyEvent(key_name)
507 def send(self, device):
508 device.key_press(self.name)
509 return True
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 )
518 def get_event_name(self):
519 return self.name
521# restart event
522class ReInstallAppEvent(InputEvent):
523 """
524 an event to restart the app with a fresh state
525 """
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)
543 @staticmethod
544 def get_random_instance(device, app):
545 return None
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)
558 def get_event_str(self, state, content_free=False):
559 return "%s()" % self.__class__.__name__
561 def get_event_name(self):
562 return "ReInstallApp"
564class UIEvent(InputEvent):
565 """
566 This class describes a UI event of app, such as touch, click, etc
567 """
569 def __init__(self):
570 super().__init__()
572 def send(self, device):
573 raise NotImplementedError
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))
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)
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
596 return DeviceState.get_view_center(view_dict=view)
597 return x, y
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})"
609 def set_view(self, view):
610 self.view = view
612 def set_xy(self, x, y):
613 self.x = x
614 self.y = y
616class TouchEvent(UIEvent):
617 """
618 a touch on screen
619 """
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)
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)
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
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)
655 def get_views(self):
656 return [self.view] if self.view else []
658 def get_event_name(self):
659 return "Click"
661class LongTouchEvent(UIEvent):
662 """
663 a long touch on screen
664 """
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)
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)
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
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)
701 def get_views(self):
702 return [self.view] if self.view else []
704 def get_event_name(self):
705 return "LongClick"
707class SwipeEvent(UIEvent):
708 """
709 a drag gesture on screen
710 """
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
726 self.start_x = start_x
727 self.start_y = start_y
728 self.start_view = start_view
730 self.end_x = end_x
731 self.end_y = end_y
732 self.end_view = end_view
734 self.duration = duration
736 if event_dict is not None:
737 self.__dict__.update(event_dict)
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)
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
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)
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)
776 return "%s(%s, %s, duration=%s)" % (
777 self.__class__.__name__,
778 start_view_str,
779 end_view_str,
780 self.duration,
781 )
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
791 def get_event_name(self):
792 return self.get_event_str()
794class ScrollEvent(UIEvent):
795 """
796 swipe gesture
797 """
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
807 if event_dict is not None:
808 self.__dict__.update(event_dict)
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)
817 def send(self, device):
818 if self.view is not None:
819 from .device_state import DeviceState
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()
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
833 start_x, start_y = x, y
834 end_x, end_y = x, y
835 duration = 500
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
850 device.view_drag((start_x, start_y), (end_x, end_y), duration)
851 return True
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 )
871 def get_views(self):
872 return [self.view] if self.view else []
874 def get_event_name(self):
875 return "Scroll "+self.direction
877class SetTextEvent(UIEvent):
878 """
879 input text to target UI
880 """
882 @staticmethod
883 def get_random_instance(device, app):
884 pass
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)
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
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()
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)
935 def get_views(self):
936 return [self.view] if self.view else []
938 def set_text(self, text):
939 self.text = text
941 def get_event_name(self):
942 return "SetText "+self.text
944class SearchEvent(InputEvent):
945 """
946 use the uiautomator2's search method
947 """
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)
956 @staticmethod
957 def get_random_instance(device, app):
958 return None
960 def send(self, device):
961 d = device.u2
962 d.send_action("search")
964 def get_event_str(self, state):
965 return "%s(time=%s)" % (self.__class__.__name__, self.time)
967 def get_event_name(self):
968 return "Search"
970class SetTextAndSearchEvent(InputEvent):
971 """
972 use the uiautomator2's send_keys and search method
973 """
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)
982 @staticmethod
983 def get_random_instance(device, app):
984 return None
986 def send(self, device):
987 d = device.u2
988 d.send_keys(self.text)
989 d.send_action("search")
991 def get_event_str(self, state):
992 return "%s(text=%s)" % (self.__class__.__name__, self.text)
994 def get_event_name(self):
995 return "SetTextAndSearch "+self.text
997class IntentEvent(InputEvent):
998 """
999 An event describing an intent
1000 """
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)
1017 @staticmethod
1018 def get_random_instance(device, app):
1019 pass
1021 def send(self, device):
1022 device.send_intent(intent=self.intent)
1023 time.sleep(2)
1024 return True
1026 def get_event_str(self, state):
1027 return "%s(intent='%s')" % (self.__class__.__name__, self.intent)
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]
1035class SpawnEvent(InputEvent):
1036 """
1037 An event to spawn then stop testing
1038 """
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)
1046 @staticmethod
1047 def get_random_instance(device, app):
1048 return None
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
1071 proxy = xmlrpc.client.ServerProxy(master)
1072 proxy.spawn(device.serial, init_script_json)
1074 def get_event_str(self, state):
1075 return "%s()" % self.__class__.__name__
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}