1 # SPDX-License-Identifier: Apache-2.0 2 # 3 # Copyright (C) 2015, ARM Limited and contributors. 4 # 5 # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 # not use this file except in compliance with the License. 7 # You may obtain a copy of the License at 8 # 9 # http://www.apache.org/licenses/LICENSE-2.0 10 # 11 # Unless required by applicable law or agreed to in writing, software 12 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 # See the License for the specific language governing permissions and 15 # limitations under the License. 16 # 17 18 import logging 19 20 from devlib.utils.android import adb_command 21 from devlib import TargetError 22 from devlib.utils.android_build import Build 23 from time import sleep 24 import os 25 import re 26 import time 27 import json 28 import pexpect as pe 29 from time import sleep 30 31 GET_FRAMESTATS_CMD = 'shell dumpsys gfxinfo {} > {}' 32 ADB_INSTALL_CMD = 'install -g -r {}' 33 BOARD_CONFIG_FILE = 'board.json' 34 35 # See https://developer.android.com/reference/android/content/Intent.html#setFlags(int) 36 FLAG_ACTIVITY_NEW_TASK = 0x10000000 37 FLAG_ACTIVITY_CLEAR_TASK = 0x00008000 38 39 class System(object): 40 """ 41 Collection of Android related services 42 """ 43 44 @staticmethod 45 def systrace_start(target, trace_file, time=None, 46 events=['gfx', 'view', 'sched', 'freq', 'idle'], 47 conf=None): 48 buffsize = "40000" 49 log = logging.getLogger('System') 50 51 # Android needs good TGID caching support, until atrace has it, 52 # just increase the cache size to avoid missing TGIDs (and also comms) 53 target.target.execute("echo 8192 > /sys/kernel/debug/tracing/saved_cmdlines_size") 54 55 # Override systrace defaults from target conf 56 if conf and ('systrace' in conf): 57 if 'categories' in conf['systrace']: 58 events = conf['systrace']['categories'] 59 if 'extra_categories' in conf['systrace']: 60 events += conf['systrace']['extra_categories'] 61 if 'buffsize' in conf['systrace']: 62 buffsize = int(conf['systrace']['buffsize']) 63 if 'extra_events' in conf['systrace']: 64 for ev in conf['systrace']['extra_events']: 65 log.info("systrace_start: Enabling extra ftrace event {}".format(ev)) 66 ev_file = target.target.execute("ls /sys/kernel/debug/tracing/events/*/{}/enable".format(ev)) 67 cmd = "echo 1 > {}".format(ev_file) 68 target.target.execute(cmd, as_root=True) 69 if 'event_triggers' in conf['systrace']: 70 for ev in conf['systrace']['event_triggers'].keys(): 71 tr_file = target.target.execute("ls /sys/kernel/debug/tracing/events/*/{}/trigger".format(ev)) 72 cmd = "echo {} > {}".format(conf['systrace']['event_triggers'][ev], tr_file) 73 target.target.execute(cmd, as_root=True, check_exit_code=False) 74 75 # Check which systrace binary is available under CATAPULT_HOME 76 for systrace in ['systrace.py', 'run_systrace.py']: 77 systrace_path = os.path.join(target.CATAPULT_HOME, 'systrace', 78 'systrace', systrace) 79 if os.path.isfile(systrace_path): 80 break 81 else: 82 log.warning("Systrace binary not available under CATAPULT_HOME: %s!", 83 target.CATAPULT_HOME) 84 return None 85 86 # Format the command according to the specified arguments 87 device = target.conf.get('device', '') 88 if device: 89 device = "-e {}".format(device) 90 systrace_pattern = "{} {} -o {} {} -b {}" 91 trace_cmd = systrace_pattern.format(systrace_path, device, 92 trace_file, " ".join(events), buffsize) 93 if time is not None: 94 trace_cmd += " -t {}".format(time) 95 96 log.info('SysTrace: %s', trace_cmd) 97 98 # Actually spawn systrace 99 return pe.spawn(trace_cmd) 100 101 @staticmethod 102 def systrace_wait(target, systrace_output): 103 systrace_output.wait() 104 105 @staticmethod 106 def set_airplane_mode(target, on=True): 107 """ 108 Set airplane mode 109 """ 110 ap_mode = 1 if on else 0 111 ap_state = 'true' if on else 'false' 112 113 try: 114 target.execute('settings put global airplane_mode_on {}'\ 115 .format(ap_mode), as_root=True) 116 target.execute('am broadcast '\ 117 '-a android.intent.action.AIRPLANE_MODE '\ 118 '--ez state {}'\ 119 .format(ap_state), as_root=True) 120 except TargetError: 121 log = logging.getLogger('System') 122 log.warning('Failed to toggle airplane mode, permission denied.') 123 124 @staticmethod 125 def _set_svc(target, cmd, on=True): 126 mode = 'enable' if on else 'disable' 127 try: 128 target.execute('svc {} {}'.format(cmd, mode), as_root=True) 129 except TargetError: 130 log = logging.getLogger('System') 131 log.warning('Failed to toggle {} mode, permission denied.'\ 132 .format(cmd)) 133 134 @staticmethod 135 def set_mobile_data(target, on=True): 136 """ 137 Set mobile data connectivity 138 """ 139 System._set_svc(target, 'data', on) 140 141 @staticmethod 142 def set_wifi(target, on=True): 143 """ 144 Set mobile data connectivity 145 """ 146 System._set_svc(target, 'wifi', on) 147 148 @staticmethod 149 def set_nfc(target, on=True): 150 """ 151 Set mobile data connectivity 152 """ 153 System._set_svc(target, 'nfc', on) 154 155 @staticmethod 156 def get_property(target, prop): 157 """ 158 Get the value of a system property 159 """ 160 try: 161 value = target.execute('getprop {}'.format(prop), as_root=True) 162 except TargetError: 163 log = logging.getLogger('System') 164 log.warning('Failed to get prop {}'.format(prop)) 165 return value.strip() 166 167 @staticmethod 168 def get_boolean_property(target, prop): 169 """ 170 Get a boolean system property and return whether its value corresponds to True 171 """ 172 return System.get_property(target, prop) in {'yes', 'true', 'on', '1', 'y'} 173 174 @staticmethod 175 def set_property(target, prop, value, restart=False): 176 """ 177 Set a system property, then run adb shell stop && start if necessary 178 179 When restart=True, this function clears logcat to avoid detecting old 180 "Boot is finished" messages. 181 """ 182 try: 183 target.execute('setprop {} {}'.format(prop, value), as_root=True) 184 except TargetError: 185 log = logging.getLogger('System') 186 log.warning('Failed to set {} to {}.'.format(prop, value)) 187 if not restart: 188 return 189 190 target.execute('logcat -c', check_exit_code=False) 191 BOOT_FINISHED_RE = re.compile(r'Boot is finished') 192 logcat = target.background('logcat SurfaceFlinger:* *:S') 193 target.execute('stop && start', as_root=True) 194 while True: 195 message = logcat.stdout.readline(1024) 196 match = BOOT_FINISHED_RE.search(message) 197 if match: 198 return 199 200 @staticmethod 201 def start_app(target, apk_name): 202 """ 203 Start the main activity of the specified application 204 205 :param apk_name: name of the apk 206 :type apk_name: str 207 """ 208 target.execute('monkey -p {} -c android.intent.category.LAUNCHER 1'\ 209 .format(apk_name)) 210 211 @staticmethod 212 def start_activity(target, apk_name, activity_name): 213 """ 214 Start an application by specifying package and activity name. 215 216 :param apk_name: name of the apk 217 :type apk_name: str 218 219 :param activity_name: name of the activity to launch 220 :type activity_name: str 221 """ 222 target.execute('am start -n {}/{}'.format(apk_name, activity_name)) 223 224 @staticmethod 225 def start_action(target, action, action_args=''): 226 """ 227 Start an activity by specifying an action. 228 229 :param action: action to be executed 230 :type action: str 231 232 :param action_args: arguments for the activity 233 :type action_args: str 234 """ 235 target.execute('am start -a {} {}'.format(action, action_args)) 236 237 @staticmethod 238 def screen_always_on(target, enable=True): 239 """ 240 Keep the screen always on 241 242 :param enable: True or false 243 """ 244 param = 'true' 245 if not enable: 246 param = 'false' 247 248 log = logging.getLogger('System') 249 log.info('Setting screen always on to {}'.format(param)) 250 target.execute('svc power stayon {}'.format(param)) 251 252 @staticmethod 253 def view_uri(target, uri, force_new=True): 254 """ 255 Start a view activity by specifying a URI 256 257 :param uri: URI of the item to display 258 :type uri: str 259 260 :param force_new: Force the viewing application to be 261 relaunched if it is already running 262 :type force_new: bool 263 """ 264 arguments = '-d {}'.format(uri) 265 266 if force_new: 267 # Activity flags ensure the app is restarted 268 arguments = '{} -f {}'.format(arguments, 269 FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK) 270 271 System.start_action(target, 'android.intent.action.VIEW', arguments) 272 # Wait for the viewing application to be completely loaded 273 sleep(5) 274 275 @staticmethod 276 def force_stop(target, apk_name, clear=False): 277 """ 278 Stop the application and clear its data if necessary. 279 280 :param target: instance of devlib Android target 281 :type target: devlib.target.AndroidTarget 282 283 :param apk_name: name of the apk 284 :type apk_name: str 285 286 :param clear: clear application data 287 :type clear: bool 288 """ 289 target.execute('am force-stop {}'.format(apk_name)) 290 if clear: 291 target.execute('pm clear {}'.format(apk_name)) 292 293 @staticmethod 294 def force_suspend_start(target): 295 """ 296 Force the device to go into suspend. If a wakelock is held, the device 297 will go into idle instead. 298 299 :param target: instance of devlib Android target 300 :type target: devlib.target.AndroidTarget 301 302 """ 303 target.execute('dumpsys deviceidle force-idle deep') 304 305 @staticmethod 306 def force_suspend_stop(target): 307 """ 308 Stop forcing the device to suspend/idle. 309 310 :param target: instance of devlib Android target 311 :type target: devlib.target.AndroidTarget 312 313 """ 314 target.execute('dumpsys deviceidle unforce') 315 316 @staticmethod 317 def tap(target, x, y, absolute=False): 318 """ 319 Tap a given point on the screen. 320 321 :param target: instance of devlib Android target 322 :type target: devlib.target.AndroidTarget 323 324 :param x: horizontal coordinate 325 :type x: int 326 327 :param y: vertical coordinate 328 :type y: int 329 330 :param absolute: use absolute coordinates or percentage of screen 331 resolution 332 :type absolute: bool 333 """ 334 if not absolute: 335 w, h = target.screen_resolution 336 x = w * x / 100 337 y = h * y / 100 338 339 target.execute('input tap {} {}'.format(x, y)) 340 341 @staticmethod 342 def vswipe(target, y_low_pct, y_top_pct, duration='', swipe_up=True): 343 """ 344 Vertical swipe 345 346 :param target: instance of devlib Android target 347 :type target: devlib.target.AndroidTarget 348 349 :param y_low_pct: vertical lower coordinate percentage 350 :type y_low_pct: int 351 352 :param y_top_pct: vertical upper coordinate percentage 353 :type y_top_pct: int 354 355 :param duration: duration of the swipe in milliseconds 356 :type duration: int 357 358 :param swipe_up: swipe up or down 359 :type swipe_up: bool 360 """ 361 w, h = target.screen_resolution 362 x = w / 2 363 if swipe_up: 364 y1 = h * y_top_pct / 100 365 y2 = h * y_low_pct / 100 366 else: 367 y1 = h * y_low_pct / 100 368 y2 = h * y_top_pct / 100 369 370 target.execute('input swipe {} {} {} {} {}'\ 371 .format(x, y1, x, y2, duration)) 372 373 @staticmethod 374 def hswipe(target, x_left_pct, x_right_pct, duration='', swipe_right=True): 375 """ 376 Horizontal swipe 377 378 :param target: instance of devlib Android target 379 :type target: devlib.target.AndroidTarget 380 381 :param x_left_pct: horizontal left coordinate percentage 382 :type x_left_pct: int 383 384 :param x_right_pct: horizontal right coordinate percentage 385 :type x_right_pct: int 386 387 :param duration: duration of the swipe in milliseconds 388 :type duration: int 389 390 :param swipe_right: swipe right or left 391 :type swipe_right: bool 392 """ 393 w, h = target.screen_resolution 394 y = h / 2 395 if swipe_right: 396 x1 = w * x_left_pct / 100 397 x2 = w * x_right_pct / 100 398 else: 399 x1 = w * x_right_pct / 100 400 x2 = w * x_left_pct / 100 401 target.execute('input swipe {} {} {} {} {}'\ 402 .format(x1, y, x2, y, duration)) 403 404 @staticmethod 405 def menu(target): 406 """ 407 Press MENU button 408 409 :param target: instance of devlib Android target 410 :type target: devlib.target.AndroidTarget 411 """ 412 target.execute('input keyevent KEYCODE_MENU') 413 414 @staticmethod 415 def home(target): 416 """ 417 Press HOME button 418 419 :param target: instance of devlib Android target 420 :type target: devlib.target.AndroidTarget 421 """ 422 target.execute('input keyevent KEYCODE_HOME') 423 424 @staticmethod 425 def back(target): 426 """ 427 Press BACK button 428 429 :param target: instance of devlib Android target 430 :type target: devlib.target.AndroidTarget 431 """ 432 target.execute('input keyevent KEYCODE_BACK') 433 434 @staticmethod 435 def wakeup(target): 436 """ 437 Wake up the system if its sleeping 438 439 :param target: instance of devlib Android target 440 :type target: devlib.target.AndroidTarget 441 """ 442 target.execute('input keyevent KEYCODE_WAKEUP') 443 444 @staticmethod 445 def sleep(target): 446 """ 447 Make system sleep if its awake 448 449 :param target: instance of devlib Android target 450 :type target: devlib.target.AndroidTarget 451 """ 452 target.execute('input keyevent KEYCODE_SLEEP') 453 454 @staticmethod 455 def volume(target, times=1, direction='down'): 456 """ 457 Increase or decrease volume 458 459 :param target: instance of devlib Android target 460 :type target: devlib.target.AndroidTarget 461 462 :param times: number of times to perform operation 463 :type times: int 464 465 :param direction: which direction to increase (up/down) 466 :type direction: str 467 """ 468 for i in range(times): 469 if direction == 'up': 470 target.execute('input keyevent KEYCODE_VOLUME_UP') 471 elif direction == 'down': 472 target.execute('input keyevent KEYCODE_VOLUME_DOWN') 473 474 @staticmethod 475 def wakelock(target, name='lisa', take=False): 476 """ 477 Take or release wakelock 478 479 :param target: instance of devlib Android target 480 :type target: devlib.target.AndroidTarget 481 482 :param name: name of the wakelock 483 :type name: str 484 485 :param take: whether to take or release the wakelock 486 :type take: bool 487 """ 488 path = '/sys/power/wake_lock' if take else '/sys/power/wake_unlock' 489 target.execute('echo {} > {}'.format(name, path)) 490 491 @staticmethod 492 def gfxinfo_reset(target, apk_name): 493 """ 494 Reset gfxinfo frame statistics for a given app. 495 496 :param target: instance of devlib Android target 497 :type target: devlib.target.AndroidTarget 498 499 :param apk_name: name of the apk 500 :type apk_name: str 501 """ 502 target.execute('dumpsys gfxinfo {} reset'.format(apk_name)) 503 sleep(1) 504 505 @staticmethod 506 def surfaceflinger_reset(target, apk_name): 507 """ 508 Reset SurfaceFlinger layer statistics for a given app. 509 510 :param target: instance of devlib Android target 511 :type target: devlib.target.AndroidTarget 512 513 :param apk_name: name of the apk 514 :type apk_name: str 515 """ 516 target.execute('dumpsys SurfaceFlinger {} reset'.format(apk_name)) 517 518 @staticmethod 519 def logcat_reset(target): 520 """ 521 Clears the logcat buffer. 522 523 :param target: instance of devlib Android target 524 :type target: devlib.target.AndroidTarget 525 """ 526 target.execute('logcat -c') 527 528 @staticmethod 529 def gfxinfo_get(target, apk_name, out_file): 530 """ 531 Collect frame statistics for the given app. 532 533 :param target: instance of devlib Android target 534 :type target: devlib.target.AndroidTarget 535 536 :param apk_name: name of the apk 537 :type apk_name: str 538 539 :param out_file: output file name 540 :type out_file: str 541 """ 542 adb_command(target.adb_name, 543 GET_FRAMESTATS_CMD.format(apk_name, out_file)) 544 545 @staticmethod 546 def surfaceflinger_get(target, apk_name, out_file): 547 """ 548 Collect SurfaceFlinger layer statistics for the given app. 549 550 :param target: instance of devlib Android target 551 :type target: devlib.target.AndroidTarget 552 553 :param apk_name: name of the apk 554 :type apk_name: str 555 556 :param out_file: output file name 557 :type out_file: str 558 """ 559 adb_command(target.adb_name, 560 'shell dumpsys SurfaceFlinger {} > {}'.format(apk_name, out_file)) 561 562 @staticmethod 563 def logcat_get(target, out_file): 564 """ 565 Collect the logs from logcat. 566 567 :param target: instance of devlib Android target 568 :type target: devlib.target.AndroidTarget 569 570 :param out_file: output file name 571 :type out_file: str 572 """ 573 adb_command(target.adb_name, 'logcat * -d > {}'.format(out_file)) 574 575 @staticmethod 576 def monkey(target, apk_name, event_count=1): 577 """ 578 Wrapper for adb monkey tool. 579 580 The Monkey is a program that runs on your emulator or device and 581 generates pseudo-random streams of user events such as clicks, touches, 582 or gestures, as well as a number of system-level events. You can use 583 the Monkey to stress-test applications that you are developing, in a 584 random yet repeatable manner. 585 586 Full documentation is available at: 587 588 https://developer.android.com/studio/test/monkey.html 589 590 :param target: instance of devlib Android target 591 :type target: devlib.target.AndroidTarget 592 593 :param apk_name: name of the apk 594 :type apk_name: str 595 596 :param event_count: number of events to generate 597 :type event_count: int 598 """ 599 target.execute('monkey -p {} {}'.format(apk_name, event_count)) 600 601 @staticmethod 602 def list_packages(target, apk_filter=''): 603 """ 604 List the packages matching the specified filter 605 606 :param target: instance of devlib Android target 607 :type target: devlib.target.AndroidTarget 608 609 :param apk_filter: a substring which must be part of the package name 610 :type apk_filter: str 611 """ 612 packages = [] 613 614 pkgs = target.execute('cmd package list packages {}'\ 615 .format(apk_filter.lower())) 616 for pkg in pkgs.splitlines(): 617 packages.append(pkg.replace('package:', '')) 618 packages.sort() 619 620 if len(packages): 621 return packages 622 return None 623 624 @staticmethod 625 def packages_info(target, apk_filter=''): 626 """ 627 Get a dictionary of installed APKs and related information 628 629 :param target: instance of devlib Android target 630 :type target: devlib.target.AndroidTarget 631 632 :param apk_filter: a substring which must be part of the package name 633 :type apk_filter: str 634 """ 635 packages = {} 636 637 pkgs = target.execute('cmd package list packages {}'\ 638 .format(apk_filter.lower())) 639 for pkg in pkgs.splitlines(): 640 pkg = pkg.replace('package:', '') 641 # Lookup for additional APK information 642 apk = target.execute('pm path {}'.format(pkg)) 643 apk = apk.replace('package:', '') 644 packages[pkg] = { 645 'apk' : apk.strip() 646 } 647 648 if len(packages): 649 return packages 650 return None 651 652 653 @staticmethod 654 def install_apk(target, apk_path): 655 """ 656 Get a dictionary of installed APKs and related information 657 658 :param target: instance of devlib Android target 659 :type target: devlib.target.AndroidTarget 660 661 :param apk_path: path to application 662 :type apk_path: str 663 """ 664 adb_command(target.adb_name, ADB_INSTALL_CMD.format(apk_path)) 665 666 @staticmethod 667 def contains_package(target, package): 668 """ 669 Returns true if the package exists on the device 670 671 :param target: instance of devlib Android target 672 :type target: devlib.target.AndroidTarget 673 674 :param package: the name of the package 675 :type package: str 676 """ 677 packages = System.list_packages(target) 678 if not packages: 679 return None 680 681 return package in packages 682 683 @staticmethod 684 def grant_permission(target, package, permission): 685 """ 686 Grant permission to a package 687 688 :param target: instance of devlib Android target 689 :type target: devlib.target.AndroidTarget 690 691 :param package: the name of the package 692 :type package: str 693 694 :param permission: the name of the permission 695 :type permission: str 696 """ 697 target.execute('pm grant {} {}'.format(package, permission)) 698 699 @staticmethod 700 def reset_permissions(target, package): 701 """ 702 Reset the permission for a package 703 704 :param target: instance of devlib Android target 705 :type target: devlib.target.AndroidTarget 706 707 :param package: the name of the package 708 :type package: str 709 """ 710 target.execute('pm reset-permissions {}'.format(package)) 711 712 @staticmethod 713 def find_config_file(test_env): 714 # Try device-specific config file first 715 board_cfg_file = os.path.join(test_env.DEVICE_LISA_HOME, BOARD_CONFIG_FILE) 716 717 if not os.path.exists(board_cfg_file): 718 # Try local config file $LISA_HOME/libs/utils/platforms/$TARGET_PRODUCT.json 719 board_cfg_file = 'libs/utils/platforms/{}.json'.format(test_env.TARGET_PRODUCT) 720 board_cfg_file = os.path.join(test_env.LISA_HOME, board_cfg_file) 721 if not os.path.exists(board_cfg_file): 722 return None 723 return board_cfg_file 724 725 @staticmethod 726 def read_config_file(board_cfg_file): 727 with open(board_cfg_file, "r") as fh: 728 board_config = json.load(fh) 729 return board_config 730 731 @staticmethod 732 def reimage(test_env, kernel_path='', update_cfg=''): 733 """ 734 Get a reference to the specified Android workload 735 736 :param test_env: target test environment 737 :type test_env: TestEnv 738 739 :param kernel_path: path to kernel sources, required if reimage option is used 740 :type kernel_path: str 741 742 :param update_cfg: update configuration name from board_cfg.json 743 :type update_cfg: str 744 745 """ 746 # Find board config file from device-specific or local directory 747 board_cfg_file = System.find_config_file(test_env) 748 if board_cfg_file == None: 749 raise RuntimeError('Board config file is not found') 750 751 # Read build config file 752 board_config = System.read_config_file(board_cfg_file) 753 if board_config == None: 754 raise RuntimeError('Board config file {} is invalid'.format(board_cfg_file)) 755 756 # Read update-config section and execute appropriate scripts 757 update_config = board_config['update-config'][update_cfg] 758 if update_config == None: 759 raise RuntimeError('Update config \'{}\' is not found'.format(update_cfg)) 760 761 board_cfg_dir = os.path.dirname(os.path.realpath(board_cfg_file)) 762 build_script = update_config['build-script'] 763 flash_script = update_config['flash-script'] 764 build_script = os.path.join(board_cfg_dir, build_script) 765 flash_script = os.path.join(board_cfg_dir, flash_script) 766 767 cmd_prefix = "LOCAL_KERNEL_HOME='{}' ".format(kernel_path) 768 bld = Build(test_env) 769 bld.exec_cmd(cmd_prefix + build_script) 770 bld.exec_cmd(cmd_prefix + flash_script) 771 772 773 # vim :set tabstop=4 shiftwidth=4 expandtab 774