@@ -2,8 +2,11 @@ import * as vs from "vscode";
22import { flatMap } from "../../shared/utils" ;
33import { findProjectFolders , fsPath } from "../../shared/utils/fs" ;
44import { getDartWorkspaceFolders } from "../../shared/vscode/utils" ;
5+ import { LogCategory } from "../enums" ;
56import * as f from "../flutter/daemon_interfaces" ;
6- import { IFlutterDaemon , Logger } from "../interfaces" ;
7+ import { CustomEmulator , CustomEmulatorDefinition , Emulator , EmulatorCreator , IFlutterDaemon , Logger } from "../interfaces" ;
8+ import { logProcess } from "../logging" ;
9+ import { safeSpawn } from "../processes" ;
710import { unique } from "../utils/array" ;
811import { isRunningLocally } from "./utils" ;
912
@@ -14,7 +17,7 @@ export class FlutterDeviceManager implements vs.Disposable {
1417 public currentDevice ?: f . Device ;
1518 private readonly knownEmulatorNames : { [ key : string ] : string } = { } ;
1619
17- constructor ( private readonly logger : Logger , private daemon : IFlutterDaemon , private readonly autoSelectNewlyConnectedDevices : boolean ) {
20+ constructor ( private readonly logger : Logger , private daemon : IFlutterDaemon , private readonly config : { flutterCustomEmulators : CustomEmulatorDefinition [ ] } , private readonly autoSelectNewlyConnectedDevices : boolean ) {
1821 this . statusBarItem = vs . window . createStatusBarItem ( vs . StatusBarAlignment . Right , 1 ) ;
1922 this . statusBarItem . tooltip = "Flutter" ;
2023 this . statusBarItem . command = "flutter.selectDevice" ;
@@ -36,7 +39,7 @@ export class FlutterDeviceManager implements vs.Disposable {
3639 this . subscriptions . forEach ( ( s ) => s . dispose ( ) ) ;
3740 }
3841
39- public isSupported ( types : f . PlatformType [ ] | undefined , device : { platformType : f . PlatformType | null | undefined } | undefined ) {
42+ public isSupported ( types : f . PlatformType [ ] | undefined , device : { platformType ? : f . PlatformType | null | undefined } | undefined ) {
4043 // If we don't get any types to filter, assume everything is valid.
4144 return device && ( ! types || ! types . length || ! device . platformType || types . indexOf ( device . platformType ) !== - 1 ) ;
4245 }
@@ -147,13 +150,23 @@ export class FlutterDeviceManager implements vs.Disposable {
147150 this . currentDevice = undefined ;
148151 this . statusBarItem . text = `Creating ${ emulatorTypeLabel } ...` ;
149152 await this . createEmulator ( ) ;
153+ this . updateStatusBar ( ) ;
150154 break ;
151155 case "emulator" :
152156 // Clear the current device so we can wait for the new one
153157 // to connect.
154158 this . currentDevice = undefined ;
155159 this . statusBarItem . text = `Launching ${ emulatorTypeLabel } ...` ;
156160 await this . launchEmulator ( selection . device ) ;
161+ this . updateStatusBar ( ) ;
162+ break ;
163+ case "custom-emulator" :
164+ // Clear the current device so we can wait for the new one
165+ // to connect.
166+ this . currentDevice = undefined ;
167+ this . statusBarItem . text = `Launching ${ emulatorTypeLabel } ...` ;
168+ await this . launchCustomEmulator ( selection . device ) ;
169+ this . updateStatusBar ( ) ;
157170 break ;
158171 case "device" :
159172 this . currentDevice = selection . device ;
@@ -230,21 +243,36 @@ export class FlutterDeviceManager implements vs.Disposable {
230243 }
231244 }
232245
233- private async getEmulators ( ) : Promise < f . Emulator [ ] > {
246+ private async getEmulators ( ) : Promise < Emulator [ ] > {
234247 try {
235248 const emus = await this . daemon . getEmulators ( ) ;
236249
250+ const allEmulatorsByID : { [ key : string ] : Emulator } = { } ;
251+ for ( const e of emus ) {
252+ allEmulatorsByID [ e . id ] = {
253+ category : e . category ,
254+ id : e . id ,
255+ name : e . name || e . id ,
256+ platformType : e . platformType ,
257+ type : "emulator" ,
258+ } ;
259+ }
260+
261+ // Add/overwrite any custom emulators.
262+ for ( const e of this . config . flutterCustomEmulators ) {
263+ allEmulatorsByID [ e . id ] = {
264+ ...e ,
265+ type : "custom-emulator" ,
266+ } ;
267+ }
268+
269+ const allEmulators = Object . values ( allEmulatorsByID ) ;
270+
237271 // Whenever we see emulators, record all their names.
238- for ( const e of emus . filter ( ( e ) => e . id ) )
272+ for ( const e of allEmulators )
239273 this . knownEmulatorNames [ e . id ] = e . name ;
240274
241- return emus . map ( ( e ) => ( {
242- category : e . category ,
243- id : e . id ,
244- name : e . name || e . id ,
245- platformType : e . platformType ,
246- type : "emulator" ,
247- } ) ) ;
275+ return allEmulators ;
248276 } catch ( e ) {
249277 this . logger . error ( { message : e } ) ;
250278 return [ ] ;
@@ -282,6 +310,8 @@ export class FlutterDeviceManager implements vs.Disposable {
282310 return this . createEmulator ( ) ;
283311 } else if ( selectedEmulator && selectedEmulator . device && selectedEmulator . device . type === "emulator" ) {
284312 return this . launchEmulator ( selectedEmulator . device ) ;
313+ } else if ( selectedEmulator && selectedEmulator . device && selectedEmulator . device . type === "custom-emulator" ) {
314+ return this . launchCustomEmulator ( selectedEmulator . device ) ;
285315 } else {
286316 return ! ! ( this . currentDevice ) ;
287317 }
@@ -328,25 +358,22 @@ export class FlutterDeviceManager implements vs.Disposable {
328358 . map ( ( e ) => ( {
329359 alwaysShow : false ,
330360 description : showAsEmulators ? `${ e . category || "mobile" } ${ this . emulatorLabel ( e . platformType ) } ` : e . platformType || undefined ,
331- device : {
332- ...e ,
333- type : "emulator" ,
334- } ,
361+ device : e ,
335362 label : showAsEmulators ? `Start ${ e . name } ` : e . name ,
336363 } ) ) ;
337364
338365 // Add an option to create a new emulator if the daemon supports it.
339366 if ( this . daemon . capabilities . canCreateEmulators && this . isSupported ( supportedTypes , { platformType : "android" } ) ) {
340367 emulators . push ( {
341368 alwaysShow : true ,
342- device : { type : "emulator-creator" , platformType : "android" } ,
369+ device : { type : "emulator-creator" , platformType : "android" , name : "Create Android emulator" } as EmulatorCreator ,
343370 label : "Create Android emulator" ,
344371 } ) ;
345372 }
346373 return emulators ;
347374 }
348375
349- private async launchEmulator ( emulator : { id : string , name : string } ) : Promise < boolean > {
376+ private async launchEmulator ( emulator : f . FlutterEmulator ) : Promise < boolean > {
350377 try {
351378 await vs . window . withProgress ( {
352379 location : vs . ProgressLocation . Notification ,
@@ -370,6 +397,37 @@ export class FlutterDeviceManager implements vs.Disposable {
370397 await new Promise ( ( resolve ) => setTimeout ( resolve , 1000 ) ) ;
371398 return true ;
372399 }
400+
401+ private async launchCustomEmulator ( emulator : CustomEmulator ) : Promise < boolean > {
402+ try {
403+ await vs . window . withProgress ( {
404+ location : vs . ProgressLocation . Notification ,
405+ } , async ( progress ) => {
406+ progress . report ( { message : `Launching ${ emulator . name } ...` } ) ;
407+ const binPath = emulator . executable ;
408+ const args = emulator . args || [ ] ;
409+
410+ const customEmulatorProc = safeSpawn ( undefined , emulator . executable , args , { } ) ;
411+ this . logger . info ( `(PROC ${ customEmulatorProc . pid } ) Spawned ${ binPath } ${ args . join ( " " ) } ` , LogCategory . CommandProcesses ) ;
412+ logProcess ( this . logger , LogCategory . CommandProcesses , customEmulatorProc ) ;
413+
414+ progress . report ( { message : `Waiting for ${ emulator . name } to connect...` } ) ;
415+ // Wait up to 60 seconds for emulator to launch.
416+ for ( let i = 0 ; i < 120 ; i ++ ) {
417+ await new Promise ( ( resolve ) => setTimeout ( resolve , 500 ) ) ;
418+ if ( this . currentDevice )
419+ return ;
420+ }
421+ throw new Error ( "Emulator didn't connect within 60 seconds" ) ;
422+ } ) ;
423+ } catch ( e ) {
424+ vs . window . showErrorMessage ( `Failed to launch ${ emulator . name } : ${ e } ` ) ;
425+ return false ;
426+ }
427+ // Wait an additional second to try and void some possible races.
428+ await new Promise ( ( resolve ) => setTimeout ( resolve , 1000 ) ) ;
429+ return true ;
430+ }
373431}
374432
375- type PickableDevice = vs . QuickPickItem & { device : f . Device | f . Emulator | f . EmulatorCreator } ;
433+ type PickableDevice = vs . QuickPickItem & { device : f . Device | Emulator | EmulatorCreator } ;
0 commit comments