diff --git a/bin/android_sdk_version b/bin/android_sdk_version index 547f41bd..6140e11d 100755 --- a/bin/android_sdk_version +++ b/bin/android_sdk_version @@ -19,7 +19,7 @@ under the License. */ -var android_sdk_version = require('./lib/android_sdk_version'); +var android_sdk_version = require('./lib/android_sdk'); android_sdk_version.run().done(null, function(err) { console.log(err); diff --git a/bin/lib/android_sdk_version.js b/bin/lib/android_sdk.js similarity index 90% rename from bin/lib/android_sdk_version.js rename to bin/lib/android_sdk.js index 79af2727..dbec25e2 100755 --- a/bin/lib/android_sdk_version.js +++ b/bin/lib/android_sdk.js @@ -62,3 +62,17 @@ module.exports.run = function() { return Q.all([get_sdks()]); }; +module.exports.version_string_to_api_level = { + '4.0': 14, + '4.0.3': 15, + '4.1': 16, + '4.2': 17, + '4.3': 18, + '4.4': 19, + '4.4W': 20, + '5.0': 21, + '5.1': 22, + '6.0': 23, + '7.0': 24, + '7.1.1': 25 +}; diff --git a/bin/lib/create.js b/bin/lib/create.js index ef5827ac..0bbb5c94 100755 --- a/bin/lib/create.js +++ b/bin/lib/create.js @@ -147,8 +147,8 @@ function copyScripts(projectPath) { shell.cp('-r', path.join(ROOT, 'node_modules'), destScriptsDir); shell.cp(path.join(ROOT, 'bin', 'check_reqs*'), destScriptsDir); shell.cp(path.join(ROOT, 'bin', 'lib', 'check_reqs.js'), path.join(projectPath, 'cordova', 'lib', 'check_reqs.js')); - shell.cp(path.join(ROOT, 'bin', 'android_sdk_version'), path.join(destScriptsDir, 'android_sdk_version')); - shell.cp(path.join(ROOT, 'bin', 'lib', 'android_sdk_version.js'), path.join(projectPath, 'cordova', 'lib', 'android_sdk_version.js')); + shell.cp(path.join(ROOT, 'bin', 'android_sdk_version*'), destScriptsDir); + shell.cp(path.join(ROOT, 'bin', 'lib', 'android_sdk.js'), path.join(projectPath, 'cordova', 'lib', 'android_sdk.js')); } /** diff --git a/bin/templates/cordova/lib/emulator.js b/bin/templates/cordova/lib/emulator.js index ff1e261c..7a58479a 100644 --- a/bin/templates/cordova/lib/emulator.js +++ b/bin/templates/cordova/lib/emulator.js @@ -29,9 +29,11 @@ var AndroidManifest = require('./AndroidManifest'); var events = require('cordova-common').events; var spawn = require('cordova-common').superspawn.spawn; var CordovaError = require('cordova-common').CordovaError; +var shelljs = require('shelljs'); var Q = require('q'); var os = require('os'); +var fs = require('fs'); var child_process = require('child_process'); // constants @@ -42,18 +44,16 @@ var NUM_INSTALL_RETRIES = 3; var CHECK_BOOTED_INTERVAL = 3 * ONE_SECOND; // in milliseconds var EXEC_KILL_SIGNAL = 'SIGKILL'; -/** - * Returns a Promise for a list of emulator images in the form of objects - * { - name : , - path : , - target : , - abi : , - skin : - } - */ -module.exports.list_images = function() { - return spawn('android', ['list', 'avds']) +function forgivingWhichSync(cmd) { + try { + return fs.realpathSync(shelljs.which(cmd)); + } catch (e) { + return ''; + } +} + +function list_images_using_avdmanager() { + return spawn('avdmanager', ['list', 'avd']) .then(function(output) { var response = output.split('\n'); var emulator_list = []; @@ -70,14 +70,32 @@ module.exports.list_images = function() { i++; img_obj['path'] = response[i].split('Path: ')[1].replace('\r', ''); } - if (response[i + 1].match(/\(API\slevel\s/) || (response[i + 2] && response[i + 2].match(/\(API\slevel\s/))) { + if (response[i + 1].match(/Target:\s/)) { i++; - var secondLine = response[i + 1].match(/\(API\slevel\s/) ? response[i + 1] : ''; - img_obj['target'] = (response[i] + secondLine).split('Target: ')[1].replace('\r', ''); - } - if (response[i + 1].match(/ABI:\s/)) { - i++; - img_obj['abi'] = response[i].split('ABI: ')[1].replace('\r', ''); + if (response[i + 1].match(/ABI:\s/)) { + img_obj['abi'] = response[i + 1].split('ABI: ')[1].replace('\r', ''); + } + // This next conditional just aims to match the old output of `android list avd` + // We do so so that we don't have to change the logic when parsing for the + // best emulator target to spawn (see below in `best_image`) + // This allows us to transitionally support both `android` and `avdmanager` binaries, + // depending on what SDK version the user has + if (response[i + 1].match(/Based\son:\s/)) { + img_obj['target'] = response[i + 1].split('Based on:')[1]; + if (img_obj['target'].match(/Tag\/ABI:\s/)) { + img_obj['target'] = img_obj['target'].split('Tag/ABI:')[0].replace('\r', '').trim(); + if (img_obj['target'].indexOf('(') > -1) { + img_obj['target'] = img_obj['target'].substr(0, img_obj['target'].indexOf('(') - 1).trim(); + } + } + var version_string = img_obj['target'].replace(/Android\s+/, ''); + + var android_sdk = require('./android_sdk'); + var api_level = android_sdk.version_string_to_api_level[version_string]; + if (api_level) { + img_obj['target'] += ' (API level ' + api_level + ')'; + } + } } if (response[i + 1].match(/Skin:\s/)) { i++; @@ -94,6 +112,73 @@ module.exports.list_images = function() { } return emulator_list; }); +} + +/** + * Returns a Promise for a list of emulator images in the form of objects + * { + name : , + path : , + target : , + abi : , + skin : + } + */ +module.exports.list_images = function() { + if (forgivingWhichSync('android')) { + return spawn('android', ['list', 'avds']) + .then(function(output) { + var response = output.split('\n'); + var emulator_list = []; + for (var i = 1; i < response.length; i++) { + // To return more detailed information use img_obj + var img_obj = {}; + if (response[i].match(/Name:\s/)) { + img_obj['name'] = response[i].split('Name: ')[1].replace('\r', ''); + if (response[i + 1].match(/Device:\s/)) { + i++; + img_obj['device'] = response[i].split('Device: ')[1].replace('\r', ''); + } + if (response[i + 1].match(/Path:\s/)) { + i++; + img_obj['path'] = response[i].split('Path: ')[1].replace('\r', ''); + } + if (response[i + 1].match(/\(API\slevel\s/) || (response[i + 2] && response[i + 2].match(/\(API\slevel\s/))) { + i++; + var secondLine = response[i + 1].match(/\(API\slevel\s/) ? response[i + 1] : ''; + img_obj['target'] = (response[i] + secondLine).split('Target: ')[1].replace('\r', ''); + } + if (response[i + 1].match(/ABI:\s/)) { + i++; + img_obj['abi'] = response[i].split('ABI: ')[1].replace('\r', ''); + } + if (response[i + 1].match(/Skin:\s/)) { + i++; + img_obj['skin'] = response[i].split('Skin: ')[1].replace('\r', ''); + } + + emulator_list.push(img_obj); + } + /* To just return a list of names use this + if (response[i].match(/Name:\s/)) { + emulator_list.push(response[i].split('Name: ')[1].replace('\r', ''); + }*/ + + } + return emulator_list; + }).catch(function(stderr) { + // try to use `avdmanager` in case `android` has problems + // this likely means the target machine is using a newer version of + // the android sdk, and possibly `avdmanager` is available. + return list_images_using_avdmanager(); + }); + } else if (forgivingWhichSync('avdmanager')) { + return list_images_using_avdmanager(); + } else { + return Q().then(function() { + throw new CordovaError('Could not find either `android` or `avdmanager` on your $PATH! Are you sure the Android SDK is installed and available?'); + }); + } }; /** @@ -199,10 +284,13 @@ module.exports.start = function(emulator_ID, boot_timeout) { }).then(function(emulatorId) { return self.get_available_port() .then(function (port) { + // Figure out the directory the emulator binary runs in, and set the cwd to that directory. + // Workaround for https://code.google.com/p/android/issues/detail?id=235461 + var emulator_dir = path.dirname(shelljs.which('emulator')); var args = ['-avd', emulatorId, '-port', port]; // Don't wait for it to finish, since the emulator will probably keep running for a long time. child_process - .spawn('emulator', args, { stdio: 'inherit', detached: true }) + .spawn('emulator', args, { stdio: 'inherit', detached: true, cwd: emulator_dir }) .unref(); // wait for emulator to start