|
1 | 1 | import { test } from 'vitest'; |
2 | 2 | import assert from 'node:assert/strict'; |
3 | | -import { openAndroidApp } from '../app-lifecycle.ts'; |
| 3 | +import { listAndroidApps, openAndroidApp } from '../app-lifecycle.ts'; |
4 | 4 | import { withAndroidAdbProvider } from '../adb-executor.ts'; |
5 | 5 | import type { DeviceInfo } from '@agent-device/kernel/device'; |
6 | 6 | import { AppError } from '@agent-device/kernel/errors'; |
@@ -755,6 +755,122 @@ test('openAndroidApp fallback resolve-activity includes MAIN/LAUNCHER flags', as |
755 | 755 | ); |
756 | 756 | }); |
757 | 757 |
|
| 758 | +test('openAndroidApp TV fallback carries the resolved LAUNCHER category into the component launch', async () => { |
| 759 | + // A Fire OS app that declares no LEANBACK_LAUNCHER activity resolves under LAUNCHER only; the |
| 760 | + // fallback launch must carry that category, not the target's LEANBACK_LAUNCHER. |
| 761 | + await withFakeAdb( |
| 762 | + (args) => { |
| 763 | + if (args[0] === 'shell' && args[1] === 'am' && args[2] === 'start') { |
| 764 | + if (args.includes('-p')) { |
| 765 | + return [ |
| 766 | + 'Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.DEFAULT,android.intent.category.LEANBACK_LAUNCHER] pkg=com.example.tvapp }', |
| 767 | + 'Error: Activity not started, unable to resolve Intent { act=android.intent.action.MAIN cat=[android.intent.category.DEFAULT,android.intent.category.LEANBACK_LAUNCHER] flg=0x10000000 pkg=com.example.tvapp }', |
| 768 | + ].join('\n'); |
| 769 | + } |
| 770 | + return 'Status: ok'; |
| 771 | + } |
| 772 | + if ( |
| 773 | + args[0] === 'shell' && |
| 774 | + args[1] === 'cmd' && |
| 775 | + args[2] === 'package' && |
| 776 | + args[3] === 'resolve-activity' |
| 777 | + ) { |
| 778 | + if (args.includes('android.intent.category.LEANBACK_LAUNCHER')) { |
| 779 | + return { stdout: '', stderr: 'No activity found', exitCode: 1 }; |
| 780 | + } |
| 781 | + return 'com.example.tvapp/com.example.tvapp.MainActivity'; |
| 782 | + } |
| 783 | + return undefined; |
| 784 | + }, |
| 785 | + async ({ calls, device }) => { |
| 786 | + await openAndroidApp(device, 'com.example.tvapp'); |
| 787 | + assert.deepEqual(calls, [ |
| 788 | + [ |
| 789 | + 'shell', |
| 790 | + 'am', |
| 791 | + 'start', |
| 792 | + '-W', |
| 793 | + '-a', |
| 794 | + 'android.intent.action.MAIN', |
| 795 | + '-c', |
| 796 | + 'android.intent.category.DEFAULT', |
| 797 | + '-c', |
| 798 | + 'android.intent.category.LEANBACK_LAUNCHER', |
| 799 | + '-p', |
| 800 | + 'com.example.tvapp', |
| 801 | + ], |
| 802 | + // resolve-activity tries LEANBACK_LAUNCHER first on a TV target... |
| 803 | + [ |
| 804 | + 'shell', |
| 805 | + 'cmd', |
| 806 | + 'package', |
| 807 | + 'resolve-activity', |
| 808 | + '--brief', |
| 809 | + '-a', |
| 810 | + 'android.intent.action.MAIN', |
| 811 | + '-c', |
| 812 | + 'android.intent.category.LEANBACK_LAUNCHER', |
| 813 | + 'com.example.tvapp', |
| 814 | + ], |
| 815 | + // ...then LAUNCHER, which resolves the component. |
| 816 | + [ |
| 817 | + 'shell', |
| 818 | + 'cmd', |
| 819 | + 'package', |
| 820 | + 'resolve-activity', |
| 821 | + '--brief', |
| 822 | + '-a', |
| 823 | + 'android.intent.action.MAIN', |
| 824 | + '-c', |
| 825 | + 'android.intent.category.LAUNCHER', |
| 826 | + 'com.example.tvapp', |
| 827 | + ], |
| 828 | + // The fallback launch uses the category the component resolved under. |
| 829 | + [ |
| 830 | + 'shell', |
| 831 | + 'am', |
| 832 | + 'start', |
| 833 | + '-W', |
| 834 | + '-a', |
| 835 | + 'android.intent.action.MAIN', |
| 836 | + '-c', |
| 837 | + 'android.intent.category.DEFAULT', |
| 838 | + '-c', |
| 839 | + 'android.intent.category.LAUNCHER', |
| 840 | + '-n', |
| 841 | + 'com.example.tvapp/com.example.tvapp.MainActivity', |
| 842 | + ], |
| 843 | + ]); |
| 844 | + }, |
| 845 | + { device: { ...ANDROID_EMULATOR, target: 'tv' } }, |
| 846 | + ); |
| 847 | +}); |
| 848 | + |
| 849 | +test('listAndroidApps on a TV target queries LEANBACK_LAUNCHER then LAUNCHER', async () => { |
| 850 | + await withFakeAdb( |
| 851 | + (args) => { |
| 852 | + if (args.includes('query-activities')) { |
| 853 | + return args.includes('android.intent.category.LAUNCHER') |
| 854 | + ? 'com.example.fireosapp/.MainActivity\n' |
| 855 | + : ''; |
| 856 | + } |
| 857 | + return undefined; |
| 858 | + }, |
| 859 | + async ({ calls, device }) => { |
| 860 | + const apps = await listAndroidApps(device, 'all'); |
| 861 | + assert.deepEqual(apps, [{ package: 'com.example.fireosapp', name: 'Fireosapp' }]); |
| 862 | + const queriedCategories = calls |
| 863 | + .filter((call) => call.includes('query-activities')) |
| 864 | + .map((call) => call.at(-1)); |
| 865 | + assert.deepEqual(queriedCategories, [ |
| 866 | + 'android.intent.category.LEANBACK_LAUNCHER', |
| 867 | + 'android.intent.category.LAUNCHER', |
| 868 | + ]); |
| 869 | + }, |
| 870 | + { device: { ...ANDROID_EMULATOR, target: 'tv' } }, |
| 871 | + ); |
| 872 | +}); |
| 873 | + |
758 | 874 | // In-process replacement for the old PATH-stub default adb script: any |
759 | 875 | // `am start` succeeds with the "Status: ok" marker (so the primary launch |
760 | 876 | // never falls back to resolve-activity), everything else is empty success. |
|
0 commit comments