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 22import os 23import pexpect as pe 24 25GET_FRAMESTATS_CMD = 'shell dumpsys gfxinfo {} > {}' 26 27class System(object): 28 """ 29 Collection of Android related services 30 """ 31 32 @staticmethod 33 def systrace_start(target, trace_file, time=None, 34 events=['gfx', 'view', 'sched', 'freq', 'idle'], 35 conf=None): 36 buffsize = "40000" 37 log = logging.getLogger('System') 38 39 # Android needs good TGID caching support, until atrace has it, 40 # just increase the cache size to avoid missing TGIDs (and also comms) 41 target.target.execute("echo 8192 > /sys/kernel/debug/tracing/saved_cmdlines_size") 42 43 # Override systrace defaults from target conf 44 if conf and ('systrace' in conf): 45 if 'categories' in conf['systrace']: 46 events = conf['systrace']['categories'] 47 if 'extra_categories' in conf['systrace']: 48 events += conf['systrace']['extra_categories'] 49 if 'buffsize' in conf['systrace']: 50 buffsize = int(conf['systrace']['buffsize']) 51 if 'extra_events' in conf['systrace']: 52 for ev in conf['systrace']['extra_events']: 53 log.info("systrace_start: Enabling extra ftrace event {}".format(ev)) 54 ev_file = target.target.execute("ls /sys/kernel/debug/tracing/events/*/{}/enable".format(ev)) 55 cmd = "echo 1 > {}".format(ev_file) 56 target.target.execute(cmd, as_root=True) 57 if 'event_triggers' in conf['systrace']: 58 for ev in conf['systrace']['event_triggers'].keys(): 59 tr_file = target.target.execute("ls /sys/kernel/debug/tracing/events/*/{}/trigger".format(ev)) 60 cmd = "echo {} > {}".format(conf['systrace']['event_triggers'][ev], tr_file) 61 target.target.execute(cmd, as_root=True, check_exit_code=False) 62 63 # Check which systrace binary is available under CATAPULT_HOME 64 for systrace in ['systrace.py', 'run_systrace.py']: 65 systrace_path = os.path.join(target.CATAPULT_HOME, 'systrace', 66 'systrace', systrace) 67 if os.path.isfile(systrace_path): 68 break 69 else: 70 log.warning("Systrace binary not available under CATAPULT_HOME: %s!", 71 target.CATAPULT_HOME) 72 return None 73 74 # Format the command according to the specified arguments 75 device = target.conf.get('device', '') 76 if device: 77 device = "-e {}".format(device) 78 systrace_pattern = "{} {} -o {} {} -b {}" 79 trace_cmd = systrace_pattern.format(systrace_path, device, 80 trace_file, " ".join(events), buffsize) 81 if time is not None: 82 trace_cmd += " -t {}".format(time) 83 84 log.info('SysTrace: %s', trace_cmd) 85 86 # Actually spawn systrace 87 return pe.spawn(trace_cmd) 88 89 @staticmethod 90 def systrace_wait(target, systrace_output): 91 systrace_output.wait() 92 93 @staticmethod 94 def set_airplane_mode(target, on=True): 95 """ 96 Set airplane mode 97 """ 98 ap_mode = 1 if on else 0 99 ap_state = 'true' if on else 'false' 100 101 try: 102 target.execute('settings put global airplane_mode_on {}'\ 103 .format(ap_mode), as_root=True) 104 target.execute('am broadcast '\ 105 '-a android.intent.action.AIRPLANE_MODE '\ 106 '--ez state {}'\ 107 .format(ap_state), as_root=True) 108 except TargetError: 109 log = logging.getLogger('System') 110 log.warning('Failed to toggle airplane mode, permission denied.') 111 112 @staticmethod 113 def _set_svc(target, cmd, on=True): 114 mode = 'enable' if on else 'disable' 115 try: 116 target.execute('svc {} {}'.format(cmd, mode), as_root=True) 117 except TargetError: 118 log = logging.getLogger('System') 119 log.warning('Failed to toggle {} mode, permission denied.'\ 120 .format(cmd)) 121 122 @staticmethod 123 def set_mobile_data(target, on=True): 124 """ 125 Set mobile data connectivity 126 """ 127 System._set_svc(target, 'data', on) 128 129 @staticmethod 130 def set_wifi(target, on=True): 131 """ 132 Set mobile data connectivity 133 """ 134 System._set_svc(target, 'wifi', on) 135 136 @staticmethod 137 def set_nfc(target, on=True): 138 """ 139 Set mobile data connectivity 140 """ 141 System._set_svc(target, 'nfc', on) 142 143 @staticmethod 144 def start_app(target, apk_name): 145 """ 146 Start the main activity of the specified application 147 148 :param apk_name: name of the apk 149 :type apk_name: str 150 """ 151 target.execute('monkey -p {} -c android.intent.category.LAUNCHER 1'\ 152 .format(apk_name)) 153 154 @staticmethod 155 def start_activity(target, apk_name, activity_name): 156 """ 157 Start an application by specifying package and activity name. 158 159 :param apk_name: name of the apk 160 :type apk_name: str 161 162 :param activity_name: name of the activity to launch 163 :type activity_name: str 164 """ 165 target.execute('am start -n {}/{}'.format(apk_name, activity_name)) 166 167 @staticmethod 168 def start_action(target, action, action_args=''): 169 """ 170 Start an activity by specifying an action. 171 172 :param action: action to be executed 173 :type action: str 174 175 :param action_args: arguments for the activity 176 :type action_args: str 177 """ 178 target.execute('am start -a {} {}'.format(action, action_args)) 179 180 @staticmethod 181 def screen_always_on(target, enable=True): 182 """ 183 Keep the screen always on 184 185 :param enable: True or false 186 """ 187 param = 'true' 188 if not enable: 189 param = 'false' 190 191 log = logging.getLogger('System') 192 log.info('Setting screen always on to {}'.format(param)) 193 target.execute('svc power stayon {}'.format(param)) 194 195 @staticmethod 196 def force_stop(target, apk_name, clear=False): 197 """ 198 Stop the application and clear its data if necessary. 199 200 :param target: instance of devlib Android target 201 :type target: devlib.target.AndroidTarget 202 203 :param apk_name: name of the apk 204 :type apk_name: str 205 206 :param clear: clear application data 207 :type clear: bool 208 """ 209 target.execute('am force-stop {}'.format(apk_name)) 210 if clear: 211 target.execute('pm clear {}'.format(apk_name)) 212 213 @staticmethod 214 def tap(target, x, y, absolute=False): 215 """ 216 Tap a given point on the screen. 217 218 :param target: instance of devlib Android target 219 :type target: devlib.target.AndroidTarget 220 221 :param x: horizontal coordinate 222 :type x: int 223 224 :param y: vertical coordinate 225 :type y: int 226 227 :param absolute: use absolute coordinates or percentage of screen 228 resolution 229 :type absolute: bool 230 """ 231 if not absolute: 232 w, h = target.screen_resolution 233 x = w * x / 100 234 y = h * y / 100 235 236 target.execute('input tap {} {}'.format(x, y)) 237 238 @staticmethod 239 def vswipe(target, y_low_pct, y_top_pct, duration='', swipe_up=True): 240 """ 241 Vertical swipe 242 243 :param target: instance of devlib Android target 244 :type target: devlib.target.AndroidTarget 245 246 :param y_low_pct: vertical lower coordinate percentage 247 :type y_low_pct: int 248 249 :param y_top_pct: vertical upper coordinate percentage 250 :type y_top_pct: int 251 252 :param duration: duration of the swipe in milliseconds 253 :type duration: int 254 255 :param swipe_up: swipe up or down 256 :type swipe_up: bool 257 """ 258 w, h = target.screen_resolution 259 x = w / 2 260 if swipe_up: 261 y1 = h * y_top_pct / 100 262 y2 = h * y_low_pct / 100 263 else: 264 y1 = h * y_low_pct / 100 265 y2 = h * y_top_pct / 100 266 267 target.execute('input swipe {} {} {} {} {}'\ 268 .format(x, y1, x, y2, duration)) 269 270 @staticmethod 271 def hswipe(target, x_left_pct, x_right_pct, duration='', swipe_right=True): 272 """ 273 Horizontal swipe 274 275 :param target: instance of devlib Android target 276 :type target: devlib.target.AndroidTarget 277 278 :param x_left_pct: horizontal left coordinate percentage 279 :type x_left_pct: int 280 281 :param x_right_pct: horizontal right coordinate percentage 282 :type x_right_pct: int 283 284 :param duration: duration of the swipe in milliseconds 285 :type duration: int 286 287 :param swipe_right: swipe right or left 288 :type swipe_right: bool 289 """ 290 w, h = target.screen_resolution 291 y = h / 2 292 if swipe_right: 293 x1 = w * x_left_pct / 100 294 x2 = w * x_right_pct / 100 295 else: 296 x1 = w * x_right_pct / 100 297 x2 = w * x_left_pct / 100 298 target.execute('input swipe {} {} {} {} {}'\ 299 .format(x1, y, x2, y, duration)) 300 301 @staticmethod 302 def menu(target): 303 """ 304 Press MENU button 305 306 :param target: instance of devlib Android target 307 :type target: devlib.target.AndroidTarget 308 """ 309 target.execute('input keyevent KEYCODE_MENU') 310 311 @staticmethod 312 def home(target): 313 """ 314 Press HOME button 315 316 :param target: instance of devlib Android target 317 :type target: devlib.target.AndroidTarget 318 """ 319 target.execute('input keyevent KEYCODE_HOME') 320 321 @staticmethod 322 def back(target): 323 """ 324 Press BACK button 325 326 :param target: instance of devlib Android target 327 :type target: devlib.target.AndroidTarget 328 """ 329 target.execute('input keyevent KEYCODE_BACK') 330 331 @staticmethod 332 def wakeup(target): 333 """ 334 Wake up the system if its sleeping 335 336 :param target: instance of devlib Android target 337 :type target: devlib.target.AndroidTarget 338 """ 339 target.execute('input keyevent KEYCODE_WAKEUP') 340 341 @staticmethod 342 def sleep(target): 343 """ 344 Make system sleep if its awake 345 346 :param target: instance of devlib Android target 347 :type target: devlib.target.AndroidTarget 348 """ 349 target.execute('input keyevent KEYCODE_SLEEP') 350 351 @staticmethod 352 def volume(target, times=1, direction='down'): 353 """ 354 Increase or decrease volume 355 356 :param target: instance of devlib Android target 357 :type target: devlib.target.AndroidTarget 358 359 :param times: number of times to perform operation 360 :type times: int 361 362 :param direction: which direction to increase (up/down) 363 :type direction: str 364 """ 365 for i in range(times): 366 if direction == 'up': 367 target.execute('input keyevent KEYCODE_VOLUME_UP') 368 elif direction == 'down': 369 target.execute('input keyevent KEYCODE_VOLUME_DOWN') 370 371 @staticmethod 372 def wakelock(target, name='lisa', take=False): 373 """ 374 Take or release wakelock 375 376 :param target: instance of devlib Android target 377 :type target: devlib.target.AndroidTarget 378 379 :param name: name of the wakelock 380 :type name: str 381 382 :param take: whether to take or release the wakelock 383 :type take: bool 384 """ 385 path = '/sys/power/wake_lock' if take else '/sys/power/wake_unlock' 386 target.execute('echo {} > {}'.format(name, path)) 387 388 @staticmethod 389 def gfxinfo_reset(target, apk_name): 390 """ 391 Reset gfxinfo frame statistics for a given app. 392 393 :param target: instance of devlib Android target 394 :type target: devlib.target.AndroidTarget 395 396 :param apk_name: name of the apk 397 :type apk_name: str 398 """ 399 target.execute('dumpsys gfxinfo {} reset'.format(apk_name)) 400 401 @staticmethod 402 def surfaceflinger_reset(target, apk_name): 403 """ 404 Reset SurfaceFlinger layer statistics for a given app. 405 406 :param target: instance of devlib Android target 407 :type target: devlib.target.AndroidTarget 408 409 :param apk_name: name of the apk 410 :type apk_name: str 411 """ 412 target.execute('dumpsys SurfaceFlinger {} reset'.format(apk_name)) 413 414 @staticmethod 415 def logcat_reset(target): 416 """ 417 Clears the logcat buffer. 418 419 :param target: instance of devlib Android target 420 :type target: devlib.target.AndroidTarget 421 """ 422 target.execute('logcat -c') 423 424 @staticmethod 425 def gfxinfo_get(target, apk_name, out_file): 426 """ 427 Collect frame statistics for the given app. 428 429 :param target: instance of devlib Android target 430 :type target: devlib.target.AndroidTarget 431 432 :param apk_name: name of the apk 433 :type apk_name: str 434 435 :param out_file: output file name 436 :type out_file: str 437 """ 438 adb_command(target.adb_name, 439 GET_FRAMESTATS_CMD.format(apk_name, out_file)) 440 441 @staticmethod 442 def surfaceflinger_get(target, apk_name, out_file): 443 """ 444 Collect SurfaceFlinger layer statistics for the given app. 445 446 :param target: instance of devlib Android target 447 :type target: devlib.target.AndroidTarget 448 449 :param apk_name: name of the apk 450 :type apk_name: str 451 452 :param out_file: output file name 453 :type out_file: str 454 """ 455 adb_command(target.adb_name, 456 'shell dumpsys SurfaceFlinger {} > {}'.format(apk_name, out_file)) 457 458 @staticmethod 459 def logcat_get(target, out_file): 460 """ 461 Collect the logs from logcat. 462 463 :param target: instance of devlib Android target 464 :type target: devlib.target.AndroidTarget 465 466 :param out_file: output file name 467 :type out_file: str 468 """ 469 adb_command(target.adb_name, 'logcat * -d > {}'.format(out_file)) 470 471 @staticmethod 472 def monkey(target, apk_name, event_count=1): 473 """ 474 Wrapper for adb monkey tool. 475 476 The Monkey is a program that runs on your emulator or device and 477 generates pseudo-random streams of user events such as clicks, touches, 478 or gestures, as well as a number of system-level events. You can use 479 the Monkey to stress-test applications that you are developing, in a 480 random yet repeatable manner. 481 482 Full documentation is available at: 483 484 https://developer.android.com/studio/test/monkey.html 485 486 :param target: instance of devlib Android target 487 :type target: devlib.target.AndroidTarget 488 489 :param apk_name: name of the apk 490 :type apk_name: str 491 492 :param event_count: number of events to generate 493 :type event_count: int 494 """ 495 target.execute('monkey -p {} {}'.format(apk_name, event_count)) 496 497 @staticmethod 498 def list_packages(target, apk_filter=''): 499 """ 500 List the packages matching the specified filter 501 502 :param target: instance of devlib Android target 503 :type target: devlib.target.AndroidTarget 504 505 :param apk_filter: a substring which must be part of the package name 506 :type apk_filter: str 507 """ 508 packages = [] 509 510 pkgs = target.execute('cmd package list packages {}'\ 511 .format(apk_filter.lower())) 512 for pkg in pkgs.splitlines(): 513 packages.append(pkg.replace('package:', '')) 514 packages.sort() 515 516 if len(packages): 517 return packages 518 return None 519 520 @staticmethod 521 def packages_info(target, apk_filter=''): 522 """ 523 Get a dictionary of installed APKs and related information 524 525 :param target: instance of devlib Android target 526 :type target: devlib.target.AndroidTarget 527 528 :param apk_filter: a substring which must be part of the package name 529 :type apk_filter: str 530 """ 531 packages = {} 532 533 pkgs = target.execute('cmd package list packages {}'\ 534 .format(apk_filter.lower())) 535 for pkg in pkgs.splitlines(): 536 pkg = pkg.replace('package:', '') 537 # Lookup for additional APK information 538 apk = target.execute('pm path {}'.format(pkg)) 539 apk = apk.replace('package:', '') 540 packages[pkg] = { 541 'apk' : apk.strip() 542 } 543 544 if len(packages): 545 return packages 546 return None 547 548# vim :set tabstop=4 shiftwidth=4 expandtab 549