Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Android helper package start/stop controls #49

Merged
merged 2 commits into from
Jan 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions lgl_android_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -785,26 +791,33 @@ 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)

# 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

Expand Down
44 changes: 44 additions & 0 deletions lglpy/android/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
75 changes: 75 additions & 0 deletions lglpy/android/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
'''
Expand Down Expand Up @@ -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:
'''
Expand Down
1 change: 0 additions & 1 deletion lglpy/timeline/drawable/drawable_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import bisect

from lglpy.timeline.drawable.primitive_rectangle import PrimitiveRectangle
from lglpy.timeline.drawable.world_drawable import WorldDrawableLine


class DrawableChannel:
Expand Down
7 changes: 2 additions & 5 deletions lglpy/timeline/drawable/timeline_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions lglpy/timeline/gui/timeline/info_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand Down
1 change: 0 additions & 1 deletion lglpy/timeline/gui/timeline/timeline_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down