• 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
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