You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by GitBox <gi...@apache.org> on 2017/11/29 17:55:44 UTC

[GitHub] infil00p closed pull request #389: CB-11244: Studio Project Compatibility: Now with merge commit

infil00p closed pull request #389: CB-11244: Studio Project Compatibility: Now with merge commit
URL: https://github.com/apache/cordova-android/pull/389
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/bin/lib/create.js b/bin/lib/create.js
index 40df36658..c96ea6cf9 100755
--- a/bin/lib/create.js
+++ b/bin/lib/create.js
@@ -26,9 +26,10 @@ var fs = require('fs');
 var check_reqs = require('./../templates/cordova/lib/check_reqs');
 var ROOT = path.join(__dirname, '..', '..');
 
-var MIN_SDK_VERSION = 16;
+var MIN_SDK_VERSION = 19;
 
 var CordovaError = require('cordova-common').CordovaError;
+var AndroidStudio = require('../templates/cordova/lib/AndroidStudio');
 var AndroidManifest = require('../templates/cordova/lib/AndroidManifest');
 
 // Export all helper functions, and make sure internally within this module, we
@@ -54,10 +55,16 @@ function getFrameworkDir (projectPath, shared) {
     return shared ? path.join(ROOT, 'framework') : path.join(projectPath, 'CordovaLib');
 }
 
-function copyJsAndLibrary (projectPath, shared, projectName) {
+function copyJsAndLibrary (projectPath, shared, projectName, isLegacy) {
     var nestedCordovaLibPath = getFrameworkDir(projectPath, false);
     var srcCordovaJsPath = path.join(ROOT, 'bin', 'templates', 'project', 'assets', 'www', 'cordova.js');
-    shell.cp('-f', srcCordovaJsPath, path.join(projectPath, 'assets', 'www', 'cordova.js'));
+    var app_path = path.join(projectPath, 'app', 'src', 'main');
+
+    if (isLegacy) {
+        app_path = projectPath;
+    }
+
+    shell.cp('-f', srcCordovaJsPath, path.join(app_path, 'assets', 'www', 'cordova.js'));
 
     // Copy the cordova.js file to platforms/<platform>/platform_www/
     // The www dir is nuked on each prepare so we keep cordova.js in platform_www
@@ -70,7 +77,7 @@ function copyJsAndLibrary (projectPath, shared, projectName) {
 
     // Don't fail if there are no old jars.
     exports.setShellFatal(false, function () {
-        shell.ls(path.join(projectPath, 'libs', 'cordova-*.jar')).forEach(function (oldJar) {
+        shell.ls(path.join(app_path, 'libs', 'cordova-*.jar')).forEach(function (oldJar) {
             console.log('Deleting ' + oldJar);
             shell.rm('-f', oldJar);
         });
@@ -136,16 +143,24 @@ function writeProjectProperties (projectPath, target_api) {
     fs.writeFileSync(dstPath, data);
 }
 
-function prepBuildFiles (projectPath) {
+// This makes no sense, what if you're building with a different build system?
+function prepBuildFiles (projectPath, builder) {
     var buildModule = require(path.resolve(projectPath, 'cordova/lib/builders/builders'));
-    buildModule.getBuilder('gradle').prepBuildFiles();
+    buildModule.getBuilder(builder).prepBuildFiles();
 }
 
-function copyBuildRules (projectPath) {
+function copyBuildRules (projectPath, isLegacy) {
     var srcDir = path.join(ROOT, 'bin', 'templates', 'project');
 
-    shell.cp('-f', path.join(srcDir, 'build.gradle'), projectPath);
-    shell.cp('-f', path.join(srcDir, 'wrapper.gradle'), projectPath);
+    if (isLegacy) {
+        // The project's build.gradle is identical to the earlier build.gradle, so it should still work
+        shell.cp('-f', path.join(srcDir, 'legacy', 'build.gradle'), projectPath);
+        shell.cp('-f', path.join(srcDir, 'wrapper.gradle'), projectPath);
+    } else {
+        shell.cp('-f', path.join(srcDir, 'build.gradle'), projectPath);
+        shell.cp('-f', path.join(srcDir, 'app', 'build.gradle'), path.join(projectPath, 'app'));
+        shell.cp('-f', path.join(srcDir, 'wrapper.gradle'), projectPath);
+    }
 }
 
 function copyScripts (projectPath) {
@@ -271,25 +286,37 @@ exports.create = function (project_path, config, options, events) {
 
             exports.setShellFatal(true, function () {
                 var project_template_dir = options.customTemplate || path.join(ROOT, 'bin', 'templates', 'project');
+                var app_path = path.join(project_path, 'app', 'src', 'main');
+
                 // copy project template
-                shell.cp('-r', path.join(project_template_dir, 'assets'), project_path);
-                shell.cp('-r', path.join(project_template_dir, 'res'), project_path);
+                shell.mkdir('-p', app_path);
+                shell.cp('-r', path.join(project_template_dir, 'assets'), app_path);
+                shell.cp('-r', path.join(project_template_dir, 'res'), app_path);
                 shell.cp(path.join(project_template_dir, 'gitignore'), path.join(project_path, '.gitignore'));
 
                 // Manually create directories that would be empty within the template (since git doesn't track directories).
-                shell.mkdir(path.join(project_path, 'libs'));
+                shell.mkdir(path.join(app_path, 'libs'));
 
                 // copy cordova.js, cordova.jar
                 exports.copyJsAndLibrary(project_path, options.link, safe_activity_name);
 
+                // Set up ther Android Studio paths
+                var java_path = path.join(app_path, 'java');
+                var assets_path = path.join(app_path, 'assets');
+                var resource_path = path.join(app_path, 'res');
+                shell.mkdir('-p', java_path);
+                shell.mkdir('-p', assets_path);
+                shell.mkdir('-p', resource_path);
+
                 // interpolate the activity name and package
                 var packagePath = package_name.replace(/\./g, path.sep);
-                var activity_dir = path.join(project_path, 'src', packagePath);
+                var activity_dir = path.join(java_path, packagePath);
                 var activity_path = path.join(activity_dir, safe_activity_name + '.java');
+
                 shell.mkdir('-p', activity_dir);
                 shell.cp('-f', path.join(project_template_dir, 'Activity.java'), activity_path);
                 shell.sed('-i', /__ACTIVITY__/, safe_activity_name, activity_path);
-                shell.sed('-i', /__NAME__/, project_name, path.join(project_path, 'res', 'values', 'strings.xml'));
+                shell.sed('-i', /__NAME__/, project_name, path.join(app_path, 'res', 'values', 'strings.xml'));
                 shell.sed('-i', /__ID__/, package_name, activity_path);
 
                 var manifest = new AndroidManifest(path.join(project_template_dir, 'AndroidManifest.xml'));
@@ -297,7 +324,7 @@ exports.create = function (project_path, config, options, events) {
                     .setTargetSdkVersion(target_api.split('-')[1])
                     .getActivity().setName(safe_activity_name);
 
-                var manifest_path = path.join(project_path, 'AndroidManifest.xml');
+                var manifest_path = path.join(app_path, 'AndroidManifest.xml');
                 manifest.write(manifest_path);
 
                 exports.copyScripts(project_path);
@@ -305,7 +332,7 @@ exports.create = function (project_path, config, options, events) {
             });
             // Link it to local android install.
             exports.writeProjectProperties(project_path, target_api);
-            exports.prepBuildFiles(project_path);
+            exports.prepBuildFiles(project_path, 'studio');
             events.emit('log', generateDoneMessage('create', options.link));
         }).thenResolve(project_path);
 };
@@ -326,7 +353,18 @@ exports.update = function (projectPath, options, events) {
     return Q()
         .then(function () {
 
-            var manifest = new AndroidManifest(path.join(projectPath, 'AndroidManifest.xml'));
+            var isAndroidStudio = AndroidStudio.isAndroidStudioProject(projectPath);
+            var isLegacy = !isAndroidStudio;
+            var manifest = null;
+            var builder = 'gradle';
+
+            if (isAndroidStudio) {
+                manifest = new AndroidManifest(path.join(projectPath, 'app', 'main', 'AndroidManifest.xml'));
+                builder = 'studio';
+            } else {
+                manifest = new AndroidManifest(path.join(projectPath, 'AndroidManifest.xml'));
+                builder = 'gradle';
+            }
 
             if (Number(manifest.getMinSdkVersion()) < MIN_SDK_VERSION) {
                 events.emit('verbose', 'Updating minSdkVersion to ' + MIN_SDK_VERSION + ' in AndroidManifest.xml');
@@ -338,11 +376,11 @@ exports.update = function (projectPath, options, events) {
             var projectName = manifest.getActivity().getName();
             var target_api = check_reqs.get_target();
 
-            exports.copyJsAndLibrary(projectPath, options.link, projectName);
+            exports.copyJsAndLibrary(projectPath, options.link, projectName, isLegacy);
             exports.copyScripts(projectPath);
-            exports.copyBuildRules(projectPath);
+            exports.copyBuildRules(projectPath, isLegacy);
             exports.writeProjectProperties(projectPath, target_api);
-            exports.prepBuildFiles(projectPath);
+            exports.prepBuildFiles(projectPath, builder);
             events.emit('log', generateDoneMessage('update', options.link));
         }).thenResolve(projectPath);
 };
diff --git a/bin/templates/cordova/Api.js b/bin/templates/cordova/Api.js
index 107108ea9..e97f5380a 100644
--- a/bin/templates/cordova/Api.js
+++ b/bin/templates/cordova/Api.js
@@ -56,6 +56,7 @@ function setupEvents (externalEventEmitter) {
 function Api (platform, platformRootDir, events) {
     this.platform = PLATFORM;
     this.root = path.resolve(__dirname, '..');
+    this.builder = 'gradle';
 
     setupEvents(events);
 
@@ -71,6 +72,7 @@ function Api (platform, platformRootDir, events) {
         strings: path.join(self.root, 'res/values/strings.xml'),
         manifest: path.join(self.root, 'AndroidManifest.xml'),
         build: path.join(self.root, 'build'),
+        javaSrc: path.join(self.root, 'src'),
         // NOTE: Due to platformApi spec we need to return relative paths here
         cordovaJs: 'bin/templates/project/assets/www/cordova.js',
         cordovaJsSrc: 'cordova-js-src'
@@ -79,10 +81,13 @@ function Api (platform, platformRootDir, events) {
     // XXX Override some locations for Android Studio projects
     if (AndroidStudio.isAndroidStudioProject(self.root) === true) {
         selfEvents.emit('log', 'Android Studio project detected');
+        this.builder = 'studio';
         this.android_studio = true;
         this.locations.configXml = path.join(self.root, 'app/src/main/res/xml/config.xml');
-        this.locations.strings = path.join(self.root, 'app/src/main/res/xml/strings.xml');
+        this.locations.strings = path.join(self.root, 'app/src/main/res/values/strings.xml');
         this.locations.manifest = path.join(self.root, 'app/src/main/AndroidManifest.xml');
+        // We could have Java Source, we could have other languages
+        this.locations.javaSrc = path.join(self.root, 'app/src/main/java/');
         this.locations.www = path.join(self.root, 'app/src/main/assets/www');
         this.locations.res = path.join(self.root, 'app/src/main/res');
     }
@@ -226,7 +231,6 @@ Api.prototype.addPlugin = function (plugin, installOptions) {
         // CB-11964: Do a clean when installing the plugin code to get around
         // the Gradle bug introduced by the Android Gradle Plugin Version 2.2
         // TODO: Delete when the next version of Android Gradle plugin comes out
-
         // Since clean doesn't just clean the build, it also wipes out www, we need
         // to pass additional options.
 
@@ -243,9 +247,9 @@ Api.prototype.addPlugin = function (plugin, installOptions) {
         return PluginManager.get(self.platform, self.locations, project).addPlugin(plugin, installOptions);
     }).then(function () {
         if (plugin.getFrameworks(this.platform).length === 0) return;
-
         selfEvents.emit('verbose', 'Updating build files since android plugin contained <framework>');
-        require('./lib/builders/builders').getBuilder('gradle').prepBuildFiles();
+          // This should pick the correct builder, not just get gradle
+        require('./lib/builders/builders').getBuilder(this.builder).prepBuildFiles();
     }.bind(this))
         // CB-11022 Return truthy value to prevent running prepare after
         .thenResolve(true);
@@ -278,7 +282,7 @@ Api.prototype.removePlugin = function (plugin, uninstallOptions) {
             if (plugin.getFrameworks(this.platform).length === 0) return;
 
             selfEvents.emit('verbose', 'Updating build files since android plugin contained <framework>');
-            require('./lib/builders/builders').getBuilder('gradle').prepBuildFiles();
+            require('./lib/builders/builders').getBuilder(this.builder).prepBuildFiles();
         }.bind(this))
         // CB-11022 Return truthy value to prevent running prepare after
         .thenResolve(true);
@@ -331,6 +335,9 @@ Api.prototype.removePlugin = function (plugin, uninstallOptions) {
  */
 Api.prototype.build = function (buildOptions) {
     var self = this;
+    if (this.android_studio) {
+        buildOptions.studio = true;
+    }
     return require('./lib/check_reqs').run().then(function () {
         return require('./lib/build').run.call(self, buildOptions);
     }).then(function (buildResults) {
@@ -374,6 +381,14 @@ Api.prototype.run = function (runOptions) {
  */
 Api.prototype.clean = function (cleanOptions) {
     var self = this;
+    if (this.android_studio) {
+        // This will lint, checking for null won't
+        if (typeof cleanOptions === 'undefined') {
+            cleanOptions = {};
+        }
+        cleanOptions.studio = true;
+    }
+
     return require('./lib/check_reqs').run().then(function () {
         return require('./lib/build').runClean.call(self, cleanOptions);
     }).then(function () {
diff --git a/bin/templates/cordova/lib/AndroidStudio.js b/bin/templates/cordova/lib/AndroidStudio.js
index 860301af8..fbcb926f2 100644
--- a/bin/templates/cordova/lib/AndroidStudio.js
+++ b/bin/templates/cordova/lib/AndroidStudio.js
@@ -11,8 +11,8 @@ 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'];
+    var eclipseFiles = ['AndroidManifest.xml', 'libs', 'res'];
+    var androidStudioFiles = ['app', 'app/src/main'];
 
     // assume it is an AS project and not an Eclipse project
     var isEclipse = false;
diff --git a/bin/templates/cordova/lib/build.js b/bin/templates/cordova/lib/build.js
index 7c2238ace..e33cfae08 100644
--- a/bin/templates/cordova/lib/build.js
+++ b/bin/templates/cordova/lib/build.js
@@ -35,7 +35,7 @@ function parseOpts (options, resolvedTarget, projectRoot) {
     options = options || {};
     options.argv = nopt({
         gradle: Boolean,
-        ant: Boolean,
+        studio: Boolean,
         prepenv: Boolean,
         versionCode: String,
         minSdkVersion: String,
@@ -47,15 +47,22 @@ function parseOpts (options, resolvedTarget, projectRoot) {
         keystoreType: String
     }, {}, options.argv, 0);
 
+    // Android Studio Build method is the default
     var ret = {
         buildType: options.release ? 'release' : 'debug',
-        buildMethod: process.env.ANDROID_BUILD || 'gradle',
+        buildMethod: process.env.ANDROID_BUILD || 'studio',
         prepEnv: options.argv.prepenv,
         arch: resolvedTarget && resolvedTarget.arch,
         extraArgs: []
     };
 
-    if (options.argv.ant || options.argv.gradle) { ret.buildMethod = options.argv.ant ? 'ant' : 'gradle'; }
+    if (options.argv.gradle || options.argv.studio) {
+        ret.buildMethod = options.argv.studio ? 'studio' : 'gradle';
+    }
+
+    // This comes from cordova/run
+    if (options.studio) ret.buildMethod = 'studio';
+    if (options.gradle) ret.buildMethod = 'gradle';
 
     if (options.nobuild) ret.buildMethod = 'none';
 
@@ -142,6 +149,7 @@ module.exports.runClean = function (options) {
  */
 module.exports.run = function (options, optResolvedTarget) {
     var opts = parseOpts(options, optResolvedTarget, this.root);
+    console.log(opts.buildMethod);
     var builder = builders.getBuilder(opts.buildMethod);
     return builder.prepEnv(opts).then(function () {
         if (opts.prepEnv) {
diff --git a/bin/templates/cordova/lib/builders/AntBuilder.js b/bin/templates/cordova/lib/builders/AntBuilder.js
deleted file mode 100644
index cc08c2329..000000000
--- a/bin/templates/cordova/lib/builders/AntBuilder.js
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
-       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.
-*/
-/* eslint no-unused-vars: 0 */
-
-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/bin/templates/cordova/lib/builders/GenericBuilder.js b/bin/templates/cordova/lib/builders/GenericBuilder.js
index 6515533af..892aa38f4 100644
--- a/bin/templates/cordova/lib/builders/GenericBuilder.js
+++ b/bin/templates/cordova/lib/builders/GenericBuilder.js
@@ -24,20 +24,15 @@ 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'),
+        studio: path.join(this.root, 'app', 'build', 'outputs', 'apk'),
         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();
 };
@@ -59,37 +54,6 @@ GenericBuilder.prototype.findOutputApks = function (build_type, 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) {
diff --git a/bin/templates/cordova/lib/builders/GradleBuilder.js b/bin/templates/cordova/lib/builders/GradleBuilder.js
index 53b4e3379..8237c739e 100644
--- a/bin/templates/cordova/lib/builders/GradleBuilder.js
+++ b/bin/templates/cordova/lib/builders/GradleBuilder.js
@@ -82,12 +82,50 @@ GradleBuilder.prototype.runGradleWrapper = function (gradle_cmd, gradle_file) {
     }
 };
 
+/*
+ * We need to kill this in a fire.
+ */
+
+GradleBuilder.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)
+    };
+};
+
+GradleBuilder.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);
+};
+
 // 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;
+
+    // Check and copy the gradle file into the subproject.
+    // Called by the loop below this function def.
     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
@@ -98,11 +136,15 @@ GradleBuilder.prototype.prepBuildFiles = function () {
             shell.cp('-f', pluginBuildGradle, subProjectGradle);
         }
     };
+
+    // Some dependencies on Android don't use gradle, or don't have default
+    // gradle files.  This copies a dummy gradle file into them
     for (var i = 0; i < subProjects.length; ++i) {
-        if (subProjects[i] !== 'CordovaLib') {
+        if (subProjects[i] !== 'CordovaLib' && subProjects[i] !== 'app') {
             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) {
@@ -121,6 +163,10 @@ GradleBuilder.prototype.prepBuildFiles = function () {
     var buildGradle = fs.readFileSync(path.join(this.root, 'build.gradle'), 'utf8');
     var depsList = '';
     var root = this.root;
+
+    // Cordova Plugins can be written as library modules that would use Cordova as a
+    // dependency.  Because we need to make sure that Cordova is compiled only once for
+    // dexing, we make sure to exclude CordovaLib from these modules
     var insertExclude = function (p) {
         var gradlePath = path.join(root, p, 'build.gradle');
         var projectGradleFile = fs.readFileSync(gradlePath, 'utf-8');
@@ -130,12 +176,14 @@ GradleBuilder.prototype.prepBuildFiles = function () {
             depsList += '\n';
         }
     };
+
     subProjects.forEach(function (p) {
         console.log('Subproject Path: ' + p);
         var libName = p.replace(/[/\\]/g, ':').replace(name + '-', '');
         depsList += '    implementation(project(path: "' + libName + '"))';
         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:+'],
@@ -160,6 +208,9 @@ GradleBuilder.prototype.prepBuildFiles = function () {
         }
         depsList += '    compile "' + mavenRef + '"\n';
     });
+
+    // This code is dangerous and actually writes gradle declarations directly into the build.gradle
+    // Try not to mess with this if possible
     buildGradle = buildGradle.replace(/(SUB-PROJECT DEPENDENCIES START)[\s\S]*(\/\/ SUB-PROJECT DEPENDENCIES END)/, '$1\n' + depsList + '    $2');
     var includeList = '';
     propertiesObj.gradleIncludes.forEach(function (includePath) {
diff --git a/bin/templates/cordova/lib/builders/StudioBuilder.js b/bin/templates/cordova/lib/builders/StudioBuilder.js
new file mode 100644
index 000000000..38923e145
--- /dev/null
+++ b/bin/templates/cordova/lib/builders/StudioBuilder.js
@@ -0,0 +1,302 @@
+/*
+       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 StudioBuilder (projectRoot) {
+    GenericBuilder.call(this, projectRoot);
+
+    this.binDirs = {gradle: this.binDirs.studio};
+}
+
+util.inherits(StudioBuilder, GenericBuilder);
+
+StudioBuilder.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;
+};
+
+/*
+ * This returns a promise
+ */
+
+StudioBuilder.prototype.runGradleWrapper = function (gradle_cmd) {
+    var gradlePath = path.join(this.root, 'gradlew');
+    var wrapperGradle = path.join(this.root, 'wrapper.gradle');
+    if (fs.existsSync(gradlePath)) {
+        // Literally do nothing, for some reason this works, while !fs.existsSync didn't on Windows
+    } else {
+        return spawn(gradle_cmd, ['-p', this.root, 'wrapper', '-b', wrapperGradle], {stdio: 'inherit'});
+    }
+};
+
+StudioBuilder.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)
+    };
+};
+
+StudioBuilder.prototype.extractRealProjectNameFromManifest = function () {
+    var manifestPath = path.join(this.root, 'app', 'src', 'main', '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);
+};
+
+// Makes the project buildable, minus the gradle wrapper.
+StudioBuilder.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;
+
+    // Check and copy the gradle file into the subproject
+    // Called by the loop before this function def
+
+    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;
+    });
+
+    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, 'app', '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 + '-', '');
+        if (libName !== 'app') {
+            depsList += '    implementation(project(path: ":' + libName + '"))';
+            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');
+    // This needs to be stored in the app gradle, not the root grade
+    fs.writeFileSync(path.join(this.root, 'app', 'build.gradle'), buildGradle);
+};
+
+StudioBuilder.prototype.prepEnv = function (opts) {
+    var self = this;
+    return check_reqs.check_gradle()
+      .then(function (gradlePath) {
+          return self.runGradleWrapper(gradlePath);
+      }).then(function () {
+          return self.prepBuildFiles();
+      }).then(function () {
+          // 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 = process.env['CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL'] || 'https\\://services.gradle.org/distributions/gradle-4.1-all.zip';
+          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.
+ */
+StudioBuilder.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);
+    });
+};
+
+StudioBuilder.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 = StudioBuilder;
+
+function isAutoGenerated (file) {
+    return fs.existsSync(file) && fs.readFileSync(file, 'utf8').indexOf(MARKER) > 0;
+}
diff --git a/bin/templates/cordova/lib/builders/builders.js b/bin/templates/cordova/lib/builders/builders.js
index a08a58815..aedf9be52 100644
--- a/bin/templates/cordova/lib/builders/builders.js
+++ b/bin/templates/cordova/lib/builders/builders.js
@@ -20,8 +20,8 @@
 var CordovaError = require('cordova-common').CordovaError;
 
 var knownBuilders = {
-    ant: 'AntBuilder',
     gradle: 'GradleBuilder',
+    studio: 'StudioBuilder',
     none: 'GenericBuilder'
 };
 
diff --git a/bin/templates/cordova/lib/device.js b/bin/templates/cordova/lib/device.js
index e30d044e2..84b509479 100644
--- a/bin/templates/cordova/lib/device.js
+++ b/bin/templates/cordova/lib/device.js
@@ -81,7 +81,7 @@ module.exports.install = function (target, buildResults) {
         return module.exports.resolveTarget(target);
     }).then(function (resolvedTarget) {
         var apk_path = build.findBestApkForArchitecture(buildResults, resolvedTarget.arch);
-        var manifest = new AndroidManifest(path.join(__dirname, '../../AndroidManifest.xml'));
+        var manifest = new AndroidManifest(path.join(__dirname, '../../app/src/main/AndroidManifest.xml'));
         var pkgName = manifest.getPackageId();
         var launchName = pkgName + '/.' + manifest.getActivity().getName();
         events.emit('log', 'Using apk: ' + apk_path);
diff --git a/bin/templates/cordova/lib/emulator.js b/bin/templates/cordova/lib/emulator.js
index edb7c800f..0a23084ea 100644
--- a/bin/templates/cordova/lib/emulator.js
+++ b/bin/templates/cordova/lib/emulator.js
@@ -432,7 +432,12 @@ module.exports.resolveTarget = function (target) {
 module.exports.install = function (givenTarget, buildResults) {
 
     var target;
-    var manifest = new AndroidManifest(path.join(__dirname, '../../AndroidManifest.xml'));
+    // We need to find the proper path to the Android Manifest
+    var manifestPath = path.join(__dirname, '..', '..', 'app', 'src', 'main', 'AndroidManifest.xml');
+    if (buildResults.buildMethod === 'gradle') {
+        manifestPath = path.join(__dirname, '../../AndroidManifest.xml');
+    }
+    var manifest = new AndroidManifest(manifestPath);
     var pkgName = manifest.getPackageId();
 
     // resolve the target emulator
diff --git a/bin/templates/cordova/lib/pluginHandlers.js b/bin/templates/cordova/lib/pluginHandlers.js
index d14baba65..842489ae0 100644
--- a/bin/templates/cordova/lib/pluginHandlers.js
+++ b/bin/templates/cordova/lib/pluginHandlers.js
@@ -1,7 +1,4 @@
 /*
- *
- * 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
@@ -33,8 +30,19 @@ var handlers = {
 
             var dest = path.join(obj.targetDir, path.basename(obj.src));
 
+            // TODO: This code needs to be replaced, since the core plugins need to be re-mapped to a different location in
+            // a later plugins release.  This is for legacy plugins to work with Cordova.
+
             if (options && options.android_studio === true) {
-                dest = path.join('app/src/main/java', obj.targetDir.substring(4), path.basename(obj.src));
+                // If a Java file is using the new directory structure, don't penalize it
+                if (!obj.targetDir.includes('app/src/main')) {
+                    if (obj.src.endsWith('.java')) {
+                        dest = path.join('app/src/main/java', obj.targetDir.substring(4), path.basename(obj.src));
+                    } else if (obj.src.endsWith('.xml')) {
+                        // We are making a huge assumption here that XML files will be going to res/xml or values/xml
+                        dest = path.join('app/src/main', obj.targetDir, path.basename(obj.src));
+                    }
+                }
             }
 
             if (options && options.force) {
@@ -71,10 +79,18 @@ var handlers = {
     },
     'resource-file': {
         install: function (obj, plugin, project, options) {
-            copyFile(plugin.dir, obj.src, project.projectDir, path.normalize(obj.target), !!(options && options.link));
+            var dest = path.normalize(obj.target);
+            if (options && options.android_studio === true) {
+                dest = path.join('app/src/main', dest);
+            }
+            copyFile(plugin.dir, obj.src, project.projectDir, dest, !!(options && options.link));
         },
         uninstall: function (obj, plugin, project, options) {
-            removeFile(project.projectDir, path.normalize(obj.target));
+            var dest = path.normalize(obj.target);
+            if (options && options.android_studio === true) {
+                dest = path.join('app/src/main', dest);
+            }
+            removeFile(project.projectDir, dest);
         }
     },
     'framework': {
diff --git a/bin/templates/cordova/lib/prepare.js b/bin/templates/cordova/lib/prepare.js
index 39f9ae4e5..ac63f8a68 100644
--- a/bin/templates/cordova/lib/prepare.js
+++ b/bin/templates/cordova/lib/prepare.js
@@ -199,7 +199,8 @@ function updateProjectAccordingTo (platformConfig, locations) {
         .setTargetSdkVersion(platformConfig.getPreference('android-targetSdkVersion', 'android'))
         .write();
 
-    var javaPattern = path.join(locations.root, 'src', manifestId.replace(/\./g, '/'), '*.java');
+    // Java file paths shouldn't be hard coded
+    var javaPattern = path.join(locations.javaSrc, manifestId.replace(/\./g, '/'), '*.java');
     var java_files = shell.ls(javaPattern).filter(function (f) {
         return shell.grep(/extends\s+CordovaActivity/g, f);
     });
@@ -210,7 +211,7 @@ function updateProjectAccordingTo (platformConfig, locations) {
         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', androidPkgName.replace(/\./g, '/'), path.basename(java_files[0]));
+    var destFile = path.join(locations.root, 'app', 'src', 'main', 'java', androidPkgName.replace(/\./g, '/'), path.basename(java_files[0]));
     shell.mkdir('-p', path.dirname(destFile));
     shell.sed(/package [\w\.]*;/, 'package ' + androidPkgName + ';', java_files[0]).to(destFile);
     events.emit('verbose', 'Wrote out Android package name "' + androidPkgName + '" to ' + destFile);
diff --git a/bin/templates/project/app/build.gradle b/bin/templates/project/app/build.gradle
new file mode 100644
index 000000000..c5f5b2332
--- /dev/null
+++ b/bin/templates/project/app/build.gradle
@@ -0,0 +1,324 @@
+/*
+       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.
+*/
+
+apply plugin: 'com.android.application'
+
+buildscript {
+    repositories {
+        mavenCentral()
+        jcenter()
+        maven {
+            url "https://maven.google.com"
+        }
+    }
+
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.0.0'
+    }
+}
+
+// Allow plugins to declare Maven dependencies via build-extras.gradle.
+allprojects {
+    repositories {
+        mavenCentral();
+        jcenter()
+    }
+}
+
+task wrapper(type: Wrapper) {
+    gradleVersion = '4.1.0'
+}
+
+// Configuration properties. Set these via environment variables, build-extras.gradle, or gradle.properties.
+// Refer to: http://www.gradle.org/docs/current/userguide/tutorial_this_and_that.html
+ext {
+    apply from: '../CordovaLib/cordova.gradle'
+    // The value for android.compileSdkVersion.
+    if (!project.hasProperty('cdvCompileSdkVersion')) {
+        cdvCompileSdkVersion = null;
+    }
+    // The value for android.buildToolsVersion.
+    if (!project.hasProperty('cdvBuildToolsVersion')) {
+        cdvBuildToolsVersion = null;
+    }
+    // Sets the versionCode to the given value.
+    if (!project.hasProperty('cdvVersionCode')) {
+        cdvVersionCode = null
+    }
+    // Sets the minSdkVersion to the given value.
+    if (!project.hasProperty('cdvMinSdkVersion')) {
+        cdvMinSdkVersion = null
+    }
+    // Whether to build architecture-specific APKs.
+    if (!project.hasProperty('cdvBuildMultipleApks')) {
+        cdvBuildMultipleApks = null
+    }
+    // Whether to append a 0 "abi digit" to versionCode when only a single APK is build 
+    if (!project.hasProperty('cdvVersionCodeForceAbiDigit')) {
+        cdvVersionCodeForceAbiDigit = null
+    }
+    // .properties files to use for release signing.
+    if (!project.hasProperty('cdvReleaseSigningPropertiesFile')) {
+        cdvReleaseSigningPropertiesFile = null
+    }
+    // .properties files to use for debug signing.
+    if (!project.hasProperty('cdvDebugSigningPropertiesFile')) {
+        cdvDebugSigningPropertiesFile = null
+    }
+    // Set by build.js script.
+    if (!project.hasProperty('cdvBuildArch')) {
+        cdvBuildArch = null
+    }
+
+    // Plugin gradle extensions can append to this to have code run at the end.
+    cdvPluginPostBuildExtras = []
+}
+
+// PLUGIN GRADLE EXTENSIONS START
+// PLUGIN GRADLE EXTENSIONS END
+
+def hasBuildExtras = file('build-extras.gradle').exists()
+if (hasBuildExtras) {
+    apply from: 'build-extras.gradle'
+}
+
+// Set property defaults after extension .gradle files.
+if (ext.cdvCompileSdkVersion == null) {
+    ext.cdvCompileSdkVersion = privateHelpers.getProjectTarget()
+    //ext.cdvCompileSdkVersion = project.ext.defaultCompileSdkVersion
+}
+if (ext.cdvBuildToolsVersion == null) {
+    ext.cdvBuildToolsVersion = privateHelpers.findLatestInstalledBuildTools()
+    //ext.cdvBuildToolsVersion = project.ext.defaultBuildToolsVersion
+}
+if (ext.cdvDebugSigningPropertiesFile == null && file('debug-signing.properties').exists()) {
+    ext.cdvDebugSigningPropertiesFile = 'debug-signing.properties'
+}
+if (ext.cdvReleaseSigningPropertiesFile == null && file('release-signing.properties').exists()) {
+    ext.cdvReleaseSigningPropertiesFile = 'release-signing.properties'
+}
+
+// Cast to appropriate types.
+ext.cdvBuildMultipleApks = cdvBuildMultipleApks == null ? false : cdvBuildMultipleApks.toBoolean();
+ext.cdvVersionCodeForceAbiDigit = cdvVersionCodeForceAbiDigit == null ? false : cdvVersionCodeForceAbiDigit.toBoolean();
+ext.cdvMinSdkVersion = cdvMinSdkVersion == null ? null : defaultMinSdkVersion
+ext.cdvVersionCode = cdvVersionCode == null ? null : Integer.parseInt('' + cdvVersionCode)
+
+def computeBuildTargetName(debugBuild) {
+    def ret = 'assemble'
+    if (cdvBuildMultipleApks && cdvBuildArch) {
+        def arch = cdvBuildArch == 'arm' ? 'armv7' : cdvBuildArch
+        ret += '' + arch.toUpperCase().charAt(0) + arch.substring(1);
+    }
+    return ret + (debugBuild ? 'Debug' : 'Release')
+}
+
+// Make cdvBuild a task that depends on the debug/arch-sepecific task.
+task cdvBuildDebug
+cdvBuildDebug.dependsOn {
+    return computeBuildTargetName(true)
+}
+
+task cdvBuildRelease
+cdvBuildRelease.dependsOn {
+    return computeBuildTargetName(false)
+}
+
+task cdvPrintProps << {
+    println('cdvCompileSdkVersion=' + cdvCompileSdkVersion)
+    println('cdvBuildToolsVersion=' + cdvBuildToolsVersion)
+    println('cdvVersionCode=' + cdvVersionCode)
+    println('cdvVersionCodeForceAbiDigit=' + cdvVersionCodeForceAbiDigit)
+    println('cdvMinSdkVersion=' + cdvMinSdkVersion)
+    println('cdvBuildMultipleApks=' + cdvBuildMultipleApks)
+    println('cdvReleaseSigningPropertiesFile=' + cdvReleaseSigningPropertiesFile)
+    println('cdvDebugSigningPropertiesFile=' + cdvDebugSigningPropertiesFile)
+    println('cdvBuildArch=' + cdvBuildArch)
+    println('computedVersionCode=' + android.defaultConfig.versionCode)
+    android.productFlavors.each { flavor ->
+        println('computed' + flavor.name.capitalize() + 'VersionCode=' + flavor.versionCode)
+    }
+}
+
+android {
+
+    defaultConfig {
+        versionCode cdvVersionCode ?: new BigInteger("" + privateHelpers.extractIntFromManifest("versionCode"))
+        applicationId privateHelpers.extractStringFromManifest("package")
+
+        if (cdvMinSdkVersion != null) {
+            minSdkVersion cdvMinSdkVersion
+        }
+    }
+
+    lintOptions {
+      abortOnError false;
+    }
+
+    compileSdkVersion cdvCompileSdkVersion
+    buildToolsVersion cdvBuildToolsVersion
+
+    //This code exists for Crosswalk and other Native APIs.
+    //By default, we multiply the existing version code in the Android Manifest by 10 and 
+    //add a number for each architecture.  If you are not using Crosswalk or SQLite, you can
+    //ignore this chunk of code, and your version codes will be respected.
+
+    if (Boolean.valueOf(cdvBuildMultipleApks)) {
+        flavorDimensions "default"
+
+        productFlavors {
+            armeabi {
+                versionCode defaultConfig.versionCode*10 + 1
+                ndk {
+                    abiFilters = ["armeabi"]
+                }
+            }
+            armv7 {
+                versionCode defaultConfig.versionCode*10 + 2
+                ndk {
+                    abiFilters = ["armeabi-v7a"]
+                }
+            }
+            arm64 {
+                versionCode defaultConfig.versionCode*10 + 3
+                ndk {
+                    abiFilters = ["arm64-v8a"]
+                }
+            }
+            x86 {
+                versionCode defaultConfig.versionCode*10 + 4
+                ndk {
+                    abiFilters = ["x86"]
+                }
+            }
+            x86_64 {
+                versionCode defaultConfig.versionCode*10 + 5
+                ndk {
+                    abiFilters = ["x86_64"]
+                }
+            }
+        }
+    } else if (Boolean.valueOf(cdvVersionCodeForceAbiDigit)) {
+        // This provides compatibility to the default logic for versionCode before cordova-android 5.2.0
+        defaultConfig {
+            versionCode defaultConfig.versionCode*10
+        }
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    if (cdvReleaseSigningPropertiesFile) {
+        signingConfigs {
+            release {
+                // These must be set or Gradle will complain (even if they are overridden).
+                keyAlias = ""
+                keyPassword = "__unset" // And these must be set to non-empty in order to have the signing step added to the task graph.
+                storeFile = null
+                storePassword = "__unset"
+            }
+        }
+        buildTypes {
+            release {
+                signingConfig signingConfigs.release
+            }
+        }
+        addSigningProps(cdvReleaseSigningPropertiesFile, signingConfigs.release)
+    }
+    if (cdvDebugSigningPropertiesFile) {
+        addSigningProps(cdvDebugSigningPropertiesFile, signingConfigs.debug)
+    }
+}
+
+/*
+ * WARNING: Cordova Lib and platform scripts do management inside of this code here,
+ * if you are adding the dependencies manually, do so outside the comments, otherwise
+ * the Cordova tools will overwrite them
+ */
+
+
+dependencies {
+    implementation fileTree(dir: 'libs', include: '*.jar')
+    // SUB-PROJECT DEPENDENCIES START
+    debugCompile(project(path: ":CordovaLib", configuration: "debug"))
+    releaseCompile(project(path: ":CordovaLib", configuration: "release"))
+    // SUB-PROJECT DEPENDENCIES END
+}
+
+def promptForReleaseKeyPassword() {
+    if (!cdvReleaseSigningPropertiesFile) {
+        return;
+    }
+    if ('__unset'.equals(android.signingConfigs.release.storePassword)) {
+        android.signingConfigs.release.storePassword = privateHelpers.promptForPassword('Enter key store password: ')
+    }
+    if ('__unset'.equals(android.signingConfigs.release.keyPassword)) {
+        android.signingConfigs.release.keyPassword = privateHelpers.promptForPassword('Enter key password: ');
+    }
+}
+
+gradle.taskGraph.whenReady { taskGraph ->
+    taskGraph.getAllTasks().each() { task ->
+      if(['validateReleaseSigning', 'validateSigningRelease', 'validateSigningArmv7Release', 'validateSigningX76Release'].contains(task.name)) {
+         promptForReleaseKeyPassword()
+      }
+    }
+}
+
+def addSigningProps(propsFilePath, signingConfig) {
+    def propsFile = file(propsFilePath)
+    def props = new Properties()
+    propsFile.withReader { reader ->
+        props.load(reader)
+    }
+
+    def storeFile = new File(props.get('key.store') ?: privateHelpers.ensureValueExists(propsFilePath, props, 'storeFile'))
+    if (!storeFile.isAbsolute()) {
+        storeFile = RelativePath.parse(true, storeFile.toString()).getFile(propsFile.getParentFile())
+    }
+    if (!storeFile.exists()) {
+        throw new FileNotFoundException('Keystore file does not exist: ' + storeFile.getAbsolutePath())
+    }
+    signingConfig.keyAlias = props.get('key.alias') ?: privateHelpers.ensureValueExists(propsFilePath, props, 'keyAlias')
+    signingConfig.keyPassword = props.get('keyPassword', props.get('key.alias.password', signingConfig.keyPassword))
+    signingConfig.storeFile = storeFile
+    signingConfig.storePassword = props.get('storePassword', props.get('key.store.password', signingConfig.storePassword))
+    def storeType = props.get('storeType', props.get('key.store.type', ''))
+    if (!storeType) {
+        def filename = storeFile.getName().toLowerCase();
+        if (filename.endsWith('.p12') || filename.endsWith('.pfx')) {
+            storeType = 'pkcs12'
+        } else {
+            storeType = signingConfig.storeType // "jks"
+        }
+    }
+    signingConfig.storeType = storeType
+}
+
+for (def func : cdvPluginPostBuildExtras) {
+    func()
+}
+
+// This can be defined within build-extras.gradle as:
+//     ext.postBuildExtras = { ... code here ... }
+if (hasProperty('postBuildExtras')) {
+    postBuildExtras()
+}
diff --git a/bin/templates/project/build.gradle b/bin/templates/project/build.gradle
index 07d4dfa6c..fb8082da2 100644
--- a/bin/templates/project/build.gradle
+++ b/bin/templates/project/build.gradle
@@ -1,23 +1,22 @@
-/*
-       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.
+/* 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.
 */
 
-apply plugin: 'com.android.application'
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
 
 buildscript {
     repositories {
@@ -26,17 +25,14 @@ buildscript {
             url "https://maven.google.com"
         }
     }
-
-    // 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
     dependencies {
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
         classpath 'com.android.tools.build:gradle:3.0.0'
     }
 }
 
-// Allow plugins to declare Maven dependencies via build-extras.gradle.
 allprojects {
     repositories {
         jcenter()
@@ -44,298 +40,15 @@ allprojects {
             url "https://maven.google.com"
         }
     }
-}
-
-task wrapper(type: Wrapper) {
-    gradleVersion = '4.1.0'
-}
-
-// Configuration properties. Set these via environment variables, build-extras.gradle, or gradle.properties.
-// Refer to: http://www.gradle.org/docs/current/userguide/tutorial_this_and_that.html
-ext {
-    apply from: 'CordovaLib/cordova.gradle'
-    // The value for android.compileSdkVersion.
-    if (!project.hasProperty('cdvCompileSdkVersion')) {
-        cdvCompileSdkVersion = null;
-    }
-    // The value for android.buildToolsVersion.
-    if (!project.hasProperty('cdvBuildToolsVersion')) {
-        cdvBuildToolsVersion = null;
-    }
-    // Sets the versionCode to the given value.
-    if (!project.hasProperty('cdvVersionCode')) {
-        cdvVersionCode = null
-    }
-    // Sets the minSdkVersion to the given value.
-    if (!project.hasProperty('cdvMinSdkVersion')) {
-        cdvMinSdkVersion = null
-    }
-    // Whether to build architecture-specific APKs.
-    if (!project.hasProperty('cdvBuildMultipleApks')) {
-        cdvBuildMultipleApks = null
-    }
-    // Whether to append a 0 "abi digit" to versionCode when only a single APK is build 
-    if (!project.hasProperty('cdvVersionCodeForceAbiDigit')) {
-        cdvVersionCodeForceAbiDigit = null
-    }
-    // .properties files to use for release signing.
-    if (!project.hasProperty('cdvReleaseSigningPropertiesFile')) {
-        cdvReleaseSigningPropertiesFile = null
-    }
-    // .properties files to use for debug signing.
-    if (!project.hasProperty('cdvDebugSigningPropertiesFile')) {
-        cdvDebugSigningPropertiesFile = null
-    }
-    // Set by build.js script.
-    if (!project.hasProperty('cdvBuildArch')) {
-        cdvBuildArch = null
-    }
-
-    // Plugin gradle extensions can append to this to have code run at the end.
-    cdvPluginPostBuildExtras = []
-}
-
-// PLUGIN GRADLE EXTENSIONS START
-// PLUGIN GRADLE EXTENSIONS END
-
-def hasBuildExtras = file('build-extras.gradle').exists()
-if (hasBuildExtras) {
-    apply from: 'build-extras.gradle'
-}
-
-// Set property defaults after extension .gradle files.
-if (ext.cdvCompileSdkVersion == null) {
-    ext.cdvCompileSdkVersion = privateHelpers.getProjectTarget()
-}
-if (ext.cdvBuildToolsVersion == null) {
-    ext.cdvBuildToolsVersion = privateHelpers.findLatestInstalledBuildTools()
-}
-if (ext.cdvDebugSigningPropertiesFile == null && file('debug-signing.properties').exists()) {
-    ext.cdvDebugSigningPropertiesFile = 'debug-signing.properties'
-}
-if (ext.cdvReleaseSigningPropertiesFile == null && file('release-signing.properties').exists()) {
-    ext.cdvReleaseSigningPropertiesFile = 'release-signing.properties'
-}
-
-// Cast to appropriate types.
-ext.cdvBuildMultipleApks = cdvBuildMultipleApks == null ? false : cdvBuildMultipleApks.toBoolean();
-ext.cdvVersionCodeForceAbiDigit = cdvVersionCodeForceAbiDigit == null ? false : cdvVersionCodeForceAbiDigit.toBoolean();
-ext.cdvMinSdkVersion = cdvMinSdkVersion == null ? null : Integer.parseInt('' + cdvMinSdkVersion)
-ext.cdvVersionCode = cdvVersionCode == null ? null : Integer.parseInt('' + cdvVersionCode)
-
-def computeBuildTargetName(debugBuild) {
-    def ret = 'assemble'
-    if (cdvBuildMultipleApks && cdvBuildArch) {
-        def arch = cdvBuildArch == 'arm' ? 'armv7' : cdvBuildArch
-        ret += '' + arch.toUpperCase().charAt(0) + arch.substring(1);
-    }
-    return ret + (debugBuild ? 'Debug' : 'Release')
-}
-
-// Make cdvBuild a task that depends on the debug/arch-sepecific task.
-task cdvBuildDebug
-cdvBuildDebug.dependsOn {
-    return computeBuildTargetName(true)
-}
-
-task cdvBuildRelease
-cdvBuildRelease.dependsOn {
-    return computeBuildTargetName(false)
-}
-
-task cdvPrintProps << {
-    println('cdvCompileSdkVersion=' + cdvCompileSdkVersion)
-    println('cdvBuildToolsVersion=' + cdvBuildToolsVersion)
-    println('cdvVersionCode=' + cdvVersionCode)
-    println('cdvVersionCodeForceAbiDigit=' + cdvVersionCodeForceAbiDigit)
-    println('cdvMinSdkVersion=' + cdvMinSdkVersion)
-    println('cdvBuildMultipleApks=' + cdvBuildMultipleApks)
-    println('cdvReleaseSigningPropertiesFile=' + cdvReleaseSigningPropertiesFile)
-    println('cdvDebugSigningPropertiesFile=' + cdvDebugSigningPropertiesFile)
-    println('cdvBuildArch=' + cdvBuildArch)
-    println('computedVersionCode=' + android.defaultConfig.versionCode)
-    android.productFlavors.each { flavor ->
-        println('computed' + flavor.name.capitalize() + 'VersionCode=' + flavor.versionCode)
-    }
-}
-
-android {
-    sourceSets {
-        main {
-            manifest.srcFile 'AndroidManifest.xml'
-            java.srcDirs = ['src']
-            resources.srcDirs = ['src']
-            aidl.srcDirs = ['src']
-            renderscript.srcDirs = ['src']
-            res.srcDirs = ['res']
-            assets.srcDirs = ['assets']
-            jniLibs.srcDirs = ['libs']
-        }
+    //This replaces project.properties w.r.t. build settings
+    project.ext {
+      defaultBuildToolsVersion="25.0.2" //String
+      defaultMinSdkVersion=19 //Integer - Minimum requirement is Android 4.4
+      defaultTargetSdkVersion=26 //Integer - We ALWAYS target the latest by default
+      defaultCompileSdkVersion=26 //Integer - We ALWAYS compile with the latest by default
     }
-
-    defaultConfig {
-        versionCode cdvVersionCode ?: new BigInteger("" + privateHelpers.extractIntFromManifest("versionCode"))
-        applicationId privateHelpers.extractStringFromManifest("package")
-
-        if (cdvMinSdkVersion != null) {
-            minSdkVersion cdvMinSdkVersion
-        }
-    }
-
-    lintOptions {
-      abortOnError false;
-    }
-
-    compileSdkVersion cdvCompileSdkVersion
-    buildToolsVersion cdvBuildToolsVersion
-
-    if (Boolean.valueOf(cdvBuildMultipleApks)) {
-        flavorDimensions "default"
-
-        productFlavors {
-            armeabi {
-                versionCode defaultConfig.versionCode*10 + 1
-                ndk {
-                    abiFilters = ["armeabi"]
-                }
-            }
-            armv7 {
-                versionCode defaultConfig.versionCode*10 + 2
-                ndk {
-                    abiFilters = ["armeabi-v7a"]
-                }
-            }
-            arm64 {
-                versionCode defaultConfig.versionCode*10 + 3
-                ndk {
-                    abiFilters = ["arm64-v8a"]
-                }
-            }
-            x86 {
-                versionCode defaultConfig.versionCode*10 + 4
-                ndk {
-                    abiFilters = ["x86"]
-                }
-            }
-            x86_64 {
-                versionCode defaultConfig.versionCode*10 + 5
-                ndk {
-                    abiFilters = ["x86_64"]
-                }
-            }
-        }
-    } else if (Boolean.valueOf(cdvVersionCodeForceAbiDigit)) {
-        // This provides compatibility to the default logic for versionCode before cordova-android 5.2.0
-        defaultConfig {
-            versionCode defaultConfig.versionCode*10
-        }
-    }
-    /*
-
-    ELSE NOTHING! DON'T MESS WITH THE VERSION CODE IF YOU DON'T HAVE TO!
-
-    else if (!cdvVersionCode) {
-      def minSdkVersion = cdvMinSdkVersion ?: privateHelpers.extractIntFromManifest("minSdkVersion")
-      // Vary versionCode by the two most common API levels:
-      // 14 is ICS, which is the lowest API level for many apps.
-      // 20 is Lollipop, which is the lowest API level for the updatable system webview.
-      if (minSdkVersion >= 20) {
-        defaultConfig.versionCode += 9
-      } else if (minSdkVersion >= 14) {
-        defaultConfig.versionCode += 8
-      }
-    }
-    */
-
-    compileOptions {
-        sourceCompatibility JavaVersion.VERSION_1_6
-        targetCompatibility JavaVersion.VERSION_1_6
-    }
-
-    if (cdvReleaseSigningPropertiesFile) {
-        signingConfigs {
-            release {
-                // These must be set or Gradle will complain (even if they are overridden).
-                keyAlias = ""
-                keyPassword = "__unset" // And these must be set to non-empty in order to have the signing step added to the task graph.
-                storeFile = null
-                storePassword = "__unset"
-            }
-        }
-        buildTypes {
-            release {
-                signingConfig signingConfigs.release
-            }
-        }
-        addSigningProps(cdvReleaseSigningPropertiesFile, signingConfigs.release)
-    }
-    if (cdvDebugSigningPropertiesFile) {
-        addSigningProps(cdvDebugSigningPropertiesFile, signingConfigs.debug)
-    }
-}
-
-dependencies {
-    implementation fileTree(dir: 'libs', include: '*.jar')
-    // SUB-PROJECT DEPENDENCIES START
-    // SUB-PROJECT DEPENDENCIES END
-}
-
-def promptForReleaseKeyPassword() {
-    if (!cdvReleaseSigningPropertiesFile) {
-        return;
-    }
-    if ('__unset'.equals(android.signingConfigs.release.storePassword)) {
-        android.signingConfigs.release.storePassword = privateHelpers.promptForPassword('Enter key store password: ')
-    }
-    if ('__unset'.equals(android.signingConfigs.release.keyPassword)) {
-        android.signingConfigs.release.keyPassword = privateHelpers.promptForPassword('Enter key password: ');
-    }
-}
-
-gradle.taskGraph.whenReady { taskGraph ->
-    taskGraph.getAllTasks().each() { task ->
-        if (['validateReleaseSigning', 'validateSigningRelease', 'validateSigningArmv7Release', 'validateSigningX86Release'].contains(task.name)) {
-            promptForReleaseKeyPassword()
-        }
-    }
-}
-
-def addSigningProps(propsFilePath, signingConfig) {
-    def propsFile = file(propsFilePath)
-    def props = new Properties()
-    propsFile.withReader { reader ->
-        props.load(reader)
-    }
-
-    def storeFile = new File(props.get('key.store') ?: privateHelpers.ensureValueExists(propsFilePath, props, 'storeFile'))
-    if (!storeFile.isAbsolute()) {
-        storeFile = RelativePath.parse(true, storeFile.toString()).getFile(propsFile.getParentFile())
-    }
-    if (!storeFile.exists()) {
-        throw new FileNotFoundException('Keystore file does not exist: ' + storeFile.getAbsolutePath())
-    }
-    signingConfig.keyAlias = props.get('key.alias') ?: privateHelpers.ensureValueExists(propsFilePath, props, 'keyAlias')
-    signingConfig.keyPassword = props.get('keyPassword', props.get('key.alias.password', signingConfig.keyPassword))
-    signingConfig.storeFile = storeFile
-    signingConfig.storePassword = props.get('storePassword', props.get('key.store.password', signingConfig.storePassword))
-    def storeType = props.get('storeType', props.get('key.store.type', ''))
-    if (!storeType) {
-        def filename = storeFile.getName().toLowerCase();
-        if (filename.endsWith('.p12') || filename.endsWith('.pfx')) {
-            storeType = 'pkcs12'
-        } else {
-            storeType = signingConfig.storeType // "jks"
-        }
-    }
-    signingConfig.storeType = storeType
-}
-
-for (def func : cdvPluginPostBuildExtras) {
-    func()
 }
 
-// This can be defined within build-extras.gradle as:
-//     ext.postBuildExtras = { ... code here ... }
-if (hasProperty('postBuildExtras')) {
-    postBuildExtras()
+task clean(type: Delete) {
+    delete rootProject.buildDir
 }
diff --git a/bin/templates/project/legacy/build.gradle b/bin/templates/project/legacy/build.gradle
new file mode 100644
index 000000000..ef2297194
--- /dev/null
+++ b/bin/templates/project/legacy/build.gradle
@@ -0,0 +1,311 @@
+/*
+       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.
+*/
+
+apply plugin: 'com.android.application'
+
+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
+    dependencies {
+        classpath 'com.android.tools.build:gradle:2.2.3'
+    }
+}
+
+// Allow plugins to declare Maven dependencies via build-extras.gradle.
+allprojects {
+    repositories {
+        mavenCentral();
+        jcenter()
+    }
+}
+
+task wrapper(type: Wrapper) {
+    gradleVersion = '2.14.1'
+}
+
+// Configuration properties. Set these via environment variables, build-extras.gradle, or gradle.properties.
+// Refer to: http://www.gradle.org/docs/current/userguide/tutorial_this_and_that.html
+ext {
+    apply from: 'CordovaLib/cordova.gradle'
+    // The value for android.compileSdkVersion.
+    if (!project.hasProperty('cdvCompileSdkVersion')) {
+        cdvCompileSdkVersion = null;
+    }
+    // The value for android.buildToolsVersion.
+    if (!project.hasProperty('cdvBuildToolsVersion')) {
+        cdvBuildToolsVersion = null;
+    }
+    // Sets the versionCode to the given value.
+    if (!project.hasProperty('cdvVersionCode')) {
+        cdvVersionCode = null
+    }
+    // Sets the minSdkVersion to the given value.
+    if (!project.hasProperty('cdvMinSdkVersion')) {
+        cdvMinSdkVersion = null
+    }
+    // Whether to build architecture-specific APKs.
+    if (!project.hasProperty('cdvBuildMultipleApks')) {
+        cdvBuildMultipleApks = null
+    }
+    // .properties files to use for release signing.
+    if (!project.hasProperty('cdvReleaseSigningPropertiesFile')) {
+        cdvReleaseSigningPropertiesFile = null
+    }
+    // .properties files to use for debug signing.
+    if (!project.hasProperty('cdvDebugSigningPropertiesFile')) {
+        cdvDebugSigningPropertiesFile = null
+    }
+    // Set by build.js script.
+    if (!project.hasProperty('cdvBuildArch')) {
+        cdvBuildArch = null
+    }
+
+    // Plugin gradle extensions can append to this to have code run at the end.
+    cdvPluginPostBuildExtras = []
+}
+
+// PLUGIN GRADLE EXTENSIONS START
+// PLUGIN GRADLE EXTENSIONS END
+
+def hasBuildExtras = file('build-extras.gradle').exists()
+if (hasBuildExtras) {
+    apply from: 'build-extras.gradle'
+}
+
+// Set property defaults after extension .gradle files.
+if (ext.cdvCompileSdkVersion == null) {
+    ext.cdvCompileSdkVersion = privateHelpers.getProjectTarget()
+}
+if (ext.cdvBuildToolsVersion == null) {
+    ext.cdvBuildToolsVersion = privateHelpers.findLatestInstalledBuildTools()
+}
+if (ext.cdvDebugSigningPropertiesFile == null && file('debug-signing.properties').exists()) {
+    ext.cdvDebugSigningPropertiesFile = 'debug-signing.properties'
+}
+if (ext.cdvReleaseSigningPropertiesFile == null && file('release-signing.properties').exists()) {
+    ext.cdvReleaseSigningPropertiesFile = 'release-signing.properties'
+}
+
+// Cast to appropriate types.
+ext.cdvBuildMultipleApks = cdvBuildMultipleApks == null ? false : cdvBuildMultipleApks.toBoolean();
+ext.cdvMinSdkVersion = cdvMinSdkVersion == null ? null : Integer.parseInt('' + cdvMinSdkVersion)
+ext.cdvVersionCode = cdvVersionCode == null ? null : Integer.parseInt('' + cdvVersionCode)
+
+def computeBuildTargetName(debugBuild) {
+    def ret = 'assemble'
+    if (cdvBuildMultipleApks && cdvBuildArch) {
+        def arch = cdvBuildArch == 'arm' ? 'armv7' : cdvBuildArch
+        ret += '' + arch.toUpperCase().charAt(0) + arch.substring(1);
+    }
+    return ret + (debugBuild ? 'Debug' : 'Release')
+}
+
+// Make cdvBuild a task that depends on the debug/arch-sepecific task.
+task cdvBuildDebug
+cdvBuildDebug.dependsOn {
+    return computeBuildTargetName(true)
+}
+
+task cdvBuildRelease
+cdvBuildRelease.dependsOn {
+    return computeBuildTargetName(false)
+}
+
+task cdvPrintProps << {
+    println('cdvCompileSdkVersion=' + cdvCompileSdkVersion)
+    println('cdvBuildToolsVersion=' + cdvBuildToolsVersion)
+    println('cdvVersionCode=' + cdvVersionCode)
+    println('cdvMinSdkVersion=' + cdvMinSdkVersion)
+    println('cdvBuildMultipleApks=' + cdvBuildMultipleApks)
+    println('cdvReleaseSigningPropertiesFile=' + cdvReleaseSigningPropertiesFile)
+    println('cdvDebugSigningPropertiesFile=' + cdvDebugSigningPropertiesFile)
+    println('cdvBuildArch=' + cdvBuildArch)
+    println('computedVersionCode=' + android.defaultConfig.versionCode)
+    android.productFlavors.each { flavor ->
+        println('computed' + flavor.name.capitalize() + 'VersionCode=' + flavor.versionCode)
+    }
+}
+
+android {
+    sourceSets {
+        main {
+            manifest.srcFile 'AndroidManifest.xml'
+            java.srcDirs = ['src']
+            resources.srcDirs = ['src']
+            aidl.srcDirs = ['src']
+            renderscript.srcDirs = ['src']
+            res.srcDirs = ['res']
+            assets.srcDirs = ['assets']
+            jniLibs.srcDirs = ['libs']
+        }
+    }
+
+    defaultConfig {
+        versionCode cdvVersionCode ?: new BigInteger("" + privateHelpers.extractIntFromManifest("versionCode"))
+        applicationId privateHelpers.extractStringFromManifest("package")
+
+        if (cdvMinSdkVersion != null) {
+            minSdkVersion cdvMinSdkVersion
+        }
+    }
+
+    lintOptions {
+      abortOnError false;
+    }
+
+    compileSdkVersion cdvCompileSdkVersion
+    buildToolsVersion cdvBuildToolsVersion
+
+    if (Boolean.valueOf(cdvBuildMultipleApks)) {
+        productFlavors {
+            armv7 {
+                versionCode defaultConfig.versionCode*10 + 2
+                ndk {
+                    abiFilters "armeabi-v7a", ""
+                }
+            }
+            x86 {
+                versionCode defaultConfig.versionCode*10 + 4
+                ndk {
+                    abiFilters "x86", ""
+                }
+            }
+            all {
+                ndk {
+                    abiFilters "all", ""
+                }
+            }
+        }
+    }
+    /*
+
+    ELSE NOTHING! DON'T MESS WITH THE VERSION CODE IF YOU DON'T HAVE TO!
+
+    else if (!cdvVersionCode) {
+      def minSdkVersion = cdvMinSdkVersion ?: privateHelpers.extractIntFromManifest("minSdkVersion")
+      // Vary versionCode by the two most common API levels:
+      // 14 is ICS, which is the lowest API level for many apps.
+      // 20 is Lollipop, which is the lowest API level for the updatable system webview.
+      if (minSdkVersion >= 20) {
+        defaultConfig.versionCode += 9
+      } else if (minSdkVersion >= 14) {
+        defaultConfig.versionCode += 8
+      }
+    }
+    */
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_6
+        targetCompatibility JavaVersion.VERSION_1_6
+    }
+
+    if (cdvReleaseSigningPropertiesFile) {
+        signingConfigs {
+            release {
+                // These must be set or Gradle will complain (even if they are overridden).
+                keyAlias = ""
+                keyPassword = "__unset" // And these must be set to non-empty in order to have the signing step added to the task graph.
+                storeFile = null
+                storePassword = "__unset"
+            }
+        }
+        buildTypes {
+            release {
+                signingConfig signingConfigs.release
+            }
+        }
+        addSigningProps(cdvReleaseSigningPropertiesFile, signingConfigs.release)
+    }
+    if (cdvDebugSigningPropertiesFile) {
+        addSigningProps(cdvDebugSigningPropertiesFile, signingConfigs.debug)
+    }
+}
+
+dependencies {
+    compile fileTree(dir: 'libs', include: '*.jar')
+    // SUB-PROJECT DEPENDENCIES START
+    // SUB-PROJECT DEPENDENCIES END
+}
+
+def promptForReleaseKeyPassword() {
+    if (!cdvReleaseSigningPropertiesFile) {
+        return;
+    }
+    if ('__unset'.equals(android.signingConfigs.release.storePassword)) {
+        android.signingConfigs.release.storePassword = privateHelpers.promptForPassword('Enter key store password: ')
+    }
+    if ('__unset'.equals(android.signingConfigs.release.keyPassword)) {
+        android.signingConfigs.release.keyPassword = privateHelpers.promptForPassword('Enter key password: ');
+    }
+}
+
+gradle.taskGraph.whenReady { taskGraph ->
+    taskGraph.getAllTasks().each() { task ->
+        if (task.name == 'validateReleaseSigning' || task.name == 'validateSigningRelease') {
+            promptForReleaseKeyPassword()
+        }
+    }
+}
+
+def addSigningProps(propsFilePath, signingConfig) {
+    def propsFile = file(propsFilePath)
+    def props = new Properties()
+    propsFile.withReader { reader ->
+        props.load(reader)
+    }
+
+    def storeFile = new File(props.get('key.store') ?: privateHelpers.ensureValueExists(propsFilePath, props, 'storeFile'))
+    if (!storeFile.isAbsolute()) {
+        storeFile = RelativePath.parse(true, storeFile.toString()).getFile(propsFile.getParentFile())
+    }
+    if (!storeFile.exists()) {
+        throw new FileNotFoundException('Keystore file does not exist: ' + storeFile.getAbsolutePath())
+    }
+    signingConfig.keyAlias = props.get('key.alias') ?: privateHelpers.ensureValueExists(propsFilePath, props, 'keyAlias')
+    signingConfig.keyPassword = props.get('keyPassword', props.get('key.alias.password', signingConfig.keyPassword))
+    signingConfig.storeFile = storeFile
+    signingConfig.storePassword = props.get('storePassword', props.get('key.store.password', signingConfig.storePassword))
+    def storeType = props.get('storeType', props.get('key.store.type', ''))
+    if (!storeType) {
+        def filename = storeFile.getName().toLowerCase();
+        if (filename.endsWith('.p12') || filename.endsWith('.pfx')) {
+            storeType = 'pkcs12'
+        } else {
+            storeType = signingConfig.storeType // "jks"
+        }
+    }
+    signingConfig.storeType = storeType
+}
+
+for (def func : cdvPluginPostBuildExtras) {
+    func()
+}
+
+// This can be defined within build-extras.gradle as:
+//     ext.postBuildExtras = { ... code here ... }
+if (hasProperty('postBuildExtras')) {
+    postBuildExtras()
+}
diff --git a/bin/templates/project/project.properties b/bin/templates/project/project.properties
index ddd3a0608..6ea5ac23c 100644
--- a/bin/templates/project/project.properties
+++ b/bin/templates/project/project.properties
@@ -1,15 +1,13 @@
-# This file is automatically generated by Android Tools.
-# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
-#
-# This file must be checked in Version Control Systems.
-#
-# To customize properties used by the Ant build system edit
-# "ant.properties", and override values to adapt the script to your
-# project structure.
-#
-# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
-#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+# This file was originally created by the Android Tools, but is now
+# used by cordova-android to manage the state of the various third party
+# libraries used in your application
 
+# This is the Library Module that contains the Cordova Library, this is not
+# required when using an AAR
 android.library.reference.1=CordovaLib
+
+# This is the application project.  This is only required for Android Studio Gradle projects
+android.library.reference.2=app
+
 # Project target.
 target=This_gets_replaced
diff --git a/framework/AndroidManifest.xml b/framework/AndroidManifest.xml
index 3feb903cf..26a0aed0e 100755
--- a/framework/AndroidManifest.xml
+++ b/framework/AndroidManifest.xml
@@ -19,5 +19,5 @@
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
       package="org.apache.cordova" android:versionName="1.0" android:versionCode="1">
-    <uses-sdk android:minSdkVersion="14" />
+    <uses-sdk android:minSdkVersion="16" />
 </manifest>
diff --git a/framework/build.gradle b/framework/build.gradle
index 1ad622c9a..6d4840862 100644
--- a/framework/build.gradle
+++ b/framework/build.gradle
@@ -50,8 +50,8 @@ android {
     publishNonDefault true
 
     compileOptions {
-        sourceCompatibility JavaVersion.VERSION_1_6
-        targetCompatibility JavaVersion.VERSION_1_6
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
     }
 
     sourceSets {
diff --git a/framework/cordova.gradle b/framework/cordova.gradle
index 21a01bb58..0131a2652 100644
--- a/framework/cordova.gradle
+++ b/framework/cordova.gradle
@@ -29,7 +29,11 @@ String doEnsureValueExists(filePath, props, key) {
 
 String doGetProjectTarget() {
     def props = new Properties()
-    file('project.properties').withReader { reader ->
+    def propertiesFile = 'project.properties';
+    if(!(file(propertiesFile).exists())) {
+      propertiesFile = '../project.properties';
+    }
+    file(propertiesFile).withReader { reader ->
         props.load(reader)
     }
     return doEnsureValueExists('project.properties', props, 'target')
diff --git a/framework/src/org/apache/cordova/CordovaActivity.java b/framework/src/org/apache/cordova/CordovaActivity.java
index 85eeb53a5..e2ed1cb82 100755
--- a/framework/src/org/apache/cordova/CordovaActivity.java
+++ b/framework/src/org/apache/cordova/CordovaActivity.java
@@ -319,6 +319,7 @@ public void onDestroy() {
     /**
      * Called when view focus is changed
      */
+    @SuppressLint("InlinedApi")
     @Override
     public void onWindowFocusChanged(boolean hasFocus) {
         super.onWindowFocusChanged(hasFocus);
diff --git a/framework/src/org/apache/cordova/CordovaBridge.java b/framework/src/org/apache/cordova/CordovaBridge.java
index 9459a1134..28c407f3d 100644
--- a/framework/src/org/apache/cordova/CordovaBridge.java
+++ b/framework/src/org/apache/cordova/CordovaBridge.java
@@ -18,6 +18,8 @@ Licensed to the Apache Software Foundation (ASF) under one
 */
 package org.apache.cordova;
 
+import android.annotation.SuppressLint;
+
 import java.security.SecureRandom;
 
 import org.json.JSONArray;
@@ -110,6 +112,9 @@ public boolean isSecretEstablished() {
     }
 
     /** Called by cordova.js to initialize the bridge. */
+    //On old Androids SecureRandom isn't really secure, this is the least of your problems if
+    //you're running Android 4.3 and below in 2017
+    @SuppressLint("TrulyRandom")
     int generateBridgeSecret() {
         SecureRandom randGen = new SecureRandom();
         expectedBridgeSecret = randGen.nextInt(Integer.MAX_VALUE);
diff --git a/framework/src/org/apache/cordova/CordovaClientCertRequest.java b/framework/src/org/apache/cordova/CordovaClientCertRequest.java
index 5dd0ecaec..ccda0272a 100644
--- a/framework/src/org/apache/cordova/CordovaClientCertRequest.java
+++ b/framework/src/org/apache/cordova/CordovaClientCertRequest.java
@@ -22,10 +22,12 @@ Licensed to the Apache Software Foundation (ASF) under one
 import java.security.PrivateKey;
 import java.security.cert.X509Certificate;
 
+import android.annotation.SuppressLint;
 import android.webkit.ClientCertRequest;
 
 /**
  * Implementation of the ICordovaClientCertRequest for Android WebView.
+ *
  */
 public class CordovaClientCertRequest implements ICordovaClientCertRequest {
 
@@ -38,6 +40,7 @@ public CordovaClientCertRequest(ClientCertRequest request) {
     /**
      * Cancel this request
      */
+    @SuppressLint("NewApi")
     public void cancel()
     {
         request.cancel();
@@ -46,6 +49,7 @@ public void cancel()
     /*
      * Returns the host name of the server requesting the certificate.
      */
+    @SuppressLint("NewApi")
     public String getHost()
     {
         return request.getHost();
@@ -54,6 +58,7 @@ public String getHost()
     /*
      * Returns the acceptable types of asymmetric keys (can be null).
      */
+    @SuppressLint("NewApi")
     public String[] getKeyTypes()
     {
         return request.getKeyTypes();
@@ -62,6 +67,7 @@ public String getHost()
     /*
      * Returns the port number of the server requesting the certificate.
      */
+    @SuppressLint("NewApi")
     public int getPort()
     {
         return request.getPort();
@@ -70,6 +76,7 @@ public int getPort()
     /*
      * Returns the acceptable certificate issuers for the certificate matching the private key (can be null).
      */
+    @SuppressLint("NewApi")
     public Principal[] getPrincipals()
     {
         return request.getPrincipals();
@@ -78,6 +85,7 @@ public int getPort()
     /*
      * Ignore the request for now. Do not remember user's choice.
      */
+    @SuppressLint("NewApi")
     public void ignore()
     {
         request.ignore();
@@ -89,6 +97,7 @@ public void ignore()
      * @param privateKey The privateKey
      * @param chain The certificate chain 
      */
+    @SuppressLint("NewApi")
     public void proceed(PrivateKey privateKey, X509Certificate[] chain)
     {
         request.proceed(privateKey, chain);
diff --git a/framework/src/org/apache/cordova/CordovaInterfaceImpl.java b/framework/src/org/apache/cordova/CordovaInterfaceImpl.java
index 3c459025f..9a6e9245f 100644
--- a/framework/src/org/apache/cordova/CordovaInterfaceImpl.java
+++ b/framework/src/org/apache/cordova/CordovaInterfaceImpl.java
@@ -19,6 +19,7 @@ Licensed to the Apache Software Foundation (ASF) under one
 
 package org.apache.cordova;
 
+import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
@@ -227,6 +228,7 @@ public void requestPermission(CordovaPlugin plugin, int requestCode, String perm
         requestPermissions(plugin, requestCode, permissions);
     }
 
+        @SuppressLint("NewApi")
     public void requestPermissions(CordovaPlugin plugin, int requestCode, String [] permissions) {
         int mappedRequestCode = permissionResultCallbacks.registerCallback(plugin, requestCode);
         getActivity().requestPermissions(permissions, mappedRequestCode);
diff --git a/framework/src/org/apache/cordova/CordovaWebViewImpl.java b/framework/src/org/apache/cordova/CordovaWebViewImpl.java
index 85a0b5f5d..fb99c3443 100644
--- a/framework/src/org/apache/cordova/CordovaWebViewImpl.java
+++ b/framework/src/org/apache/cordova/CordovaWebViewImpl.java
@@ -18,6 +18,7 @@ Licensed to the Apache Software Foundation (ASF) under one
 */
 package org.apache.cordova;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
@@ -91,6 +92,7 @@ public void init(CordovaInterface cordova) {
         init(cordova, new ArrayList<PluginEntry>(), new CordovaPreferences());
     }
 
+    @SuppressLint("Assert")
     @Override
     public void init(CordovaInterface cordova, List<PluginEntry> pluginEntries, CordovaPreferences preferences) {
         if (this.cordova != null) {
diff --git a/framework/src/org/apache/cordova/engine/SystemWebViewEngine.java b/framework/src/org/apache/cordova/engine/SystemWebViewEngine.java
index 9ea97bc83..d15b0eeec 100755
--- a/framework/src/org/apache/cordova/engine/SystemWebViewEngine.java
+++ b/framework/src/org/apache/cordova/engine/SystemWebViewEngine.java
@@ -254,6 +254,9 @@ private void enableRemoteDebugging() {
         }
     }
 
+    // Yeah, we know, which is why we makes ure that we don't do this if the bridge is
+    // below JELLYBEAN_MR1.  It'd be great if lint was just a little smarter.
+    @SuppressLint("AddJavascriptInterface")
     private static void exposeJsInterface(WebView webView, CordovaBridge bridge) {
         if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)) {
             LOG.i(TAG, "Disabled addJavascriptInterface() bridge since Android version is old.");
diff --git a/package.json b/package.json
index 23c68e5f2..d8601d8e7 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,7 @@
   "license": "Apache-2.0",
   "dependencies": {
     "android-versions": "^1.2.1",
-    "cordova-common": "^2.1.0",
+    "cordova-common": "^2.2.0",
     "elementtree": "0.1.6",
     "nopt": "^3.0.1",
     "properties-parser": "^0.2.3",
diff --git a/spec/unit/create.spec.js b/spec/unit/create.spec.js
index bfd6295e0..77eeaa365 100644
--- a/spec/unit/create.spec.js
+++ b/spec/unit/create.spec.js
@@ -127,6 +127,7 @@ describe('create', function () {
         var Manifest_mock = function () {};
         var revert_manifest_mock;
         var project_path = path.join('some', 'path');
+        var app_path = path.join(project_path, 'app', 'src', 'main');
         var default_templates = path.join(__dirname, '..', '..', 'bin', 'templates', 'project');
         var fake_android_target = 'android-1337';
         beforeEach(function () {
@@ -217,15 +218,15 @@ describe('create', function () {
         describe('happy path', function () {
             it('should copy project templates from a specified custom template', function (done) {
                 create.create(project_path, config_mock, {customTemplate: '/template/path'}, events_mock).then(function () {
-                    expect(shell.cp).toHaveBeenCalledWith('-r', path.join('/template/path', 'assets'), project_path);
-                    expect(shell.cp).toHaveBeenCalledWith('-r', path.join('/template/path', 'res'), project_path);
+                    expect(shell.cp).toHaveBeenCalledWith('-r', path.join('/template/path', 'assets'), app_path);
+                    expect(shell.cp).toHaveBeenCalledWith('-r', path.join('/template/path', 'res'), app_path);
                     expect(shell.cp).toHaveBeenCalledWith(path.join('/template/path', 'gitignore'), path.join(project_path, '.gitignore'));
                 }).fail(fail).done(done);
             });
             it('should copy project templates from the default templates location if no custom template is provided', function (done) {
                 create.create(project_path, config_mock, {}, events_mock).then(function () {
-                    expect(shell.cp).toHaveBeenCalledWith('-r', path.join(default_templates, 'assets'), project_path);
-                    expect(shell.cp).toHaveBeenCalledWith('-r', path.join(default_templates, 'res'), project_path);
+                    expect(shell.cp).toHaveBeenCalledWith('-r', path.join(default_templates, 'assets'), app_path);
+                    expect(shell.cp).toHaveBeenCalledWith('-r', path.join(default_templates, 'res'), app_path);
                     expect(shell.cp).toHaveBeenCalledWith(path.join(default_templates, 'gitignore'), path.join(project_path, '.gitignore'));
                 }).fail(fail).done(done);
             });
@@ -237,13 +238,13 @@ describe('create', function () {
             it('should create a java src directory based on the provided project package name', function (done) {
                 config_mock.packageName.and.returnValue('org.apache.cordova');
                 create.create(project_path, config_mock, {}, events_mock).then(function () {
-                    expect(shell.mkdir).toHaveBeenCalledWith('-p', path.join(project_path, 'src', 'org', 'apache', 'cordova'));
+                    expect(shell.mkdir).toHaveBeenCalledWith('-p', path.join(app_path, 'java', 'org', 'apache', 'cordova'));
                 }).fail(fail).done(done);
             });
             it('should copy, rename and interpolate the template Activity java class with the project-specific activity name and package name', function (done) {
                 config_mock.packageName.and.returnValue('org.apache.cordova');
                 config_mock.android_activityName.and.returnValue('CEEDEEVEE');
-                var activity_path = path.join(project_path, 'src', 'org', 'apache', 'cordova', 'CEEDEEVEE.java');
+                var activity_path = path.join(app_path, 'java', 'org', 'apache', 'cordova', 'CEEDEEVEE.java');
                 create.create(project_path, config_mock, {}, events_mock).then(function () {
                     expect(shell.cp).toHaveBeenCalledWith('-f', path.join(default_templates, 'Activity.java'), activity_path);
                     expect(shell.sed).toHaveBeenCalledWith('-i', /__ACTIVITY__/, 'CEEDEEVEE', activity_path);
@@ -253,7 +254,7 @@ describe('create', function () {
             it('should interpolate the project name into strings.xml', function (done) {
                 config_mock.name.and.returnValue('IncredibleApp');
                 create.create(project_path, config_mock, {}, events_mock).then(function () {
-                    expect(shell.sed).toHaveBeenCalledWith('-i', /__NAME__/, 'IncredibleApp', path.join(project_path, 'res', 'values', 'strings.xml'));
+                    expect(shell.sed).toHaveBeenCalledWith('-i', /__NAME__/, 'IncredibleApp', path.join(app_path, 'res', 'values', 'strings.xml'));
                 }).fail(fail).done(done);
             });
             it('should copy template scripts into generated project', function (done) {
@@ -273,7 +274,7 @@ describe('create', function () {
             });
             it('should prepare build files', function (done) {
                 create.create(project_path, config_mock, {}, events_mock).then(function () {
-                    expect(create.prepBuildFiles).toHaveBeenCalledWith(project_path);
+                    expect(create.prepBuildFiles).toHaveBeenCalledWith(project_path, 'studio');
                 }).fail(fail).done(done);
             });
         });
diff --git a/spec/unit/pluginHandlers/handlers.spec.js b/spec/unit/pluginHandlers/handlers.spec.js
index 6e1e79e0c..dae57a91a 100644
--- a/spec/unit/pluginHandlers/handlers.spec.js
+++ b/spec/unit/pluginHandlers/handlers.spec.js
@@ -71,7 +71,7 @@ describe('android project handler', function () {
         });
 
         describe('of <resource-file> elements', function () {
-            it('Test#003 : should copy files', function () {
+            it('Test#003 : should copy files to the correct location on a non-Android Studio project', function () {
                 android['resource-file'].install(valid_resources[0], dummyPluginInfo, dummyProject);
                 expect(copyFileSpy).toHaveBeenCalledWith(dummyplugin, 'android-resource.xml', temp, path.join('res', 'xml', 'dummy.xml'), false);
             });
@@ -264,15 +264,20 @@ describe('android project handler', function () {
                 android['resource-file'].uninstall(valid_resources[0], dummyPluginInfo, dummyProject);
                 expect(removeFileSpy).toHaveBeenCalledWith(temp, path.join('res/xml/dummy.xml'));
             });
+            it('Test#021 : should remove files for Android Studio projects', function () {
+                android['resource-file'].install(valid_resources[0], dummyPluginInfo, dummyProject, {android_studio: true});
+                android['resource-file'].uninstall(valid_resources[0], dummyPluginInfo, dummyProject, {android_studio: true});
+                expect(removeFileSpy).toHaveBeenCalledWith(temp, path.join('app/src/main/res/xml/dummy.xml'));
+            });
         });
 
         describe('of <source-file> elements', function () {
-            it('Test#021 : should remove stuff by calling common.deleteJava', function () {
+            it('Test#022 : should remove stuff by calling common.deleteJava', function () {
                 android['source-file'].install(valid_source[0], dummyPluginInfo, dummyProject);
                 android['source-file'].uninstall(valid_source[0], dummyPluginInfo, dummyProject);
                 expect(deleteJavaSpy).toHaveBeenCalledWith(temp, path.join('src/com/phonegap/plugins/dummyplugin/DummyPlugin.java'));
             });
-            it('Test#022 : should remove stuff by calling common.deleteJava for Android Studio projects', function () {
+            it('Test#023 : should remove stuff by calling common.deleteJava for Android Studio projects', function () {
                 android['source-file'].install(valid_source[0], dummyPluginInfo, dummyProject, {android_studio: true});
                 android['source-file'].uninstall(valid_source[0], dummyPluginInfo, dummyProject, {android_studio: true});
                 expect(deleteJavaSpy).toHaveBeenCalledWith(temp, path.join('app/src/main/java/com/phonegap/plugins/dummyplugin/DummyPlugin.java'));
@@ -291,30 +296,30 @@ describe('android project handler', function () {
                 spyOn(dummyProject, 'removeGradleReference');
             });
 
-            it('Test#023 : should throw if framework doesn\'t have "src" attribute', function () {
+            it('Test#024 : should throw if framework doesn\'t have "src" attribute', function () {
                 expect(function () { android.framework.uninstall({}, dummyPluginInfo, dummyProject); }).toThrow();
             });
 
-            it('Test#024 : should uninstall framework without "parent" attribute into project root', function () {
+            it('Test#025 : should uninstall framework without "parent" attribute into project root', function () {
                 var framework = {src: 'plugin-lib'};
                 android.framework.uninstall(framework, dummyPluginInfo, dummyProject);
                 expect(dummyProject.removeSystemLibrary).toHaveBeenCalledWith(dummyProject.projectDir, someString);
             });
 
-            it('Test#025 : should uninstall framework with "parent" attribute into parent framework dir', function () {
+            it('Test#026 : should uninstall framework with "parent" attribute into parent framework dir', function () {
                 var childFramework = {src: 'plugin-lib2', parent: 'plugin-lib'};
                 android.framework.uninstall(childFramework, dummyPluginInfo, dummyProject);
                 expect(dummyProject.removeSystemLibrary).toHaveBeenCalledWith(path.resolve(dummyProject.projectDir, childFramework.parent), someString);
             });
 
-            it('Test#026 : should remove framework sources if "custom" attribute is set', function () {
+            it('Test#027 : should remove framework sources if "custom" attribute is set', function () {
                 var framework = {src: 'plugin-lib', custom: true};
                 android.framework.uninstall(framework, dummyPluginInfo, dummyProject);
                 expect(dummyProject.removeSubProject).toHaveBeenCalledWith(dummyProject.projectDir, someString);
                 expect(removeFileSpy).toHaveBeenCalledWith(dummyProject.projectDir, someString);
             });
 
-            it('Test#27 : should install gradleReference using project.removeGradleReference', function () {
+            it('Test#28 : should install gradleReference using project.removeGradleReference', function () {
                 var framework = {src: 'plugin-lib', custom: true, type: 'gradleReference'};
                 android.framework.uninstall(framework, dummyPluginInfo, dummyProject);
                 expect(removeFileSpy).toHaveBeenCalledWith(dummyProject.projectDir, someString);
@@ -340,13 +345,13 @@ describe('android project handler', function () {
                 });
             });
 
-            it('Test#028 : should put module to both www and platform_www when options.usePlatformWww flag is specified', function () {
+            it('Test#029 : should put module to both www and platform_www when options.usePlatformWww flag is specified', function () {
                 android['js-module'].uninstall(jsModule, dummyPluginInfo, dummyProject, {usePlatformWww: true});
                 expect(shell.rm).toHaveBeenCalledWith('-Rf', wwwDest);
                 expect(shell.rm).toHaveBeenCalledWith('-Rf', platformWwwDest);
             });
 
-            it('Test#29 : should put module to www only when options.usePlatformWww flag is not specified', function () {
+            it('Test#030 : should put module to www only when options.usePlatformWww flag is not specified', function () {
                 android['js-module'].uninstall(jsModule, dummyPluginInfo, dummyProject);
                 expect(shell.rm).toHaveBeenCalledWith('-Rf', wwwDest);
                 expect(shell.rm).not.toHaveBeenCalledWith('-Rf', platformWwwDest);
@@ -370,13 +375,13 @@ describe('android project handler', function () {
                 });
             });
 
-            it('Test#030 : should put module to both www and platform_www when options.usePlatformWww flag is specified', function () {
+            it('Test#031 : should put module to both www and platform_www when options.usePlatformWww flag is specified', function () {
                 android.asset.uninstall(asset, dummyPluginInfo, dummyProject, {usePlatformWww: true});
                 expect(shell.rm).toHaveBeenCalledWith(jasmine.any(String), wwwDest);
                 expect(shell.rm).toHaveBeenCalledWith(jasmine.any(String), platformWwwDest);
             });
 
-            it('Test#31 : should put module to www only when options.usePlatformWww flag is not specified', function () {
+            it('Test#032 : should put module to www only when options.usePlatformWww flag is not specified', function () {
                 android.asset.uninstall(asset, dummyPluginInfo, dummyProject);
                 expect(shell.rm).toHaveBeenCalledWith(jasmine.any(String), wwwDest);
                 expect(shell.rm).not.toHaveBeenCalledWith(jasmine.any(String), platformWwwDest);


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org