diff --git a/doc/source/revent.rst b/doc/source/revent.rst index e3b756ce..d5811683 100644 --- a/doc/source/revent.rst +++ b/doc/source/revent.rst @@ -17,6 +17,11 @@ to Android UI Automator for providing automation for workloads. :: info:shows info about each event char device any additional parameters make it verbose + +.. note:: There are now also WA commands that perform the below steps. + Please see ``wa show record/replay`` and ``wa record/replay --help`` + for details. + Recording --------- diff --git a/wlauto/commands/record.py b/wlauto/commands/record.py new file mode 100644 index 00000000..2fc640aa --- /dev/null +++ b/wlauto/commands/record.py @@ -0,0 +1,165 @@ +# Copyright 2014-2015 ARM Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import os +import sys + +from wlauto import ExtensionLoader, Command, settings +from wlauto.common.resources import Executable +from wlauto.core.resource import NO_ONE +from wlauto.core.resolver import ResourceResolver +from wlauto.core.configuration import RunConfiguration +from wlauto.core.agenda import Agenda + + +class RecordCommand(Command): + + name = 'record' + description = '''Performs a revent recording + + This command helps making revent recordings. It will automatically + deploy revent and even has the option of automatically opening apps. + + Revent allows you to record raw inputs such as screen swipes or button presses. + This can be useful for recording inputs for workloads such as games that don't + have XML UI layouts that can be used with UIAutomator. As a drawback from this, + revent recordings are specific to the device type they were recorded on. + + WA uses two parts to the names of revent recordings in the format, + {device_name}.{suffix}.revent. + + - device_name can either be specified manually with the ``-d`` argument or + it can be automatically determined. On Android device it will be obtained + from ``build.prop``, on Linux devices it is obtained from ``/proc/device-tree/model``. + - suffix is used by WA to determine which part of the app execution the + recording is for, currently these are either ``setup`` or ``run``. This + should be specified with the ``-s`` argument. + ''' + + def initialize(self, context): + self.context = context + self.parser.add_argument('-d', '--device', help='The name of the device') + self.parser.add_argument('-s', '--suffix', help='The suffix of the revent file, e.g. ``setup``') + self.parser.add_argument('-o', '--output', help='Directory to save the recording in') + self.parser.add_argument('-p', '--package', help='Package to launch before recording') + self.parser.add_argument('-C', '--clear', help='Clear app cache before launching it', + action="store_true") + + # Validate command options + def validate_args(self, args): + if args.clear and not args.package: + print "Package must be specified if you want to clear cache\n" + self.parser.print_help() + sys.exit() + + # pylint: disable=W0201 + def execute(self, args): + self.validate_args(args) + self.logger.info("Connecting to device...") + + ext_loader = ExtensionLoader(packages=settings.extension_packages, + paths=settings.extension_paths) + + # Setup config + self.config = RunConfiguration(ext_loader) + for filepath in settings.get_config_paths(): + self.config.load_config(filepath) + self.config.set_agenda(Agenda()) + self.config.finalize() + + context = LightContext(self.config) + + # Setup device + self.device = ext_loader.get_device(settings.device, **settings.device_config) + self.device.validate() + self.device.dynamic_modules = [] + self.device.connect() + self.device.initialize(context) + + host_binary = context.resolver.get(Executable(NO_ONE, self.device.abi, 'revent')) + self.target_binary = self.device.install_if_needed(host_binary) + + self.run(args) + + def run(self, args): + if args.device: + self.device_name = args.device + else: + self.device_name = self.device.get_device_model() + + if args.suffix: + args.suffix += "." + + revent_file = self.device.path.join(self.device.working_directory, + '{}.{}revent'.format(self.device_name, args.suffix or "")) + + if args.clear: + self.device.execute("pm clear {}".format(args.package)) + + if args.package: + self.logger.info("Starting {}".format(args.package)) + self.device.execute('monkey -p {} -c android.intent.category.LAUNCHER 1'.format(args.package)) + + self.logger.info("Press Enter when you are ready to record...") + raw_input("") + command = "{} record -t 100000 -s {}".format(self.target_binary, revent_file) + self.device.kick_off(command) + + self.logger.info("Press Enter when you have finished recording...") + raw_input("") + self.device.killall("revent") + + self.logger.info("Pulling files from device") + self.device.pull_file(revent_file, args.output or os.getcwdu()) + + +class ReplayCommand(RecordCommand): + + name = 'replay' + description = '''Replay a revent recording + + Revent allows you to record raw inputs such as screen swipes or button presses. + See ``wa show record`` to see how to make an revent recording. + ''' + + def initialize(self, context): + self.context = context + self.parser.add_argument('revent', help='The name of the file to replay') + self.parser.add_argument('-p', '--package', help='Package to launch before recording') + self.parser.add_argument('-C', '--clear', help='Clear app cache before launching it', + action="store_true") + + # pylint: disable=W0201 + def run(self, args): + self.logger.info("Pushing file to device") + self.device.push_file(args.revent, self.device.working_directory) + revent_file = self.device.path.join(self.device.working_directory, os.path.split(args.revent)[1]) + + if args.clear: + self.device.execute("pm clear {}".format(args.package)) + + if args.package: + self.logger.info("Starting {}".format(args.package)) + self.device.execute('monkey -p {} -c android.intent.category.LAUNCHER 1'.format(args.package)) + + command = "{} replay {}".format(self.target_binary, revent_file) + self.device.execute(command) + self.logger.info("Finished replay") + +# Used to satisfy the API +class LightContext(object): + def __init__(self, config): + self.resolver = ResourceResolver(config) + self.resolver.load() diff --git a/wlauto/common/android/device.py b/wlauto/common/android/device.py index 3f34cc86..d3d3684e 100644 --- a/wlauto/common/android/device.py +++ b/wlauto/common/android/device.py @@ -645,6 +645,12 @@ class AndroidDevice(BaseLinuxDevice): # pylint: disable=W0223 if se_status == 'Enforcing': self.execute('setenforce 0', as_root=True) + def get_device_model(self): + try: + return self.getprop(prop='ro.product.device') + except KeyError: + return None + # Internal methods: do not use outside of the class. def _update_build_properties(self, filepath, props): diff --git a/wlauto/common/android/workload.py b/wlauto/common/android/workload.py index 73d54650..16e24927 100644 --- a/wlauto/common/android/workload.py +++ b/wlauto/common/android/workload.py @@ -302,7 +302,7 @@ class ReventWorkload(Workload): if _call_super: super(ReventWorkload, self).__init__(device, **kwargs) devpath = self.device.path - self.on_device_revent_binary = devpath.join(self.device.working_directory, 'revent') + self.on_device_revent_binary = devpath.join(self.device.binaries_directory, 'revent') self.on_device_setup_revent = devpath.join(self.device.working_directory, '{}.setup.revent'.format(self.device.name)) self.on_device_run_revent = devpath.join(self.device.working_directory, '{}.run.revent'.format(self.device.name)) self.setup_timeout = kwargs.get('setup_timeout', self.default_setup_timeout) diff --git a/wlauto/common/bin/arm64/revent b/wlauto/common/bin/arm64/revent index d33cd01c..1b1b5e8c 100755 Binary files a/wlauto/common/bin/arm64/revent and b/wlauto/common/bin/arm64/revent differ diff --git a/wlauto/common/bin/armeabi/revent b/wlauto/common/bin/armeabi/revent index aa09b8a8..f908b4d3 100755 Binary files a/wlauto/common/bin/armeabi/revent and b/wlauto/common/bin/armeabi/revent differ diff --git a/wlauto/common/linux/device.py b/wlauto/common/linux/device.py index 6d59de1f..32924af4 100644 --- a/wlauto/common/linux/device.py +++ b/wlauto/common/linux/device.py @@ -536,6 +536,14 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method command = 'cd {} && {}'.format(in_directory, command) return self.execute(command, background=background, as_root=as_root, timeout=timeout) + def get_device_model(self): + if self.file_exists("/proc/device-tree/model"): + raw_model = self.execute("cat /proc/device-tree/model") + return '_'.join(raw_model.split()[:2]) + # Right now we don't know any other way to get device model + # info in linux on arm platforms + return None + # internal methods def _check_ready(self): diff --git a/wlauto/external/revent/revent.c b/wlauto/external/revent/revent.c index e314b66f..667bb51c 100644 --- a/wlauto/external/revent/revent.c +++ b/wlauto/external/revent/revent.c @@ -23,6 +23,8 @@ #include #include #include +#include +#include #ifdef ANDROID #include @@ -91,14 +93,14 @@ typedef struct { bool_t verbose = FALSE; - +bool_t wait_for_stdin = TRUE; bool_t is_numeric(char *string) { int len = strlen(string); int i = 0; - while(i < len) + while(i < len) { if(!isdigit(string[i])) return FALSE; @@ -115,7 +117,7 @@ off_t get_file_size(const char *filename) { return st.st_size; die("Cannot determine size of %s: %s\n", filename, strerror(errno)); -} +} int inpdev_init(inpdev_t **inpdev, int devid) { @@ -242,15 +244,15 @@ int replay_buffer_init(replay_buffer_t **buffer, const char *logfile) die("out of memory\n"); int fdin = open(logfile, O_RDONLY); - if (fdin < 0) + if (fdin < 0) die("Could not open eventlog %s\n", logfile); size_t rb = read(fdin, &(buff->num_fds), sizeof(buff->num_fds)); - if (rb!=sizeof(buff->num_fds)) + if (rb!=sizeof(buff->num_fds)) die("problems reading eventlog\n"); buff->fds = malloc(sizeof(int) * buff->num_fds); - if (!buff->fds) + if (!buff->fds) die("out of memory\n"); int32_t len, i; @@ -258,10 +260,10 @@ int replay_buffer_init(replay_buffer_t **buffer, const char *logfile) for (i = 0; i < buff->num_fds; i++) { memset(path_buff, 0, sizeof(path_buff)); rb = read(fdin, &len, sizeof(len)); - if (rb!=sizeof(len)) + if (rb!=sizeof(len)) die("problems reading eventlog\n"); rb = read(fdin, &path_buff[0], len); - if (rb != len) + if (rb != len) die("problems reading eventlog\n"); buff->fds[i] = open(path_buff, O_WRONLY | O_NDELAY); @@ -274,7 +276,7 @@ int replay_buffer_init(replay_buffer_t **buffer, const char *logfile) i = 0; while(1) { rb = read(fdin, &rep_ev, sizeof(rep_ev)); - if (rb < (int)sizeof(rep_ev)) + if (rb < (int)sizeof(rep_ev)) break; if (i == 0) { @@ -321,7 +323,7 @@ int replay_buffer_play(replay_buffer_t *buff) struct input_event ev = (buff->events[i]).event; while((i < buff->num_events) && !timercmp(&ev.time, &last_event_delta, !=)) { rb = write(buff->fds[idx], &ev, sizeof(ev)); - if (rb!=sizeof(ev)) + if (rb!=sizeof(ev)) die("problems writing\n"); dprintf("replayed event: type %d code %d value %d\n", ev.type, ev.code, ev.value); @@ -347,101 +349,6 @@ void replay(const char *logfile) replay_buffer_close(replay_buffer); } -void record(inpdev_t *inpdev, int delay, const char *logfile) -{ - fd_set readfds; - FILE* fdout; - struct input_event ev; - int32_t i; - int32_t _padding = 0xdeadbeef; - int32_t maxfd = 0; - int32_t keydev=0; - - int* fds = malloc(sizeof(int)*inpdev->id_pathc); - if (!fds) die("out of memory\n"); - - fdout = fopen(logfile, "wb"); - if (!fdout) die("Could not open eventlog %s\n", logfile); - - fwrite(&inpdev->id_pathc, sizeof(inpdev->id_pathc), 1, fdout); - for (i=0; iid_pathc; i++) { - int32_t len = strlen(inpdev->id_pathv[i]); - fwrite(&len, sizeof(len), 1, fdout); - fwrite(inpdev->id_pathv[i], len, 1, fdout); - } - - for (i=0; i < inpdev->id_pathc; i++) - { - fds[i] = open(inpdev->id_pathv[i], O_RDONLY); - if (fds[i]>maxfd) maxfd = fds[i]; - dprintf("opened %s with %d\n", inpdev->id_pathv[i], fds[i]); - if (fds[i]<0) die("could not open \%s\n", inpdev->id_pathv[i]); - } - - int count =0; - struct timeval tout; - while(1) - { - FD_ZERO(&readfds); - FD_SET(STDIN_FILENO, &readfds); - for (i=0; i < inpdev->id_pathc; i++) - FD_SET(fds[i], &readfds); - /* wait for input */ - tout.tv_sec = delay; - tout.tv_usec = 0; - int32_t r = select(maxfd+1, &readfds, NULL, NULL, &tout); - /* dprintf("got %d (err %d)\n", r, errno); */ - if (!r) break; - if (FD_ISSET(STDIN_FILENO, &readfds)) { - // in this case the key down for the return key will be recorded - // so we need to up the key up - memset(&ev, 0, sizeof(ev)); - ev.type = EV_KEY; - ev.code = KEY_ENTER; - ev.value = 0; - gettimeofday(&ev.time, NULL); - fwrite(&keydev, sizeof(keydev), 1, fdout); - fwrite(&_padding, sizeof(_padding), 1, fdout); - fwrite(&ev, sizeof(ev), 1, fdout); - memset(&ev, 0, sizeof(ev)); // SYN - gettimeofday(&ev.time, NULL); - fwrite(&keydev, sizeof(keydev), 1, fdout); - fwrite(&_padding, sizeof(_padding), 1, fdout); - fwrite(&ev, sizeof(ev), 1, fdout); - dprintf("added fake return exiting...\n"); - break; - } - - for (i=0; i < inpdev->id_pathc; i++) - { - if (FD_ISSET(fds[i], &readfds)) - { - dprintf("Got event from %s\n", inpdev->id_pathv[i]); - memset(&ev, 0, sizeof(ev)); - size_t rb = read(fds[i], (void*) &ev, sizeof(ev)); - dprintf("%d event: type %d code %d value %d\n", - (unsigned int)rb, ev.type, ev.code, ev.value); - if (ev.type == EV_KEY && ev.code == KEY_ENTER && ev.value == 1) - keydev = i; - fwrite(&i, sizeof(i), 1, fdout); - fwrite(&_padding, sizeof(_padding), 1, fdout); - fwrite(&ev, sizeof(ev), 1, fdout); - count++; - } - } - } - - for (i=0; i < inpdev->id_pathc; i++) - { - close(fds[i]); - } - - fclose(fdout); - free(fds); - dprintf("Recorded %d events\n", count); -} - - void usage() { printf("usage:\n revent [-h] [-v] COMMAND [OPTIONS] \n" @@ -490,7 +397,7 @@ void revent_args_init(revent_args_t **rargs, int argc, char** argv) revent_args->file = NULL; int opt; - while ((opt = getopt(argc, argv, "ht:d:v")) != -1) + while ((opt = getopt(argc, argv, "ht:d:vs")) != -1) { switch (opt) { case 'h': @@ -516,6 +423,10 @@ void revent_args_init(revent_args_t **rargs, int argc, char** argv) case 'v': verbose = TRUE; break; + case 's': + wait_for_stdin = FALSE; + break; + default: die("Unexpected option: %c", opt); } @@ -526,13 +437,13 @@ void revent_args_init(revent_args_t **rargs, int argc, char** argv) usage(); die("Must specify a command.\n"); } - if (!strcmp(argv[next_arg], "record")) + if (!strcmp(argv[next_arg], "record")) revent_args->mode = RECORD; - else if (!strcmp(argv[next_arg], "replay")) + else if (!strcmp(argv[next_arg], "replay")) revent_args->mode = REPLAY; - else if (!strcmp(argv[next_arg], "dump")) + else if (!strcmp(argv[next_arg], "dump")) revent_args->mode = DUMP; - else if (!strcmp(argv[next_arg], "info")) + else if (!strcmp(argv[next_arg], "info")) revent_args->mode = INFO; else { usage(); @@ -569,15 +480,138 @@ int revent_args_close(revent_args_t *rargs) return 0; } +int* fds = NULL; +FILE* fdout = NULL; +revent_args_t *rargs = NULL; +inpdev_t *inpdev = NULL; +int count; + +void term_handler(int signum) +{ + int32_t i; + for (i=0; i < inpdev->id_pathc; i++) + { + close(fds[i]); + } + + fclose(fdout); + free(fds); + dprintf("Recorded %d events\n", count); + + inpdev_close(inpdev); + revent_args_close(rargs); + exit(0); +} + +void record(inpdev_t *inpdev, int delay, const char *logfile) +{ + fd_set readfds; + struct input_event ev; + int32_t i; + int32_t _padding = 0xdeadbeef; + int32_t maxfd = 0; + int32_t keydev=0; + + //signal handler + struct sigaction action; + memset(&action, 0, sizeof(struct sigaction)); + action.sa_handler = term_handler; + sigaction(SIGTERM, &action, NULL); + + fds = malloc(sizeof(int)*inpdev->id_pathc); + if (!fds) die("out of memory\n"); + + fdout = fopen(logfile, "wb"); + if (!fdout) die("Could not open eventlog %s\n", logfile); + + fwrite(&inpdev->id_pathc, sizeof(inpdev->id_pathc), 1, fdout); + for (i=0; iid_pathc; i++) { + int32_t len = strlen(inpdev->id_pathv[i]); + fwrite(&len, sizeof(len), 1, fdout); + fwrite(inpdev->id_pathv[i], len, 1, fdout); + } + + for (i=0; i < inpdev->id_pathc; i++) + { + fds[i] = open(inpdev->id_pathv[i], O_RDONLY); + if (fds[i]>maxfd) maxfd = fds[i]; + dprintf("opened %s with %d\n", inpdev->id_pathv[i], fds[i]); + if (fds[i]<0) die("could not open \%s\n", inpdev->id_pathv[i]); + } + + count = 0; + struct timeval tout; + while(1) + { + FD_ZERO(&readfds); + if (wait_for_stdin) + { + FD_SET(STDIN_FILENO, &readfds); + } + for (i=0; i < inpdev->id_pathc; i++) + FD_SET(fds[i], &readfds); + /* wait for input */ + tout.tv_sec = delay; + tout.tv_usec = 0; + int32_t r = select(maxfd+1, &readfds, NULL, NULL, &tout); + /* dprintf("got %d (err %d)\n", r, errno); */ + if (!r) break; + if (wait_for_stdin && FD_ISSET(STDIN_FILENO, &readfds)) { + // in this case the key down for the return key will be recorded + // so we need to up the key up + memset(&ev, 0, sizeof(ev)); + ev.type = EV_KEY; + ev.code = KEY_ENTER; + ev.value = 0; + gettimeofday(&ev.time, NULL); + fwrite(&keydev, sizeof(keydev), 1, fdout); + fwrite(&_padding, sizeof(_padding), 1, fdout); + fwrite(&ev, sizeof(ev), 1, fdout); + memset(&ev, 0, sizeof(ev)); // SYN + gettimeofday(&ev.time, NULL); + fwrite(&keydev, sizeof(keydev), 1, fdout); + fwrite(&_padding, sizeof(_padding), 1, fdout); + fwrite(&ev, sizeof(ev), 1, fdout); + dprintf("added fake return exiting...\n"); + break; + } + + for (i=0; i < inpdev->id_pathc; i++) + { + if (FD_ISSET(fds[i], &readfds)) + { + dprintf("Got event from %s\n", inpdev->id_pathv[i]); + memset(&ev, 0, sizeof(ev)); + size_t rb = read(fds[i], (void*) &ev, sizeof(ev)); + dprintf("%d event: type %d code %d value %d\n", + (unsigned int)rb, ev.type, ev.code, ev.value); + if (ev.type == EV_KEY && ev.code == KEY_ENTER && ev.value == 1) + keydev = i; + fwrite(&i, sizeof(i), 1, fdout); + fwrite(&_padding, sizeof(_padding), 1, fdout); + fwrite(&ev, sizeof(ev), 1, fdout); + count++; + } + } + } + + for (i=0; i < inpdev->id_pathc; i++) + { + close(fds[i]); + } + + fclose(fdout); + free(fds); + dprintf("Recorded %d events\n", count); +} + int main(int argc, char** argv) { int i; char *logfile = NULL; - revent_args_t *rargs; revent_args_init(&rargs, argc, argv); - inpdev_t *inpdev; inpdev_init(&inpdev, rargs->device_number); switch(rargs->mode) { @@ -600,4 +634,3 @@ int main(int argc, char** argv) revent_args_close(rargs); return 0; } -