11///<reference path="../.d.ts"/>
22"use strict" ;
3- import * as helpers from "../common/helpers" ;
43import * as path from "path" ;
5- import * as util from "util" ;
4+ import * as net from "net" ;
5+ import Future = require( "fibers/future" ) ;
66
77class AndroidDebugService implements IDebugService {
8- private static ENV_DEBUG_IN_FILENAME = "envDebug.in" ;
9- private static ENV_DEBUG_OUT_FILENAME = "envDebug.out" ;
10- private static DEFAULT_NODE_INSPECTOR_URL = "http://127.0.0.1:8080/debug" ;
11- private static PACKAGE_EXTERNAL_DIR_TEMPLATE = "/sdcard/Android/data/%s/files/" ;
8+ private static DEFAULT_NODE_INSPECTOR_URL = "http://127.0.0.1:8080/debug" ;
129
1310 private _device : Mobile . IAndroidDevice = null ;
1411
@@ -19,13 +16,10 @@ class AndroidDebugService implements IDebugService {
1916 private $logger : ILogger ,
2017 private $options : IOptions ,
2118 private $childProcess : IChildProcess ,
22- private $mobileHelper : Mobile . IMobileHelper ,
23- private $hostInfo : IHostInfo ,
24- private $errors : IErrors ,
25- private $opener : IOpener ,
26- private $staticConfig : IStaticConfig ,
27- private $utils : IUtils ,
28- private $config : IConfiguration ) { }
19+ private $hostInfo : IHostInfo ,
20+ private $errors : IErrors ,
21+ private $opener : IOpener ,
22+ private $config : IConfiguration ) { }
2923
3024 private get platform ( ) { return "android" ; }
3125
@@ -43,14 +37,60 @@ class AndroidDebugService implements IDebugService {
4337 : this . debugOnDevice ( ) ;
4438 }
4539
46- public debugOnEmulator ( ) : IFuture < void > {
40+ private debugOnEmulator ( ) : IFuture < void > {
4741 return ( ( ) => {
4842 this . $platformService . deployOnEmulator ( this . platform ) . wait ( ) ;
4943 this . debugOnDevice ( ) . wait ( ) ;
5044 } ) . future < void > ( ) ( ) ;
5145 }
5246
53- public debugOnDevice ( ) : IFuture < void > {
47+ private isPortAvailable ( candidatePort : number ) : IFuture < boolean > {
48+ let future = new Future < boolean > ( ) ;
49+ let server = net . createServer ( ) ;
50+ server . on ( "error" , ( err : Error ) => {
51+ future . return ( false ) ;
52+ } ) ;
53+ server . listen ( candidatePort , ( err : Error ) => {
54+ server . once ( "close" , ( ) => {
55+ future . return ( true ) ;
56+ } ) ;
57+ server . close ( ) ;
58+ } ) ;
59+
60+ return future ;
61+ }
62+
63+ private getForwardedLocalDebugPortForPackageName ( deviceId : string , packageName : string ) : IFuture < number > {
64+ return ( ( ) => {
65+ let port = - 1 ;
66+ let forwardsResult = this . device . adb . executeCommand ( [ "forward" , "--list" ] ) . wait ( ) ;
67+
68+ //matches 123a188909e6czzc tcp:40001 localabstract:org.nativescript.testUnixSockets-debug
69+ let regexp = new RegExp ( `(?:${ deviceId } tcp:)([\\d]+)(?= localabstract:${ packageName } -debug)` , "g" ) ;
70+ let match = regexp . exec ( forwardsResult ) ;
71+ if ( match ) {
72+ port = parseInt ( match [ 1 ] ) ;
73+ } else {
74+ let candidatePort = 40000 ;
75+ while ( ! this . isPortAvailable ( candidatePort ++ ) . wait ( ) ) {
76+ if ( candidatePort > 65534 ) {
77+ this . $errors . failWithoutHelp ( "Unable to find free local port." ) ;
78+ }
79+ }
80+ port = candidatePort ;
81+
82+ this . unixSocketForward ( port , packageName + "-debug" ) . wait ( ) ;
83+ }
84+
85+ return port ;
86+ } ) . future < number > ( ) ( ) ;
87+ }
88+
89+ private unixSocketForward ( local : number , remote : string ) : IFuture < void > {
90+ return this . device . adb . executeCommand ( [ "forward" , `tcp:${ local . toString ( ) } ` , `localabstract:${ remote } ` ] ) ;
91+ }
92+
93+ private debugOnDevice ( ) : IFuture < void > {
5494 return ( ( ) => {
5595 let packageFile = "" ;
5696
@@ -78,183 +118,102 @@ class AndroidDebugService implements IDebugService {
78118 }
79119
80120 private debugCore ( device : Mobile . IAndroidDevice , packageFile : string , packageName : string ) : IFuture < void > {
81- return ( ( ) => {
121+ return ( ( ) => {
82122 this . device = device ;
83123
84- if ( this . $options . getPort ) {
85- this . printDebugPort ( packageName ) . wait ( ) ;
86- } else if ( this . $options . start ) {
87- this . attachDebugger ( packageName ) ;
88- } else if ( this . $options . stop ) {
89- this . detachDebugger ( packageName ) . wait ( ) ;
90- } else if ( this . $options . debugBrk ) {
91- this . startAppWithDebugger ( packageFile , packageName ) . wait ( ) ;
92- }
93- } ) . future < void > ( ) ( ) ;
94- }
95-
96- private printDebugPort ( packageName : string ) : IFuture < void > {
97- return ( ( ) => {
98- let res = this . device . adb . executeShellCommand ( [ "am" , "broadcast" , "-a" , packageName + "-GetDbgPort" ] ) . wait ( ) ;
99- this . $logger . info ( res ) ;
100- } ) . future < void > ( ) ( ) ;
101- }
102-
103- private attachDebugger ( packageName : string ) : void {
104- let startDebuggerCommand = [ "am" , "broadcast" , "-a" , '\"${packageName}-Debug\"' , "--ez" , "enable" , "true" ] ;
105- let port = this . $options . debugPort ;
106-
107- if ( port > 0 ) {
108- startDebuggerCommand . push ( "--ei" , "debuggerPort" , port . toString ( ) ) ;
109- this . device . adb . executeShellCommand ( startDebuggerCommand ) . wait ( ) ;
110- } else {
111- let res = this . device . adb . executeShellCommand ( [ "am" , "broadcast" , "-a" , packageName + "-Debug" , "--ez" , "enable" , "true" ] ) . wait ( ) ;
112- let match = res . match ( / r e s u l t = ( \d ) + / ) ;
113- if ( match ) {
114- port = match [ 0 ] . substring ( 7 ) ;
115- } else {
116- port = 0 ;
117- }
118- }
119- if ( ( 0 < port ) && ( port < 65536 ) ) {
120- this . tcpForward ( port , port ) . wait ( ) ;
121- this . startDebuggerClient ( port ) . wait ( ) ;
122- this . openDebuggerClient ( AndroidDebugService . DEFAULT_NODE_INSPECTOR_URL + "?port=" + port ) ;
123- } else {
124- this . $logger . info ( "Cannot detect debug port." ) ;
125- }
126- }
127-
128- private detachDebugger ( packageName : string ) : IFuture < void > {
129- return this . device . adb . executeShellCommand ( [ "am" , "broadcast" , "-a" , `${ packageName } -Debug` , "--ez" , "enable" , "false" ] ) ;
130- }
131-
132- private startAppWithDebugger ( packageFile : string , packageName : string ) : IFuture < void > {
133- return ( ( ) => {
134- if ( ! this . $options . emulator ) {
135- this . device . applicationManager . uninstallApplication ( packageName ) . wait ( ) ;
136- this . device . applicationManager . installApplication ( packageFile ) . wait ( ) ;
137- }
138- this . debugStartCore ( ) . wait ( ) ;
139- } ) . future < void > ( ) ( ) ;
140- }
141-
142- public debugStart ( ) : IFuture < void > {
143- return ( ( ) => {
144- this . $devicesService . initialize ( { platform : this . platform , deviceId : this . $options . device } ) . wait ( ) ;
145- let action = ( device : Mobile . IAndroidDevice ) : IFuture < void > => {
146- this . device = device ;
147- return this . debugStartCore ( ) ;
148- } ;
149- this . $devicesService . execute ( action ) . wait ( ) ;
150- } ) . future < void > ( ) ( ) ;
151- }
152-
153- private debugStartCore ( ) : IFuture < void > {
154- return ( ( ) => {
155- let packageName = this . $projectData . projectId ;
156- let packageDir = util . format ( AndroidDebugService . PACKAGE_EXTERNAL_DIR_TEMPLATE , packageName ) ;
157- let envDebugOutFullpath = this . $mobileHelper . buildDevicePath ( packageDir , AndroidDebugService . ENV_DEBUG_OUT_FILENAME ) ;
158-
159- this . device . adb . executeShellCommand ( [ "rm" , `${ envDebugOutFullpath } ` ] ) . wait ( ) ;
160- this . device . adb . executeShellCommand ( [ "mkdir" , "-p" , `${ packageDir } ` ] ) . wait ( ) ;
161-
162- let debugBreakPath = this . $mobileHelper . buildDevicePath ( packageDir , "debugbreak" ) ;
163- this . device . adb . executeShellCommand ( [ `cat /dev/null > ${ debugBreakPath } ` ] ) . wait ( ) ;
164-
165- this . device . applicationManager . stopApplication ( packageName ) . wait ( ) ;
166- this . device . applicationManager . startApplication ( packageName ) . wait ( ) ;
167-
168- let dbgPort = this . startAndGetPort ( packageName ) . wait ( ) ;
169- if ( dbgPort > 0 ) {
170- this . tcpForward ( dbgPort , dbgPort ) . wait ( ) ;
171- this . startDebuggerClient ( dbgPort ) . wait ( ) ;
172- this . openDebuggerClient ( AndroidDebugService . DEFAULT_NODE_INSPECTOR_URL + "?port=" + dbgPort ) ;
173- }
174- } ) . future < void > ( ) ( ) ;
175- }
176-
177- private tcpForward ( src : Number , dest : Number ) : IFuture < void > {
178- return this . device . adb . executeCommand ( [ "forward" , `tcp:${ src . toString ( ) } ` , `tcp:${ dest . toString ( ) } ` ] ) ;
179- }
180-
181- private startDebuggerClient ( port : Number ) : IFuture < void > {
182- return ( ( ) => {
183- let nodeInspectorModuleFilePath = require . resolve ( "node-inspector" ) ;
184- let nodeInspectorModuleDir = path . dirname ( nodeInspectorModuleFilePath ) ;
185- let nodeInspectorFullPath = path . join ( nodeInspectorModuleDir , "bin" , "inspector" ) ;
186- this . $childProcess . spawn ( process . argv [ 0 ] , [ nodeInspectorFullPath , "--debug-port" , port . toString ( ) ] , { stdio : "ignore" , detached : true } ) ;
187- } ) . future < void > ( ) ( ) ;
188- }
189-
190- private openDebuggerClient ( url : string ) : void {
191- let defaultDebugUI = "chrome" ;
192- if ( this . $hostInfo . isDarwin ) {
193- defaultDebugUI = "Google Chrome" ;
194- }
195- if ( this . $hostInfo . isLinux ) {
196- defaultDebugUI = "google-chrome" ;
197- }
124+ if ( this . $options . getPort ) {
125+ this . printDebugPort ( device . deviceInfo . identifier , packageName ) . wait ( ) ;
126+ } else if ( this . $options . start ) {
127+ this . attachDebugger ( device . deviceInfo . identifier , packageName ) . wait ( ) ;
128+ } else if ( this . $options . stop ) {
129+ this . detachDebugger ( packageName ) . wait ( ) ;
130+ } else if ( this . $options . debugBrk ) {
131+ this . startAppWithDebugger ( packageFile , packageName ) . wait ( ) ;
132+ }
133+ } ) . future < void > ( ) ( ) ;
134+ }
135+
136+ private printDebugPort ( deviceId : string , packageName : string ) : IFuture < void > {
137+ return ( ( ) => {
138+ let port = this . getForwardedLocalDebugPortForPackageName ( deviceId , packageName ) . wait ( ) ;
139+ this . $logger . info ( port ) ;
140+ } ) . future < void > ( ) ( ) ;
141+ }
142+
143+ private attachDebugger ( deviceId : string , packageName : string ) : IFuture < void > {
144+ return ( ( ) => {
145+ let startDebuggerCommand = [ "am" , "broadcast" , "-a" , '\"${packageName}-debug\"' , "--ez" , "enable" , "true" ] ;
146+ this . device . adb . executeShellCommand ( startDebuggerCommand ) . wait ( ) ;
147+
148+ let port = this . getForwardedLocalDebugPortForPackageName ( deviceId , packageName ) . wait ( ) ;
149+ this . startDebuggerClient ( port ) . wait ( ) ;
150+ this . openDebuggerClient ( AndroidDebugService . DEFAULT_NODE_INSPECTOR_URL + "?port=" + port ) ;
151+ } ) . future < void > ( ) ( ) ;
152+ }
153+
154+ private detachDebugger ( packageName : string ) : IFuture < void > {
155+ return this . device . adb . executeShellCommand ( [ "am" , "broadcast" , "-a" , `${ packageName } -debug` , "--ez" , "enable" , "false" ] ) ;
156+ }
157+
158+ private startAppWithDebugger ( packageFile : string , packageName : string ) : IFuture < void > {
159+ return ( ( ) => {
160+ if ( ! this . $options . emulator ) {
161+ this . device . applicationManager . uninstallApplication ( packageName ) . wait ( ) ;
162+ this . device . applicationManager . installApplication ( packageFile ) . wait ( ) ;
163+ }
164+ this . debugStartCore ( ) . wait ( ) ;
165+ } ) . future < void > ( ) ( ) ;
166+ }
167+
168+ public debugStart ( ) : IFuture < void > {
169+ return ( ( ) => {
170+ this . $devicesService . initialize ( { platform : this . platform , deviceId : this . $options . device } ) . wait ( ) ;
171+ let action = ( device : Mobile . IAndroidDevice ) : IFuture < void > => {
172+ this . device = device ;
173+ return this . debugStartCore ( ) ;
174+ } ;
175+ this . $devicesService . execute ( action ) . wait ( ) ;
176+ } ) . future < void > ( ) ( ) ;
177+ }
178+
179+ private debugStartCore ( ) : IFuture < void > {
180+ return ( ( ) => {
181+ let packageName = this . $projectData . projectId ;
182+
183+ this . device . adb . executeShellCommand ( [ `cat /dev/null > /data/local/tmp/${ packageName } -debugbreak` ] ) . wait ( ) ;
184+
185+ this . device . applicationManager . stopApplication ( packageName ) . wait ( ) ;
186+ this . device . applicationManager . startApplication ( packageName ) . wait ( ) ;
187+
188+ let localDebugPort = this . getForwardedLocalDebugPortForPackageName ( this . device . deviceInfo . identifier , packageName ) . wait ( ) ;
189+ this . startDebuggerClient ( localDebugPort ) . wait ( ) ;
190+ this . openDebuggerClient ( AndroidDebugService . DEFAULT_NODE_INSPECTOR_URL + "?port=" + localDebugPort ) ;
191+ } ) . future < void > ( ) ( ) ;
192+ }
193+
194+ private startDebuggerClient ( port : Number ) : IFuture < void > {
195+ return ( ( ) => {
196+ let nodeInspectorModuleFilePath = require . resolve ( "node-inspector" ) ;
197+ let nodeInspectorModuleDir = path . dirname ( nodeInspectorModuleFilePath ) ;
198+ let nodeInspectorFullPath = path . join ( nodeInspectorModuleDir , "bin" , "inspector" ) ;
199+ this . $childProcess . spawn ( process . argv [ 0 ] , [ nodeInspectorFullPath , "--debug-port" , port . toString ( ) ] , { stdio : "ignore" , detached : true } ) ;
200+ } ) . future < void > ( ) ( ) ;
201+ }
202+
203+ private openDebuggerClient ( url : string ) : void {
204+ let defaultDebugUI = "chrome" ;
205+ if ( this . $hostInfo . isDarwin ) {
206+ defaultDebugUI = "Google Chrome" ;
207+ }
208+ if ( this . $hostInfo . isLinux ) {
209+ defaultDebugUI = "google-chrome" ;
210+ }
198211
199212 let debugUI = this . $config . ANDROID_DEBUG_UI || defaultDebugUI ;
200213 let child = this . $opener . open ( url , debugUI ) ;
201214 if ( ! child ) {
202215 this . $errors . failWithoutHelp ( `Unable to open ${ debugUI } .` ) ;
203216 }
204- }
205-
206- private checkIfRunning ( packageName : string ) : boolean {
207- let packageDir = util . format ( AndroidDebugService . PACKAGE_EXTERNAL_DIR_TEMPLATE , packageName ) ;
208- let envDebugOutFullpath = packageDir + AndroidDebugService . ENV_DEBUG_OUT_FILENAME ;
209- let isRunning = this . checkIfFileExists ( envDebugOutFullpath ) . wait ( ) ;
210- return isRunning ;
211- }
212-
213- private checkIfFileExists ( filename : string ) : IFuture < boolean > {
214- return ( ( ) => {
215- let res = this . device . adb . executeShellCommand ( [ `test -f ${ filename } && echo 'yes' || echo 'no'` ] ) . wait ( ) ;
216- let exists = res . indexOf ( 'yes' ) > - 1 ;
217- return exists ;
218- } ) . future < boolean > ( ) ( ) ;
219- }
220-
221- private startAndGetPort ( packageName : string ) : IFuture < number > {
222- return ( ( ) => {
223- let port = - 1 ;
224- let timeout = this . $utils . getParsedTimeout ( 90 ) ;
225-
226- let packageDir = util . format ( AndroidDebugService . PACKAGE_EXTERNAL_DIR_TEMPLATE , packageName ) ;
227- let envDebugInFullpath = packageDir + AndroidDebugService . ENV_DEBUG_IN_FILENAME ;
228- this . device . adb . executeShellCommand ( [ "rm" , `${ envDebugInFullpath } ` ] ) . wait ( ) ;
229-
230- let isRunning = false ;
231- for ( let i = 0 ; i < timeout ; i ++ ) {
232- helpers . sleep ( 1000 /* ms */ ) ;
233- isRunning = this . checkIfRunning ( packageName ) ;
234- if ( isRunning ) {
235- break ;
236- }
237- }
238-
239- if ( isRunning ) {
240- this . device . adb . executeShellCommand ( [ `cat /dev/null > ${ envDebugInFullpath } ` ] ) . wait ( ) ;
241-
242- for ( let i = 0 ; i < timeout ; i ++ ) {
243- helpers . sleep ( 1000 /* ms */ ) ;
244- let envDebugOutFullpath = packageDir + AndroidDebugService . ENV_DEBUG_OUT_FILENAME ;
245- let exists = this . checkIfFileExists ( envDebugOutFullpath ) . wait ( ) ;
246- if ( exists ) {
247- let res = this . device . adb . executeShellCommand ( [ "cat" , envDebugOutFullpath ] ) . wait ( ) ;
248- let match = res . match ( / P O R T = ( \d ) + / ) ;
249- if ( match ) {
250- port = parseInt ( match [ 0 ] . substring ( 5 ) , 10 ) ;
251- break ;
252- }
253- }
254- }
255- }
256- return port ;
257- } ) . future < number > ( ) ( ) ;
258- }
217+ }
259218}
260219$injector . register ( "androidDebugService" , AndroidDebugService ) ;
0 commit comments