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 18import logging 19 20from devlib.utils.android import adb_command 21from devlib import TargetError 22from devlib.utils.android_build import Build 23from time import sleep 24import os 25import re 26import time 27import json 28import pexpect as pe 29from time import sleep 30 31GET_FRAMESTATS_CMD = 'shell dumpsys gfxinfo {} > {}' 32ADB_INSTALL_CMD = 'install -g -r {}' 33BOARD_CONFIG_FILE = 'board.json' 34 35# See https://developer.android.com/reference/android/content/Intent.html#setFlags(int) 36FLAG_ACTIVITY_NEW_TASK = 0x10000000 37FLAG_ACTIVITY_CLEAR_TASK = 0x00008000 38 39class 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