From 984db45576a413bd83b867230d834995ddf67547 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Thu, 24 Sep 2026 00:20:11 -0400 Subject: [PATCH] [tools, scripts] (P12): tcp_device_server.py --surface window|pbuffer (default pbuffer, unchanged) starts the on-screen display Activity on a lit screen, and --backend pins either server --- scripts/ci/tcp_lane_tools_test.py | 35 +++++++++++++ tools/trace_replay/tcp_device_server.py | 65 +++++++++++++++++++++---- 2 files changed, 90 insertions(+), 10 deletions(-) diff --git a/scripts/ci/tcp_lane_tools_test.py b/scripts/ci/tcp_lane_tools_test.py index e5bced60..e0bad667 100644 --- a/scripts/ci/tcp_lane_tools_test.py +++ b/scripts/ci/tcp_lane_tools_test.py @@ -227,6 +227,41 @@ class DeviceIdleExemption(unittest.TestCase): self.assertFalse(state.exists()) +class DeviceServerSurface(unittest.TestCase): + """P12 D9: tcp_device_server.py starts one of the two device servers - the offscreen + supervisor service (the default, unchanged) or the on-screen display Activity.""" + + def started(self, *argv): + spec = importlib.util.spec_from_file_location('tcp_device_server', ROOT / 'tools/trace_replay/tcp_device_server.py') + device = importlib.util.module_from_spec(spec) + spec.loader.exec_module(device) + commands = [] + with patch.object(device, 'shell', side_effect=lambda _adb, command: commands.append(command) or ''), \ + patch('builtins.print'): + device.main(['start', '--package', 'pkg', '--listen', 'tcp://127.0.0.1:40613', + '--token', 'a-token-of-sixteen-bytes', '--env', 'A=1', *argv]) + return commands + + def test_the_default_is_still_the_offscreen_supervisor_service(self): + self.assertEqual(self.started(), [[ + 'am', 'start-foreground-service', '-n', 'pkg/top.mobilegl.plugin.MobileGLServerService', + '--es', 'listen', 'tcp://127.0.0.1:40613', '--es', 'token', 'a-token-of-sixteen-bytes', + '--es', 'env', 'A=1']]) + self.assertEqual(self.started('--surface', 'pbuffer'), self.started()) + # A pinned backend reaches the supervisor through its environment. + self.assertEqual(self.started('--backend', 'DirectVulkan')[0][-1], 'A=1;MOBILEGL_BACKEND_TYPE=DirectVulkan') + + def test_window_starts_the_display_activity_with_the_same_extras_on_a_lit_screen(self): + commands = self.started('--surface', 'window', '--backend', 'DirectVulkan') + self.assertEqual(commands, [ + ['input', 'keyevent', 'KEYCODE_WAKEUP'], + ['am', 'start', '-n', 'pkg/top.mobilegl.plugin.MobileGLDisplayActivity', + '--es', 'listen', 'tcp://127.0.0.1:40613', '--es', 'token', 'a-token-of-sixteen-bytes', + '--es', 'env', 'A=1', '--es', 'backend', 'DirectVulkan']]) + # Without --backend the Activity gets no pin extra (the first session's backend is pinned). + self.assertNotIn('backend', self.started('--surface', 'window')[1]) + + @unittest.skipUnless(sys.platform.startswith('linux'), 'supervisor fixture uses Linux /proc') class Supervisor(unittest.TestCase): def test_readiness_does_not_consume_a_connection_and_stop_owns_only_its_pid(self): diff --git a/tools/trace_replay/tcp_device_server.py b/tools/trace_replay/tcp_device_server.py index 1f8909f8..0b8b577f 100644 --- a/tools/trace_replay/tcp_device_server.py +++ b/tools/trace_replay/tcp_device_server.py @@ -1,5 +1,15 @@ #!/usr/bin/env python3 -"""Start or stop the trace APK's foreground TCP supervisor through adb.""" +"""Start or stop the trace APK's TCP server through adb. + +Two servers, one at a time (P12 D8; whichever starts last owns the port): + --surface pbuffer (default) the foreground MobileGLServerService: an exec'd supervisor that forks + one offscreen session child per client (the P6.5 path). + --surface window the on-screen MobileGLDisplayActivity (process :mglwin): the server runs + in the Activity's own process and a client with MOBILEGL_IPC_SURFACE=server + renders its window surface onto the Activity's SurfaceView (P12 D7/D9). + Needs the screen on and unlocked for its Surface to exist. +`stop` force-stops the package, which ends either. +""" import argparse import json from pathlib import Path @@ -51,8 +61,29 @@ def restore_idle(adb, serial, package, state_path): print('Restored the original device idle whitelist setting for ' + package) -def main(): - parser = argparse.ArgumentParser(description=__doc__) +SERVICE = 'top.mobilegl.plugin.MobileGLServerService' +DISPLAY_ACTIVITY = 'top.mobilegl.plugin.MobileGLDisplayActivity' + + +def start_command(args): + """The `am` command that starts the requested server, with the extras both servers share.""" + env = list(args.env) + extras = ['--es', 'listen', args.listen, '--es', 'token', args.token] + if args.surface == 'window': + # The display Activity takes the backend as its own extra (the pin for its process). + extras += ['--es', 'env', ';'.join(env)] + if args.backend: + extras += ['--es', 'backend', args.backend] + return ['am', 'start', '-n', args.package + '/' + DISPLAY_ACTIVITY] + extras + # The supervisor pins a backend through its environment (a disagreeing Hello is refused). + if args.backend: + env.append('MOBILEGL_BACKEND_TYPE=' + args.backend) + extras += ['--es', 'env', ';'.join(env)] + return ['am', 'start-foreground-service', '-n', args.package + '/' + SERVICE] + extras + + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument('action', choices=('start', 'stop')) parser.add_argument('--adb', default='adb') parser.add_argument('--serial', default='2f7cbe2e') @@ -65,7 +96,13 @@ def main(): help='Temporarily exempt this package from Doze; stop restores its previous setting') parser.add_argument('--state-file', type=Path, help='Saved original idle setting (default: ~/.cache/mobilegl/tcp-server/-.json)') - args = parser.parse_args() + parser.add_argument('--surface', choices=('pbuffer', 'window'), default='pbuffer', + help='pbuffer (default): the offscreen supervisor service; window: the on-screen ' + 'display Activity (clients opt in with MOBILEGL_IPC_SURFACE=server)') + parser.add_argument('--backend', choices=('DirectGLES', 'DirectVulkan'), + help='pin the server to one backend (a client asking for the other is refused); ' + 'without it the on-screen server pins the first session\'s') + args = parser.parse_args(argv) adb = [args.adb, '-s', args.serial] state_name = re.sub(r'[^A-Za-z0-9_.-]', '_', args.serial + '-' + args.package) + '.json' state_path = args.state_file or (Path.home() / '.cache/mobilegl/tcp-server' / state_name) @@ -74,18 +111,26 @@ def main(): else: if args.allow_idle: allow_idle(adb, args.serial, args.package, state_path) - command = ['am', 'start-foreground-service', '-n', args.package + '/top.mobilegl.plugin.MobileGLServerService', - '--es', 'listen', args.listen, '--es', 'token', args.token, - '--es', 'env', ';'.join(args.env)] + if args.surface == 'window': + # The Activity's SurfaceView exists only on a lit screen. + shell(adb, ['input', 'keyevent', 'KEYCODE_WAKEUP']) + command = start_command(args) print(shell(adb, command), end='') if args.action == 'stop': restore_idle(adb, args.serial, args.package, state_path) if args.action == 'start' and args.forward: subprocess.run(adb + ['forward', 'tcp:40613', 'tcp:40613'], check=True) if args.action == 'start': - print('Read readiness: adb -s ' + args.serial + ' logcat -s MobileGLServer') - print('Client: MOBILEGL_TRANSPORT=spawn MOBILEGL_IPC_DATA=stream ' - 'MOBILEGL_IPC_REQUIRE_SAME_BUILD=1 MOBILEGL_IPC_CONTROL=tcp://:40613') + if args.surface == 'window': + print('Read readiness: adb -s ' + args.serial + ' logcat -s MobileGL MobileGLDisplay ' + '(MG_Remote server: ... listening on ... in-process display server, display installed)') + print('Client: MOBILEGL_TRANSPORT=spawn MOBILEGL_IPC_DATA=stream MOBILEGL_IPC_SURFACE=server ' + 'MOBILEGL_IPC_REQUIRE_SAME_BUILD=1 MOBILEGL_IPC_CONTROL=tcp://:40613 ' + '(trace_replay --window-surface)') + else: + print('Read readiness: adb -s ' + args.serial + ' logcat -s MobileGLServer') + print('Client: MOBILEGL_TRANSPORT=spawn MOBILEGL_IPC_DATA=stream ' + 'MOBILEGL_IPC_REQUIRE_SAME_BUILD=1 MOBILEGL_IPC_CONTROL=tcp://:40613') if __name__ == '__main__':