summaryrefslogtreecommitdiff
path: root/StoneIsland/platforms/android/cordova/lib
diff options
context:
space:
mode:
Diffstat (limited to 'StoneIsland/platforms/android/cordova/lib')
-rw-r--r--StoneIsland/platforms/android/cordova/lib/Adb.js105
-rw-r--r--StoneIsland/platforms/android/cordova/lib/AndroidManifest.js161
-rw-r--r--StoneIsland/platforms/android/cordova/lib/AndroidProject.js210
-rw-r--r--StoneIsland/platforms/android/cordova/lib/AndroidStudio.js42
-rwxr-xr-xStoneIsland/platforms/android/cordova/lib/appinfo.js41
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/lib/build.js590
-rw-r--r--StoneIsland/platforms/android/cordova/lib/builders/AntBuilder.js156
-rw-r--r--StoneIsland/platforms/android/cordova/lib/builders/GenericBuilder.js147
-rw-r--r--StoneIsland/platforms/android/cordova/lib/builders/GradleBuilder.js266
-rw-r--r--StoneIsland/platforms/android/cordova/lib/builders/builders.js47
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/lib/check_reqs.js87
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/lib/device.js81
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/lib/emulator.js339
-rwxr-xr-xStoneIsland/platforms/android/cordova/lib/exec.js68
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/lib/install-device.bat0
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/lib/install-emulator.bat0
-rwxr-xr-xStoneIsland/platforms/android/cordova/lib/list-devices15
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/lib/list-devices.bat0
-rwxr-xr-xStoneIsland/platforms/android/cordova/lib/list-emulator-images14
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/lib/list-emulator-images.bat0
-rwxr-xr-xStoneIsland/platforms/android/cordova/lib/list-started-emulators14
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/lib/list-started-emulators.bat0
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/lib/log.js0
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/lib/plugin-build.gradle17
-rw-r--r--StoneIsland/platforms/android/cordova/lib/pluginHandlers.js308
-rw-r--r--StoneIsland/platforms/android/cordova/lib/prepare.js431
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/lib/retry.js6
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/lib/run.js99
-rwxr-xr-xStoneIsland/platforms/android/cordova/lib/spawn.js50
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/lib/start-emulator.bat0
30 files changed, 2329 insertions, 965 deletions
diff --git a/StoneIsland/platforms/android/cordova/lib/Adb.js b/StoneIsland/platforms/android/cordova/lib/Adb.js
new file mode 100644
index 00000000..84ae707e
--- /dev/null
+++ b/StoneIsland/platforms/android/cordova/lib/Adb.js
@@ -0,0 +1,105 @@
+/**
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+var Q = require('q');
+var os = require('os');
+var events = require('cordova-common').events;
+var spawn = require('cordova-common').superspawn.spawn;
+var CordovaError = require('cordova-common').CordovaError;
+
+var Adb = {};
+
+function isDevice(line) {
+ return line.match(/\w+\tdevice/) && !line.match(/emulator/);
+}
+
+function isEmulator(line) {
+ return line.match(/device/) && line.match(/emulator/);
+}
+
+/**
+ * Lists available/connected devices and emulators
+ *
+ * @param {Object} opts Various options
+ * @param {Boolean} opts.emulators Specifies whether this method returns
+ * emulators only
+ *
+ * @return {Promise<String[]>} list of available/connected
+ * devices/emulators
+ */
+Adb.devices = function (opts) {
+ return spawn('adb', ['devices'], {cwd: os.tmpdir()})
+ .then(function(output) {
+ return output.split('\n').filter(function (line) {
+ // Filter out either real devices or emulators, depending on options
+ return (line && opts && opts.emulators) ? isEmulator(line) : isDevice(line);
+ }).map(function (line) {
+ return line.replace(/\tdevice/, '').replace('\r', '');
+ });
+ });
+};
+
+Adb.install = function (target, packagePath, opts) {
+ events.emit('verbose', 'Installing apk ' + packagePath + ' on target ' + target + '...');
+ var args = ['-s', target, 'install'];
+ if (opts && opts.replace) args.push('-r');
+ return spawn('adb', args.concat(packagePath), {cwd: os.tmpdir()})
+ .then(function(output) {
+ // 'adb install' seems to always returns no error, even if installation fails
+ // so we catching output to detect installation failure
+ if (output.match(/Failure/)) {
+ if (output.match(/INSTALL_PARSE_FAILED_NO_CERTIFICATES/)) {
+ output += '\n\n' + 'Sign the build using \'-- --keystore\' or \'--buildConfig\'' +
+ ' or sign and deploy the unsigned apk manually using Android tools.';
+ } else if (output.match(/INSTALL_FAILED_VERSION_DOWNGRADE/)) {
+ output += '\n\n' + 'You\'re trying to install apk with a lower versionCode that is already installed.' +
+ '\nEither uninstall an app or increment the versionCode.';
+ }
+
+ return Q.reject(new CordovaError('Failed to install apk to device: ' + output));
+ }
+ });
+};
+
+Adb.uninstall = function (target, packageId) {
+ events.emit('verbose', 'Uninstalling package ' + packageId + ' from target ' + target + '...');
+ return spawn('adb', ['-s', target, 'uninstall', packageId], {cwd: os.tmpdir()});
+};
+
+Adb.shell = function (target, shellCommand) {
+ events.emit('verbose', 'Running adb shell command "' + shellCommand + '" on target ' + target + '...');
+ var args = ['-s', target, 'shell'];
+ shellCommand = shellCommand.split(/\s+/);
+ return spawn('adb', args.concat(shellCommand), {cwd: os.tmpdir()})
+ .catch(function (output) {
+ return Q.reject(new CordovaError('Failed to execute shell command "' +
+ shellCommand + '"" on device: ' + output));
+ });
+};
+
+Adb.start = function (target, activityName) {
+ events.emit('verbose', 'Starting application "' + activityName + '" on target ' + target + '...');
+ return Adb.shell(target, 'am start -W -a android.intent.action.MAIN -n' + activityName)
+ .catch(function (output) {
+ return Q.reject(new CordovaError('Failed to start application "' +
+ activityName + '"" on device: ' + output));
+ });
+};
+
+module.exports = Adb;
diff --git a/StoneIsland/platforms/android/cordova/lib/AndroidManifest.js b/StoneIsland/platforms/android/cordova/lib/AndroidManifest.js
new file mode 100644
index 00000000..8248f593
--- /dev/null
+++ b/StoneIsland/platforms/android/cordova/lib/AndroidManifest.js
@@ -0,0 +1,161 @@
+/**
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+var fs = require('fs');
+var et = require('elementtree');
+var xml= require('cordova-common').xmlHelpers;
+
+var DEFAULT_ORIENTATION = 'default';
+
+/** Wraps an AndroidManifest file */
+function AndroidManifest(path) {
+ this.path = path;
+ this.doc = xml.parseElementtreeSync(path);
+ if (this.doc.getroot().tag !== 'manifest') {
+ throw new Error('AndroidManifest at ' + path + ' has incorrect root node name (expected "manifest")');
+ }
+}
+
+AndroidManifest.prototype.getVersionName = function() {
+ return this.doc.getroot().attrib['android:versionName'];
+};
+
+AndroidManifest.prototype.setVersionName = function(versionName) {
+ this.doc.getroot().attrib['android:versionName'] = versionName;
+ return this;
+};
+
+AndroidManifest.prototype.getVersionCode = function() {
+ return this.doc.getroot().attrib['android:versionCode'];
+};
+
+AndroidManifest.prototype.setVersionCode = function(versionCode) {
+ this.doc.getroot().attrib['android:versionCode'] = versionCode;
+ return this;
+};
+
+AndroidManifest.prototype.getPackageId = function() {
+ /*jshint -W069 */
+ return this.doc.getroot().attrib['package'];
+ /*jshint +W069 */
+};
+
+AndroidManifest.prototype.setPackageId = function(pkgId) {
+ /*jshint -W069 */
+ this.doc.getroot().attrib['package'] = pkgId;
+ /*jshint +W069 */
+ return this;
+};
+
+AndroidManifest.prototype.getActivity = function() {
+ var activity = this.doc.getroot().find('./application/activity');
+ return {
+ getName: function () {
+ return activity.attrib['android:name'];
+ },
+ setName: function (name) {
+ if (!name) {
+ delete activity.attrib['android:name'];
+ } else {
+ activity.attrib['android:name'] = name;
+ }
+ return this;
+ },
+ getOrientation: function () {
+ return activity.attrib['android:screenOrientation'];
+ },
+ setOrientation: function (orientation) {
+ if (!orientation || orientation.toLowerCase() === DEFAULT_ORIENTATION) {
+ delete activity.attrib['android:screenOrientation'];
+ } else {
+ activity.attrib['android:screenOrientation'] = orientation;
+ }
+ return this;
+ },
+ getLaunchMode: function () {
+ return activity.attrib['android:launchMode'];
+ },
+ setLaunchMode: function (launchMode) {
+ if (!launchMode) {
+ delete activity.attrib['android:launchMode'];
+ } else {
+ activity.attrib['android:launchMode'] = launchMode;
+ }
+ return this;
+ }
+ };
+};
+
+['minSdkVersion', 'maxSdkVersion', 'targetSdkVersion']
+.forEach(function(sdkPrefName) {
+ // Copy variable reference to avoid closure issues
+ var prefName = sdkPrefName;
+
+ AndroidManifest.prototype['get' + capitalize(prefName)] = function() {
+ var usesSdk = this.doc.getroot().find('./uses-sdk');
+ return usesSdk && usesSdk.attrib['android:' + prefName];
+ };
+
+ AndroidManifest.prototype['set' + capitalize(prefName)] = function(prefValue) {
+ var usesSdk = this.doc.getroot().find('./uses-sdk');
+
+ if (!usesSdk && prefValue) { // if there is no required uses-sdk element, we should create it first
+ usesSdk = new et.Element('uses-sdk');
+ this.doc.getroot().append(usesSdk);
+ }
+
+ if (prefValue) {
+ usesSdk.attrib['android:' + prefName] = prefValue;
+ }
+
+ return this;
+ };
+});
+
+AndroidManifest.prototype.getDebuggable = function() {
+ return this.doc.getroot().find('./application').attrib['android:debuggable'] === 'true';
+};
+
+AndroidManifest.prototype.setDebuggable = function(value) {
+ var application = this.doc.getroot().find('./application');
+ if (value) {
+ application.attrib['android:debuggable'] = 'true';
+ } else {
+ // The default value is "false", so we can remove attribute at all.
+ delete application.attrib['android:debuggable'];
+ }
+ return this;
+};
+
+/**
+ * Writes manifest to disk syncronously. If filename is specified, then manifest
+ * will be written to that file
+ *
+ * @param {String} [destPath] File to write manifest to. If omitted,
+ * manifest will be written to file it has been read from.
+ */
+AndroidManifest.prototype.write = function(destPath) {
+ fs.writeFileSync(destPath || this.path, this.doc.write({indent: 4}), 'utf-8');
+};
+
+module.exports = AndroidManifest;
+
+function capitalize (str) {
+ return str.charAt(0).toUpperCase() + str.slice(1);
+}
diff --git a/StoneIsland/platforms/android/cordova/lib/AndroidProject.js b/StoneIsland/platforms/android/cordova/lib/AndroidProject.js
new file mode 100644
index 00000000..fa1c6129
--- /dev/null
+++ b/StoneIsland/platforms/android/cordova/lib/AndroidProject.js
@@ -0,0 +1,210 @@
+/**
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+var fs = require('fs');
+var path = require('path');
+var properties_parser = require('properties-parser');
+var AndroidManifest = require('./AndroidManifest');
+var AndroidStudio = require('./AndroidStudio');
+var pluginHandlers = require('./pluginHandlers');
+
+var projectFileCache = {};
+
+function addToPropertyList(projectProperties, key, value) {
+ var i = 1;
+ while (projectProperties.get(key + '.' + i))
+ i++;
+
+ projectProperties.set(key + '.' + i, value);
+ projectProperties.dirty = true;
+}
+
+function removeFromPropertyList(projectProperties, key, value) {
+ var i = 1;
+ var currentValue;
+ while ((currentValue = projectProperties.get(key + '.' + i))) {
+ if (currentValue === value) {
+ while ((currentValue = projectProperties.get(key + '.' + (i + 1)))) {
+ projectProperties.set(key + '.' + i, currentValue);
+ i++;
+ }
+ projectProperties.set(key + '.' + i);
+ break;
+ }
+ i++;
+ }
+ projectProperties.dirty = true;
+}
+
+function getRelativeLibraryPath (parentDir, subDir) {
+ var libraryPath = path.relative(parentDir, subDir);
+ return (path.sep == '\\') ? libraryPath.replace(/\\/g, '/') : libraryPath;
+}
+
+function AndroidProject(projectDir) {
+ this._propertiesEditors = {};
+ this._subProjectDirs = {};
+ this._dirty = false;
+ this.projectDir = projectDir;
+ this.platformWww = path.join(this.projectDir, 'platform_www');
+ this.www = path.join(this.projectDir, 'assets/www');
+ if(AndroidStudio.isAndroidStudioProject(projectDir) === true) {
+ this.www = path.join(this.projectDir, 'app/src/main/assets/www');
+ }
+}
+
+AndroidProject.getProjectFile = function (projectDir) {
+ if (!projectFileCache[projectDir]) {
+ projectFileCache[projectDir] = new AndroidProject(projectDir);
+ }
+
+ return projectFileCache[projectDir];
+};
+
+AndroidProject.purgeCache = function (projectDir) {
+ if (projectDir) {
+ delete projectFileCache[projectDir];
+ } else {
+ projectFileCache = {};
+ }
+};
+
+/**
+ * Reads the package name out of the Android Manifest file
+ *
+ * @param {String} projectDir The absolute path to the directory containing the project
+ *
+ * @return {String} The name of the package
+ */
+AndroidProject.prototype.getPackageName = function() {
+ var manifestPath = path.join(this.projectDir, 'AndroidManifest.xml');
+ if(AndroidStudio.isAndroidStudioProject(this.projectDir) === true) {
+ manifestPath = path.join(this.projectDir, 'app/src/main/AndroidManifest.xml');
+ }
+ return new AndroidManifest(manifestPath).getPackageId();
+};
+
+AndroidProject.prototype.getCustomSubprojectRelativeDir = function(plugin_id, src) {
+ // All custom subprojects are prefixed with the last portion of the package id.
+ // This is to avoid collisions when opening multiple projects in Eclipse that have subprojects with the same name.
+ var packageName = this.getPackageName();
+ var lastDotIndex = packageName.lastIndexOf('.');
+ var prefix = packageName.substring(lastDotIndex + 1);
+ var subRelativeDir = path.join(plugin_id, prefix + '-' + path.basename(src));
+ return subRelativeDir;
+};
+
+AndroidProject.prototype.addSubProject = function(parentDir, subDir) {
+ var parentProjectFile = path.resolve(parentDir, 'project.properties');
+ var subProjectFile = path.resolve(subDir, 'project.properties');
+ var parentProperties = this._getPropertiesFile(parentProjectFile);
+ // TODO: Setting the target needs to happen only for pre-3.7.0 projects
+ if (fs.existsSync(subProjectFile)) {
+ var subProperties = this._getPropertiesFile(subProjectFile);
+ subProperties.set('target', parentProperties.get('target'));
+ subProperties.dirty = true;
+ this._subProjectDirs[subDir] = true;
+ }
+ addToPropertyList(parentProperties, 'android.library.reference', getRelativeLibraryPath(parentDir, subDir));
+
+ this._dirty = true;
+};
+
+AndroidProject.prototype.removeSubProject = function(parentDir, subDir) {
+ var parentProjectFile = path.resolve(parentDir, 'project.properties');
+ var parentProperties = this._getPropertiesFile(parentProjectFile);
+ removeFromPropertyList(parentProperties, 'android.library.reference', getRelativeLibraryPath(parentDir, subDir));
+ delete this._subProjectDirs[subDir];
+ this._dirty = true;
+};
+
+AndroidProject.prototype.addGradleReference = function(parentDir, subDir) {
+ var parentProjectFile = path.resolve(parentDir, 'project.properties');
+ var parentProperties = this._getPropertiesFile(parentProjectFile);
+ addToPropertyList(parentProperties, 'cordova.gradle.include', getRelativeLibraryPath(parentDir, subDir));
+ this._dirty = true;
+};
+
+AndroidProject.prototype.removeGradleReference = function(parentDir, subDir) {
+ var parentProjectFile = path.resolve(parentDir, 'project.properties');
+ var parentProperties = this._getPropertiesFile(parentProjectFile);
+ removeFromPropertyList(parentProperties, 'cordova.gradle.include', getRelativeLibraryPath(parentDir, subDir));
+ this._dirty = true;
+};
+
+AndroidProject.prototype.addSystemLibrary = function(parentDir, value) {
+ var parentProjectFile = path.resolve(parentDir, 'project.properties');
+ var parentProperties = this._getPropertiesFile(parentProjectFile);
+ addToPropertyList(parentProperties, 'cordova.system.library', value);
+ this._dirty = true;
+};
+
+AndroidProject.prototype.removeSystemLibrary = function(parentDir, value) {
+ var parentProjectFile = path.resolve(parentDir, 'project.properties');
+ var parentProperties = this._getPropertiesFile(parentProjectFile);
+ removeFromPropertyList(parentProperties, 'cordova.system.library', value);
+ this._dirty = true;
+};
+
+AndroidProject.prototype.write = function() {
+ if (!this._dirty) {
+ return;
+ }
+ this._dirty = false;
+
+ for (var filename in this._propertiesEditors) {
+ var editor = this._propertiesEditors[filename];
+ if (editor.dirty) {
+ fs.writeFileSync(filename, editor.toString());
+ editor.dirty = false;
+ }
+ }
+};
+
+AndroidProject.prototype._getPropertiesFile = function (filename) {
+ if (!this._propertiesEditors[filename]) {
+ if (fs.existsSync(filename)) {
+ this._propertiesEditors[filename] = properties_parser.createEditor(filename);
+ } else {
+ this._propertiesEditors[filename] = properties_parser.createEditor();
+ }
+ }
+
+ return this._propertiesEditors[filename];
+};
+
+AndroidProject.prototype.getInstaller = function (type) {
+ return pluginHandlers.getInstaller(type);
+};
+
+AndroidProject.prototype.getUninstaller = function (type) {
+ return pluginHandlers.getUninstaller(type);
+};
+
+/*
+ * This checks if an Android project is clean or has old build artifacts
+ */
+
+AndroidProject.prototype.isClean = function() {
+ var build_path = path.join(this.projectDir, 'build');
+ //If the build directory doesn't exist, it's clean
+ return !(fs.existsSync(build_path));
+};
+
+module.exports = AndroidProject;
diff --git a/StoneIsland/platforms/android/cordova/lib/AndroidStudio.js b/StoneIsland/platforms/android/cordova/lib/AndroidStudio.js
new file mode 100644
index 00000000..335b334b
--- /dev/null
+++ b/StoneIsland/platforms/android/cordova/lib/AndroidStudio.js
@@ -0,0 +1,42 @@
+/*
+ * This is a simple routine that checks if project is an Android Studio Project
+ *
+ * @param {String} root Root folder of the project
+ */
+
+/*jshint esnext: false */
+
+var path = require('path');
+var fs = require('fs');
+var CordovaError = require('cordova-common').CordovaError;
+
+module.exports.isAndroidStudioProject = function isAndroidStudioProject(root) {
+ var eclipseFiles = ['AndroidManifest.xml', 'libs', 'res', 'project.properties', 'platform_www'];
+ var androidStudioFiles = ['app', 'gradle', 'app/src/main/res'];
+
+ // assume it is an AS project and not an Eclipse project
+ var isEclipse = false;
+ var isAS = true;
+
+ if(!fs.existsSync(root)) {
+ throw new CordovaError('AndroidStudio.js:inAndroidStudioProject root does not exist: ' + root);
+ }
+
+ // if any of the following exists, then we are not an ASProj
+ eclipseFiles.forEach(function(file) {
+ if(fs.existsSync(path.join(root, file))) {
+ isEclipse = true;
+ }
+ });
+
+ // if it is NOT an eclipse project, check that all required files exist
+ if(!isEclipse) {
+ androidStudioFiles.forEach(function(file){
+ if(!fs.existsSync(path.join(root, file))) {
+ console.log('missing file :: ' + file);
+ isAS = false;
+ }
+ });
+ }
+ return (!isEclipse && isAS);
+};
diff --git a/StoneIsland/platforms/android/cordova/lib/appinfo.js b/StoneIsland/platforms/android/cordova/lib/appinfo.js
deleted file mode 100755
index 080c2ba8..00000000
--- a/StoneIsland/platforms/android/cordova/lib/appinfo.js
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/usr/bin/env node
-
-/*
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements. See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership. The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied. See the License for the
- specific language governing permissions and limitations
- under the License.
-*/
-
-var path = require('path');
-var fs = require('fs');
-var cachedAppInfo = null;
-
-function readAppInfoFromManifest() {
- var manifestPath = path.join(__dirname, '..', '..', 'AndroidManifest.xml');
- var manifestData = fs.readFileSync(manifestPath, {encoding:'utf8'});
- var packageName = /\bpackage\s*=\s*"(.+?)"/.exec(manifestData);
- if (!packageName) throw new Error('Could not find package name within ' + manifestPath);
- var activityTag = /<activity\b[\s\S]*<\/activity>/.exec(manifestData);
- if (!activityTag) throw new Error('Could not find <activity> within ' + manifestPath);
- var activityName = /\bandroid:name\s*=\s*"(.+?)"/.exec(activityTag);
- if (!activityName) throw new Error('Could not find android:name within ' + manifestPath);
-
- return packageName[1] + '/.' + activityName[1];
-}
-
-exports.getActivityName = function() {
- return (cachedAppInfo = cachedAppInfo || readAppInfoFromManifest());
-};
diff --git a/StoneIsland/platforms/android/cordova/lib/build.js b/StoneIsland/platforms/android/cordova/lib/build.js
index aa9f3d01..bd613da2 100755..100644
--- a/StoneIsland/platforms/android/cordova/lib/build.js
+++ b/StoneIsland/platforms/android/cordova/lib/build.js
@@ -19,477 +19,68 @@
under the License.
*/
-/* jshint sub:true */
-
-var shell = require('shelljs'),
- spawn = require('./spawn'),
- Q = require('q'),
+var Q = require('q'),
path = require('path'),
fs = require('fs'),
- os = require('os'),
- ROOT = path.join(__dirname, '..', '..');
-var check_reqs = require('./check_reqs');
-var exec = require('./exec');
-
+ nopt = require('nopt');
-var SIGNING_PROPERTIES = '-signing.properties';
-var MARKER = 'YOUR CHANGES WILL BE ERASED!';
-var TEMPLATE =
- '# This file is automatically generated.\n' +
- '# Do not modify this file -- ' + MARKER + '\n';
-
-function findApks(directory) {
- var ret = [];
- if (fs.existsSync(directory)) {
- fs.readdirSync(directory).forEach(function(p) {
- if (path.extname(p) == '.apk') {
- ret.push(path.join(directory, p));
- }
- });
- }
- return ret;
-}
+var Adb = require('./Adb');
-function sortFilesByDate(files) {
- return files.map(function(p) {
- return { p: p, t: fs.statSync(p).mtime };
- }).sort(function(a, b) {
- var timeDiff = b.t - a.t;
- return timeDiff === 0 ? a.p.length - b.p.length : timeDiff;
- }).map(function(p) { return p.p; });
-}
+var builders = require('./builders/builders');
+var events = require('cordova-common').events;
+var spawn = require('cordova-common').superspawn.spawn;
+var CordovaError = require('cordova-common').CordovaError;
-function isAutoGenerated(file) {
- if(fs.existsSync(file)) {
- var fileContents = fs.readFileSync(file, 'utf8');
- return fileContents.indexOf(MARKER) > 0;
- }
- return false;
-}
+function parseOpts(options, resolvedTarget, projectRoot) {
+ options = options || {};
+ options.argv = nopt({
+ gradle: Boolean,
+ ant: Boolean,
+ prepenv: Boolean,
+ versionCode: String,
+ minSdkVersion: String,
+ gradleArg: [String, Array],
+ keystore: path,
+ alias: String,
+ storePassword: String,
+ password: String,
+ keystoreType: String
+ }, {}, options.argv, 0);
-function findOutputApksHelper(dir, build_type, arch) {
- var ret = findApks(dir).filter(function(candidate) {
- // Need to choose between release and debug .apk.
- if (build_type === 'debug') {
- return /-debug/.exec(candidate) && !/-unaligned|-unsigned/.exec(candidate);
- }
- if (build_type === 'release') {
- return /-release/.exec(candidate) && !/-unaligned/.exec(candidate);
- }
- return true;
- });
- ret = sortFilesByDate(ret);
- if (ret.length === 0) {
- return ret;
- }
- // Assume arch-specific build if newest apk has -x86 or -arm.
- var archSpecific = !!/-x86|-arm/.exec(ret[0]);
- // And show only arch-specific ones (or non-arch-specific)
- ret = ret.filter(function(p) {
- /*jshint -W018 */
- return !!/-x86|-arm/.exec(p) == archSpecific;
- /*jshint +W018 */
- });
- if (archSpecific && ret.length > 1) {
- ret = ret.filter(function(p) {
- return p.indexOf('-' + arch) != -1;
- });
- }
-
- return ret;
-}
-
-function hasCustomRules() {
- return fs.existsSync(path.join(ROOT, 'custom_rules.xml'));
-}
-
-function extractRealProjectNameFromManifest(projectPath) {
- var manifestPath = path.join(projectPath, 'AndroidManifest.xml');
- var manifestData = fs.readFileSync(manifestPath, 'utf8');
- var m = /<manifest[\s\S]*?package\s*=\s*"(.*?)"/i.exec(manifestData);
- if (!m) {
- throw new Error('Could not find package name in ' + manifestPath);
- }
-
- var packageName=m[1];
- var lastDotIndex = packageName.lastIndexOf('.');
- return packageName.substring(lastDotIndex + 1);
-}
-
-function extractProjectNameFromManifest(projectPath) {
- var manifestPath = path.join(projectPath, 'AndroidManifest.xml');
- var manifestData = fs.readFileSync(manifestPath, 'utf8');
- var m = /<activity[\s\S]*?android:name\s*=\s*"(.*?)"/i.exec(manifestData);
- if (!m) {
- throw new Error('Could not find activity name in ' + manifestPath);
- }
- return m[1];
-}
-
-function findAllUniq(data, r) {
- var s = {};
- var m;
- while ((m = r.exec(data))) {
- s[m[1]] = 1;
- }
- return Object.keys(s);
-}
-
-function readProjectProperties() {
- var data = fs.readFileSync(path.join(ROOT, 'project.properties'), 'utf8');
- return {
- libs: findAllUniq(data, /^\s*android\.library\.reference\.\d+=(.*)(?:\s|$)/mg),
- gradleIncludes: findAllUniq(data, /^\s*cordova\.gradle\.include\.\d+=(.*)(?:\s|$)/mg),
- systemLibs: findAllUniq(data, /^\s*cordova\.system\.library\.\d+=(.*)(?:\s|$)/mg)
+ var ret = {
+ buildType: options.release ? 'release' : 'debug',
+ buildMethod: process.env.ANDROID_BUILD || 'gradle',
+ prepEnv: options.argv.prepenv,
+ arch: resolvedTarget && resolvedTarget.arch,
+ extraArgs: []
};
-}
-
-var builders = {
- ant: {
- getArgs: function(cmd, opts) {
- var args = [cmd, '-f', path.join(ROOT, 'build.xml')];
- // custom_rules.xml is required for incremental builds.
- if (hasCustomRules()) {
- args.push('-Dout.dir=ant-build', '-Dgen.absolute.dir=ant-gen');
- }
- if(opts.packageInfo) {
- args.push('-propertyfile=' + path.join(ROOT, opts.buildType + SIGNING_PROPERTIES));
- }
- return args;
- },
- prepEnv: function(opts) {
- return check_reqs.check_ant()
- .then(function() {
- // Copy in build.xml on each build so that:
- // A) we don't require the Android SDK at project creation time, and
- // B) we always use the SDK's latest version of it.
- var sdkDir = process.env['ANDROID_HOME'];
- var buildTemplate = fs.readFileSync(path.join(sdkDir, 'tools', 'lib', 'build.template'), 'utf8');
- function writeBuildXml(projectPath) {
- var newData = buildTemplate.replace('PROJECT_NAME', extractProjectNameFromManifest(ROOT));
- fs.writeFileSync(path.join(projectPath, 'build.xml'), newData);
- if (!fs.existsSync(path.join(projectPath, 'local.properties'))) {
- fs.writeFileSync(path.join(projectPath, 'local.properties'), TEMPLATE);
- }
- }
- writeBuildXml(ROOT);
- var propertiesObj = readProjectProperties();
- var subProjects = propertiesObj.libs;
- for (var i = 0; i < subProjects.length; ++i) {
- writeBuildXml(path.join(ROOT, subProjects[i]));
- }
- if (propertiesObj.systemLibs.length > 0) {
- throw new Error('Project contains at least one plugin that requires a system library. This is not supported with ANT. Please build using gradle.');
- }
+ if (options.argv.ant || options.argv.gradle)
+ ret.buildMethod = options.argv.ant ? 'ant' : 'gradle';
- var propertiesFile = opts.buildType + SIGNING_PROPERTIES;
- var propertiesFilePath = path.join(ROOT, propertiesFile);
- if (opts.packageInfo) {
- fs.writeFileSync(propertiesFilePath, TEMPLATE + opts.packageInfo.toProperties());
- } else if(isAutoGenerated(propertiesFilePath)) {
- shell.rm('-f', propertiesFilePath);
- }
- });
- },
+ if (options.nobuild) ret.buildMethod = 'none';
- /*
- * Builds the project with ant.
- * Returns a promise.
- */
- build: function(opts) {
- // Without our custom_rules.xml, we need to clean before building.
- var ret = Q();
- if (!hasCustomRules()) {
- // clean will call check_ant() for us.
- ret = this.clean(opts);
- }
+ if (options.argv.versionCode)
+ ret.extraArgs.push('-PcdvVersionCode=' + options.argv.versionCode);
- var args = this.getArgs(opts.buildType == 'debug' ? 'debug' : 'release', opts);
- return check_reqs.check_ant()
- .then(function() {
- console.log('Executing: ant ' + args.join(' '));
- return spawn('ant', args);
- });
- },
-
- clean: function(opts) {
- var args = this.getArgs('clean', opts);
- return check_reqs.check_ant()
- .then(function() {
- return spawn('ant', args);
- });
- },
+ if (options.argv.minSdkVersion)
+ ret.extraArgs.push('-PcdvMinSdkVersion=' + options.argv.minSdkVersion);
- findOutputApks: function(build_type) {
- var binDir = path.join(ROOT, hasCustomRules() ? 'ant-build' : 'bin');
- return findOutputApksHelper(binDir, build_type, null);
- }
- },
- gradle: {
- getArgs: function(cmd, opts) {
- if (cmd == 'release') {
- cmd = 'cdvBuildRelease';
- } else if (cmd == 'debug') {
- cmd = 'cdvBuildDebug';
- }
- var args = [cmd, '-b', path.join(ROOT, 'build.gradle')];
- if (opts.arch) {
- args.push('-PcdvBuildArch=' + opts.arch);
- }
-
- // 10 seconds -> 6 seconds
- args.push('-Dorg.gradle.daemon=true');
- args.push.apply(args, opts.extraArgs);
- // Shaves another 100ms, but produces a "try at own risk" warning. Not worth it (yet):
- // args.push('-Dorg.gradle.parallel=true');
- return args;
- },
-
- // Makes the project buildable, minus the gradle wrapper.
- prepBuildFiles: function() {
- var projectPath = ROOT;
- // Update the version of build.gradle in each dependent library.
- var pluginBuildGradle = path.join(projectPath, 'cordova', 'lib', 'plugin-build.gradle');
- var propertiesObj = readProjectProperties();
- var subProjects = propertiesObj.libs;
- for (var i = 0; i < subProjects.length; ++i) {
- if (subProjects[i] !== 'CordovaLib') {
- shell.cp('-f', pluginBuildGradle, path.join(ROOT, subProjects[i], 'build.gradle'));
- }
- }
-
- var name = extractRealProjectNameFromManifest(ROOT);
- //Remove the proj.id/name- prefix from projects: https://issues.apache.org/jira/browse/CB-9149
- var settingsGradlePaths = subProjects.map(function(p){
- var realDir=p.replace(/[/\\]/g, ':');
- var libName=realDir.replace(name+'-','');
- var str='include ":'+libName+'"\n';
- if(realDir.indexOf(name+'-')!==-1)
- str+='project(":'+libName+'").projectDir = new File("'+p+'")\n';
- return str;
- });
-
- // Write the settings.gradle file.
- fs.writeFileSync(path.join(projectPath, 'settings.gradle'),
- '// GENERATED FILE - DO NOT EDIT\n' +
- 'include ":"\n' + settingsGradlePaths.join(''));
- // Update dependencies within build.gradle.
- var buildGradle = fs.readFileSync(path.join(projectPath, 'build.gradle'), 'utf8');
- var depsList = '';
- subProjects.forEach(function(p) {
- var libName=p.replace(/[/\\]/g, ':').replace(name+'-','');
- depsList += ' debugCompile project(path: "' + libName + '", configuration: "debug")\n';
- depsList += ' releaseCompile project(path: "' + libName + '", configuration: "release")\n';
- });
- // For why we do this mapping: https://issues.apache.org/jira/browse/CB-8390
- var SYSTEM_LIBRARY_MAPPINGS = [
- [/^\/?extras\/android\/support\/(.*)$/, 'com.android.support:support-$1:+'],
- [/^\/?google\/google_play_services\/libproject\/google-play-services_lib\/?$/, 'com.google.android.gms:play-services:+']
- ];
- propertiesObj.systemLibs.forEach(function(p) {
- var mavenRef;
- // It's already in gradle form if it has two ':'s
- if (/:.*:/.exec(p)) {
- mavenRef = p;
- } else {
- for (var i = 0; i < SYSTEM_LIBRARY_MAPPINGS.length; ++i) {
- var pair = SYSTEM_LIBRARY_MAPPINGS[i];
- if (pair[0].exec(p)) {
- mavenRef = p.replace(pair[0], pair[1]);
- break;
- }
- }
- if (!mavenRef) {
- throw new Error('Unsupported system library (does not work with gradle): ' + p);
- }
- }
- depsList += ' compile "' + mavenRef + '"\n';
- });
- buildGradle = buildGradle.replace(/(SUB-PROJECT DEPENDENCIES START)[\s\S]*(\/\/ SUB-PROJECT DEPENDENCIES END)/, '$1\n' + depsList + ' $2');
- var includeList = '';
- propertiesObj.gradleIncludes.forEach(function(includePath) {
- includeList += 'apply from: "' + includePath + '"\n';
- });
- buildGradle = buildGradle.replace(/(PLUGIN GRADLE EXTENSIONS START)[\s\S]*(\/\/ PLUGIN GRADLE EXTENSIONS END)/, '$1\n' + includeList + '$2');
- fs.writeFileSync(path.join(projectPath, 'build.gradle'), buildGradle);
- },
-
- prepEnv: function(opts) {
- var self = this;
- return check_reqs.check_gradle()
- .then(function() {
- return self.prepBuildFiles();
- }).then(function() {
- // Copy the gradle wrapper on each build so that:
- // A) we don't require the Android SDK at project creation time, and
- // B) we always use the SDK's latest version of it.
- var projectPath = ROOT;
- // check_reqs ensures that this is set.
- var sdkDir = process.env['ANDROID_HOME'];
- var wrapperDir = path.join(sdkDir, 'tools', 'templates', 'gradle', 'wrapper');
- if (process.platform == 'win32') {
- shell.rm('-f', path.join(projectPath, 'gradlew.bat'));
- shell.cp(path.join(wrapperDir, 'gradlew.bat'), projectPath);
- } else {
- shell.rm('-f', path.join(projectPath, 'gradlew'));
- shell.cp(path.join(wrapperDir, 'gradlew'), projectPath);
- }
- shell.rm('-rf', path.join(projectPath, 'gradle', 'wrapper'));
- shell.mkdir('-p', path.join(projectPath, 'gradle'));
- shell.cp('-r', path.join(wrapperDir, 'gradle', 'wrapper'), path.join(projectPath, 'gradle'));
-
- // If the gradle distribution URL is set, make sure it points to version we want.
- // If it's not set, do nothing, assuming that we're using a future version of gradle that we don't want to mess with.
- // For some reason, using ^ and $ don't work. This does the job, though.
- var distributionUrlRegex = /distributionUrl.*zip/;
- var distributionUrl = 'distributionUrl=http\\://services.gradle.org/distributions/gradle-2.2.1-all.zip';
- var gradleWrapperPropertiesPath = path.join(projectPath, 'gradle', 'wrapper', 'gradle-wrapper.properties');
- shell.chmod('u+w', gradleWrapperPropertiesPath);
- shell.sed('-i', distributionUrlRegex, distributionUrl, gradleWrapperPropertiesPath);
-
- var propertiesFile = opts.buildType + SIGNING_PROPERTIES;
- var propertiesFilePath = path.join(ROOT, propertiesFile);
- if (opts.packageInfo) {
- fs.writeFileSync(propertiesFilePath, TEMPLATE + opts.packageInfo.toProperties());
- } else if (isAutoGenerated(propertiesFilePath)) {
- shell.rm('-f', propertiesFilePath);
- }
- });
- },
-
- /*
- * Builds the project with gradle.
- * Returns a promise.
- */
- build: function(opts) {
- var wrapper = path.join(ROOT, 'gradlew');
- var args = this.getArgs(opts.buildType == 'debug' ? 'debug' : 'release', opts);
- return Q().then(function() {
- console.log('Running: ' + wrapper + ' ' + args.join(' '));
- return spawn(wrapper, args);
- });
- },
-
- clean: function(opts) {
- var builder = this;
- var wrapper = path.join(ROOT, 'gradlew');
- var args = builder.getArgs('clean', opts);
- return Q().then(function() {
- console.log('Running: ' + wrapper + ' ' + args.join(' '));
- return spawn(wrapper, args);
- });
- },
-
- findOutputApks: function(build_type, arch) {
- var binDir = path.join(ROOT, 'build', 'outputs', 'apk');
- return findOutputApksHelper(binDir, build_type, arch);
- }
- },
-
- none: {
- prepEnv: function() {
- return Q();
- },
- build: function() {
- console.log('Skipping build...');
- return Q(null);
- },
- clean: function() {
- return Q();
- },
- findOutputApks: function(build_type, arch) {
- return sortFilesByDate(builders.ant.findOutputApks(build_type, arch).concat(builders.gradle.findOutputApks(build_type, arch)));
- }
+ if (options.argv.gradleArg) {
+ ret.extraArgs = ret.extraArgs.concat(options.argv.gradleArg);
}
-};
-module.exports.isBuildFlag = function(flag) {
- return /^--(debug|release|ant|gradle|nobuild|versionCode=|minSdkVersion=|gradleArg=|keystore=|alias=|password=|storePassword=|keystoreType=|buildConfig=)/.exec(flag);
-};
+ var packageArgs = {};
-function parseOpts(options, resolvedTarget) {
- // Backwards-compatibility: Allow a single string argument
- if (typeof options == 'string') options = [options];
+ if (options.argv.keystore)
+ packageArgs.keystore = path.relative(projectRoot, path.resolve(options.argv.keystore));
- var ret = {
- buildType: 'debug',
- buildMethod: process.env['ANDROID_BUILD'] || 'gradle',
- arch: null,
- extraArgs: []
- };
+ ['alias','storePassword','password','keystoreType'].forEach(function (flagName) {
+ if (options.argv[flagName])
+ packageArgs[flagName] = options.argv[flagName];
+ });
- var multiValueArgs = {
- 'versionCode': true,
- 'minSdkVersion': true,
- 'gradleArg': true,
- 'keystore' : true,
- 'alias' : true,
- 'password' : true,
- 'storePassword' : true,
- 'keystoreType' : true,
- 'buildConfig' : true
- };
- var packageArgs = {};
- var buildConfig;
- // Iterate through command line options
- for (var i=0; options && (i < options.length); ++i) {
- if (/^--/.exec(options[i])) {
- var keyValue = options[i].substring(2).split('=');
- var flagName = keyValue.shift();
- var flagValue = keyValue.join('=');
- if (multiValueArgs[flagName] && !flagValue) {
- flagValue = options[i + 1];
- ++i;
- }
- switch(flagName) {
- case 'debug':
- case 'release':
- ret.buildType = flagName;
- break;
- case 'ant':
- case 'gradle':
- ret.buildMethod = flagName;
- break;
- case 'device':
- case 'emulator':
- // Don't need to do anything special to when building for device vs emulator.
- // iOS uses this flag to switch on architecture.
- break;
- case 'prepenv' :
- ret.prepEnv = true;
- break;
- case 'nobuild' :
- ret.buildMethod = 'none';
- break;
- case 'versionCode':
- ret.extraArgs.push('-PcdvVersionCode=' + flagValue);
- break;
- case 'minSdkVersion':
- ret.extraArgs.push('-PcdvMinSdkVersion=' + flagValue);
- break;
- case 'gradleArg':
- ret.extraArgs.push(flagValue);
- break;
- case 'keystore':
- packageArgs.keystore = path.relative(ROOT, path.resolve(flagValue));
- break;
- case 'alias':
- case 'storePassword':
- case 'password':
- case 'keystoreType':
- packageArgs[flagName] = flagValue;
- break;
- case 'buildConfig':
- buildConfig = flagValue;
- break;
- default :
- console.warn('Build option --\'' + flagName + '\' not recognized (ignoring).');
- }
- } else {
- console.warn('Build option \'' + options[i] + '\' not recognized (ignoring).');
- }
- }
+ var buildConfig = options.buildConfig;
// If some values are not specified as command line arguments - use build config to supplement them.
// Command line arguemnts have precedence over build config.
@@ -497,16 +88,17 @@ function parseOpts(options, resolvedTarget) {
if (!fs.existsSync(buildConfig)) {
throw new Error('Specified build config file does not exist: ' + buildConfig);
}
- console.log('Reading build config file: '+ path.resolve(buildConfig));
- var config = JSON.parse(fs.readFileSync(buildConfig, 'utf8'));
+ events.emit('log', 'Reading build config file: '+ path.resolve(buildConfig));
+ var buildjson = fs.readFileSync(buildConfig, 'utf8');
+ var config = JSON.parse(buildjson.replace(/^\ufeff/, '')); // Remove BOM
if (config.android && config.android[ret.buildType]) {
var androidInfo = config.android[ret.buildType];
if(androidInfo.keystore && !packageArgs.keystore) {
- if(path.isAbsolute(androidInfo.keystore)) {
- packageArgs.keystore = androidInfo.keystore;
- } else {
- packageArgs.keystore = path.relative(ROOT, path.join(path.dirname(buildConfig), androidInfo.keystore));
+ if(androidInfo.keystore.substr(0,1) === '~') {
+ androidInfo.keystore = process.env.HOME + androidInfo.keystore.substr(1);
}
+ packageArgs.keystore = path.resolve(path.dirname(buildConfig), androidInfo.keystore);
+ events.emit('log', 'Reading the keystore from: ' + packageArgs.keystore);
}
['alias', 'storePassword', 'password','keystoreType'].forEach(function (key){
@@ -514,6 +106,7 @@ function parseOpts(options, resolvedTarget) {
});
}
}
+
if (packageArgs.keystore && packageArgs.alias) {
ret.packageInfo = new PackageInfo(packageArgs.keystore, packageArgs.alias, packageArgs.storePassword,
packageArgs.password, packageArgs.keystoreType);
@@ -521,10 +114,9 @@ function parseOpts(options, resolvedTarget) {
if(!ret.packageInfo) {
if(Object.keys(packageArgs).length > 0) {
- console.warn('\'keystore\' and \'alias\' need to be specified to generate a signed archive.');
+ events.emit('warn', '\'keystore\' and \'alias\' need to be specified to generate a signed archive.');
}
}
- ret.arch = resolvedTarget && resolvedTarget.arch;
return ret;
}
@@ -534,41 +126,39 @@ function parseOpts(options, resolvedTarget) {
* Returns a promise.
*/
module.exports.runClean = function(options) {
- var opts = parseOpts(options);
- var builder = builders[opts.buildMethod];
+ var opts = parseOpts(options, null, this.root);
+ var builder = builders.getBuilder(opts.buildMethod);
return builder.prepEnv(opts)
.then(function() {
return builder.clean(opts);
- }).then(function() {
- shell.rm('-rf', path.join(ROOT, 'out'));
-
- ['debug', 'release'].forEach(function(config) {
- var propertiesFilePath = path.join(ROOT, config + SIGNING_PROPERTIES);
- if(isAutoGenerated(propertiesFilePath)){
- shell.rm('-f', propertiesFilePath);
- }
- });
});
};
-/*
- * Builds the project with the specifed options
- * Returns a promise.
+/**
+ * Builds the project with the specifed options.
+ *
+ * @param {BuildOptions} options A set of options. See PlatformApi.build
+ * method documentation for reference.
+ * @param {Object} optResolvedTarget A deployment target. Used to pass
+ * target architecture from upstream 'run' call. TODO: remove this option in
+ * favor of setting buildOptions.archs field.
+ *
+ * @return {Promise<Object>} Promise, resolved with built packages
+ * information.
*/
module.exports.run = function(options, optResolvedTarget) {
- var opts = parseOpts(options, optResolvedTarget);
- var builder = builders[opts.buildMethod];
+ var opts = parseOpts(options, optResolvedTarget, this.root);
+ var builder = builders.getBuilder(opts.buildMethod);
return builder.prepEnv(opts)
.then(function() {
if (opts.prepEnv) {
- console.log('Build file successfully prepared.');
+ events.emit('verbose', 'Build file successfully prepared.');
return;
}
return builder.build(opts)
.then(function() {
var apkPaths = builder.findOutputApks(opts.buildType, opts.arch);
- console.log('Built the following apk(s):');
- console.log(' ' + apkPaths.join('\n '));
+ events.emit('log', 'Built the following apk(s): \n\t' + apkPaths.join('\n\t'));
return {
apkPaths: apkPaths,
buildType: opts.buildType,
@@ -578,46 +168,38 @@ module.exports.run = function(options, optResolvedTarget) {
});
};
-// Called by plugman after installing plugins, and by create script after creating project.
-module.exports.prepBuildFiles = function() {
- var builder = builders['gradle'];
- return builder.prepBuildFiles();
-};
-
/*
* Detects the architecture of a device/emulator
* Returns "arm" or "x86".
*/
module.exports.detectArchitecture = function(target) {
function helper() {
- return exec('adb -s ' + target + ' shell cat /proc/cpuinfo', os.tmpdir())
+ return Adb.shell(target, 'cat /proc/cpuinfo')
.then(function(output) {
- if (/intel/i.exec(output)) {
- return 'x86';
- }
- return 'arm';
+ return /intel/i.exec(output) ? 'x86' : 'arm';
});
}
// It sometimes happens (at least on OS X), that this command will hang forever.
// To fix it, either unplug & replug device, or restart adb server.
- return helper().timeout(1000, 'Device communication timed out. Try unplugging & replugging the device.')
+ return helper()
+ .timeout(1000, new CordovaError('Device communication timed out. Try unplugging & replugging the device.'))
.then(null, function(err) {
if (/timed out/.exec('' + err)) {
// adb kill-server doesn't seem to do the trick.
// Could probably find a x-platform version of killall, but I'm not actually
// sure that this scenario even happens on non-OSX machines.
- return exec('killall adb')
+ events.emit('verbose', 'adb timed out while detecting device/emulator architecture. Killing adb and trying again.');
+ return spawn('killall', ['adb'])
.then(function() {
- console.log('adb seems hung. retrying.');
return helper()
.then(null, function() {
// The double kill is sadly often necessary, at least on mac.
- console.log('Now device not found... restarting adb again.');
- return exec('killall adb')
+ events.emit('warn', 'adb timed out a second time while detecting device/emulator architecture. Killing adb and trying again.');
+ return spawn('killall', ['adb'])
.then(function() {
return helper()
.then(null, function() {
- return Q.reject('USB is flakey. Try unplugging & replugging the device.');
+ return Q.reject(new CordovaError('adb timed out a third time while detecting device/emulator architecture. Try unplugging & replugging the device.'));
});
});
});
@@ -632,16 +214,18 @@ module.exports.detectArchitecture = function(target) {
module.exports.findBestApkForArchitecture = function(buildResults, arch) {
var paths = buildResults.apkPaths.filter(function(p) {
+ var apkName = path.basename(p);
if (buildResults.buildType == 'debug') {
- return /-debug/.exec(p);
+ return /-debug/.exec(apkName);
}
- return !/-debug/.exec(p);
+ return !/-debug/.exec(apkName);
});
var archPattern = new RegExp('-' + arch);
var hasArchPattern = /-x86|-arm/;
for (var i = 0; i < paths.length; ++i) {
- if (hasArchPattern.exec(paths[i])) {
- if (archPattern.exec(paths[i])) {
+ var apkName = path.basename(paths[i]);
+ if (hasArchPattern.exec(apkName)) {
+ if (archPattern.exec(apkName)) {
return paths[i];
}
} else {
@@ -695,7 +279,7 @@ PackageInfo.prototype = {
};
module.exports.help = function() {
- console.log('Usage: ' + path.relative(process.cwd(), path.join(ROOT, 'cordova', 'build')) + ' [flags] [Signed APK flags]');
+ console.log('Usage: ' + path.relative(process.cwd(), path.join('../build')) + ' [flags] [Signed APK flags]');
console.log('Flags:');
console.log(' \'--debug\': will build project in debug mode (default)');
console.log(' \'--release\': will build project for release');
diff --git a/StoneIsland/platforms/android/cordova/lib/builders/AntBuilder.js b/StoneIsland/platforms/android/cordova/lib/builders/AntBuilder.js
new file mode 100644
index 00000000..4e0f71ab
--- /dev/null
+++ b/StoneIsland/platforms/android/cordova/lib/builders/AntBuilder.js
@@ -0,0 +1,156 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+var Q = require('q');
+var fs = require('fs');
+var path = require('path');
+var util = require('util');
+var shell = require('shelljs');
+var spawn = require('cordova-common').superspawn.spawn;
+var CordovaError = require('cordova-common').CordovaError;
+var check_reqs = require('../check_reqs');
+
+var SIGNING_PROPERTIES = '-signing.properties';
+var MARKER = 'YOUR CHANGES WILL BE ERASED!';
+var TEMPLATE =
+ '# This file is automatically generated.\n' +
+ '# Do not modify this file -- ' + MARKER + '\n';
+
+var GenericBuilder = require('./GenericBuilder');
+
+function AntBuilder (projectRoot) {
+ GenericBuilder.call(this, projectRoot);
+
+ this.binDirs = {ant: this.binDirs.ant};
+}
+
+util.inherits(AntBuilder, GenericBuilder);
+
+AntBuilder.prototype.getArgs = function(cmd, opts) {
+ var args = [cmd, '-f', path.join(this.root, 'build.xml')];
+ // custom_rules.xml is required for incremental builds.
+ if (hasCustomRules(this.root)) {
+ args.push('-Dout.dir=ant-build', '-Dgen.absolute.dir=ant-gen');
+ }
+ if(opts.packageInfo) {
+ args.push('-propertyfile=' + path.join(this.root, opts.buildType + SIGNING_PROPERTIES));
+ }
+ return args;
+};
+
+AntBuilder.prototype.prepEnv = function(opts) {
+ var self = this;
+ return check_reqs.check_ant()
+ .then(function() {
+ // Copy in build.xml on each build so that:
+ // A) we don't require the Android SDK at project creation time, and
+ // B) we always use the SDK's latest version of it.
+ /*jshint -W069 */
+ var sdkDir = process.env['ANDROID_HOME'];
+ /*jshint +W069 */
+ var buildTemplate = fs.readFileSync(path.join(sdkDir, 'tools', 'lib', 'build.template'), 'utf8');
+ function writeBuildXml(projectPath) {
+ var newData = buildTemplate.replace('PROJECT_NAME', self.extractRealProjectNameFromManifest());
+ fs.writeFileSync(path.join(projectPath, 'build.xml'), newData);
+ if (!fs.existsSync(path.join(projectPath, 'local.properties'))) {
+ fs.writeFileSync(path.join(projectPath, 'local.properties'), TEMPLATE);
+ }
+ }
+ writeBuildXml(self.root);
+ var propertiesObj = self.readProjectProperties();
+ var subProjects = propertiesObj.libs;
+ for (var i = 0; i < subProjects.length; ++i) {
+ writeBuildXml(path.join(self.root, subProjects[i]));
+ }
+ if (propertiesObj.systemLibs.length > 0) {
+ throw new CordovaError('Project contains at least one plugin that requires a system library. This is not supported with ANT. Use gradle instead.');
+ }
+
+ var propertiesFile = opts.buildType + SIGNING_PROPERTIES;
+ var propertiesFilePath = path.join(self.root, propertiesFile);
+ if (opts.packageInfo) {
+ fs.writeFileSync(propertiesFilePath, TEMPLATE + opts.packageInfo.toProperties());
+ } else if(isAutoGenerated(propertiesFilePath)) {
+ shell.rm('-f', propertiesFilePath);
+ }
+ });
+};
+
+/*
+ * Builds the project with ant.
+ * Returns a promise.
+ */
+AntBuilder.prototype.build = function(opts) {
+ // Without our custom_rules.xml, we need to clean before building.
+ var ret = Q();
+ if (!hasCustomRules(this.root)) {
+ // clean will call check_ant() for us.
+ ret = this.clean(opts);
+ }
+
+ var args = this.getArgs(opts.buildType == 'debug' ? 'debug' : 'release', opts);
+ return check_reqs.check_ant()
+ .then(function() {
+ return spawn('ant', args, {stdio: 'pipe'});
+ }).progress(function (stdio){
+ if (stdio.stderr) {
+ process.stderr.write(stdio.stderr);
+ } else {
+ process.stdout.write(stdio.stdout);
+ }
+ }).catch(function (error) {
+ if (error.toString().indexOf('Unable to resolve project target') >= 0) {
+ return check_reqs.check_android_target(error).then(function() {
+ // If due to some odd reason - check_android_target succeeds
+ // we should still fail here.
+ return Q.reject(error);
+ });
+ }
+ return Q.reject(error);
+ });
+};
+
+AntBuilder.prototype.clean = function(opts) {
+ var args = this.getArgs('clean', opts);
+ var self = this;
+ return check_reqs.check_ant()
+ .then(function() {
+ return spawn('ant', args, {stdio: 'inherit'});
+ })
+ .then(function () {
+ shell.rm('-rf', path.join(self.root, 'out'));
+
+ ['debug', 'release'].forEach(function(config) {
+ var propertiesFilePath = path.join(self.root, config + SIGNING_PROPERTIES);
+ if(isAutoGenerated(propertiesFilePath)){
+ shell.rm('-f', propertiesFilePath);
+ }
+ });
+ });
+};
+
+module.exports = AntBuilder;
+
+function hasCustomRules(projectRoot) {
+ return fs.existsSync(path.join(projectRoot, 'custom_rules.xml'));
+}
+
+function isAutoGenerated(file) {
+ return fs.existsSync(file) && fs.readFileSync(file, 'utf8').indexOf(MARKER) > 0;
+}
diff --git a/StoneIsland/platforms/android/cordova/lib/builders/GenericBuilder.js b/StoneIsland/platforms/android/cordova/lib/builders/GenericBuilder.js
new file mode 100644
index 00000000..362da431
--- /dev/null
+++ b/StoneIsland/platforms/android/cordova/lib/builders/GenericBuilder.js
@@ -0,0 +1,147 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+var Q = require('q');
+var fs = require('fs');
+var path = require('path');
+var shell = require('shelljs');
+var events = require('cordova-common').events;
+var CordovaError = require('cordova-common').CordovaError;
+
+function GenericBuilder (projectDir) {
+ this.root = projectDir || path.resolve(__dirname, '../../..');
+ this.binDirs = {
+ ant: path.join(this.root, hasCustomRules(this.root) ? 'ant-build' : 'bin'),
+ gradle: path.join(this.root, 'build', 'outputs', 'apk')
+ };
+}
+
+function hasCustomRules(projectRoot) {
+ return fs.existsSync(path.join(projectRoot, 'custom_rules.xml'));
+}
+
+GenericBuilder.prototype.prepEnv = function() {
+ return Q();
+};
+
+GenericBuilder.prototype.build = function() {
+ events.emit('log', 'Skipping build...');
+ return Q(null);
+};
+
+GenericBuilder.prototype.clean = function() {
+ return Q();
+};
+
+GenericBuilder.prototype.findOutputApks = function(build_type, arch) {
+ var self = this;
+ return Object.keys(this.binDirs)
+ .reduce(function (result, builderName) {
+ var binDir = self.binDirs[builderName];
+ return result.concat(findOutputApksHelper(binDir, build_type, builderName === 'ant' ? null : arch));
+ }, [])
+ .sort(apkSorter);
+};
+
+GenericBuilder.prototype.readProjectProperties = function () {
+ function findAllUniq(data, r) {
+ var s = {};
+ var m;
+ while ((m = r.exec(data))) {
+ s[m[1]] = 1;
+ }
+ return Object.keys(s);
+ }
+
+ var data = fs.readFileSync(path.join(this.root, 'project.properties'), 'utf8');
+ return {
+ libs: findAllUniq(data, /^\s*android\.library\.reference\.\d+=(.*)(?:\s|$)/mg),
+ gradleIncludes: findAllUniq(data, /^\s*cordova\.gradle\.include\.\d+=(.*)(?:\s|$)/mg),
+ systemLibs: findAllUniq(data, /^\s*cordova\.system\.library\.\d+=(.*)(?:\s|$)/mg)
+ };
+};
+
+GenericBuilder.prototype.extractRealProjectNameFromManifest = function () {
+ var manifestPath = path.join(this.root, 'AndroidManifest.xml');
+ var manifestData = fs.readFileSync(manifestPath, 'utf8');
+ var m = /<manifest[\s\S]*?package\s*=\s*"(.*?)"/i.exec(manifestData);
+ if (!m) {
+ throw new CordovaError('Could not find package name in ' + manifestPath);
+ }
+
+ var packageName=m[1];
+ var lastDotIndex = packageName.lastIndexOf('.');
+ return packageName.substring(lastDotIndex + 1);
+};
+
+module.exports = GenericBuilder;
+
+function apkSorter(fileA, fileB) {
+ // De-prioritize unsigned builds
+ var unsignedRE = /-unsigned/;
+ if (unsignedRE.exec(fileA)) {
+ return 1;
+ } else if (unsignedRE.exec(fileB)) {
+ return -1;
+ }
+
+ var timeDiff = fs.statSync(fileA).mtime - fs.statSync(fileB).mtime;
+ return timeDiff === 0 ? fileA.length - fileB.length : timeDiff;
+}
+
+function findOutputApksHelper(dir, build_type, arch) {
+ var shellSilent = shell.config.silent;
+ shell.config.silent = true;
+
+ var ret = shell.ls(path.join(dir, '*.apk'))
+ .filter(function(candidate) {
+ var apkName = path.basename(candidate);
+ // Need to choose between release and debug .apk.
+ if (build_type === 'debug') {
+ return /-debug/.exec(apkName) && !/-unaligned|-unsigned/.exec(apkName);
+ }
+ if (build_type === 'release') {
+ return /-release/.exec(apkName) && !/-unaligned/.exec(apkName);
+ }
+ return true;
+ })
+ .sort(apkSorter);
+
+ shellSilent = shellSilent;
+
+ if (ret.length === 0) {
+ return ret;
+ }
+ // Assume arch-specific build if newest apk has -x86 or -arm.
+ var archSpecific = !!/-x86|-arm/.exec(path.basename(ret[0]));
+ // And show only arch-specific ones (or non-arch-specific)
+ ret = ret.filter(function(p) {
+ /*jshint -W018 */
+ return !!/-x86|-arm/.exec(path.basename(p)) == archSpecific;
+ /*jshint +W018 */
+ });
+
+ if (archSpecific && ret.length > 1 && arch) {
+ ret = ret.filter(function(p) {
+ return path.basename(p).indexOf('-' + arch) != -1;
+ });
+ }
+
+ return ret;
+}
diff --git a/StoneIsland/platforms/android/cordova/lib/builders/GradleBuilder.js b/StoneIsland/platforms/android/cordova/lib/builders/GradleBuilder.js
new file mode 100644
index 00000000..f415646e
--- /dev/null
+++ b/StoneIsland/platforms/android/cordova/lib/builders/GradleBuilder.js
@@ -0,0 +1,266 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+var Q = require('q');
+var fs = require('fs');
+var util = require('util');
+var path = require('path');
+var shell = require('shelljs');
+var spawn = require('cordova-common').superspawn.spawn;
+var CordovaError = require('cordova-common').CordovaError;
+var check_reqs = require('../check_reqs');
+
+var GenericBuilder = require('./GenericBuilder');
+
+var MARKER = 'YOUR CHANGES WILL BE ERASED!';
+var SIGNING_PROPERTIES = '-signing.properties';
+var TEMPLATE =
+ '# This file is automatically generated.\n' +
+ '# Do not modify this file -- ' + MARKER + '\n';
+
+function GradleBuilder (projectRoot) {
+ GenericBuilder.call(this, projectRoot);
+
+ this.binDirs = {gradle: this.binDirs.gradle};
+}
+
+util.inherits(GradleBuilder, GenericBuilder);
+
+GradleBuilder.prototype.getArgs = function(cmd, opts) {
+ if (cmd == 'release') {
+ cmd = 'cdvBuildRelease';
+ } else if (cmd == 'debug') {
+ cmd = 'cdvBuildDebug';
+ }
+ var args = [cmd, '-b', path.join(this.root, 'build.gradle')];
+ if (opts.arch) {
+ args.push('-PcdvBuildArch=' + opts.arch);
+ }
+
+ // 10 seconds -> 6 seconds
+ args.push('-Dorg.gradle.daemon=true');
+ // to allow dex in process
+ args.push('-Dorg.gradle.jvmargs=-Xmx2048m');
+ // allow NDK to be used - required by Gradle 1.5 plugin
+ args.push('-Pandroid.useDeprecatedNdk=true');
+ args.push.apply(args, opts.extraArgs);
+ // Shaves another 100ms, but produces a "try at own risk" warning. Not worth it (yet):
+ // args.push('-Dorg.gradle.parallel=true');
+ return args;
+};
+
+// Makes the project buildable, minus the gradle wrapper.
+GradleBuilder.prototype.prepBuildFiles = function() {
+ // Update the version of build.gradle in each dependent library.
+ var pluginBuildGradle = path.join(this.root, 'cordova', 'lib', 'plugin-build.gradle');
+ var propertiesObj = this.readProjectProperties();
+ var subProjects = propertiesObj.libs;
+ var checkAndCopy = function(subProject, root) {
+ var subProjectGradle = path.join(root, subProject, 'build.gradle');
+ // This is the future-proof way of checking if a file exists
+ // This must be synchronous to satisfy a Travis test
+ try {
+ fs.accessSync(subProjectGradle, fs.F_OK);
+ } catch (e) {
+ shell.cp('-f', pluginBuildGradle, subProjectGradle);
+ }
+ };
+ for (var i = 0; i < subProjects.length; ++i) {
+ if (subProjects[i] !== 'CordovaLib') {
+ checkAndCopy(subProjects[i], this.root);
+ }
+ }
+ var name = this.extractRealProjectNameFromManifest();
+ //Remove the proj.id/name- prefix from projects: https://issues.apache.org/jira/browse/CB-9149
+ var settingsGradlePaths = subProjects.map(function(p){
+ var realDir=p.replace(/[/\\]/g, ':');
+ var libName=realDir.replace(name+'-','');
+ var str='include ":'+libName+'"\n';
+ if(realDir.indexOf(name+'-')!==-1)
+ str+='project(":'+libName+'").projectDir = new File("'+p+'")\n';
+ return str;
+ });
+
+ // Write the settings.gradle file.
+ fs.writeFileSync(path.join(this.root, 'settings.gradle'),
+ '// GENERATED FILE - DO NOT EDIT\n' +
+ 'include ":"\n' + settingsGradlePaths.join(''));
+ // Update dependencies within build.gradle.
+ var buildGradle = fs.readFileSync(path.join(this.root, 'build.gradle'), 'utf8');
+ var depsList = '';
+ var root = this.root;
+ var insertExclude = function(p) {
+ var gradlePath = path.join(root, p, 'build.gradle');
+ var projectGradleFile = fs.readFileSync(gradlePath, 'utf-8');
+ if(projectGradleFile.indexOf('CordovaLib') != -1) {
+ depsList += '{\n exclude module:("CordovaLib")\n }\n';
+ }
+ else {
+ depsList +='\n';
+ }
+ };
+ subProjects.forEach(function(p) {
+ console.log('Subproject Path: ' + p);
+ var libName=p.replace(/[/\\]/g, ':').replace(name+'-','');
+ depsList += ' debugCompile(project(path: "' + libName + '", configuration: "debug"))';
+ insertExclude(p);
+ depsList += ' releaseCompile(project(path: "' + libName + '", configuration: "release"))';
+ insertExclude(p);
+ });
+ // For why we do this mapping: https://issues.apache.org/jira/browse/CB-8390
+ var SYSTEM_LIBRARY_MAPPINGS = [
+ [/^\/?extras\/android\/support\/(.*)$/, 'com.android.support:support-$1:+'],
+ [/^\/?google\/google_play_services\/libproject\/google-play-services_lib\/?$/, 'com.google.android.gms:play-services:+']
+ ];
+ propertiesObj.systemLibs.forEach(function(p) {
+ var mavenRef;
+ // It's already in gradle form if it has two ':'s
+ if (/:.*:/.exec(p)) {
+ mavenRef = p;
+ } else {
+ for (var i = 0; i < SYSTEM_LIBRARY_MAPPINGS.length; ++i) {
+ var pair = SYSTEM_LIBRARY_MAPPINGS[i];
+ if (pair[0].exec(p)) {
+ mavenRef = p.replace(pair[0], pair[1]);
+ break;
+ }
+ }
+ if (!mavenRef) {
+ throw new CordovaError('Unsupported system library (does not work with gradle): ' + p);
+ }
+ }
+ depsList += ' compile "' + mavenRef + '"\n';
+ });
+ buildGradle = buildGradle.replace(/(SUB-PROJECT DEPENDENCIES START)[\s\S]*(\/\/ SUB-PROJECT DEPENDENCIES END)/, '$1\n' + depsList + ' $2');
+ var includeList = '';
+ propertiesObj.gradleIncludes.forEach(function(includePath) {
+ includeList += 'apply from: "' + includePath + '"\n';
+ });
+ buildGradle = buildGradle.replace(/(PLUGIN GRADLE EXTENSIONS START)[\s\S]*(\/\/ PLUGIN GRADLE EXTENSIONS END)/, '$1\n' + includeList + '$2');
+ fs.writeFileSync(path.join(this.root, 'build.gradle'), buildGradle);
+};
+
+GradleBuilder.prototype.prepEnv = function(opts) {
+ var self = this;
+ return check_reqs.check_gradle()
+ .then(function() {
+ return self.prepBuildFiles();
+ }).then(function() {
+ // Copy the gradle wrapper on each build so that:
+ // A) we don't require the Android SDK at project creation time, and
+ // B) we always use the SDK's latest version of it.
+ // check_reqs ensures that this is set.
+ /*jshint -W069 */
+ var sdkDir = process.env['ANDROID_HOME'];
+ /*jshint +W069 */
+ var wrapperDir = path.join(sdkDir, 'tools', 'templates', 'gradle', 'wrapper');
+ if (process.platform == 'win32') {
+ shell.rm('-f', path.join(self.root, 'gradlew.bat'));
+ shell.cp(path.join(wrapperDir, 'gradlew.bat'), self.root);
+ } else {
+ shell.rm('-f', path.join(self.root, 'gradlew'));
+ shell.cp(path.join(wrapperDir, 'gradlew'), self.root);
+ }
+ shell.rm('-rf', path.join(self.root, 'gradle', 'wrapper'));
+ shell.mkdir('-p', path.join(self.root, 'gradle'));
+ shell.cp('-r', path.join(wrapperDir, 'gradle', 'wrapper'), path.join(self.root, 'gradle'));
+
+ // If the gradle distribution URL is set, make sure it points to version we want.
+ // If it's not set, do nothing, assuming that we're using a future version of gradle that we don't want to mess with.
+ // For some reason, using ^ and $ don't work. This does the job, though.
+ var distributionUrlRegex = /distributionUrl.*zip/;
+ /*jshint -W069 */
+ var distributionUrl = process.env['CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL'] || 'https\\://services.gradle.org/distributions/gradle-2.14.1-all.zip';
+ /*jshint +W069 */
+ var gradleWrapperPropertiesPath = path.join(self.root, 'gradle', 'wrapper', 'gradle-wrapper.properties');
+ shell.chmod('u+w', gradleWrapperPropertiesPath);
+ shell.sed('-i', distributionUrlRegex, 'distributionUrl='+distributionUrl, gradleWrapperPropertiesPath);
+
+ var propertiesFile = opts.buildType + SIGNING_PROPERTIES;
+ var propertiesFilePath = path.join(self.root, propertiesFile);
+ if (opts.packageInfo) {
+ fs.writeFileSync(propertiesFilePath, TEMPLATE + opts.packageInfo.toProperties());
+ } else if (isAutoGenerated(propertiesFilePath)) {
+ shell.rm('-f', propertiesFilePath);
+ }
+ });
+};
+
+/*
+ * Builds the project with gradle.
+ * Returns a promise.
+ */
+GradleBuilder.prototype.build = function(opts) {
+ var wrapper = path.join(this.root, 'gradlew');
+ var args = this.getArgs(opts.buildType == 'debug' ? 'debug' : 'release', opts);
+
+ return spawn(wrapper, args, {stdio: 'pipe'})
+ .progress(function (stdio){
+ if (stdio.stderr) {
+ /*
+ * Workaround for the issue with Java printing some unwanted information to
+ * stderr instead of stdout.
+ * This function suppresses 'Picked up _JAVA_OPTIONS' message from being
+ * printed to stderr. See https://issues.apache.org/jira/browse/CB-9971 for
+ * explanation.
+ */
+ var suppressThisLine = /^Picked up _JAVA_OPTIONS: /i.test(stdio.stderr.toString());
+ if (suppressThisLine) {
+ return;
+ }
+ process.stderr.write(stdio.stderr);
+ } else {
+ process.stdout.write(stdio.stdout);
+ }
+ }).catch(function (error) {
+ if (error.toString().indexOf('failed to find target with hash string') >= 0) {
+ return check_reqs.check_android_target(error).then(function() {
+ // If due to some odd reason - check_android_target succeeds
+ // we should still fail here.
+ return Q.reject(error);
+ });
+ }
+ return Q.reject(error);
+ });
+};
+
+GradleBuilder.prototype.clean = function(opts) {
+ var builder = this;
+ var wrapper = path.join(this.root, 'gradlew');
+ var args = builder.getArgs('clean', opts);
+ return Q().then(function() {
+ return spawn(wrapper, args, {stdio: 'inherit'});
+ })
+ .then(function () {
+ shell.rm('-rf', path.join(builder.root, 'out'));
+
+ ['debug', 'release'].forEach(function(config) {
+ var propertiesFilePath = path.join(builder.root, config + SIGNING_PROPERTIES);
+ if(isAutoGenerated(propertiesFilePath)){
+ shell.rm('-f', propertiesFilePath);
+ }
+ });
+ });
+};
+
+module.exports = GradleBuilder;
+
+function isAutoGenerated(file) {
+ return fs.existsSync(file) && fs.readFileSync(file, 'utf8').indexOf(MARKER) > 0;
+}
diff --git a/StoneIsland/platforms/android/cordova/lib/builders/builders.js b/StoneIsland/platforms/android/cordova/lib/builders/builders.js
new file mode 100644
index 00000000..4921c49a
--- /dev/null
+++ b/StoneIsland/platforms/android/cordova/lib/builders/builders.js
@@ -0,0 +1,47 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+var CordovaError = require('cordova-common').CordovaError;
+
+var knownBuilders = {
+ ant: 'AntBuilder',
+ gradle: 'GradleBuilder',
+ none: 'GenericBuilder'
+};
+
+/**
+ * Helper method that instantiates and returns a builder for specified build
+ * type.
+ *
+ * @param {String} builderType Builder name to construct and return. Must
+ * be one of 'ant', 'gradle' or 'none'
+ *
+ * @return {Builder} A builder instance for specified build type.
+ */
+module.exports.getBuilder = function (builderType, projectRoot) {
+ if (!knownBuilders[builderType])
+ throw new CordovaError('Builder ' + builderType + ' is not supported.');
+
+ try {
+ var Builder = require('./' + knownBuilders[builderType]);
+ return new Builder(projectRoot);
+ } catch (err) {
+ throw new CordovaError('Failed to instantiate ' + knownBuilders[builderType] + ' builder: ' + err);
+ }
+};
diff --git a/StoneIsland/platforms/android/cordova/lib/check_reqs.js b/StoneIsland/platforms/android/cordova/lib/check_reqs.js
index 9d251596..ac6fa4c1 100755..100644
--- a/StoneIsland/platforms/android/cordova/lib/check_reqs.js
+++ b/StoneIsland/platforms/android/cordova/lib/check_reqs.js
@@ -26,15 +26,14 @@ var shelljs = require('shelljs'),
Q = require('q'),
path = require('path'),
fs = require('fs'),
- which = require('which'),
ROOT = path.join(__dirname, '..', '..');
+var CordovaError = require('cordova-common').CordovaError;
var isWindows = process.platform == 'win32';
function forgivingWhichSync(cmd) {
try {
- // TODO: Should use shelljs.which() here to have one less dependency.
- return fs.realpathSync(which.sync(cmd));
+ return fs.realpathSync(shelljs.which(cmd));
} catch (e) {
return '';
}
@@ -43,7 +42,7 @@ function forgivingWhichSync(cmd) {
function tryCommand(cmd, errMsg, catchStderr) {
var d = Q.defer();
child_process.exec(cmd, function(err, stdout, stderr) {
- if (err) d.reject(new Error(errMsg));
+ if (err) d.reject(new CordovaError(errMsg));
// Sometimes it is necessary to return an stderr instead of stdout in case of success, since
// some commands prints theirs output to stderr instead of stdout. 'javac' is the example
else d.resolve((catchStderr ? stderr : stdout).trim());
@@ -83,12 +82,12 @@ module.exports.check_ant = function() {
module.exports.check_gradle = function() {
var sdkDir = process.env['ANDROID_HOME'];
if (!sdkDir)
- return Q.reject('Could not find gradle wrapper within Android SDK. Could not find Android SDK directory.\n' +
- 'Might need to install Android SDK or set up \'ANDROID_HOME\' env variable.');
+ return Q.reject(new CordovaError('Could not find gradle wrapper within Android SDK. Could not find Android SDK directory.\n' +
+ 'Might need to install Android SDK or set up \'ANDROID_HOME\' env variable.'));
var wrapperDir = path.join(sdkDir, 'tools', 'templates', 'gradle', 'wrapper');
if (!fs.existsSync(wrapperDir)) {
- return Q.reject(new Error('Could not find gradle wrapper within Android SDK. Might need to update your Android SDK.\n' +
+ return Q.reject(new CordovaError('Could not find gradle wrapper within Android SDK. Might need to update your Android SDK.\n' +
'Looked here: ' + wrapperDir));
}
return Q.when();
@@ -120,7 +119,7 @@ module.exports.check_java = function() {
if (fs.existsSync(path.join(maybeJavaHome, 'lib', 'tools.jar'))) {
process.env['JAVA_HOME'] = maybeJavaHome;
} else {
- throw new Error(msg);
+ throw new CordovaError(msg);
}
}
} else if (isWindows) {
@@ -143,22 +142,21 @@ module.exports.check_java = function() {
}
}
}).then(function() {
- var msg =
- 'Failed to run "java -version", make sure that you have a JDK installed.\n' +
- 'You can get it from: http://www.oracle.com/technetwork/java/javase/downloads.\n';
- if (process.env['JAVA_HOME']) {
- msg += 'Your JAVA_HOME is invalid: ' + process.env['JAVA_HOME'] + '\n';
- }
- return tryCommand('java -version', msg)
- .then(function() {
+ var msg =
+ 'Failed to run "javac -version", make sure that you have a JDK installed.\n' +
+ 'You can get it from: http://www.oracle.com/technetwork/java/javase/downloads.\n';
+ if (process.env['JAVA_HOME']) {
+ msg += 'Your JAVA_HOME is invalid: ' + process.env['JAVA_HOME'] + '\n';
+ }
// We use tryCommand with catchStderr = true, because
// javac writes version info to stderr instead of stdout
- return tryCommand('javac -version', msg, true);
- }).then(function (output) {
- var match = /javac ((?:\d+\.)+(?:\d+))/i.exec(output)[1];
- return match && match[1];
+ return tryCommand('javac -version', msg, true)
+ .then(function (output) {
+ //Let's check for at least Java 8, and keep it future proof so we can support Java 10
+ var match = /javac ((?:1\.)(?:[8-9]\.)(?:\d+))|((?:1\.)(?:[1-9]\d+\.)(?:\d+))/i.exec(output);
+ return match && match[1];
+ });
});
- });
};
// Returns a promise.
@@ -212,7 +210,7 @@ module.exports.check_android = function() {
process.env['ANDROID_HOME'] = grandParentDir;
hasAndroidHome = true;
} else {
- throw new Error('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' +
+ throw new CordovaError('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' +
'Detected \'android\' command at ' + parentDir + ' but no \'tools\' directory found near.\n' +
'Try reinstall Android SDK or update your PATH to include path to valid SDK directory.');
}
@@ -221,27 +219,32 @@ module.exports.check_android = function() {
process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'platform-tools');
}
if (!process.env['ANDROID_HOME']) {
- throw new Error('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' +
+ throw new CordovaError('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' +
'Failed to find \'android\' command in your \'PATH\'. Try update your \'PATH\' to include path to valid SDK directory.');
}
if (!fs.existsSync(process.env['ANDROID_HOME'])) {
- throw new Error('\'ANDROID_HOME\' environment variable is set to non-existent path: ' + process.env['ANDROID_HOME'] +
+ throw new CordovaError('\'ANDROID_HOME\' environment variable is set to non-existent path: ' + process.env['ANDROID_HOME'] +
'\nTry update it manually to point to valid SDK directory.');
}
+ return hasAndroidHome;
});
};
-module.exports.getAbsoluteAndroidCmd = function() {
- return forgivingWhichSync('android').replace(/(\s)/g, '\\$1');
+module.exports.getAbsoluteAndroidCmd = function () {
+ var cmd = forgivingWhichSync('android');
+ if (process.platform === 'win32') {
+ return '"' + cmd + '"';
+ }
+ return cmd.replace(/(\s)/g, '\\$1');
};
-module.exports.check_android_target = function(valid_target) {
+module.exports.check_android_target = function(originalError) {
// valid_target can look like:
// android-19
// android-L
// Google Inc.:Google APIs:20
// Google Inc.:Glass Development Kit Preview:20
- if (!valid_target) valid_target = module.exports.get_target();
+ var valid_target = module.exports.get_target();
var msg = 'Android SDK not found. Make sure that it is installed. If it is not at the default location, set the ANDROID_HOME environment variable.';
return tryCommand('android list targets --compact', msg)
.then(function(output) {
@@ -251,24 +254,38 @@ module.exports.check_android_target = function(valid_target) {
}
var androidCmd = module.exports.getAbsoluteAndroidCmd();
- throw new Error('Please install Android target: "' + valid_target + '".\n\n' +
+ var msg = 'Please install Android target: "' + valid_target + '".\n\n' +
'Hint: Open the SDK manager by running: ' + androidCmd + '\n' +
'You will require:\n' +
'1. "SDK Platform" for ' + valid_target + '\n' +
'2. "Android SDK Platform-tools (latest)\n' +
- '3. "Android SDK Build-tools" (latest)');
+ '3. "Android SDK Build-tools" (latest)';
+ if (originalError) {
+ msg = originalError + '\n' + msg;
+ }
+ throw new CordovaError(msg);
});
};
// Returns a promise.
module.exports.run = function() {
- return Q.all([this.check_java(), this.check_android().then(this.check_android_target)])
- .then(function() {
- console.log('ANDROID_HOME=' + process.env['ANDROID_HOME']);
- console.log('JAVA_HOME=' + process.env['JAVA_HOME']);
- });
+ return Q.all([this.check_java(), this.check_android()])
+ .then(function(values) {
+ console.log('ANDROID_HOME=' + process.env['ANDROID_HOME']);
+ console.log('JAVA_HOME=' + process.env['JAVA_HOME']);
+
+ if (!values[0]) {
+ throw new CordovaError('Requirements check failed for JDK 1.8 or greater');
+ }
+
+
+ if (!values[1]) {
+ throw new CordovaError('Requirements check failed for Android SDK');
+ }
+ });
};
+
/**
* Object thar represents one of requirements for current platform.
* @param {String} id The unique identifier for this requirements.
diff --git a/StoneIsland/platforms/android/cordova/lib/device.js b/StoneIsland/platforms/android/cordova/lib/device.js
index c13fdc40..4b171db6 100755..100644
--- a/StoneIsland/platforms/android/cordova/lib/device.js
+++ b/StoneIsland/platforms/android/cordova/lib/device.js
@@ -19,40 +19,30 @@
under the License.
*/
-var exec = require('./exec'),
- Q = require('q'),
- os = require('os'),
- build = require('./build'),
- appinfo = require('./appinfo');
+var Q = require('q'),
+ build = require('./build');
+var path = require('path');
+var Adb = require('./Adb');
+var AndroidManifest = require('./AndroidManifest');
+var spawn = require('cordova-common').superspawn.spawn;
+var CordovaError = require('cordova-common').CordovaError;
+var events = require('cordova-common').events;
/**
* Returns a promise for the list of the device ID's found
* @param lookHarder When true, try restarting adb if no devices are found.
*/
module.exports.list = function(lookHarder) {
- function helper() {
- return exec('adb devices', os.tmpdir())
- .then(function(output) {
- var response = output.split('\n');
- var device_list = [];
- for (var i = 1; i < response.length; i++) {
- if (response[i].match(/\w+\tdevice/) && !response[i].match(/emulator/)) {
- device_list.push(response[i].replace(/\tdevice/, '').replace('\r', ''));
- }
- }
- return device_list;
- });
- }
- return helper()
+ return Adb.devices()
.then(function(list) {
if (list.length === 0 && lookHarder) {
// adb kill-server doesn't seem to do the trick.
// Could probably find a x-platform version of killall, but I'm not actually
// sure that this scenario even happens on non-OSX machines.
- return exec('killall adb')
+ return spawn('killall', ['adb'])
.then(function() {
- console.log('Restarting adb to see if more devices are detected.');
- return helper();
+ events.emit('verbose', 'Restarting adb to see if more devices are detected.');
+ return Adb.devices();
}, function() {
// For non-killall OS's.
return list;
@@ -66,7 +56,7 @@ module.exports.resolveTarget = function(target) {
return this.list(true)
.then(function(device_list) {
if (!device_list || !device_list.length) {
- return Q.reject('ERROR: Failed to deploy to device, no devices found.');
+ return Q.reject(new CordovaError('Failed to deploy to device, no devices found.'));
}
// default device
target = target || device_list[0];
@@ -95,27 +85,36 @@ module.exports.install = function(target, buildResults) {
return module.exports.resolveTarget(target);
}).then(function(resolvedTarget) {
var apk_path = build.findBestApkForArchitecture(buildResults, resolvedTarget.arch);
- var launchName = appinfo.getActivityName();
- console.log('Using apk: ' + apk_path);
- console.log('Installing app on device...');
- var cmd = 'adb -s ' + resolvedTarget.target + ' install -r "' + apk_path + '"';
- return exec(cmd, os.tmpdir())
- .then(function(output) {
- if (output.match(/Failure/)) return Q.reject('ERROR: Failed to install apk to device: ' + output);
+ var manifest = new AndroidManifest(path.join(__dirname, '../../AndroidManifest.xml'));
+ var pkgName = manifest.getPackageId();
+ var launchName = pkgName + '/.' + manifest.getActivity().getName();
+ events.emit('log', 'Using apk: ' + apk_path);
+ events.emit('log', 'Package name: ' + pkgName);
- //unlock screen
- var cmd = 'adb -s ' + resolvedTarget.target + ' shell input keyevent 82';
- return exec(cmd, os.tmpdir());
- }, function(err) { return Q.reject('ERROR: Failed to install apk to device: ' + err); })
+ return Adb.install(resolvedTarget.target, apk_path, {replace: true})
+ .catch(function (error) {
+ // CB-9557 CB-10157 only uninstall and reinstall app if the one that
+ // is already installed on device was signed w/different certificate
+ if (!/INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES/.test(error.toString()))
+ throw error;
+
+ events.emit('warn', 'Uninstalling app from device and reinstalling it again because the ' +
+ 'installed app already signed with different key');
+
+ // This promise is always resolved, even if 'adb uninstall' fails to uninstall app
+ // or the app doesn't installed at all, so no error catching needed.
+ return Adb.uninstall(resolvedTarget.target, pkgName)
+ .then(function() {
+ return Adb.install(resolvedTarget.target, apk_path, {replace: true});
+ });
+ })
.then(function() {
- // launch the application
- console.log('Launching application...');
- var cmd = 'adb -s ' + resolvedTarget.target + ' shell am start -W -a android.intent.action.MAIN -n ' + launchName;
- return exec(cmd, os.tmpdir());
+ //unlock screen
+ return Adb.shell(resolvedTarget.target, 'input keyevent 82');
+ }).then(function() {
+ return Adb.start(resolvedTarget.target, launchName);
}).then(function() {
- console.log('LAUNCH SUCCESS');
- }, function(err) {
- return Q.reject('ERROR: Failed to launch application on device: ' + err);
+ events.emit('log', 'LAUNCH SUCCESS');
});
});
};
diff --git a/StoneIsland/platforms/android/cordova/lib/emulator.js b/StoneIsland/platforms/android/cordova/lib/emulator.js
index e81dd679..ff1e261c 100755..100644
--- a/StoneIsland/platforms/android/cordova/lib/emulator.js
+++ b/StoneIsland/platforms/android/cordova/lib/emulator.js
@@ -21,11 +21,14 @@
/* jshint sub:true */
-var exec = require('./exec');
-var appinfo = require('./appinfo');
var retry = require('./retry');
var build = require('./build');
-var check_reqs = require('./check_reqs');
+var path = require('path');
+var Adb = require('./Adb');
+var AndroidManifest = require('./AndroidManifest');
+var events = require('cordova-common').events;
+var spawn = require('cordova-common').superspawn.spawn;
+var CordovaError = require('cordova-common').CordovaError;
var Q = require('q');
var os = require('os');
@@ -33,8 +36,10 @@ var child_process = require('child_process');
// constants
var ONE_SECOND = 1000; // in milliseconds
-var INSTALL_COMMAND_TIMEOUT = 120 * ONE_SECOND; // in milliseconds
+var ONE_MINUTE = 60 * ONE_SECOND; // in milliseconds
+var INSTALL_COMMAND_TIMEOUT = 5 * ONE_MINUTE; // in milliseconds
var NUM_INSTALL_RETRIES = 3;
+var CHECK_BOOTED_INTERVAL = 3 * ONE_SECOND; // in milliseconds
var EXEC_KILL_SIGNAL = 'SIGKILL';
/**
@@ -48,7 +53,7 @@ var EXEC_KILL_SIGNAL = 'SIGKILL';
}
*/
module.exports.list_images = function() {
- return exec('android list avds')
+ return spawn('android', ['list', 'avds'])
.then(function(output) {
var response = output.split('\n');
var emulator_list = [];
@@ -57,13 +62,18 @@ module.exports.list_images = function() {
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/)) {
+ if (response[i + 1].match(/\(API\slevel\s/) || (response[i + 2] && response[i + 2].match(/\(API\slevel\s/))) {
i++;
- img_obj['target'] = response[i].replace('\r', '');
+ 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++;
@@ -92,11 +102,15 @@ module.exports.list_images = function() {
* Returns a promise.
*/
module.exports.best_image = function() {
- var project_target = check_reqs.get_target().replace('android-', '');
return this.list_images()
.then(function(images) {
+ // Just return undefined if there is no images
+ if (images.length === 0) return;
+
var closest = 9999;
var best = images[0];
+ // Loading check_reqs at run-time to avoid test-time vs run-time directory structure difference issue
+ var project_target = require('./check_reqs').get_target().replace('android-', '');
for (var i in images) {
var target = images[i].target;
if(target) {
@@ -115,22 +129,12 @@ module.exports.best_image = function() {
// Returns a promise.
module.exports.list_started = function() {
- return exec('adb devices', os.tmpdir())
- .then(function(output) {
- var response = output.split('\n');
- var started_emulator_list = [];
- for (var i = 1; i < response.length; i++) {
- if (response[i].match(/device/) && response[i].match(/emulator/)) {
- started_emulator_list.push(response[i].replace(/\tdevice/, '').replace('\r', ''));
- }
- }
- return started_emulator_list;
- });
+ return Adb.devices({emulators: true});
};
// Returns a promise.
module.exports.list_targets = function() {
- return exec('android list targets', os.tmpdir())
+ return spawn('android', ['list', 'targets'], {cwd: os.tmpdir()})
.then(function(output) {
var target_out = output.split('\n');
var targets = [];
@@ -144,108 +148,139 @@ module.exports.list_targets = function() {
};
/*
+ * Gets unused port for android emulator, between 5554 and 5584
+ * Returns a promise.
+ */
+module.exports.get_available_port = function () {
+ var self = this;
+
+ return self.list_started()
+ .then(function (emulators) {
+ for (var p = 5584; p >= 5554; p-=2) {
+ if (emulators.indexOf('emulator-' + p) === -1) {
+ events.emit('verbose', 'Found available port: ' + p);
+ return p;
+ }
+ }
+ throw new CordovaError('Could not find an available avd port');
+ });
+};
+
+/*
* Starts an emulator with the given ID,
* and returns the started ID of that emulator.
- * If no ID is given it will used the first image available,
+ * If no ID is given it will use the first image available,
* if no image is available it will error out (maybe create one?).
+ * If no boot timeout is given or the value is negative it will wait forever for
+ * the emulator to boot
*
* Returns a promise.
*/
-module.exports.start = function(emulator_ID) {
+module.exports.start = function(emulator_ID, boot_timeout) {
var self = this;
- var emulator_id, num_started, started_emulators;
- return self.list_started()
- .then(function(list) {
- started_emulators = list;
- num_started = started_emulators.length;
- if (!emulator_ID) {
- return self.list_images()
- .then(function(emulator_list) {
- if (emulator_list.length > 0) {
- return self.best_image()
- .then(function(best) {
- emulator_ID = best.name;
- console.log('WARNING : no emulator specified, defaulting to ' + emulator_ID);
- return emulator_ID;
- });
- } else {
- var androidCmd = check_reqs.getAbsoluteAndroidCmd();
- return Q.reject('ERROR : No emulator images (avds) found.\n' +
- '1. Download desired System Image by running: ' + androidCmd + ' sdk\n' +
- '2. Create an AVD by running: ' + androidCmd + ' avd\n' +
- 'HINT: For a faster emulator, use an Intel System Image and install the HAXM device driver\n');
- }
- });
- } else {
- return Q(emulator_ID);
- }
- }).then(function() {
- var cmd = 'emulator';
- var args = ['-avd', emulator_ID];
- var proc = child_process.spawn(cmd, args, { stdio: 'inherit', detached: true });
- proc.unref(); // Don't wait for it to finish, since the emulator will probably keep running for a long time.
- }).then(function() {
- // wait for emulator to start
- console.log('Waiting for emulator...');
- return self.wait_for_emulator(num_started);
- }).then(function(new_started) {
- if (new_started.length > 1) {
- for (var i in new_started) {
- if (started_emulators.indexOf(new_started[i]) < 0) {
- emulator_id = new_started[i];
- }
+ return Q().then(function() {
+ if (emulator_ID) return Q(emulator_ID);
+
+ return self.best_image()
+ .then(function(best) {
+ if (best && best.name) {
+ events.emit('warn', 'No emulator specified, defaulting to ' + best.name);
+ return best.name;
}
- } else {
- emulator_id = new_started[0];
- }
- if (!emulator_id) return Q.reject('ERROR : Failed to start emulator, could not find new emulator');
- //wait for emulator to boot up
- process.stdout.write('Booting up emulator (this may take a while)...');
- return self.wait_for_boot(emulator_id);
- }).then(function() {
- console.log('BOOT COMPLETE');
+ // Loading check_reqs at run-time to avoid test-time vs run-time directory structure difference issue
+ var androidCmd = require('./check_reqs').getAbsoluteAndroidCmd();
+ return Q.reject(new CordovaError('No emulator images (avds) found.\n' +
+ '1. Download desired System Image by running: ' + androidCmd + ' sdk\n' +
+ '2. Create an AVD by running: ' + androidCmd + ' avd\n' +
+ 'HINT: For a faster emulator, use an Intel System Image and install the HAXM device driver\n'));
+ });
+ }).then(function(emulatorId) {
+ return self.get_available_port()
+ .then(function (port) {
+ 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 })
+ .unref();
- //unlock screen
- return exec('adb -s ' + emulator_id + ' shell input keyevent 82', os.tmpdir());
- }).then(function() {
- //return the new emulator id for the started emulators
- return emulator_id;
+ // wait for emulator to start
+ events.emit('log', 'Waiting for emulator to start...');
+ return self.wait_for_emulator(port);
+ });
+ }).then(function(emulatorId) {
+ if (!emulatorId)
+ return Q.reject(new CordovaError('Failed to start emulator'));
+
+ //wait for emulator to boot up
+ process.stdout.write('Waiting for emulator to boot (this may take a while)...');
+ return self.wait_for_boot(emulatorId, boot_timeout)
+ .then(function(success) {
+ if (success) {
+ events.emit('log','BOOT COMPLETE');
+ //unlock screen
+ return Adb.shell(emulatorId, 'input keyevent 82')
+ .then(function() {
+ //return the new emulator id for the started emulators
+ return emulatorId;
+ });
+ } else {
+ // We timed out waiting for the boot to happen
+ return null;
+ }
+ });
});
};
/*
- * Waits for the new emulator to apear on the started-emulator list.
- * Returns a promise with a list of newly started emulators' IDs.
+ * Waits for an emulator to boot on a given port.
+ * Returns this emulator's ID in a promise.
*/
-module.exports.wait_for_emulator = function(num_running) {
+module.exports.wait_for_emulator = function(port) {
var self = this;
- return self.list_started()
- .then(function(new_started) {
- if (new_started.length > num_running) {
- return new_started;
- } else {
- return Q.delay(1000).then(function() {
- return self.wait_for_emulator(num_running);
- });
- }
- });
+ return Q().then(function() {
+ var emulator_id = 'emulator-' + port;
+ return Adb.shell(emulator_id, 'getprop dev.bootcomplete')
+ .then(function (output) {
+ if (output.indexOf('1') >= 0) {
+ return emulator_id;
+ }
+ return self.wait_for_emulator(port);
+ }, function (error) {
+ if (error && error.message &&
+ (error.message.indexOf('not found') > -1) ||
+ error.message.indexOf('device offline') > -1) {
+ // emulator not yet started, continue waiting
+ return self.wait_for_emulator(port);
+ } else {
+ // something unexpected has happened
+ throw error;
+ }
+ });
+ });
};
/*
- * Waits for the boot animation property of the emulator to switch to 'stopped'
+ * Waits for the core android process of the emulator to start. Returns a
+ * promise that resolves to a boolean indicating success. Not specifying a
+ * time_remaining or passing a negative value will cause it to wait forever
*/
-module.exports.wait_for_boot = function(emulator_id) {
+module.exports.wait_for_boot = function(emulator_id, time_remaining) {
var self = this;
- return exec('adb -s ' + emulator_id + ' shell getprop init.svc.bootanim', os.tmpdir())
+ return Adb.shell(emulator_id, 'ps')
.then(function(output) {
- if (output.match(/stopped/)) {
- return;
+ if (output.match(/android\.process\.acore/)) {
+ return true;
+ } else if (time_remaining === 0) {
+ return false;
} else {
process.stdout.write('.');
- return Q.delay(3000).then(function() {
- return self.wait_for_boot(emulator_id);
+
+ // Check at regular intervals
+ return Q.delay(time_remaining < CHECK_BOOTED_INTERVAL ? time_remaining : CHECK_BOOTED_INTERVAL).then(function() {
+ var updated_time = time_remaining >= 0 ? Math.max(time_remaining - CHECK_BOOTED_INTERVAL, 0) : time_remaining;
+ return self.wait_for_boot(emulator_id, updated_time);
});
}
});
@@ -257,9 +292,9 @@ module.exports.wait_for_boot = function(emulator_id) {
* Returns a promise.
*/
module.exports.create_image = function(name, target) {
- console.log('Creating avd named ' + name);
+ console.log('Creating new avd named ' + name);
if (target) {
- return exec('android create avd --name ' + name + ' --target ' + target)
+ return spawn('android', ['create', 'avd', '--name', name, '--target', target])
.then(null, function(error) {
console.error('ERROR : Failed to create emulator image : ');
console.error(' Do you have the latest android targets including ' + target + '?');
@@ -267,11 +302,11 @@ module.exports.create_image = function(name, target) {
});
} else {
console.log('WARNING : Project target not found, creating avd with a different target but the project may fail to install.');
- return exec('android create avd --name ' + name + ' --target ' + this.list_targets()[0])
+ return spawn('android', ['create', 'avd', '--name', name, '--target', this.list_targets()[0]])
.then(function() {
// TODO: This seems like another error case, even though it always happens.
console.error('ERROR : Unable to create an avd emulator, no targets found.');
- console.error('Please insure you have targets available by running the "android" command');
+ console.error('Ensure you have targets available by running the "android" command');
return Q.reject();
}, function(error) {
console.error('ERROR : Failed to create emulator image : ');
@@ -284,7 +319,7 @@ module.exports.resolveTarget = function(target) {
return this.list_started()
.then(function(emulator_list) {
if (emulator_list.length < 1) {
- return Q.reject('No started emulators found, please start an emultor before deploying your project.');
+ return Q.reject('No running Android emulators found, please start an emulator before deploying your project.');
}
// default emulator
@@ -309,6 +344,8 @@ module.exports.resolveTarget = function(target) {
module.exports.install = function(givenTarget, buildResults) {
var target;
+ var manifest = new AndroidManifest(path.join(__dirname, '../../AndroidManifest.xml'));
+ var pkgName = manifest.getPackageId();
// resolve the target emulator
return Q().then(function () {
@@ -324,49 +361,83 @@ module.exports.install = function(givenTarget, buildResults) {
// install the app
}).then(function () {
+ // This promise is always resolved, even if 'adb uninstall' fails to uninstall app
+ // or the app doesn't installed at all, so no error catching needed.
+ return Q.when()
+ .then(function() {
- var apk_path = build.findBestApkForArchitecture(buildResults, target.arch);
- var execOptions = {
- timeout: INSTALL_COMMAND_TIMEOUT, // in milliseconds
- killSignal: EXEC_KILL_SIGNAL
- };
+ var apk_path = build.findBestApkForArchitecture(buildResults, target.arch);
+ var execOptions = {
+ cwd: os.tmpdir(),
+ timeout: INSTALL_COMMAND_TIMEOUT, // in milliseconds
+ killSignal: EXEC_KILL_SIGNAL
+ };
- console.log('Installing app on emulator...');
- console.log('Using apk: ' + apk_path);
+ events.emit('log', 'Using apk: ' + apk_path);
+ events.emit('log', 'Package name: ' + pkgName);
+ events.emit('verbose', 'Installing app on emulator...');
- var retriedInstall = retry.retryPromise(
- NUM_INSTALL_RETRIES,
- exec, 'adb -s ' + target.target + ' install -r -d "' + apk_path + '"', os.tmpdir(), execOptions
- );
+ // A special function to call adb install in specific environment w/ specific options.
+ // Introduced as a part of fix for http://issues.apache.org/jira/browse/CB-9119
+ // to workaround sporadic emulator hangs
+ function adbInstallWithOptions(target, apk, opts) {
+ events.emit('verbose', 'Installing apk ' + apk + ' on ' + target + '...');
- return retriedInstall.then(function (output) {
- if (output.match(/Failure/)) {
- return Q.reject('Failed to install apk to emulator: ' + output);
- } else {
- console.log('INSTALL SUCCESS');
+ var command = 'adb -s ' + target + ' install -r "' + apk + '"';
+ return Q.promise(function (resolve, reject) {
+ child_process.exec(command, opts, function(err, stdout, stderr) {
+ if (err) reject(new CordovaError('Error executing "' + command + '": ' + stderr));
+ // adb does not return an error code even if installation fails. Instead it puts a specific
+ // message to stdout, so we have to use RegExp matching to detect installation failure.
+ else if (/Failure/.test(stdout)) {
+ if (stdout.match(/INSTALL_PARSE_FAILED_NO_CERTIFICATES/)) {
+ stdout += 'Sign the build using \'-- --keystore\' or \'--buildConfig\'' +
+ ' or sign and deploy the unsigned apk manually using Android tools.';
+ } else if (stdout.match(/INSTALL_FAILED_VERSION_DOWNGRADE/)) {
+ stdout += 'You\'re trying to install apk with a lower versionCode that is already installed.' +
+ '\nEither uninstall an app or increment the versionCode.';
+ }
+
+ reject(new CordovaError('Failed to install apk to emulator: ' + stdout));
+ } else resolve(stdout);
+ });
+ });
}
- }, function (err) {
- return Q.reject('Failed to install apk to emulator: ' + err);
- });
- // unlock screen
- }).then(function () {
+ function installPromise () {
+ return adbInstallWithOptions(target.target, apk_path, execOptions)
+ .catch(function (error) {
+ // CB-9557 CB-10157 only uninstall and reinstall app if the one that
+ // is already installed on device was signed w/different certificate
+ if (!/INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES/.test(error.toString()))
+ throw error;
- console.log('Unlocking screen...');
- return exec('adb -s ' + target.target + ' shell input keyevent 82', os.tmpdir());
+ events.emit('warn', 'Uninstalling app from device and reinstalling it because the ' +
+ 'currently installed app was signed with different key');
- // launch the application
- }).then(function () {
+ // This promise is always resolved, even if 'adb uninstall' fails to uninstall app
+ // or the app doesn't installed at all, so no error catching needed.
+ return Adb.uninstall(target.target, pkgName)
+ .then(function() {
+ return adbInstallWithOptions(target.target, apk_path, execOptions);
+ });
+ });
+ }
- console.log('Launching application...');
- var launchName = appinfo.getActivityName();
- var cmd = 'adb -s ' + target.target + ' shell am start -W -a android.intent.action.MAIN -n ' + launchName;
- return exec(cmd, os.tmpdir());
+ return retry.retryPromise(NUM_INSTALL_RETRIES, installPromise)
+ .then(function (output) {
+ events.emit('log', 'INSTALL SUCCESS');
+ });
+ });
+ // unlock screen
+ }).then(function () {
+ events.emit('verbose', 'Unlocking screen...');
+ return Adb.shell(target.target, 'input keyevent 82');
+ }).then(function () {
+ Adb.start(target.target, pkgName + '/.' + manifest.getActivity().getName());
// report success or failure
}).then(function (output) {
- console.log('LAUNCH SUCCESS');
- }, function (err) {
- return Q.reject('Failed to launch app on emulator: ' + err);
+ events.emit('log', 'LAUNCH SUCCESS');
});
};
diff --git a/StoneIsland/platforms/android/cordova/lib/exec.js b/StoneIsland/platforms/android/cordova/lib/exec.js
deleted file mode 100755
index 798a93ba..00000000
--- a/StoneIsland/platforms/android/cordova/lib/exec.js
+++ /dev/null
@@ -1,68 +0,0 @@
-#!/usr/bin/env node
-
-/*
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements. See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership. The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied. See the License for the
- specific language governing permissions and limitations
- under the License.
-*/
-
-var child_process = require("child_process");
-var Q = require("q");
-
-// constants
-var DEFAULT_MAX_BUFFER = 1024000;
-
-// Takes a command and optional current working directory.
-// Returns a promise that either resolves with the stdout, or
-// rejects with an error message and the stderr.
-//
-// WARNING:
-// opt_cwd is an artifact of an old design, and must
-// be removed in the future; the correct solution is
-// to pass the options object the same way that
-// child_process.exec expects
-//
-// NOTE:
-// exec documented here - https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback
-module.exports = function(cmd, opt_cwd, options) {
-
- var d = Q.defer();
-
- if (typeof options === "undefined") {
- options = {};
- }
-
- // override cwd to preserve old opt_cwd behavior
- options.cwd = opt_cwd;
-
- // set maxBuffer
- if (typeof options.maxBuffer === "undefined") {
- options.maxBuffer = DEFAULT_MAX_BUFFER;
- }
-
- try {
- child_process.exec(cmd, options, function(err, stdout, stderr) {
- if (err) d.reject("Error executing \"" + cmd + "\": " + stderr);
- else d.resolve(stdout);
- });
- } catch(e) {
- console.error("error caught: " + e);
- d.reject(e);
- }
-
- return d.promise;
-};
-
diff --git a/StoneIsland/platforms/android/cordova/lib/install-device.bat b/StoneIsland/platforms/android/cordova/lib/install-device.bat
index ac7214ac..ac7214ac 100755..100644
--- a/StoneIsland/platforms/android/cordova/lib/install-device.bat
+++ b/StoneIsland/platforms/android/cordova/lib/install-device.bat
diff --git a/StoneIsland/platforms/android/cordova/lib/install-emulator.bat b/StoneIsland/platforms/android/cordova/lib/install-emulator.bat
index 1ec67790..1ec67790 100755..100644
--- a/StoneIsland/platforms/android/cordova/lib/install-emulator.bat
+++ b/StoneIsland/platforms/android/cordova/lib/install-emulator.bat
diff --git a/StoneIsland/platforms/android/cordova/lib/list-devices b/StoneIsland/platforms/android/cordova/lib/list-devices
index e390bff6..fa84d7f6 100755
--- a/StoneIsland/platforms/android/cordova/lib/list-devices
+++ b/StoneIsland/platforms/android/cordova/lib/list-devices
@@ -22,12 +22,13 @@
var devices = require('./device');
// Usage support for when args are given
-devices.list().done(function(device_list) {
- device_list && device_list.forEach(function(dev) {
- console.log(dev);
+require('../lib/check_reqs').check_android().then(function() {
+ devices.list().done(function(device_list) {
+ device_list && device_list.forEach(function(dev) {
+ console.log(dev);
+ });
+ }, function(err) {
+ console.error('ERROR: ' + err);
+ process.exit(2);
});
-}, function(err) {
- console.error('ERROR: ' + err);
- process.exit(2);
});
-
diff --git a/StoneIsland/platforms/android/cordova/lib/list-devices.bat b/StoneIsland/platforms/android/cordova/lib/list-devices.bat
index c0bcdd9a..c0bcdd9a 100755..100644
--- a/StoneIsland/platforms/android/cordova/lib/list-devices.bat
+++ b/StoneIsland/platforms/android/cordova/lib/list-devices.bat
diff --git a/StoneIsland/platforms/android/cordova/lib/list-emulator-images b/StoneIsland/platforms/android/cordova/lib/list-emulator-images
index 996cf555..03c827fe 100755
--- a/StoneIsland/platforms/android/cordova/lib/list-emulator-images
+++ b/StoneIsland/platforms/android/cordova/lib/list-emulator-images
@@ -22,11 +22,13 @@
var emulators = require('./emulator');
// Usage support for when args are given
-emulators.list_images().done(function(emulator_list) {
- emulator_list && emulator_list.forEach(function(emu) {
- console.log(emu.name);
+require('../lib/check_reqs').check_android().then(function() {
+ emulators.list_images().done(function(emulator_list) {
+ emulator_list && emulator_list.forEach(function(emu) {
+ console.log(emu.name);
+ });
+ }, function(err) {
+ console.error('ERROR: ' + err);
+ process.exit(2);
});
-}, function(err) {
- console.error('ERROR: ' + err);
- process.exit(2);
});
diff --git a/StoneIsland/platforms/android/cordova/lib/list-emulator-images.bat b/StoneIsland/platforms/android/cordova/lib/list-emulator-images.bat
index 661cbf95..661cbf95 100755..100644
--- a/StoneIsland/platforms/android/cordova/lib/list-emulator-images.bat
+++ b/StoneIsland/platforms/android/cordova/lib/list-emulator-images.bat
diff --git a/StoneIsland/platforms/android/cordova/lib/list-started-emulators b/StoneIsland/platforms/android/cordova/lib/list-started-emulators
index 2ae8c5a8..a890dec6 100755
--- a/StoneIsland/platforms/android/cordova/lib/list-started-emulators
+++ b/StoneIsland/platforms/android/cordova/lib/list-started-emulators
@@ -22,11 +22,13 @@
var emulators = require('./emulator');
// Usage support for when args are given
-emulators.list_started().done(function(emulator_list) {
- emulator_list && emulator_list.forEach(function(emu) {
- console.log(emu);
+require('../lib/check_reqs').check_android().then(function() {
+ emulators.list_started().done(function(emulator_list) {
+ emulator_list && emulator_list.forEach(function(emu) {
+ console.log(emu);
+ });
+ }, function(err) {
+ console.error('ERROR: ' + err);
+ process.exit(2);
});
-}, function(err) {
- console.error('ERROR: ' + err);
- process.exit(2);
});
diff --git a/StoneIsland/platforms/android/cordova/lib/list-started-emulators.bat b/StoneIsland/platforms/android/cordova/lib/list-started-emulators.bat
index a4e88f7d..a4e88f7d 100755..100644
--- a/StoneIsland/platforms/android/cordova/lib/list-started-emulators.bat
+++ b/StoneIsland/platforms/android/cordova/lib/list-started-emulators.bat
diff --git a/StoneIsland/platforms/android/cordova/lib/log.js b/StoneIsland/platforms/android/cordova/lib/log.js
index ebf836d5..ebf836d5 100755..100644
--- a/StoneIsland/platforms/android/cordova/lib/log.js
+++ b/StoneIsland/platforms/android/cordova/lib/log.js
diff --git a/StoneIsland/platforms/android/cordova/lib/plugin-build.gradle b/StoneIsland/platforms/android/cordova/lib/plugin-build.gradle
index b345b90a..d1c63365 100755..100644
--- a/StoneIsland/platforms/android/cordova/lib/plugin-build.gradle
+++ b/StoneIsland/platforms/android/cordova/lib/plugin-build.gradle
@@ -21,28 +21,19 @@
buildscript {
repositories {
mavenCentral()
+ jcenter()
}
// Switch the Android Gradle plugin version requirement depending on the
// installed version of Gradle. This dependency is documented at
// http://tools.android.com/tech-docs/new-build-system/version-compatibility
// and https://issues.apache.org/jira/browse/CB-8143
- if (gradle.gradleVersion >= "2.2") {
- dependencies {
- classpath 'com.android.tools.build:gradle:1.0.0+'
- }
- } else if (gradle.gradleVersion >= "2.1") {
- dependencies {
- classpath 'com.android.tools.build:gradle:0.14.0+'
- }
- } else {
- dependencies {
- classpath 'com.android.tools.build:gradle:0.12.0+'
- }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.0.0+'
}
}
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
diff --git a/StoneIsland/platforms/android/cordova/lib/pluginHandlers.js b/StoneIsland/platforms/android/cordova/lib/pluginHandlers.js
new file mode 100644
index 00000000..5e745fd5
--- /dev/null
+++ b/StoneIsland/platforms/android/cordova/lib/pluginHandlers.js
@@ -0,0 +1,308 @@
+/*
+ *
+ * Copyright 2013 Anis Kadri
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+/* jshint unused: vars */
+
+var fs = require('fs');
+var path = require('path');
+var shell = require('shelljs');
+var events = require('cordova-common').events;
+var CordovaError = require('cordova-common').CordovaError;
+
+var handlers = {
+ 'source-file':{
+ install:function(obj, plugin, project, options) {
+ if (!obj.src) throw new CordovaError(generateAttributeError('src', 'source-file', plugin.id));
+ if (!obj.targetDir) throw new CordovaError(generateAttributeError('target-dir', 'source-file', plugin.id));
+
+ var dest = path.join(obj.targetDir, path.basename(obj.src));
+
+ if(options && options.android_studio === true) {
+ dest = path.join('app/src/main/java', obj.targetDir.substring(4), path.basename(obj.src));
+ }
+
+ if (options && options.force) {
+ copyFile(plugin.dir, obj.src, project.projectDir, dest, !!(options && options.link));
+ } else {
+ copyNewFile(plugin.dir, obj.src, project.projectDir, dest, !!(options && options.link));
+ }
+ },
+ uninstall:function(obj, plugin, project, options) {
+ var dest = path.join(obj.targetDir, path.basename(obj.src));
+
+ if(options && options.android_studio === true) {
+ dest = path.join('app/src/main/java', obj.targetDir.substring(4), path.basename(obj.src));
+ }
+
+ deleteJava(project.projectDir, dest);
+ }
+ },
+ 'lib-file':{
+ install:function(obj, plugin, project, options) {
+ var dest = path.join('libs', path.basename(obj.src));
+ if(options && options.android_studio === true) {
+ dest = path.join('app/libs', path.basename(obj.src));
+ }
+ copyFile(plugin.dir, obj.src, project.projectDir, dest, !!(options && options.link));
+ },
+ uninstall:function(obj, plugin, project, options) {
+ var dest = path.join('libs', path.basename(obj.src));
+ if(options && options.android_studio === true) {
+ dest = path.join('app/libs', path.basename(obj.src));
+ }
+ removeFile(project.projectDir, dest);
+ }
+ },
+ 'resource-file':{
+ install:function(obj, plugin, project, options) {
+ copyFile(plugin.dir, obj.src, project.projectDir, path.normalize(obj.target), !!(options && options.link));
+ },
+ uninstall:function(obj, plugin, project, options) {
+ removeFile(project.projectDir, path.normalize(obj.target));
+ }
+ },
+ 'framework': {
+ install:function(obj, plugin, project, options) {
+ var src = obj.src;
+ if (!src) throw new CordovaError(generateAttributeError('src', 'framework', plugin.id));
+
+ events.emit('verbose', 'Installing Android library: ' + src);
+ var parentDir = obj.parent ? path.resolve(project.projectDir, obj.parent) : project.projectDir;
+ var subDir;
+
+ if (obj.custom) {
+ var subRelativeDir = project.getCustomSubprojectRelativeDir(plugin.id, src);
+ copyNewFile(plugin.dir, src, project.projectDir, subRelativeDir, !!(options && options.link));
+ subDir = path.resolve(project.projectDir, subRelativeDir);
+ } else {
+ obj.type = 'sys';
+ subDir = src;
+ }
+
+ if (obj.type == 'gradleReference') {
+ project.addGradleReference(parentDir, subDir);
+ } else if (obj.type == 'sys') {
+ project.addSystemLibrary(parentDir, subDir);
+ } else {
+ project.addSubProject(parentDir, subDir);
+ }
+ },
+ uninstall:function(obj, plugin, project, options) {
+ var src = obj.src;
+ if (!src) throw new CordovaError(generateAttributeError('src', 'framework', plugin.id));
+
+ events.emit('verbose', 'Uninstalling Android library: ' + src);
+ var parentDir = obj.parent ? path.resolve(project.projectDir, obj.parent) : project.projectDir;
+ var subDir;
+
+ if (obj.custom) {
+ var subRelativeDir = project.getCustomSubprojectRelativeDir(plugin.id, src);
+ removeFile(project.projectDir, subRelativeDir);
+ subDir = path.resolve(project.projectDir, subRelativeDir);
+ // If it's the last framework in the plugin, remove the parent directory.
+ var parDir = path.dirname(subDir);
+ if (fs.existsSync(parDir) && fs.readdirSync(parDir).length === 0) {
+ fs.rmdirSync(parDir);
+ }
+ } else {
+ obj.type = 'sys';
+ subDir = src;
+ }
+
+ if (obj.type == 'gradleReference') {
+ project.removeGradleReference(parentDir, subDir);
+ } else if (obj.type == 'sys') {
+ project.removeSystemLibrary(parentDir, subDir);
+ } else {
+ project.removeSubProject(parentDir, subDir);
+ }
+ }
+ },
+ asset:{
+ install:function(obj, plugin, project, options) {
+ if (!obj.src) {
+ throw new CordovaError(generateAttributeError('src', 'asset', plugin.id));
+ }
+ if (!obj.target) {
+ throw new CordovaError(generateAttributeError('target', 'asset', plugin.id));
+ }
+
+ copyFile(plugin.dir, obj.src, project.www, obj.target);
+ if (options && options.usePlatformWww) {
+ // CB-11022 copy file to both directories if usePlatformWww is specified
+ copyFile(plugin.dir, obj.src, project.platformWww, obj.target);
+ }
+ },
+ uninstall:function(obj, plugin, project, options) {
+ var target = obj.target || obj.src;
+
+ if (!target) throw new CordovaError(generateAttributeError('target', 'asset', plugin.id));
+
+ removeFileF(path.resolve(project.www, target));
+ removeFileF(path.resolve(project.www, 'plugins', plugin.id));
+ if (options && options.usePlatformWww) {
+ // CB-11022 remove file from both directories if usePlatformWww is specified
+ removeFileF(path.resolve(project.platformWww, target));
+ removeFileF(path.resolve(project.platformWww, 'plugins', plugin.id));
+ }
+ }
+ },
+ 'js-module': {
+ install: function (obj, plugin, project, options) {
+ // Copy the plugin's files into the www directory.
+ var moduleSource = path.resolve(plugin.dir, obj.src);
+ var moduleName = plugin.id + '.' + (obj.name || path.basename(obj.src, path.extname (obj.src)));
+
+ // Read in the file, prepend the cordova.define, and write it back out.
+ var scriptContent = fs.readFileSync(moduleSource, 'utf-8').replace(/^\ufeff/, ''); // Window BOM
+ if (moduleSource.match(/.*\.json$/)) {
+ scriptContent = 'module.exports = ' + scriptContent;
+ }
+ scriptContent = 'cordova.define("' + moduleName + '", function(require, exports, module) {\n' + scriptContent + '\n});\n';
+
+ var wwwDest = path.resolve(project.www, 'plugins', plugin.id, obj.src);
+ shell.mkdir('-p', path.dirname(wwwDest));
+ fs.writeFileSync(wwwDest, scriptContent, 'utf-8');
+
+ if (options && options.usePlatformWww) {
+ // CB-11022 copy file to both directories if usePlatformWww is specified
+ var platformWwwDest = path.resolve(project.platformWww, 'plugins', plugin.id, obj.src);
+ shell.mkdir('-p', path.dirname(platformWwwDest));
+ fs.writeFileSync(platformWwwDest, scriptContent, 'utf-8');
+ }
+ },
+ uninstall: function (obj, plugin, project, options) {
+ var pluginRelativePath = path.join('plugins', plugin.id, obj.src);
+ removeFileAndParents(project.www, pluginRelativePath);
+ if (options && options.usePlatformWww) {
+ // CB-11022 remove file from both directories if usePlatformWww is specified
+ removeFileAndParents(project.platformWww, pluginRelativePath);
+ }
+ }
+ }
+};
+
+module.exports.getInstaller = function (type) {
+ if (handlers[type] && handlers[type].install) {
+ return handlers[type].install;
+ }
+
+ events.emit('verbose', '<' + type + '> is not supported for android plugins');
+};
+
+module.exports.getUninstaller = function(type) {
+ if (handlers[type] && handlers[type].uninstall) {
+ return handlers[type].uninstall;
+ }
+
+ events.emit('verbose', '<' + type + '> is not supported for android plugins');
+};
+
+function copyFile (plugin_dir, src, project_dir, dest, link) {
+ src = path.resolve(plugin_dir, src);
+ if (!fs.existsSync(src)) throw new CordovaError('"' + src + '" not found!');
+
+ // check that src path is inside plugin directory
+ var real_path = fs.realpathSync(src);
+ var real_plugin_path = fs.realpathSync(plugin_dir);
+ if (real_path.indexOf(real_plugin_path) !== 0)
+ throw new CordovaError('File "' + src + '" is located outside the plugin directory "' + plugin_dir + '"');
+
+ dest = path.resolve(project_dir, dest);
+
+ // check that dest path is located in project directory
+ if (dest.indexOf(project_dir) !== 0)
+ throw new CordovaError('Destination "' + dest + '" for source file "' + src + '" is located outside the project');
+
+ shell.mkdir('-p', path.dirname(dest));
+ if (link) {
+ symlinkFileOrDirTree(src, dest);
+ } else if (fs.statSync(src).isDirectory()) {
+ // XXX shelljs decides to create a directory when -R|-r is used which sucks. http://goo.gl/nbsjq
+ shell.cp('-Rf', src+'/*', dest);
+ } else {
+ shell.cp('-f', src, dest);
+ }
+}
+
+// Same as copy file but throws error if target exists
+function copyNewFile (plugin_dir, src, project_dir, dest, link) {
+ var target_path = path.resolve(project_dir, dest);
+ if (fs.existsSync(target_path))
+ throw new CordovaError('"' + target_path + '" already exists!');
+
+ copyFile(plugin_dir, src, project_dir, dest, !!link);
+}
+
+function symlinkFileOrDirTree(src, dest) {
+ if (fs.existsSync(dest)) {
+ shell.rm('-Rf', dest);
+ }
+
+ if (fs.statSync(src).isDirectory()) {
+ shell.mkdir('-p', dest);
+ fs.readdirSync(src).forEach(function(entry) {
+ symlinkFileOrDirTree(path.join(src, entry), path.join(dest, entry));
+ });
+ }
+ else {
+ fs.symlinkSync(path.relative(fs.realpathSync(path.dirname(dest)), src), dest);
+ }
+}
+
+// checks if file exists and then deletes. Error if doesn't exist
+function removeFile (project_dir, src) {
+ var file = path.resolve(project_dir, src);
+ shell.rm('-Rf', file);
+}
+
+// deletes file/directory without checking
+function removeFileF (file) {
+ shell.rm('-Rf', file);
+}
+
+// Sometimes we want to remove some java, and prune any unnecessary empty directories
+function deleteJava (project_dir, destFile) {
+ removeFileAndParents(project_dir, destFile, 'src');
+}
+
+function removeFileAndParents (baseDir, destFile, stopper) {
+ stopper = stopper || '.';
+ var file = path.resolve(baseDir, destFile);
+ if (!fs.existsSync(file)) return;
+
+ removeFileF(file);
+
+ // check if directory is empty
+ var curDir = path.dirname(file);
+
+ while(curDir !== path.resolve(baseDir, stopper)) {
+ if(fs.existsSync(curDir) && fs.readdirSync(curDir).length === 0) {
+ fs.rmdirSync(curDir);
+ curDir = path.resolve(curDir, '..');
+ } else {
+ // directory not empty...do nothing
+ break;
+ }
+ }
+}
+
+function generateAttributeError(attribute, element, id) {
+ return 'Required attribute "' + attribute + '" not specified in <' + element + '> element from plugin: ' + id;
+}
diff --git a/StoneIsland/platforms/android/cordova/lib/prepare.js b/StoneIsland/platforms/android/cordova/lib/prepare.js
new file mode 100644
index 00000000..10a69ea3
--- /dev/null
+++ b/StoneIsland/platforms/android/cordova/lib/prepare.js
@@ -0,0 +1,431 @@
+/**
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+var Q = require('q');
+var fs = require('fs');
+var path = require('path');
+var shell = require('shelljs');
+var events = require('cordova-common').events;
+var AndroidManifest = require('./AndroidManifest');
+var xmlHelpers = require('cordova-common').xmlHelpers;
+var CordovaError = require('cordova-common').CordovaError;
+var ConfigParser = require('cordova-common').ConfigParser;
+var FileUpdater = require('cordova-common').FileUpdater;
+var PlatformJson = require('cordova-common').PlatformJson;
+var PlatformMunger = require('cordova-common').ConfigChanges.PlatformMunger;
+var PluginInfoProvider = require('cordova-common').PluginInfoProvider;
+
+module.exports.prepare = function (cordovaProject, options) {
+ var self = this;
+
+ var platformJson = PlatformJson.load(this.locations.root, this.platform);
+ var munger = new PlatformMunger(this.platform, this.locations.root, platformJson, new PluginInfoProvider());
+
+ this._config = updateConfigFilesFrom(cordovaProject.projectConfig, munger, this.locations);
+
+ // Update own www dir with project's www assets and plugins' assets and js-files
+ return Q.when(updateWww(cordovaProject, this.locations))
+ .then(function () {
+ // update project according to config.xml changes.
+ return updateProjectAccordingTo(self._config, self.locations);
+ })
+ .then(function () {
+ updateIcons(cordovaProject, path.relative(cordovaProject.root, self.locations.res));
+ updateSplashes(cordovaProject, path.relative(cordovaProject.root, self.locations.res));
+ })
+ .then(function () {
+ events.emit('verbose', 'Prepared android project successfully');
+ });
+};
+
+module.exports.clean = function (options) {
+ // A cordovaProject isn't passed into the clean() function, because it might have
+ // been called from the platform shell script rather than the CLI. Check for the
+ // noPrepare option passed in by the non-CLI clean script. If that's present, or if
+ // there's no config.xml found at the project root, then don't clean prepared files.
+ var projectRoot = path.resolve(this.root, '../..');
+ if ((options && options.noPrepare) || !fs.existsSync(this.locations.configXml) ||
+ !fs.existsSync(this.locations.configXml)) {
+ return Q();
+ }
+
+ var projectConfig = new ConfigParser(this.locations.configXml);
+
+ var self = this;
+ return Q().then(function () {
+ cleanWww(projectRoot, self.locations);
+ cleanIcons(projectRoot, projectConfig, path.relative(projectRoot, self.locations.res));
+ cleanSplashes(projectRoot, projectConfig, path.relative(projectRoot, self.locations.res));
+ });
+};
+
+/**
+ * Updates config files in project based on app's config.xml and config munge,
+ * generated by plugins.
+ *
+ * @param {ConfigParser} sourceConfig A project's configuration that will
+ * be merged into platform's config.xml
+ * @param {ConfigChanges} configMunger An initialized ConfigChanges instance
+ * for this platform.
+ * @param {Object} locations A map of locations for this platform
+ *
+ * @return {ConfigParser} An instance of ConfigParser, that
+ * represents current project's configuration. When returned, the
+ * configuration is already dumped to appropriate config.xml file.
+ */
+function updateConfigFilesFrom(sourceConfig, configMunger, locations) {
+ events.emit('verbose', 'Generating platform-specific config.xml from defaults for android at ' + locations.configXml);
+
+ // First cleanup current config and merge project's one into own
+ // Overwrite platform config.xml with defaults.xml.
+ shell.cp('-f', locations.defaultConfigXml, locations.configXml);
+
+ // Then apply config changes from global munge to all config files
+ // in project (including project's config)
+ configMunger.reapply_global_munge().save_all();
+
+ events.emit('verbose', 'Merging project\'s config.xml into platform-specific android config.xml');
+ // Merge changes from app's config.xml into platform's one
+ var config = new ConfigParser(locations.configXml);
+ xmlHelpers.mergeXml(sourceConfig.doc.getroot(),
+ config.doc.getroot(), 'android', /*clobber=*/true);
+
+ config.write();
+ return config;
+}
+
+/**
+ * Logs all file operations via the verbose event stream, indented.
+ */
+function logFileOp(message) {
+ events.emit('verbose', ' ' + message);
+}
+
+/**
+ * Updates platform 'www' directory by replacing it with contents of
+ * 'platform_www' and app www. Also copies project's overrides' folder into
+ * the platform 'www' folder
+ *
+ * @param {Object} cordovaProject An object which describes cordova project.
+ * @param {Object} destinations An object that contains destination
+ * paths for www files.
+ */
+function updateWww(cordovaProject, destinations) {
+ var sourceDirs = [
+ path.relative(cordovaProject.root, cordovaProject.locations.www),
+ path.relative(cordovaProject.root, destinations.platformWww)
+ ];
+
+ // If project contains 'merges' for our platform, use them as another overrides
+ var merges_path = path.join(cordovaProject.root, 'merges', 'android');
+ if (fs.existsSync(merges_path)) {
+ events.emit('verbose', 'Found "merges/android" folder. Copying its contents into the android project.');
+ sourceDirs.push(path.join('merges', 'android'));
+ }
+
+ var targetDir = path.relative(cordovaProject.root, destinations.www);
+ events.emit(
+ 'verbose', 'Merging and updating files from [' + sourceDirs.join(', ') + '] to ' + targetDir);
+ FileUpdater.mergeAndUpdateDir(
+ sourceDirs, targetDir, { rootDir: cordovaProject.root }, logFileOp);
+}
+
+/**
+ * Cleans all files from the platform 'www' directory.
+ */
+function cleanWww(projectRoot, locations) {
+ var targetDir = path.relative(projectRoot, locations.www);
+ events.emit('verbose', 'Cleaning ' + targetDir);
+
+ // No source paths are specified, so mergeAndUpdateDir() will clear the target directory.
+ FileUpdater.mergeAndUpdateDir(
+ [], targetDir, { rootDir: projectRoot, all: true }, logFileOp);
+}
+
+/**
+ * Updates project structure and AndroidManifest according to project's configuration.
+ *
+ * @param {ConfigParser} platformConfig A project's configuration that will
+ * be used to update project
+ * @param {Object} locations A map of locations for this platform
+ */
+function updateProjectAccordingTo(platformConfig, locations) {
+ // Update app name by editing res/values/strings.xml
+ var name = platformConfig.name();
+ var strings = xmlHelpers.parseElementtreeSync(locations.strings);
+ strings.find('string[@name="app_name"]').text = name.replace(/\'/g, '\\\'');
+ fs.writeFileSync(locations.strings, strings.write({indent: 4}), 'utf-8');
+ events.emit('verbose', 'Wrote out android application name "' + name + '" to ' + locations.strings);
+
+ // Java packages cannot support dashes
+ var pkg = (platformConfig.android_packageName() || platformConfig.packageName()).replace(/-/g, '_');
+
+ var manifest = new AndroidManifest(locations.manifest);
+ var orig_pkg = manifest.getPackageId();
+
+ manifest.getActivity()
+ .setOrientation(platformConfig.getPreference('orientation'))
+ .setLaunchMode(findAndroidLaunchModePreference(platformConfig));
+
+ manifest.setVersionName(platformConfig.version())
+ .setVersionCode(platformConfig.android_versionCode() || default_versionCode(platformConfig.version()))
+ .setPackageId(pkg)
+ .setMinSdkVersion(platformConfig.getPreference('android-minSdkVersion', 'android'))
+ .setMaxSdkVersion(platformConfig.getPreference('android-maxSdkVersion', 'android'))
+ .setTargetSdkVersion(platformConfig.getPreference('android-targetSdkVersion', 'android'))
+ .write();
+
+ var javaPattern = path.join(locations.root, 'src', orig_pkg.replace(/\./g, '/'), '*.java');
+ var java_files = shell.ls(javaPattern).filter(function(f) {
+ return shell.grep(/extends\s+CordovaActivity/g, f);
+ });
+
+ if (java_files.length === 0) {
+ throw new CordovaError('No Java files found that extend CordovaActivity.');
+ } else if(java_files.length > 1) {
+ events.emit('log', 'Multiple candidate Java files that extend CordovaActivity found. Guessing at the first one, ' + java_files[0]);
+ }
+
+ var destFile = path.join(locations.root, 'src', pkg.replace(/\./g, '/'), path.basename(java_files[0]));
+ shell.mkdir('-p', path.dirname(destFile));
+ shell.sed(/package [\w\.]*;/, 'package ' + pkg + ';', java_files[0]).to(destFile);
+ events.emit('verbose', 'Wrote out Android package name "' + pkg + '" to ' + destFile);
+
+ if (orig_pkg !== pkg) {
+ // If package was name changed we need to remove old java with main activity
+ shell.rm('-Rf',java_files[0]);
+ // remove any empty directories
+ var currentDir = path.dirname(java_files[0]);
+ var sourcesRoot = path.resolve(locations.root, 'src');
+ while(currentDir !== sourcesRoot) {
+ if(fs.existsSync(currentDir) && fs.readdirSync(currentDir).length === 0) {
+ fs.rmdirSync(currentDir);
+ currentDir = path.resolve(currentDir, '..');
+ } else {
+ break;
+ }
+ }
+ }
+}
+
+// Consturct the default value for versionCode as
+// PATCH + MINOR * 100 + MAJOR * 10000
+// see http://developer.android.com/tools/publishing/versioning.html
+function default_versionCode(version) {
+ var nums = version.split('-')[0].split('.');
+ var versionCode = 0;
+ if (+nums[0]) {
+ versionCode += +nums[0] * 10000;
+ }
+ if (+nums[1]) {
+ versionCode += +nums[1] * 100;
+ }
+ if (+nums[2]) {
+ versionCode += +nums[2];
+ }
+
+ events.emit('verbose', 'android-versionCode not found in config.xml. Generating a code based on version in config.xml (' + version + '): ' + versionCode);
+ return versionCode;
+}
+
+function getImageResourcePath(resourcesDir, type, density, name, sourceName) {
+ if (/\.9\.png$/.test(sourceName)) {
+ name = name.replace(/\.png$/, '.9.png');
+ }
+ var resourcePath = path.join(resourcesDir, (density ? type + '-' + density : type), name);
+ return resourcePath;
+}
+
+function updateSplashes(cordovaProject, platformResourcesDir) {
+ var resources = cordovaProject.projectConfig.getSplashScreens('android');
+
+ // if there are "splash" elements in config.xml
+ if (resources.length === 0) {
+ events.emit('verbose', 'This app does not have splash screens defined');
+ return;
+ }
+
+ var resourceMap = mapImageResources(cordovaProject.root, platformResourcesDir, 'drawable', 'screen.png');
+
+ var hadMdpi = false;
+ resources.forEach(function (resource) {
+ if (!resource.density) {
+ return;
+ }
+ if (resource.density == 'mdpi') {
+ hadMdpi = true;
+ }
+ var targetPath = getImageResourcePath(
+ platformResourcesDir, 'drawable', resource.density, 'screen.png', path.basename(resource.src));
+ resourceMap[targetPath] = resource.src;
+ });
+
+ // There's no "default" drawable, so assume default == mdpi.
+ if (!hadMdpi && resources.defaultResource) {
+ var targetPath = getImageResourcePath(
+ platformResourcesDir, 'drawable', 'mdpi', 'screen.png', path.basename(resources.defaultResource.src));
+ resourceMap[targetPath] = resources.defaultResource.src;
+ }
+
+ events.emit('verbose', 'Updating splash screens at ' + platformResourcesDir);
+ FileUpdater.updatePaths(
+ resourceMap, { rootDir: cordovaProject.root }, logFileOp);
+}
+
+function cleanSplashes(projectRoot, projectConfig, platformResourcesDir) {
+ var resources = projectConfig.getSplashScreens('android');
+ if (resources.length > 0) {
+ var resourceMap = mapImageResources(projectRoot, platformResourcesDir, 'drawable', 'screen.png');
+ events.emit('verbose', 'Cleaning splash screens at ' + platformResourcesDir);
+
+ // No source paths are specified in the map, so updatePaths() will delete the target files.
+ FileUpdater.updatePaths(
+ resourceMap, { rootDir: projectRoot, all: true }, logFileOp);
+ }
+}
+
+function updateIcons(cordovaProject, platformResourcesDir) {
+ var icons = cordovaProject.projectConfig.getIcons('android');
+
+ // if there are icon elements in config.xml
+ if (icons.length === 0) {
+ events.emit('verbose', 'This app does not have launcher icons defined');
+ return;
+ }
+
+ var resourceMap = mapImageResources(cordovaProject.root, platformResourcesDir, 'mipmap', 'icon.png');
+
+ var android_icons = {};
+ var default_icon;
+ // http://developer.android.com/design/style/iconography.html
+ var sizeToDensityMap = {
+ 36: 'ldpi',
+ 48: 'mdpi',
+ 72: 'hdpi',
+ 96: 'xhdpi',
+ 144: 'xxhdpi',
+ 192: 'xxxhdpi'
+ };
+ // find the best matching icon for a given density or size
+ // @output android_icons
+ var parseIcon = function(icon, icon_size) {
+ // do I have a platform icon for that density already
+ var density = icon.density || sizeToDensityMap[icon_size];
+ if (!density) {
+ // invalid icon defition ( or unsupported size)
+ return;
+ }
+ var previous = android_icons[density];
+ if (previous && previous.platform) {
+ return;
+ }
+ android_icons[density] = icon;
+ };
+
+ // iterate over all icon elements to find the default icon and call parseIcon
+ for (var i=0; i<icons.length; i++) {
+ var icon = icons[i];
+ var size = icon.width;
+ if (!size) {
+ size = icon.height;
+ }
+ if (!size && !icon.density) {
+ if (default_icon) {
+ events.emit('verbose', 'Found extra default icon: ' + icon.src + ' (ignoring in favor of ' + default_icon.src + ')');
+ } else {
+ default_icon = icon;
+ }
+ } else {
+ parseIcon(icon, size);
+ }
+ }
+
+ // The source paths for icons and splashes are relative to
+ // project's config.xml location, so we use it as base path.
+ for (var density in android_icons) {
+ var targetPath = getImageResourcePath(
+ platformResourcesDir, 'mipmap', density, 'icon.png', path.basename(android_icons[density].src));
+ resourceMap[targetPath] = android_icons[density].src;
+ }
+
+ // There's no "default" drawable, so assume default == mdpi.
+ if (default_icon && !android_icons.mdpi) {
+ var defaultTargetPath = getImageResourcePath(
+ platformResourcesDir, 'mipmap', 'mdpi', 'icon.png', path.basename(default_icon.src));
+ resourceMap[defaultTargetPath] = default_icon.src;
+ }
+
+ events.emit('verbose', 'Updating icons at ' + platformResourcesDir);
+ FileUpdater.updatePaths(
+ resourceMap, { rootDir: cordovaProject.root }, logFileOp);
+}
+
+function cleanIcons(projectRoot, projectConfig, platformResourcesDir) {
+ var icons = projectConfig.getIcons('android');
+ if (icons.length > 0) {
+ var resourceMap = mapImageResources(projectRoot, platformResourcesDir, 'mipmap', 'icon.png');
+ events.emit('verbose', 'Cleaning icons at ' + platformResourcesDir);
+
+ // No source paths are specified in the map, so updatePaths() will delete the target files.
+ FileUpdater.updatePaths(
+ resourceMap, { rootDir: projectRoot, all: true }, logFileOp);
+ }
+}
+
+/**
+ * Gets a map containing resources of a specified name from all drawable folders in a directory.
+ */
+function mapImageResources(rootDir, subDir, type, resourceName) {
+ var pathMap = {};
+ shell.ls(path.join(rootDir, subDir, type + '-*'))
+ .forEach(function (drawableFolder) {
+ var imagePath = path.join(subDir, path.basename(drawableFolder), resourceName);
+ pathMap[imagePath] = null;
+ });
+ return pathMap;
+}
+
+/**
+ * Gets and validates 'AndroidLaunchMode' prepference from config.xml. Returns
+ * preference value and warns if it doesn't seems to be valid
+ *
+ * @param {ConfigParser} platformConfig A configParser instance for
+ * platform.
+ *
+ * @return {String} Preference's value from config.xml or
+ * default value, if there is no such preference. The default value is
+ * 'singleTop'
+ */
+function findAndroidLaunchModePreference(platformConfig) {
+ var launchMode = platformConfig.getPreference('AndroidLaunchMode');
+ if (!launchMode) {
+ // Return a default value
+ return 'singleTop';
+ }
+
+ var expectedValues = ['standard', 'singleTop', 'singleTask', 'singleInstance'];
+ var valid = expectedValues.indexOf(launchMode) >= 0;
+ if (!valid) {
+ // Note: warn, but leave the launch mode as developer wanted, in case the list of options changes in the future
+ events.emit('warn', 'Unrecognized value for AndroidLaunchMode preference: ' +
+ launchMode + '. Expected values are: ' + expectedValues.join(', '));
+ }
+
+ return launchMode;
+}
diff --git a/StoneIsland/platforms/android/cordova/lib/retry.js b/StoneIsland/platforms/android/cordova/lib/retry.js
index dc52a7d2..3cb49274 100755..100644
--- a/StoneIsland/platforms/android/cordova/lib/retry.js
+++ b/StoneIsland/platforms/android/cordova/lib/retry.js
@@ -21,7 +21,9 @@
/* jshint node: true */
-"use strict";
+'use strict';
+
+var events = require('cordova-common').events;
/*
* Retry a promise-returning function a number of times, propagating its
@@ -56,7 +58,7 @@ module.exports.retryPromise = function (attemts_left, promiseFunction) {
throw error;
}
- console.log("A retried call failed. Retrying " + attemts_left + " more time(s).");
+ events.emit('verbose', 'A retried call failed. Retrying ' + attemts_left + ' more time(s).');
// retry call self again with the same arguments, except attemts_left is now lower
var fullArguments = [attemts_left, promiseFunction].concat(promiseFunctionArguments);
diff --git a/StoneIsland/platforms/android/cordova/lib/run.js b/StoneIsland/platforms/android/cordova/lib/run.js
index 7f15448c..214a1e19 100755..100644
--- a/StoneIsland/platforms/android/cordova/lib/run.js
+++ b/StoneIsland/platforms/android/cordova/lib/run.js
@@ -25,63 +25,36 @@ var path = require('path'),
build = require('./build'),
emulator = require('./emulator'),
device = require('./device'),
- shell = require('shelljs'),
- Q = require('q');
+ Q = require('q'),
+ events = require('cordova-common').events;
-/*
- * Runs the application on a device if available.
- * If no device is found, it will use a started emulator.
- * If no started emulators are found it will attempt to start an avd.
- * If no avds are found it will error out.
- * Returns a promise.
- */
- module.exports.run = function(args) {
- var buildFlags = [];
+function getInstallTarget(runOptions) {
var install_target;
- var list = false;
-
- for (var i=2; i<args.length; i++) {
- if (build.isBuildFlag(args[i])) {
- buildFlags.push(args[i]);
- } else if (args[i] == '--device') {
- install_target = '--device';
- } else if (args[i] == '--emulator') {
- install_target = '--emulator';
- } else if (/^--target=/.exec(args[i])) {
- install_target = args[i].substring(9, args[i].length);
- } else if (args[i] == '--list') {
- list = true;
- } else {
- console.warn('Option \'' + args[i] + '\' not recognized (ignoring).');
- }
+ if (runOptions.target) {
+ install_target = runOptions.target;
+ } else if (runOptions.device) {
+ install_target = '--device';
+ } else if (runOptions.emulator) {
+ install_target = '--emulator';
}
- if (list) {
- var output = '';
- var temp = '';
- if (!install_target) {
- output += 'Available Android Devices:\n';
- temp = shell.exec(path.join(__dirname, 'list-devices'), {silent:true}).output;
- temp = temp.replace(/^(?=[^\s])/gm, '\t');
- output += temp;
- output += 'Available Android Virtual Devices:\n';
- temp = shell.exec(path.join(__dirname, 'list-emulator-images'), {silent:true}).output;
- temp = temp.replace(/^(?=[^\s])/gm, '\t');
- output += temp;
- } else if (install_target == '--emulator') {
- output += 'Available Android Virtual Devices:\n';
- temp = shell.exec(path.join(__dirname, 'list-emulator-images'), {silent:true}).output;
- temp = temp.replace(/^(?=[^\s])/gm, '\t');
- output += temp;
- } else if (install_target == '--device') {
- output += 'Available Android Devices:\n';
- temp = shell.exec(path.join(__dirname, 'list-devices'), {silent:true}).output;
- temp = temp.replace(/^(?=[^\s])/gm, '\t');
- output += temp;
- }
- console.log(output);
- return;
- }
+ return install_target;
+}
+
+/**
+ * Runs the application on a device if available. If no device is found, it will
+ * use a started emulator. If no started emulators are found it will attempt
+ * to start an avd. If no avds are found it will error out.
+ *
+ * @param {Object} runOptions various run/build options. See Api.js build/run
+ * methods for reference.
+ *
+ * @return {Promise}
+ */
+ module.exports.run = function(runOptions) {
+
+ var self = this;
+ var install_target = getInstallTarget(runOptions);
return Q()
.then(function() {
@@ -90,10 +63,10 @@ var path = require('path'),
return device.list()
.then(function(device_list) {
if (device_list.length > 0) {
- console.log('WARNING : No target specified, deploying to device \'' + device_list[0] + '\'.');
+ events.emit('warn', 'No target specified, deploying to device \'' + device_list[0] + '\'.');
install_target = device_list[0];
} else {
- console.log('WARNING : No target specified, deploying to emulator');
+ events.emit('warn', 'No target specified and no devices found, deploying to emulator');
install_target = '--emulator';
}
});
@@ -137,17 +110,25 @@ var path = require('path'),
});
});
}).then(function(resolvedTarget) {
- return build.run(buildFlags, resolvedTarget).then(function(buildResults) {
+ // Better just call self.build, but we're doing some processing of
+ // build results (according to platformApi spec) so they are in different
+ // format than emulator.install expects.
+ // TODO: Update emulator/device.install to handle this change
+ return build.run.call(self, runOptions, resolvedTarget)
+ .then(function(buildResults) {
if (resolvedTarget.isEmulator) {
- return emulator.install(resolvedTarget, buildResults);
+ return emulator.wait_for_boot(resolvedTarget.target)
+ .then(function () {
+ return emulator.install(resolvedTarget, buildResults);
+ });
}
return device.install(resolvedTarget, buildResults);
});
});
};
-module.exports.help = function(args) {
- console.log('Usage: ' + path.relative(process.cwd(), args[1]) + ' [options]');
+module.exports.help = function() {
+ console.log('Usage: ' + path.relative(process.cwd(), process.argv[1]) + ' [options]');
console.log('Build options :');
console.log(' --debug : Builds project in debug mode');
console.log(' --release : Builds project in release mode');
diff --git a/StoneIsland/platforms/android/cordova/lib/spawn.js b/StoneIsland/platforms/android/cordova/lib/spawn.js
deleted file mode 100755
index 3e500a09..00000000
--- a/StoneIsland/platforms/android/cordova/lib/spawn.js
+++ /dev/null
@@ -1,50 +0,0 @@
-#!/usr/bin/env node
-
-/*
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements. See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership. The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied. See the License for the
- specific language governing permissions and limitations
- under the License.
-*/
-
-var child_process = require('child_process'),
- Q = require('q');
-var isWindows = process.platform.slice(0, 3) == 'win';
-
-// Takes a command and optional current working directory.
-module.exports = function(cmd, args, opt_cwd) {
- var d = Q.defer();
- var opts = { cwd: opt_cwd, stdio: 'inherit' };
- try {
- // Work around spawn not being able to find .bat files.
- if (isWindows) {
- args = [['/s', '/c', '"' + [cmd].concat(args).map(function(a){if (/^[^"].* .*[^"]/.test(a)) return '"' + a + '"'; return a;}).join(' ')+'"'].join(' ')];
- cmd = 'cmd';
- opts.windowsVerbatimArguments = true;
- }
- var child = child_process.spawn(cmd, args, opts);
- child.on('exit', function(code) {
- if (code) {
- d.reject('Error code ' + code + ' for command: ' + cmd + ' with args: ' + args);
- } else {
- d.resolve();
- }
- });
- } catch(e) {
- console.error('error caught: ' + e);
- d.reject(e);
- }
- return d.promise;
-};
diff --git a/StoneIsland/platforms/android/cordova/lib/start-emulator.bat b/StoneIsland/platforms/android/cordova/lib/start-emulator.bat
index 9329d951..9329d951 100755..100644
--- a/StoneIsland/platforms/android/cordova/lib/start-emulator.bat
+++ b/StoneIsland/platforms/android/cordova/lib/start-emulator.bat