11import iOSProxyServices = require( "./../common/mobile/ios/ios-proxy-services" ) ;
22import iOSDevice = require( "./../common/mobile/ios/ios-device" ) ;
3+ import helpers = require( "../common/helpers" ) ;
34import net = require( "net" ) ;
5+ import path = require( "path" ) ;
6+ import util = require( "util" ) ;
47
58class AndroidDebugService implements IDebugService {
9+ private static ENV_DEBUG_IN_FILENAME = "envDebug.in" ;
10+ private static ENV_DEBUG_OUT_FILENAME = "envDebug.out" ;
11+ private static DEFAULT_NODE_INSPECTOR_URL = "http://127.0.0.1:8080/debug" ;
12+ private static PACKAGE_EXTERNAL_DIR_TEMPLATE = "/sdcard/Android/data/%s/files/" ;
13+
14+ private _device : Mobile . IAndroidDevice = null ;
15+
616 constructor ( private $devicesServices : Mobile . IDevicesServices ,
717 private $platformService : IPlatformService ,
818 private $platformsData : IPlatformsData ,
919 private $projectData : IProjectData ,
1020 private $logger : ILogger ,
11- private $options : IOptions ) { }
21+ private $options : IOptions ,
22+ private $childProcess : IChildProcess ,
23+ private $mobileHelper : Mobile . IMobileHelper ,
24+ private $hostInfo : IHostInfo ,
25+ private $errors : IErrors ,
26+ private $opener : IOpener ,
27+ private $staticConfig : IStaticConfig ) { }
1228
1329 private get platform ( ) { return "android" ; }
14-
30+
31+ private get device ( ) : Mobile . IAndroidDevice {
32+ return this . _device ;
33+ }
34+
35+ private set device ( newDevice ) {
36+ this . _device = newDevice ;
37+ }
38+
1539 public debug ( ) : IFuture < void > {
1640 return this . $options . emulator
1741 ? this . debugOnEmulator ( )
@@ -27,27 +51,183 @@ class AndroidDebugService implements IDebugService {
2751
2852 public debugOnDevice ( ) : IFuture < void > {
2953 return ( ( ) => {
30- var platformData = this . $platformsData . getPlatformData ( this . platform ) ;
31- var packageFile = "" ;
32- var platformData = this . $platformsData . getPlatformData ( this . platform ) ;
54+ let packageFile = "" ;
3355
3456 if ( this . $options . debugBrk ) {
3557 this . $platformService . preparePlatform ( this . platform ) . wait ( ) ;
3658
37- var cachedDeviceOption = this . $options . forDevice ;
59+ let cachedDeviceOption = this . $options . forDevice ;
3860 this . $options . forDevice = true ;
3961 this . $platformService . buildPlatform ( this . platform ) . wait ( ) ;
4062 this . $options . forDevice = ! ! cachedDeviceOption ;
4163
64+ let platformData = this . $platformsData . getPlatformData ( this . platform ) ;
4265 packageFile = this . $platformService . getLatestApplicationPackageForDevice ( platformData ) . wait ( ) . packageName ;
4366 this . $logger . out ( "Using " , packageFile ) ;
4467 }
4568
4669 this . $devicesServices . initialize ( { platform : this . platform , deviceId : this . $options . device } ) . wait ( ) ;
47- var action = ( device : Mobile . IAndroidDevice ) : IFuture < void > => { return device . debug ( packageFile , this . $projectData . projectId ) } ;
70+ let action = ( device : Mobile . IAndroidDevice ) : IFuture < void > => { return this . debugCore ( device , packageFile , this . $projectData . projectId ) } ;
4871 this . $devicesServices . execute ( action ) . wait ( ) ;
4972
5073 } ) . future < void > ( ) ( ) ;
5174 }
75+
76+ private debugCore ( device : Mobile . IAndroidDevice , packageFile : string , packageName : string ) : IFuture < void > {
77+ return ( ( ) => {
78+ this . device = device ;
79+
80+ if ( this . $options . getPort ) {
81+ this . printDebugPort ( packageName ) . wait ( ) ;
82+ } else if ( this . $options . start ) {
83+ this . attachDebugger ( packageName ) ;
84+ } else if ( this . $options . stop ) {
85+ this . detachDebugger ( packageName ) . wait ( ) ;
86+ } else if ( this . $options . debugBrk ) {
87+ this . startAppWithDebugger ( packageFile , packageName ) ;
88+ } else {
89+ this . $logger . info ( "Should specify at least one option: debug-brk, start, stop, get-port." ) ;
90+ }
91+ } ) . future < void > ( ) ( ) ;
92+ }
93+
94+ private printDebugPort ( packageName : string ) : IFuture < void > {
95+ return ( ( ) => {
96+ let res = this . $childProcess . spawnFromEvent ( this . $staticConfig . getAdbFilePath ( ) . wait ( ) , [ "shell" , "am" , "broadcast" , "-a" , packageName + "-GetDgbPort" ] , "exit" ) . wait ( ) ;
97+ this . $logger . info ( res . stdout ) ;
98+ } ) . future < void > ( ) ( ) ;
99+ }
100+
101+ private attachDebugger ( packageName : string ) : void {
102+ let startDebuggerCommand = `am broadcast -a \"${ packageName } -Debug\" --ez enable true` ;
103+ let port = this . $options . debugPort ;
104+
105+ if ( port > 0 ) {
106+ startDebuggerCommand += " --ei debuggerPort " + port ;
107+ this . device . adb . executeShellCommand ( startDebuggerCommand ) . wait ( ) ;
108+ } else {
109+ let res = this . $childProcess . spawnFromEvent ( this . $staticConfig . getAdbFilePath ( ) . wait ( ) , [ "shell" , "am" , "broadcast" , "-a" , packageName + "-Debug" , "--ez" , "enable" , "true" ] , "exit" ) . wait ( ) ;
110+ let match = res . stdout . match ( / r e s u l t = ( \d ) + / ) ;
111+ if ( match ) {
112+ port = match [ 0 ] . substring ( 7 ) ;
113+ } else {
114+ port = 0 ;
115+ }
116+ }
117+ if ( ( 0 < port ) && ( port < 65536 ) ) {
118+ this . tcpForward ( port , port ) ;
119+ this . startDebuggerClient ( port ) . wait ( ) ;
120+ this . openDebuggerClient ( AndroidDebugService . DEFAULT_NODE_INSPECTOR_URL + "?port=" + port ) ;
121+ } else {
122+ this . $logger . info ( "Cannot detect debug port." ) ;
123+ }
124+ }
125+
126+ private detachDebugger ( packageName : string ) : IFuture < void > {
127+ return this . device . adb . executeShellCommand ( this . device . deviceInfo . identifier , `shell am broadcast -a \"${ packageName } -Debug\" --ez enable false` ) ;
128+ }
129+
130+ private startAppWithDebugger ( packageFile : string , packageName : string ) : IFuture < void > {
131+ return ( ( ) => {
132+ this . device . applicationManager . uninstallApplication ( packageName ) . wait ( ) ;
133+ this . device . applicationManager . installApplication ( packageFile ) . wait ( ) ;
134+
135+ let port = this . $options . debugPort ;
136+
137+ let packageDir = util . format ( AndroidDebugService . PACKAGE_EXTERNAL_DIR_TEMPLATE , packageName ) ;
138+ let envDebugOutFullpath = this . $mobileHelper . buildDevicePath ( packageDir , AndroidDebugService . ENV_DEBUG_OUT_FILENAME ) ;
139+
140+ this . device . adb . executeShellCommand ( `rm "${ envDebugOutFullpath } "` ) . wait ( ) ;
141+ this . device . adb . executeShellCommand ( `mkdir -p "${ packageDir } "` ) . wait ( ) ;
142+
143+ let debugBreakPath = this . $mobileHelper . buildDevicePath ( packageDir , "debugbreak" ) ;
144+ this . device . adb . executeShellCommand ( `"cat /dev/null > ${ debugBreakPath } "` ) . wait ( ) ;
145+
146+ this . device . applicationManager . startApplication ( packageName ) . wait ( ) ;
147+
148+ let dbgPort = this . startAndGetPort ( packageName ) . wait ( ) ;
149+ if ( dbgPort > 0 ) {
150+ this . tcpForward ( dbgPort , dbgPort ) ;
151+ this . startDebuggerClient ( dbgPort ) . wait ( ) ;
152+ this . openDebuggerClient ( AndroidDebugService . DEFAULT_NODE_INSPECTOR_URL + "?port=" + dbgPort ) ;
153+ }
154+ } ) . future < void > ( ) ( ) ;
155+ }
156+
157+ private tcpForward ( src : Number , dest : Number ) : IFuture < void > {
158+ return this . device . adb . executeCommand ( `forward tcp:${ src . toString ( ) } tcp:${ dest . toString ( ) } ` ) ;
159+ }
160+
161+ private startDebuggerClient ( port : Number ) : IFuture < void > {
162+ return ( ( ) => {
163+ let nodeInspectorModuleFilePath = require . resolve ( "node-inspector" ) ;
164+ let nodeInspectorModuleDir = path . dirname ( nodeInspectorModuleFilePath ) ;
165+ let nodeInspectorFullPath = path . join ( nodeInspectorModuleDir , "bin" , "inspector" ) ;
166+ this . $childProcess . spawn ( process . argv [ 0 ] , [ nodeInspectorFullPath , "--debug-port" , port . toString ( ) ] , { stdio : "ignore" , detached : true } ) ;
167+ } ) . future < void > ( ) ( ) ;
168+ }
169+
170+ private openDebuggerClient ( url : string ) : void {
171+ let chrome = this . $hostInfo . isDarwin ? "Google\ Chrome" : "chrome" ;
172+ let child = this . $opener . open ( url , chrome ) ;
173+ if ( ! child ) {
174+ this . $errors . fail ( `Unable to open ${ chrome } .` ) ;
175+ }
176+ return child ;
177+ }
178+
179+ private checkIfRunning ( packageName : string ) : boolean {
180+ let packageDir = util . format ( AndroidDebugService . PACKAGE_EXTERNAL_DIR_TEMPLATE , packageName ) ;
181+ let envDebugOutFullpath = packageDir + AndroidDebugService . ENV_DEBUG_OUT_FILENAME ;
182+ let isRunning = this . checkIfFileExists ( envDebugOutFullpath ) . wait ( ) ;
183+ return isRunning ;
184+ }
185+
186+ private checkIfFileExists ( filename : string ) : IFuture < boolean > {
187+ return ( ( ) => {
188+ let args = [ "shell" , "test" , "-f" , filename , "&&" , "echo 'yes'" , "||" , "echo 'no'" ] ;
189+ let res = this . $childProcess . spawnFromEvent ( this . $staticConfig . getAdbFilePath ( ) . wait ( ) , args , "exit" ) . wait ( ) ;
190+ let exists = res . stdout . indexOf ( 'yes' ) > - 1 ;
191+ return exists ;
192+ } ) . future < boolean > ( ) ( ) ;
193+ }
194+
195+ private startAndGetPort ( packageName : string ) : IFuture < number > {
196+ return ( ( ) => {
197+ let port = - 1 ;
198+ let timeout = 60 ;
199+
200+ let packageDir = util . format ( AndroidDebugService . PACKAGE_EXTERNAL_DIR_TEMPLATE , packageName ) ;
201+ let envDebugInFullpath = packageDir + AndroidDebugService . ENV_DEBUG_IN_FILENAME ;
202+ this . device . adb . executeShellCommand ( `rm "${ envDebugInFullpath } "` ) . wait ( ) ;
203+
204+ let isRunning = false ;
205+ for ( let i = 0 ; i < timeout ; i ++ ) {
206+ helpers . sleep ( 1000 /* ms */ ) ;
207+ isRunning = this . checkIfRunning ( packageName ) ;
208+ if ( isRunning )
209+ break ;
210+ }
211+
212+ if ( isRunning ) {
213+ this . device . adb . executeShellCommand ( `"cat /dev/null > ${ envDebugInFullpath } "` ) . wait ( ) ;
214+
215+ for ( let i = 0 ; i < timeout ; i ++ ) {
216+ helpers . sleep ( 1000 /* ms */ ) ;
217+ let envDebugOutFullpath = packageDir + AndroidDebugService . ENV_DEBUG_OUT_FILENAME ;
218+ let exists = this . checkIfFileExists ( envDebugOutFullpath ) . wait ( ) ;
219+ if ( exists ) {
220+ let res = this . $childProcess . spawnFromEvent ( this . $staticConfig . getAdbFilePath ( ) . wait ( ) , [ "shell" , "cat" , envDebugOutFullpath ] , "exit" ) . wait ( ) ;
221+ let match = res . stdout . match ( / P O R T = ( \d ) + / ) ;
222+ if ( match ) {
223+ port = parseInt ( match [ 0 ] . substring ( 5 ) , 10 ) ;
224+ break ;
225+ }
226+ }
227+ }
228+ }
229+ return port ;
230+ } ) . future < number > ( ) ( ) ;
231+ }
52232}
53233$injector . register ( "androidDebugService" , AndroidDebugService ) ;
0 commit comments