• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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