@@ -5,6 +5,36 @@ import type { DeviceInfo } from '../../utils/device.ts';
55import { Deadline , retryWithPolicy } from '../../utils/retry.ts' ;
66import { classifyBootFailure } from '../boot-diagnostics.ts' ;
77
8+ const EMULATOR_SERIAL_PREFIX = 'emulator-' ;
9+ const ANDROID_BOOT_POLL_MS = 1000 ;
10+
11+ function adbArgs ( serial : string , args : string [ ] ) : string [ ] {
12+ return [ '-s' , serial , ...args ] ;
13+ }
14+
15+ function isEmulatorSerial ( serial : string ) : boolean {
16+ return serial . startsWith ( EMULATOR_SERIAL_PREFIX ) ;
17+ }
18+
19+ async function readAndroidBootProp ( serial : string ) : Promise < ExecResult > {
20+ return runCmd ( 'adb' , adbArgs ( serial , [ 'shell' , 'getprop' , 'sys.boot_completed' ] ) , {
21+ allowFailure : true ,
22+ } ) ;
23+ }
24+
25+ async function resolveAndroidDeviceName ( serial : string , rawModel : string ) : Promise < string > {
26+ const modelName = rawModel . replace ( / _ / g, ' ' ) . trim ( ) ;
27+ if ( ! isEmulatorSerial ( serial ) ) return modelName || serial ;
28+ const avd = await runCmd ( 'adb' , adbArgs ( serial , [ 'emu' , 'avd' , 'name' ] ) , {
29+ allowFailure : true ,
30+ } ) ;
31+ const avdName = avd . stdout . trim ( ) ;
32+ if ( avd . exitCode === 0 && avdName ) {
33+ return avdName . replace ( / _ / g, ' ' ) ;
34+ }
35+ return modelName || serial ;
36+ }
37+
838export async function listAndroidDevices ( ) : Promise < DeviceInfo [ ] > {
939 const adbAvailable = await whichCmd ( 'adb' ) ;
1040 if ( ! adbAvailable ) {
@@ -13,57 +43,44 @@ export async function listAndroidDevices(): Promise<DeviceInfo[]> {
1343
1444 const result = await runCmd ( 'adb' , [ 'devices' , '-l' ] ) ;
1545 const lines = result . stdout . split ( '\n' ) . map ( ( l : string ) => l . trim ( ) ) ;
16- const devices : DeviceInfo [ ] = [ ] ;
46+ const entries = lines
47+ . filter ( ( line ) => line . length > 0 && ! line . startsWith ( 'List of devices' ) )
48+ . map ( ( line ) => line . split ( / \s + / ) )
49+ . filter ( ( parts ) => parts [ 1 ] === 'device' )
50+ . map ( ( parts ) => ( {
51+ serial : parts [ 0 ] ,
52+ rawModel : ( parts . find ( ( p : string ) => p . startsWith ( 'model:' ) ) ?? '' ) . replace ( 'model:' , '' ) ,
53+ } ) ) ;
1754
18- for ( const line of lines ) {
19- if ( ! line || line . startsWith ( 'List of devices' ) ) continue ;
20- const parts = line . split ( / \s + / ) ;
21- const serial = parts [ 0 ] ;
22- const state = parts [ 1 ] ;
23- if ( state !== 'device' ) continue ;
24-
25- const modelPart = parts . find ( ( p : string ) => p . startsWith ( 'model:' ) ) ?? '' ;
26- const rawModel = modelPart . replace ( 'model:' , '' ) . replace ( / _ / g, ' ' ) . trim ( ) ;
27- let name = rawModel || serial ;
28-
29- if ( serial . startsWith ( 'emulator-' ) ) {
30- const avd = await runCmd ( 'adb' , [ '-s' , serial , 'emu' , 'avd' , 'name' ] , {
31- allowFailure : true ,
32- } ) ;
33- const avdName = ( avd . stdout as string ) . trim ( ) ;
34- if ( avd . exitCode === 0 && avdName ) {
35- name = avdName . replace ( / _ / g, ' ' ) ;
36- }
37- }
38-
39- const booted = await isAndroidBooted ( serial ) ;
40-
41- devices . push ( {
55+ const devices = await Promise . all ( entries . map ( async ( { serial, rawModel } ) => {
56+ const [ name , booted ] = await Promise . all ( [
57+ resolveAndroidDeviceName ( serial , rawModel ) ,
58+ isAndroidBooted ( serial ) ,
59+ ] ) ;
60+ return {
4261 platform : 'android' ,
4362 id : serial ,
4463 name,
45- kind : serial . startsWith ( 'emulator-' ) ? 'emulator' : 'device' ,
64+ kind : isEmulatorSerial ( serial ) ? 'emulator' : 'device' ,
4665 booted,
47- } ) ;
48- }
66+ } satisfies DeviceInfo ;
67+ } ) ) ;
4968
5069 return devices ;
5170}
5271
5372export async function isAndroidBooted ( serial : string ) : Promise < boolean > {
5473 try {
55- const result = await runCmd ( 'adb' , [ '-s' , serial , 'shell' , 'getprop' , 'sys.boot_completed' ] , {
56- allowFailure : true ,
57- } ) ;
58- return ( result . stdout as string ) . trim ( ) === '1' ;
74+ const result = await readAndroidBootProp ( serial ) ;
75+ return result . stdout . trim ( ) === '1' ;
5976 } catch {
6077 return false ;
6178 }
6279}
6380
6481export async function waitForAndroidBoot ( serial : string , timeoutMs = 60000 ) : Promise < void > {
6582 const deadline = Deadline . fromTimeoutMs ( timeoutMs ) ;
66- const maxAttempts = Math . max ( 1 , Math . ceil ( timeoutMs / 1000 ) ) ;
83+ const maxAttempts = Math . max ( 1 , Math . ceil ( timeoutMs / ANDROID_BOOT_POLL_MS ) ) ;
6784 let lastBootResult : ExecResult | undefined ;
6885 let timedOut = false ;
6986 try {
@@ -78,13 +95,9 @@ export async function waitForAndroidBoot(serial: string, timeoutMs = 60000): Pro
7895 message : 'timeout' ,
7996 } ) ;
8097 }
81- const result = await runCmd (
82- 'adb' ,
83- [ '-s' , serial , 'shell' , 'getprop' , 'sys.boot_completed' ] ,
84- { allowFailure : true } ,
85- ) ;
98+ const result = await readAndroidBootProp ( serial ) ;
8699 lastBootResult = result ;
87- if ( ( result . stdout as string ) . trim ( ) === '1' ) return ;
100+ if ( result . stdout . trim ( ) === '1' ) return ;
88101 throw new AppError ( 'COMMAND_FAILED' , 'Android device is still booting' , {
89102 serial,
90103 stdout : result . stdout ,
@@ -94,8 +107,8 @@ export async function waitForAndroidBoot(serial: string, timeoutMs = 60000): Pro
94107 } ,
95108 {
96109 maxAttempts,
97- baseDelayMs : 1000 ,
98- maxDelayMs : 1000 ,
110+ baseDelayMs : ANDROID_BOOT_POLL_MS ,
111+ maxDelayMs : ANDROID_BOOT_POLL_MS ,
99112 jitter : 0 ,
100113 shouldRetry : ( error ) => {
101114 const reason = classifyBootFailure ( {
0 commit comments