diff --git a/lgl_android_install.py b/lgl_android_install.py index 7f18dbd..dadb6d2 100755 --- a/lgl_android_install.py +++ b/lgl_android_install.py @@ -746,30 +746,36 @@ def main() -> int: conn.set_package(package) + # Determine package main activity to launch + activity = AndroidUtils.get_package_main_activity(conn) + if not activity: + print('ERROR: Package has no identifiable main activity') + return 4 + # Select layers to install and their configs need_32bit = AndroidUtils.is_package_32bit(conn, package) layers = get_layer_metadata(args.layer, need_32bit, args.symbols) if not layers: - return 4 + return 5 success = get_layer_configs(layers, args.config) if not success: - return 5 + return 6 # Install files for layer in layers: if not install_layer_binary(conn, layer): print('ERROR: Layer binary install on device failed') - return 6 + return 7 if not install_layer_config(conn, layer): print('ERROR: Layer config install on device failed') - return 7 + return 8 # Enable layers if not enable_layers(conn, layers): print('ERROR: Layer enable on device failed') - return 8 + return 9 print('Layers are installed and ready for use:') for layer in layers: @@ -785,8 +791,15 @@ def main() -> int: if args.perfetto: perfetto_conf = configure_perfetto(conn, args.perfetto) + # Restart the package so it actually loads the layer ... + AndroidUtils.stop_package(conn) + AndroidUtils.start_package(conn, activity) + input('Press any key when finished to uninstall all layers') + # Kill the package so we can cleanup + AndroidUtils.stop_package(conn) + # Disable Perfetto trace if args.perfetto and perfetto_conf: cleanup_perfetto(conn, args.perfetto, *perfetto_conf) @@ -794,17 +807,17 @@ def main() -> int: # Disable layers if not disable_layers(conn): print('ERROR: Layer disable on device failed') - return 9 + return 10 # Remove files for layer in layers: if not uninstall_layer_binary(conn, layer): print('ERROR: Layer binary uninstall from device failed') - return 10 + return 11 if not uninstall_layer_config(conn, layer): print('ERROR: Layer config uninstall from device failed') - return 11 + return 12 return 0 diff --git a/lglpy/android/test.py b/lglpy/android/test.py index b64cf69..04f6449 100644 --- a/lglpy/android/test.py +++ b/lglpy/android/test.py @@ -239,6 +239,50 @@ def test_util_package_list(self): # Test that list length reduces each time as we add filters self.assertGreater(all_packages, debug_main_packages) + def test_util_package_main(self): + ''' + Test helper to get main activity of a package. + ''' + conn = ADBConnect() + + packages = AndroidUtils.get_packages(conn, True, True) + + # Test that a package was returned + self.assertGreater(len(packages), 0) + conn.set_package(packages[0]) + + # Test that a valid activity was returned + activity = AndroidUtils.get_package_main_activity(conn) + self.assertTrue(activity) + + def test_util_package_start_stop(self): + ''' + Test helper to get main activity of a package. + ''' + conn = ADBConnect() + + packages = AndroidUtils.get_packages(conn, True, True) + + # Test that a package was returned + self.assertGreater(len(packages), 0) + conn.set_package(packages[0]) + + # Test that a valid activity was returned + activity = AndroidUtils.get_package_main_activity(conn) + + # Test we can start a package if it is not running + AndroidUtils.stop_package(conn) + started = AndroidUtils.start_package(conn, activity) + self.assertTrue(started) + + # Test we can stop a package + stopped = AndroidUtils.stop_package(conn) + self.assertTrue(stopped) + + # Test we can ignore errors if package is already stopped + stopped = AndroidUtils.stop_package(conn) + self.assertTrue(stopped) + def test_util_os_version(self): ''' Test helper to get OS version. diff --git a/lglpy/android/utils.py b/lglpy/android/utils.py index 3509cf4..83b31f4 100644 --- a/lglpy/android/utils.py +++ b/lglpy/android/utils.py @@ -145,6 +145,41 @@ def get_packages( return package_list + @staticmethod + def get_package_main_activity(conn: ADBConnect): + ''' + Get the main activity for a package. + + Args: + conn: The adb connection. + + Return: + The activity name, or None on error or if this is a system service + which has no activities. + ''' + assert conn.package, \ + 'Cannot use get__package_main_activity() without a package' + package = conn.package + + cmd = f'dumpsys package {package} | ' \ + r'grep -A1 "android.intent.action.MAIN:" | ' \ + r'tr " " "\n" | ' \ + f"grep {package}/ || " \ + r"exit 0" + + try: + output = conn.adb_run('sh', '-c', f"'{cmd}'") + output = output.strip() + output = output.replace(f'{package}/', '') + + if not output: + return None + + return output + + except sp.CalledProcessError: + return None + @classmethod def get_os_version(cls, conn: ADBConnect) -> Optional[str]: ''' @@ -284,6 +319,46 @@ def get_package_data_dir(conn: ADBConnect): except sp.CalledProcessError: return None + @staticmethod + def start_package(conn: ADBConnect, activity: str) -> bool: + ''' + Start the package for this connection. + + Args: + conn: The adb connection. + activity: The name of the activity. + + Returns: + True on success, False otherwise. + ''' + assert conn.package, \ + 'Cannot use start_package() without package' + + try: + target = f'{conn.package}/{activity}' + conn.adb_run('am', 'start', '-n', target, quote=True) + except sp.CalledProcessError: + return False + + return True + + @staticmethod + def stop_package(conn: ADBConnect) -> bool: + ''' + Stop the package for this connection. + + Return: + True if stopped or was not running, False if command failed. + ''' + assert conn.package, \ + 'Cannot use stop_package() without package' + + try: + conn.adb_run('am', 'force-stop', conn.package) + return True + except sp.CalledProcessError: + return False + @staticmethod def set_property(conn: ADBConnect, prop: str, value: str) -> bool: ''' diff --git a/lglpy/timeline/drawable/drawable_channel.py b/lglpy/timeline/drawable/drawable_channel.py index 063561b..7585fe6 100644 --- a/lglpy/timeline/drawable/drawable_channel.py +++ b/lglpy/timeline/drawable/drawable_channel.py @@ -29,7 +29,6 @@ import bisect from lglpy.timeline.drawable.primitive_rectangle import PrimitiveRectangle -from lglpy.timeline.drawable.world_drawable import WorldDrawableLine class DrawableChannel: diff --git a/lglpy/timeline/drawable/timeline_base.py b/lglpy/timeline/drawable/timeline_base.py index 264b491..1c6aa4e 100644 --- a/lglpy/timeline/drawable/timeline_base.py +++ b/lglpy/timeline/drawable/timeline_base.py @@ -36,9 +36,8 @@ from lglpy.timeline.drawable.drawable import Drawable from lglpy.timeline.drawable.style import Style from lglpy.timeline.drawable.canvas_drawable import CanvasDrawableRect, \ - CanvasDrawableRectFill, CanvasDrawableLabel, CanvasDrawableLine + CanvasDrawableRectFill, CanvasDrawableLine from lglpy.timeline.drawable.timeline_viewport import TimelineViewport -from lglpy.timeline.drawable.entry_dialog import get_entry_dialog class TimelineWidgetBase: @@ -650,9 +649,7 @@ def get_coord_str(self, cx, cy): change should be presented to the lglpy.timeline. ''' # If in the ACTION BAR then no string - start = 0 - end = self.ACTION_BAR_PIXELS - if start <= cy < self.ACTION_BAR_PIXELS: + if 0 <= cy < self.ACTION_BAR_PIXELS: return '' # Otherwise we are over the main timeline so provide timeline coords diff --git a/lglpy/timeline/gui/timeline/info_widget.py b/lglpy/timeline/gui/timeline/info_widget.py index 05e543d..b696344 100644 --- a/lglpy/timeline/gui/timeline/info_widget.py +++ b/lglpy/timeline/gui/timeline/info_widget.py @@ -28,7 +28,7 @@ from collections import defaultdict -from ...data.raw_trace import GPUStreamID, GPUStageID +from ...data.raw_trace import GPUStreamID from ...drawable.text_pane_widget import TextPaneWidget @@ -71,7 +71,7 @@ def get_frame_rate_report(self, start, end): ''' # Determine which frames are entirely in the active range out_frames = set() - in_frames = dict() + in_frames = {} for event in self.timeline_widget.drawable_trace.each_object(): frame = event.user_data.frame @@ -195,7 +195,7 @@ def ch_filt(x): metrics.append(f' {label:{label_len}} {util:>5.1f}%') util = self.get_utilization(start, end, channels) - label = f'Any stream:' + label = 'Any stream:' metrics.append(f' {label:{label_len}} {util:>5.1f}%') metrics.append('') return metrics @@ -314,7 +314,7 @@ def compute_active_event_stats_multi(self, active): if top_n_limit > 1: metrics.append(f'Top {top_n_limit} workload runtimes:') else: - metrics.append(f'Workload runtime:') + metrics.append('Workload runtime:') tags_by_cost = sorted( total_tag_time, key=total_tag_time.get, reverse=True) @@ -356,7 +356,7 @@ def get_active_event_stats(self): if self.cached_active_event == active: return self.cached_event_info - elif len(active) == 0: + if len(active) == 0: info = None else: diff --git a/lglpy/timeline/gui/timeline/timeline_widget.py b/lglpy/timeline/gui/timeline/timeline_widget.py index 55c2335..bf9968c 100644 --- a/lglpy/timeline/gui/timeline/timeline_widget.py +++ b/lglpy/timeline/gui/timeline/timeline_widget.py @@ -29,7 +29,6 @@ # pylint: disable=wrong-import-position from gi.repository import Gtk -from lglpy.timeline.drawable.world_drawable import WorldDrawableLine from lglpy.timeline.drawable.timeline_base import TimelineWidgetBase