import * as path from "path" ; import * as fs from "fs" ; import * as os from "os" ; import * as nls from "vscode-nls" ; import { OutputChannelLogger } from "../log/outputChannelLogger" ; import { ChildProcess , ISpawnResult } from "../../common/node/childProcess" ; import { IDebuggableMobileTarget } from "../mobileTarget" ; nls . config ( { messageFormat : nls . MessageFormat . bundle , bundleFormat : nls . BundleFormat . standalone , } ) ( ) ; const localize = nls . loadMessageBundle ( ) ; export enum AndroidAPILevel { Marshmallow = 23 , LOLLIPOP_MR1 = 22 , LOLLIPOP = 21 , KITKAT = 19 , JELLY_BEAN_MR2 = 18 , JELLY_BEAN_MR1 = 17 , JELLY_BEAN = 16 , ICE_CREAM_SANDWICH_MR1 = 15 , GINGERBREAD_MR1 = 10 } export class AdbHelper { public static readonly AndroidSDKEmulatorPattern = /^emulator-\d{1,5}$/ ; private childProcess : ChildProcess = new ChildProcess ( ) ; private adbExecutable : string = "" ; constructor ( projectRoot , logger ? ) { this . adbExecutable = this . getAdbPath ( projectRoot , logger ) ; } /**
     * Gets the list of Android connected devices and emulators.
     */ public getConnectedTargets ( ) { return this . childProcess . execToString ( `${ this . adbExecutable } devices` ) . then ( output => { return this . parseConnectedTargets ( output ) ; } ) ; } public apiVersion ( targetId ) { return this . executeQuery ( targetId , "shell getprop ro.build.version.sdk" ) . then ( output => parseInt ( output , 10 ) ) ; } public reverseAdb ( targetId , packagerPort ) { return this . execute ( targetId , `reverse tcp:${ packagerPort } tcp:${ packagerPort }` ) ; } public getOnlineTargets ( ) { return this . getConnectedTargets ( ) . then ( targets => { return targets . filter ( target => target . isOnline ) ; } ) ; } public async getAvdNameById ( emulatorId ) { return this . childProcess . execToString ( `${ this . adbExecutable } -s ${ emulatorId } emu avd name` ) . then ( output => { if ( output ) { return output . split ( /\r?\n|\r/g ) [ 0 ] ; } else { return null ; } } ) . catch ( ( ) => null ) ; } public async getAvdsNames ( ) { const res = await this . childProcess . execToString ( "emulator -list-avds" ) ; let emulatorsNames = [ ] ; if ( res ) { emulatorsNames = res . split ( /\r?\n|\r/g ) ; const indexOfBlank = emulatorsNames . indexOf ( "" ) ; if ( emulatorsNames . indexOf ( "" ) >= 0 ) { emulatorsNames . splice ( indexOfBlank , 1 ) ; } } return emulatorsNames ; } public async findOnlineTargetById ( targetId ) { return ( await this . getOnlineTargets ( ) ) . find ( ( target ) => target . id === targetId ) ; } public startLogCat ( adbParameters ) { return this . childProcess . spawn ( this . adbExecutable . replace ( /\"/g , "" ) , adbParameters ) ; } public parseSdkLocation ( fileContent , logger ? ) { const matches = fileContent . match ( /^sdk\.dir=(.+)$/m ) ; if ( ! matches || ! matches [ 1 ] ) { if ( logger ) { logger . log ( localize ( "NoSdkDirFoundInLocalPropertiesFile" , "No sdk.dir value found in local.properties file. Using Android SDK location from PATH." ) ) ; } return null ; } let sdkLocation = matches [ 1 ] . trim ( ) ; if ( os . platform ( ) === "win32" ) { sdkLocation = sdkLocation . replace ( /\\\\/g , "\\" ) . replace ( "\\:" , ":" ) ; } if ( logger ) { logger . log ( localize ( "UsindAndroidSDKLocationDefinedInLocalPropertiesFile" , "Using Android SDK location defined in android/local.properties file: {0}." , sdkLocation ) ) ; } return sdkLocation ; } public getAdbPath ( projectRoot , logger ? ) { const sdkLocation = this . getSdkLocationFromLocalPropertiesFile ( projectRoot , logger ) ; return sdkLocation ? `"${ path . join ( sdkLocation , "platform-tools" , "adb" ) }"` : "adb" ; } private parseConnectedTargets ( input ) { let result = [ ] ; let regex = new RegExp ( "^(\\S+)\\t(\\S+)$" , "mg" ) ; let match = regex . exec ( input ) ; while ( match != null ) { result . push ( { id : match [ 1 ] , isOnline : match [ 2 ] === "device" , isVirtualTarget : this . determineIfItIsVirtualTarget ( match [ 1 ] ) , } ) ; match = regex . exec ( input ) ; } return result ; } private determineIfItIsVirtualTarget ( id ) { return ! ! id . match ( AdbHelper . AndroidSDKEmulatorPattern ) ; } private executeQuery ( targetId , command ) { return this . childProcess . execToString ( this . generateCommandForTarget ( targetId , command ) ) ; } private execute ( targetId , command ) { return this . childProcess . execToString ( this . generateCommandForTarget ( targetId , command ) ) ; } private generateCommandForTarget ( targetId , adbCommand ) { return `${ this . adbExecutable } -s "${ targetId }" ${ adbCommand }` ; } private getSdkLocationFromLocalPropertiesFile ( projectRoot , logger ? ) { const localPropertiesFilePath = path . join ( projectRoot , "android" , "local.properties" ) ; if ( ! fs . existsSync ( localPropertiesFilePath ) ) { if ( logger ) { logger . log ( localize ( "LocalPropertiesFileDoesNotExist" , "local.properties file doesn't exist. Using Android SDK location from PATH." ) ) ; } return null ; } let fileContent ; try { fileContent = fs . readFileSync ( localPropertiesFilePath ) . toString ( ) ; } catch ( e ) { if ( logger ) { logger . log ( `${ localize ( "CouldNotReadFrom" , "Couldn't read from {0}." , localPropertiesFilePath ) }\n${ e }\n${ e . stack }` ) ; logger . log ( localize ( "UsingAndroidSDKLocationFromPATH" , "Using Android SDK location from PATH." ) ) ; } return null ; } return this . parseSdkLocation ( fileContent , logger ) ; } }