Add LAUNCHER to the real VR activity when only an activity-alias has it

Lepton's apk-info-extractor ignores <activity-alias>, and Godot 4 exports put
LAUNCHER only on an alias (com.godot.game.GodotAppLauncher). Open Saber Plus
0.7.67 installed but Lepton exited with 'APP_ACTIVITY is empty'. Count only
real activities as launchable and patch the activity's VR intent filter.
Verified on the Frame: Open Saber Plus launches and renders.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
saphidandClaude Opus 5.5 committed 2026-09-28 20:19:38 +10:00
1 parent 35ef3af54b
commit 077eab2b79
2 files changed
+24 -6

No files matched your search

+19 -3
View File
@@ -18,11 +18,11 @@ import frame_android
from test_frame_apk import pool
def manifest(utf8=False, launcher=False, category=None, samsung=False, split=False):
def manifest(utf8=False, launcher=False, category=None, samsung=False, split=False, alias=False):
strings = ['manifest', 'package', 'org.test.vr', 'application', 'activity', 'intent-filter',
'action', 'category', 'name', vr.MAIN, category or next(iter(sorted(vr.VR))),
vr.LAUNCHER, 'http://schemas.android.com/apk/res/android', 'meta-data', 'value',
'com.samsung.android.vr.application.mode', 'vr_only']
'com.samsung.android.vr.application.mode', 'vr_only', 'activity-alias']
def start(tag, attrs=()):
body = struct.pack('<IIHHHHHH', 0xffffffff, strings.index(tag), 20, 20, len(attrs), 0, 0, 0)
for name, value in attrs:
@@ -42,7 +42,12 @@ def manifest(utf8=False, launcher=False, category=None, samsung=False, split=Fal
b += end('intent-filter') + start('intent-filter')
if launcher:
b += leaf('category', [('name', vr.LAUNCHER)])
b += end('intent-filter') + end('activity') + end('application') + end('manifest')
b += end('intent-filter') + end('activity')
if alias: # Godot 4: LAUNCHER only on an alias of the VR activity
b += start('activity-alias') + start('intent-filter') + leaf('action', [('name', vr.MAIN)])
b += leaf('category', [('name', strings[10])]) + leaf('category', [('name', vr.LAUNCHER)])
b += end('intent-filter') + end('activity-alias')
b += end('application') + end('manifest')
return struct.pack('<HHI', 3, 8, len(b) + 8) + b
@@ -74,6 +79,17 @@ class VRTests(unittest.TestCase):
self.assertFalse(vr.inspect(manifest(launcher=True, split=True))[0]['launchable'])
self.assertTrue(vr.inspect(vr.add_launcher_category(manifest(launcher=True, split=True)))[0]['launchable'])
def test_alias_launcher_is_not_enough(self):
original = manifest(alias=True)
info, filters = vr.inspect(original)
self.assertFalse(info['launchable'])
self.assertTrue(info['vr_activity'])
self.assertEqual(len(filters), 1)
result = vr.add_launcher_category(original)
info, filters = vr.inspect(result)
self.assertTrue(info['launchable'])
self.assertIn(vr.LAUNCHER, filters[0]['categories'])
def test_styled_pool(self):
for utf8 in (False, True):
p = bytearray(pool(['styled'], utf8))
+5 -3
View File
@@ -16,7 +16,7 @@ def inspect(data):
tag, attrs = next(elements)
value = attrs.get('name', (None, None, None))[2]
if tag == 'intent-filter' and stack and stack[-1] in ('activity', 'activity-alias'):
current = {'actions': set(), 'categories': set(), 'templates': []}
current = {'actions': set(), 'categories': set(), 'templates': [], 'alias': stack[-1] == 'activity-alias'}
if current is not None and stack and stack[-1] == 'intent-filter':
if tag == 'action':
current['actions'].add(value)
@@ -34,8 +34,10 @@ def inspect(data):
current = None
mains = [f for f in filters if MAIN in f['actions']]
vr_filters = [f for f in mains if VR & f['categories']]
return {'launchable': any(LAUNCHER in f['categories'] for f in mains),
'vr_activity': bool(vr_filters), 'vr': bool(vr_filters) or samsung}, vr_filters
# Lepton's apk-info-extractor ignores <activity-alias>; Godot 4 puts LAUNCHER only there.
return {'launchable': any(LAUNCHER in f['categories'] for f in mains if not f['alias']),
'vr_activity': bool(vr_filters), 'vr': bool(vr_filters) or samsung}, \
[f for f in vr_filters if not f['alias']]
def _append_string(chunk, text):