1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 | // @flow import inquirer from 'inquirer' import shell from './shell' import spin from './spin.js' import ernConfig from './config' import { execp, spawnp } from './childProcess' // ============================================================================== // Misc utilities // ============================================================================== // // Returns a promise that will get resolved after a given delay (in ms) async function delay (ms: number) { return new Promise((resolve, reject) => { setTimeout(() => { resolve() }, ms) }) } // ============================================================================== // Core android stuff // ============================================================================== // // Build and run a project on an Android emulator or connected device // The `devDebug` variant will be built and launched on an emulator selected by // the user (this function prompts the user with a list of available avd to choose from) // // Assumptions : // - devDebug variant exists in the project // Params : // - projectPath : Absolute or relative path to the root of the Android projectPath // - packageName : Name of the package containing the application // Options : // - activityName : The name of the Activity to start (default "MainActivity") export async function runAndroidProject ({ projectPath, packageName, activityName = 'MainActivity' } : { projectPath: string, packageName: string, activityName?: string }) { return runAndroid({ projectPath, packageName, activityName }) } // // Run an android APK on an Android emulator or connected device // // Params : // - apkPath : Absolute or relative path to the APK // - packageName : Name of the package containing the application // Options : // - activityName : The name of the Activity to start (default "MainActivity") export async function runAndroidApk ({ apkPath, packageName, activityName = 'MainActivity' } : { apkPath: string, packageName: string, activityName?: string }) { return runAndroid({ apkPath, packageName, activityName }) } export async function runAndroid ({ packageName, activityName, apkPath, projectPath } : { packageName: string, activityName: string, apkPath?: string, projectPath?: string }) { const devices = await getDevices() // install and launch the app if 1 emulator instance is running if (devices.length === 1) { log.debug(devices[0].split('\t')[0], ' is running ...') await installAndLaunchApp({ projectPath, packageName, apkPath, activityName }) } else if (devices.length > 1) { throw new Error('More than one device/emulator is running !') } else { const avdImageName = await askUserToSelectAvdEmulator() return runAndroidUsingAvdImage({ projectPath, packageName, avdImageName, apkPath, activityName }) } } async function askUserToSelectAvdEmulator (): Promise<string> { const avdImageNames = await getAndroidAvds() let emulatorConfig = ernConfig.getValue('emulatorConfig') // Check if user has set the usePreviousEmulator flag to true if (avdImageNames && emulatorConfig.android.usePreviousEmulator) { // Get the name of previously used emulator const emulatorName = emulatorConfig.android.emulatorName // Check if avd image still exists const avdIndex = avdImageNames.indexOf(emulatorName) if (avdIndex > -1) { return `${avdImageNames[avdIndex]}` } } // if avd image is still not resolved const {avdImageName} = await inquirer.prompt([{ type: 'list', name: 'avdImageName', message: 'Choose Android emulator image', choices: avdImageNames }]) // Update the emulatorConfig if (emulatorConfig) { emulatorConfig.android.emulatorName = avdImageName ernConfig.setValue('emulatorConfig', emulatorConfig) } return `${avdImageName}` } // Does the job of actually running the app // It orchestrates a few tasks to actually get the job done // Params : // - projectPath : Absolute or relative path to the root of the Android projectPath // - packageName : name of the package containing the application // - avdImageName : name of the avd image to use (emulator image) export async function runAndroidUsingAvdImage ({ projectPath, packageName, avdImageName, activityName } : { projectPath?: string, packageName: string, avdImageName: string, activityName: string }) { // https://issuetracker.google.com/issues/37137213 spawnp( androidEmulatorPath(), ['-avd', avdImageName], { detached: true }) await spin('Waiting for device to start', waitForAndroidDevice()) await installAndLaunchApp({ projectPath, packageName, activityName }) } // Does the job of installing and running the app // It orchestrates a few tasks to actually get the job done // Params : // - projectPath : Absolute or relative path to the root of the Android projectPath // - packageName : name of the package containing the application async function installAndLaunchApp ({ projectPath, apkPath, packageName, activityName } : { projectPath?: string, apkPath?: string, packageName: string, activityName: string }) { if (projectPath) { await spin('Building and installing application', buildAndInstallApp(projectPath)) } else if (apkPath) { await spin('Installing APK', installApk(apkPath)) } await spin('Launching Android Application', Promise.resolve()) launchAndroidActivityDetached(packageName, activityName) } // Utility method that basically completes whenever the android device is ready // It check device readiness every 2 sec (poll way) export async function waitForAndroidDevice () { let androidBootAnimProp = await androidGetBootAnimProp() while (!androidBootAnimProp.toString().startsWith('stopped')) { await delay(2000) androidBootAnimProp = await androidGetBootAnimProp() } } // Utility method to know when the prop init.svc.bootanim is there // which indicates somehow that device is ready to install APK and such export async function androidGetBootAnimProp () { return execp(`${androidAdbPath()} wait-for-device shell getprop init.svc.bootanim`) } // Build & install application on the device // params : // - projectPath : Absolute or relative path to the root of the Android project // containing the application export async function buildAndInstallApp (projectPath: string) { shell.cd(projectPath) const gradlew = /^win/.test(process.platform) ? 'gradlew' : './gradlew' return execp(`${gradlew} installDebug`) } // Utility method to launch a specific activity from a given package // Params : // - packageName : name of the package containing the application // - activityName : name of the Activity to launch export async function launchAndroidActivity ( packageName: string, activityName: string) { return execp(`${androidAdbPath()} shell am start -n ${packageName}/.${activityName}`) } // Utility method to launch a specific activity from a given packager // Will spawn the command (detached mode) export function launchAndroidActivityDetached ( packageName: string, activityName: string) { spawnp( androidAdbPath(), [ 'shell', 'am', 'start', '-n', `${packageName}/.${activityName}` ], { detached: true }) } // Utility method to list all available android avd images (emulator images) export async function getAndroidAvds () { const stdout = await execp(`${androidEmulatorPath()} -list-avds`) return stdout.toString().trim().split('\n') } // Utility method to query what device instances are connected to the adb server export async function getDevices () { const stdout = await execp(`${androidAdbPath()} devices`) /* stdout for running command $adb devices List of devices attached * daemon not running. starting it now at tcp:5037 * * daemon started successfully * */ let stdOutArr = stdout.toString().trim().split('\n') // remove stdout 'List of devices attached' (position 0) // and remove stdout related to daemon return stdOutArr.filter((entry, i) => i > 0 && !entry.includes('* daemon')) } export async function installApk (pathToApk: string) { return execp(`${androidAdbPath()} install -r ${pathToApk}`) } export function androidAdbPath () : string { return process.env.ANDROID_HOME ? `${process.env.ANDROID_HOME}/platform-tools/adb` : 'adb' } export function androidEmulatorPath () : string { return process.env.ANDROID_HOME ? `${process.env.ANDROID_HOME}/tools/emulator` : 'emulator' } |