1
1
import iOSProxyServices = require( "./../common/mobile/ios/ios-proxy-services" ) ;
2
2
import iOSDevice = require( "./../common/mobile/ios/ios-device" ) ;
3
+ import helpers = require( "../common/helpers" ) ;
3
4
import net = require( "net" ) ;
5
+ import path = require( "path" ) ;
6
+ import util = require( "util" ) ;
4
7
5
8
class 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
+
6
16
constructor ( private $devicesServices : Mobile . IDevicesServices ,
7
17
private $platformService : IPlatformService ,
8
18
private $platformsData : IPlatformsData ,
9
19
private $projectData : IProjectData ,
10
20
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 ) { }
12
28
13
29
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
+
15
39
public debug ( ) : IFuture < void > {
16
40
return this . $options . emulator
17
41
? this . debugOnEmulator ( )
@@ -27,27 +51,183 @@ class AndroidDebugService implements IDebugService {
27
51
28
52
public debugOnDevice ( ) : IFuture < void > {
29
53
return ( ( ) => {
30
- var platformData = this . $platformsData . getPlatformData ( this . platform ) ;
31
- var packageFile = "" ;
32
- var platformData = this . $platformsData . getPlatformData ( this . platform ) ;
54
+ let packageFile = "" ;
33
55
34
56
if ( this . $options . debugBrk ) {
35
57
this . $platformService . preparePlatform ( this . platform ) . wait ( ) ;
36
58
37
- var cachedDeviceOption = this . $options . forDevice ;
59
+ let cachedDeviceOption = this . $options . forDevice ;
38
60
this . $options . forDevice = true ;
39
61
this . $platformService . buildPlatform ( this . platform ) . wait ( ) ;
40
62
this . $options . forDevice = ! ! cachedDeviceOption ;
41
63
64
+ let platformData = this . $platformsData . getPlatformData ( this . platform ) ;
42
65
packageFile = this . $platformService . getLatestApplicationPackageForDevice ( platformData ) . wait ( ) . packageName ;
43
66
this . $logger . out ( "Using " , packageFile ) ;
44
67
}
45
68
46
69
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 ) } ;
48
71
this . $devicesServices . execute ( action ) . wait ( ) ;
49
72
50
73
} ) . future < void > ( ) ( ) ;
51
74
}
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
+ }
52
232
}
53
233
$injector . register ( "androidDebugService" , AndroidDebugService ) ;
0 commit comments