mirror of
https://github.com/apache/cordova-android.git
synced 2026-03-16 00:00:02 +08:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cdcb4ddcd9 | ||
|
|
8e53e2aa56 |
24
.asf.yaml
24
.asf.yaml
@@ -15,30 +15,6 @@
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
github:
|
||||
description: Apache Cordova Android
|
||||
homepage: https://cordova.apache.org/
|
||||
|
||||
labels:
|
||||
- cordova
|
||||
- cordova-platform
|
||||
- android
|
||||
- java
|
||||
- mobile
|
||||
- javascript
|
||||
- nodejs
|
||||
- hacktoberfest
|
||||
|
||||
features:
|
||||
wiki: false
|
||||
issues: true
|
||||
projects: true
|
||||
|
||||
enabled_merge_buttons:
|
||||
squash: true
|
||||
merge: false
|
||||
rebase: false
|
||||
|
||||
notifications:
|
||||
commits: commits@cordova.apache.org
|
||||
issues: issues@cordova.apache.org
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [10.x, 12.x, 14.x]
|
||||
node-version: [10.x, 12.x]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
|
||||
steps:
|
||||
|
||||
65
LICENSE
65
LICENSE
@@ -200,3 +200,68 @@
|
||||
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.
|
||||
|
||||
ADDITIONAL LICENSES:
|
||||
|
||||
================================================================================
|
||||
bin/node_modules/q
|
||||
================================================================================
|
||||
|
||||
Copyright 2009–2017 Kristopher Michael Kowal. All rights reserved.
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to
|
||||
deal in the Software without restriction, including without limitation the
|
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
||||
|
||||
================================================================================
|
||||
bin/node_modules/nopt
|
||||
================================================================================
|
||||
|
||||
The ISC License
|
||||
|
||||
Copyright (c) Isaac Z. Schlueter and Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
================================================================================
|
||||
bin/node_modules/which
|
||||
================================================================================
|
||||
|
||||
The ISC License
|
||||
|
||||
Copyright (c) Isaac Z. Schlueter and Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
@@ -20,69 +20,6 @@
|
||||
-->
|
||||
## Release Notes for Cordova (Android)
|
||||
|
||||
### 9.1.0 (Apr 09, 2021)
|
||||
|
||||
**Features:**
|
||||
|
||||
* [GH-1104](https://github.com/apache/cordova-android/pull/1104) feat: support `gzip` encoding requests & use `GZIPInputStream`
|
||||
* [GH-1167](https://github.com/apache/cordova-android/pull/1167) feat: handle `intent://` scheme links with `browser_fallback_url` param
|
||||
* [GH-1179](https://github.com/apache/cordova-android/pull/1179) feat: add `repositories` support
|
||||
* [GH-1173](https://github.com/apache/cordova-android/pull/1173) feat(android-studio): display app name as project name
|
||||
* [GH-1113](https://github.com/apache/cordova-android/pull/1113) feat: `webp` support for splashscreen
|
||||
* [GH-1125](https://github.com/apache/cordova-android/pull/1125) feat(Adb): list `devices` _and_ `emulators` in one go
|
||||
|
||||
**Fixes:**
|
||||
|
||||
* [GH-1186](https://github.com/apache/cordova-android/pull/1186) fix: copy `repositories.gradle` to project on create
|
||||
* [GH-1184](https://github.com/apache/cordova-android/pull/1184) fix: unit-test failure
|
||||
* [GH-733](https://github.com/apache/cordova-android/pull/733) fix(splashscreen): nav & title bar showing in fullscreen mode
|
||||
* [GH-1157](https://github.com/apache/cordova-android/pull/1157) fix: restore key event handlers when DOM element is fullscreen
|
||||
* [GH-1073](https://github.com/apache/cordova-android/pull/1073) fix(android): Avoid Crash Report: ConcurrentModificationException
|
||||
* [GH-1148](https://github.com/apache/cordova-android/pull/1148) fix: add not null checks to prevent running on destroyed activity
|
||||
* [GH-1091](https://github.com/apache/cordova-android/pull/1091) fix: concurrent modification exception (#924)
|
||||
* [GH-1153](https://github.com/apache/cordova-android/pull/1153) fix: optional arch parameter
|
||||
* [GH-1136](https://github.com/apache/cordova-android/pull/1136) fix(prepare): `mapImageResources` always returning `[]`
|
||||
* [GH-1111](https://github.com/apache/cordova-android/pull/1111) fix(android): allow file access for existing behavior
|
||||
* [GH-1045](https://github.com/apache/cordova-android/pull/1045) fix: Reflect minimum required NodeJS
|
||||
* [GH-1084](https://github.com/apache/cordova-android/pull/1084) fix(prepare): fix pattern used to collect image resources
|
||||
* [GH-1014](https://github.com/apache/cordova-android/pull/1014) fix(`pluginHandlers`): properly check if path is inside another
|
||||
* [GH-1018](https://github.com/apache/cordova-android/pull/1018) fix: gradle ignore properties
|
||||
* [GH-1185](https://github.com/apache/cordova-android/pull/1185) fix(regression): Cannot read version of undefined caused by Java refactor
|
||||
* [GH-1117](https://github.com/apache/cordova-android/pull/1117) fix: allow changing min sdk version
|
||||
|
||||
**Refactors:**
|
||||
|
||||
* [GH-1101](https://github.com/apache/cordova-android/pull/1101) refactor: unify target resolution for devices & emulators
|
||||
* [GH-1130](https://github.com/apache/cordova-android/pull/1130) refactor: java checks
|
||||
* [GH-1099](https://github.com/apache/cordova-android/pull/1099) refactor(`ProjectBuilder`): clean up output file collection code
|
||||
* [GH-1123](https://github.com/apache/cordova-android/pull/1123) refactor: unify installation on devices & emulators
|
||||
* [GH-1102](https://github.com/apache/cordova-android/pull/1102) refactor(`check_reqs`): cleanup default Java location detection on **Windows**
|
||||
* [GH-1103](https://github.com/apache/cordova-android/pull/1103) refactor: do not kill adb on UNIX-like systems
|
||||
* [GH-1086](https://github.com/apache/cordova-android/pull/1086) refactor(retry): simplify retryPromise using modern JS
|
||||
* [GH-1085](https://github.com/apache/cordova-android/pull/1085) refactor(utils): reduce number of utils
|
||||
* [GH-1046](https://github.com/apache/cordova-android/pull/1046) refactor: Stop suppressing un-needed TruelyRandom lints
|
||||
* [GH-1016](https://github.com/apache/cordova-android/pull/1016) refactor: save `ProjectBuilder` instance in Api instance
|
||||
* [GH-1108](https://github.com/apache/cordova-android/pull/1108) refactor: remove copied Adb.install from `emulator.install`
|
||||
|
||||
**Chores:**
|
||||
|
||||
* [GH-1196](https://github.com/apache/cordova-android/pull/1196) chore: add missing header license
|
||||
* chore(asf): Update GitHub repo metadata
|
||||
* [GH-1183](https://github.com/apache/cordova-android/pull/1183) chore: rebuilt package-lock
|
||||
* [GH-1015](https://github.com/apache/cordova-android/pull/1015) chore: remove unnecessary stuff
|
||||
* [GH-1081](https://github.com/apache/cordova-android/pull/1081) chore(pkg): remove deprecated `no-op` field `"engineStrict"`
|
||||
* [GH-1019](https://github.com/apache/cordova-android/pull/1019) chore: remove unused `emulator.create_image` and its dependencies
|
||||
|
||||
**Tests & CI:**
|
||||
|
||||
* [GH-1017](https://github.com/apache/cordova-android/pull/1017) test(java): fix, improve and move clean script
|
||||
* [GH-1012](https://github.com/apache/cordova-android/pull/1012) test: fix missing stack traces in jasmine output
|
||||
* [GH-1013](https://github.com/apache/cordova-android/pull/1013) test(`pluginHandlers/common`): better setup & teardown
|
||||
* [GH-1094](https://github.com/apache/cordova-android/pull/1094) test: fix unit test failures for certain random orders
|
||||
* [GH-1094](https://github.com/apache/cordova-android/pull/1094) test: ensure single top-level describe block in test file
|
||||
* [GH-1129](https://github.com/apache/cordova-android/pull/1129) test(java): remove duplicate code in `BackButtonMultipageTest`
|
||||
* [GH-975](https://github.com/apache/cordova-android/pull/975) ci: Added Node 14.x
|
||||
|
||||
### 9.0.0 (Jun 23, 2020)
|
||||
|
||||
* [GH-1005](https://github.com/apache/cordova-android/pull/1005) chore: set AndroidX off by default
|
||||
|
||||
@@ -17,16 +17,12 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
/**
|
||||
* This script is to be run manually (e.g. by npm run clean:java-unit-tests) if
|
||||
* you want to upgrade gradlew or test its proper generation.
|
||||
*/
|
||||
|
||||
for (const variant of ['android', 'androidx']) {
|
||||
for (const file of ['gradlew', 'gradlew.bat']) {
|
||||
fs.removeSync(path.join(__dirname, variant, file));
|
||||
}
|
||||
if (fs.existsSync('./test/gradlew')) {
|
||||
fs.unlinkSync('./test/gradlew');
|
||||
}
|
||||
|
||||
if (fs.existsSync('./test/gradlew.bat')) {
|
||||
fs.unlinkSync('./test/gradlew.bat');
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
var path = require('path');
|
||||
var fs = require('fs-extra');
|
||||
var utils = require('../templates/cordova/lib/utils');
|
||||
var utils = require('../../bin/lib/utils');
|
||||
var check_reqs = require('./../templates/cordova/lib/check_reqs');
|
||||
var ROOT = path.join(__dirname, '..', '..');
|
||||
|
||||
@@ -36,7 +36,6 @@ exports.copyScripts = copyScripts;
|
||||
exports.copyBuildRules = copyBuildRules;
|
||||
exports.writeProjectProperties = writeProjectProperties;
|
||||
exports.prepBuildFiles = prepBuildFiles;
|
||||
exports.writeNameForAndroidStudio = writeNameForAndroidStudio;
|
||||
|
||||
function getFrameworkDir (projectPath, shared) {
|
||||
return shared ? path.join(ROOT, 'framework') : path.join(projectPath, 'CordovaLib');
|
||||
@@ -72,7 +71,6 @@ function copyJsAndLibrary (projectPath, shared, projectName, isLegacy) {
|
||||
fs.copySync(path.join(ROOT, 'framework', 'project.properties'), path.join(nestedCordovaLibPath, 'project.properties'));
|
||||
fs.copySync(path.join(ROOT, 'framework', 'build.gradle'), path.join(nestedCordovaLibPath, 'build.gradle'));
|
||||
fs.copySync(path.join(ROOT, 'framework', 'cordova.gradle'), path.join(nestedCordovaLibPath, 'cordova.gradle'));
|
||||
fs.copySync(path.join(ROOT, 'framework', 'repositories.gradle'), path.join(nestedCordovaLibPath, 'repositories.gradle'));
|
||||
fs.copySync(path.join(ROOT, 'framework', 'src'), path.join(nestedCordovaLibPath, 'src'));
|
||||
}
|
||||
}
|
||||
@@ -127,8 +125,6 @@ function copyBuildRules (projectPath, isLegacy) {
|
||||
} else {
|
||||
fs.copySync(path.join(srcDir, 'build.gradle'), path.join(projectPath, 'build.gradle'));
|
||||
fs.copySync(path.join(srcDir, 'app', 'build.gradle'), path.join(projectPath, 'app', 'build.gradle'));
|
||||
fs.copySync(path.join(srcDir, 'app', 'repositories.gradle'), path.join(projectPath, 'app', 'repositories.gradle'));
|
||||
fs.copySync(path.join(srcDir, 'repositories.gradle'), path.join(projectPath, 'repositories.gradle'));
|
||||
fs.copySync(path.join(srcDir, 'wrapper.gradle'), path.join(projectPath, 'wrapper.gradle'));
|
||||
}
|
||||
}
|
||||
@@ -201,19 +197,6 @@ function validateProjectName (project_name) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the name of the app in "platforms/android/.idea/.name" so that Android Studio can show that name in the
|
||||
* project listing. This is helpful to quickly look in the Android Studio listing if there are so many projects in
|
||||
* Android Studio.
|
||||
*
|
||||
* https://github.com/apache/cordova-android/issues/1172
|
||||
*/
|
||||
function writeNameForAndroidStudio (project_path, project_name) {
|
||||
const ideaPath = path.join(project_path, '.idea');
|
||||
fs.ensureDirSync(ideaPath);
|
||||
fs.writeFileSync(path.join(ideaPath, '.name'), project_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an android application with the given options.
|
||||
*
|
||||
@@ -311,7 +294,6 @@ 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.writeNameForAndroidStudio(project_path, project_name);
|
||||
events.emit('log', generateDoneMessage('create', options.link));
|
||||
}).then(() => project_path);
|
||||
};
|
||||
|
||||
47
bin/lib/utils.js
Normal file
47
bin/lib/utils.js
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
Provides a set of utility methods, which can also be spied on during unit tests.
|
||||
*/
|
||||
|
||||
// TODO: Perhaps this should live in cordova-common?
|
||||
|
||||
const fs = require('fs-extra');
|
||||
|
||||
/**
|
||||
* Reads, searches, and replaces the found occurences with replacementString and then writes the file back out.
|
||||
* A backup is not made.
|
||||
*
|
||||
* @param {string} file A file path to a readable & writable file
|
||||
* @param {RegExp} searchRegex The search regex
|
||||
* @param {string} replacementString The string to replace the found occurences
|
||||
* @returns {void}
|
||||
*/
|
||||
exports.replaceFileContents = function (file, searchRegex, replacementString) {
|
||||
// let contents;
|
||||
try {
|
||||
var contents = fs.readFileSync(file).toString();
|
||||
} catch (ex) {
|
||||
console.log('TRYING TO READ: ', file);
|
||||
throw ex;
|
||||
}
|
||||
contents = contents.replace(searchRegex, replacementString);
|
||||
fs.writeFileSync(file, contents);
|
||||
};
|
||||
8
bin/templates/cordova/Api.js
vendored
8
bin/templates/cordova/Api.js
vendored
@@ -24,7 +24,7 @@
|
||||
* This workflow would not have the `package.json` file.
|
||||
*/
|
||||
// Coho updates this line
|
||||
const VERSION = '9.1.0';
|
||||
const VERSION = '9.0.0';
|
||||
|
||||
var path = require('path');
|
||||
|
||||
@@ -85,8 +85,6 @@ class Api {
|
||||
build: path.join(this.root, 'build'),
|
||||
javaSrc: path.join(appMain, 'java')
|
||||
};
|
||||
|
||||
this._builder = require('./lib/builders/builders').getBuilder(this.root);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -163,7 +161,7 @@ class Api {
|
||||
if (plugin.getFrameworks(this.platform).length === 0) return;
|
||||
selfEvents.emit('verbose', 'Updating build files since android plugin contained <framework>');
|
||||
// This should pick the correct builder, not just get gradle
|
||||
this._builder.prepBuildFiles();
|
||||
require('./lib/builders/builders').getBuilder().prepBuildFiles();
|
||||
}.bind(this))
|
||||
// CB-11022 Return truthy value to prevent running prepare after
|
||||
.then(() => true);
|
||||
@@ -195,7 +193,7 @@ class Api {
|
||||
if (plugin.getFrameworks(this.platform).length === 0) return;
|
||||
|
||||
selfEvents.emit('verbose', 'Updating build files since android plugin contained <framework>');
|
||||
this._builder.prepBuildFiles();
|
||||
require('./lib/builders/builders').getBuilder().prepBuildFiles();
|
||||
}.bind(this))
|
||||
// CB-11022 Return truthy value to prevent running prepare after
|
||||
.then(() => true);
|
||||
|
||||
52
bin/templates/cordova/lib/Adb.js
vendored
52
bin/templates/cordova/lib/Adb.js
vendored
@@ -24,40 +24,42 @@ var CordovaError = require('cordova-common').CordovaError;
|
||||
|
||||
var Adb = {};
|
||||
|
||||
function isDevice (line) {
|
||||
return line.match(/\w+\tdevice/) && !line.match(/emulator/);
|
||||
}
|
||||
|
||||
function isEmulator (line) {
|
||||
return line.match(/device/) && line.match(/emulator/);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists available/connected devices and emulators
|
||||
*
|
||||
* @param {Object} opts Various options
|
||||
* @param {Boolean} opts.emulators Specifies whether this method returns
|
||||
* emulators only
|
||||
*
|
||||
* @return {Promise<String[]>} list of available/connected
|
||||
* devices/emulators
|
||||
*/
|
||||
Adb.devices = async function () {
|
||||
const { stdout } = await execa('adb', ['devices'], { cwd: os.tmpdir() });
|
||||
|
||||
// Split into lines & drop first one (header)
|
||||
const rawDeviceLines = stdout.trim().split(/\r?\n/).slice(1);
|
||||
|
||||
return rawDeviceLines
|
||||
.map(line => line.split('\t'))
|
||||
|
||||
// We are only interested in fully booted devices & emulators. These
|
||||
// have a state of `device`. For a list of all the other possible states
|
||||
// see https://github.com/aosp-mirror/platform_system_core/blob/2abdb1eb5b83c8f39874644af576c869815f5c5b/adb/transport.cpp#L1129
|
||||
.filter(([, state]) => state === 'device')
|
||||
|
||||
.map(([id]) => id);
|
||||
Adb.devices = function (opts) {
|
||||
return execa('adb', ['devices'], { cwd: os.tmpdir() }).then(({ stdout: output }) => {
|
||||
return output.split('\n').filter(function (line) {
|
||||
// Filter out either real devices or emulators, depending on options
|
||||
return (line && opts && opts.emulators) ? isEmulator(line) : isDevice(line);
|
||||
}).map(function (line) {
|
||||
return line.replace(/\tdevice/, '').replace('\r', '');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Adb.install = function (target, packagePath, { replace = false, execOptions = {} } = {}) {
|
||||
Adb.install = function (target, packagePath, opts) {
|
||||
events.emit('verbose', 'Installing apk ' + packagePath + ' on target ' + target + '...');
|
||||
|
||||
var args = ['-s', target, 'install'];
|
||||
if (replace) args.push('-r');
|
||||
|
||||
const opts = { cwd: os.tmpdir(), ...execOptions };
|
||||
|
||||
return execa('adb', args.concat(packagePath), opts).then(({ stdout: output }) => {
|
||||
// adb does not return an error code even if installation fails. Instead it puts a specific
|
||||
// message to stdout, so we have to use RegExp matching to detect installation failure.
|
||||
if (opts && opts.replace) args.push('-r');
|
||||
return execa('adb', args.concat(packagePath), { cwd: os.tmpdir() }).then(({ stdout: output }) => {
|
||||
// 'adb install' seems to always returns no error, even if installation fails
|
||||
// so we catching output to detect installation failure
|
||||
if (output.match(/Failure/)) {
|
||||
if (output.match(/INSTALL_PARSE_FAILED_NO_CERTIFICATES/)) {
|
||||
output += '\n\n' + 'Sign the build using \'-- --keystore\' or \'--buildConfig\'' +
|
||||
@@ -67,7 +69,7 @@ Adb.install = function (target, packagePath, { replace = false, execOptions = {}
|
||||
'\nEither uninstall an app or increment the versionCode.';
|
||||
}
|
||||
|
||||
throw new CordovaError('Failed to install apk to target: ' + output);
|
||||
return Promise.reject(new CordovaError('Failed to install apk to device: ' + output));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
48
bin/templates/cordova/lib/build.js
vendored
48
bin/templates/cordova/lib/build.js
vendored
@@ -23,7 +23,10 @@ var nopt = require('nopt');
|
||||
|
||||
var Adb = require('./Adb');
|
||||
|
||||
var builders = require('./builders/builders');
|
||||
var events = require('cordova-common').events;
|
||||
const execa = require('execa');
|
||||
var CordovaError = require('cordova-common').CordovaError;
|
||||
var PackageType = require('./PackageType');
|
||||
|
||||
module.exports.parseBuildOptions = parseOpts;
|
||||
@@ -142,7 +145,7 @@ function parseOpts (options, resolvedTarget, projectRoot) {
|
||||
*/
|
||||
module.exports.runClean = function (options) {
|
||||
var opts = parseOpts(options, null, this.root);
|
||||
var builder = this._builder;
|
||||
var builder = builders.getBuilder();
|
||||
|
||||
return builder.prepEnv(opts).then(function () {
|
||||
return builder.clean(opts);
|
||||
@@ -163,7 +166,7 @@ module.exports.runClean = function (options) {
|
||||
*/
|
||||
module.exports.run = function (options, optResolvedTarget) {
|
||||
var opts = parseOpts(options, optResolvedTarget, this.root);
|
||||
var builder = this._builder;
|
||||
var builder = builders.getBuilder();
|
||||
|
||||
return builder.prepEnv(opts).then(function () {
|
||||
if (opts.prepEnv) {
|
||||
@@ -193,8 +196,45 @@ module.exports.run = function (options, optResolvedTarget) {
|
||||
* Returns "arm" or "x86".
|
||||
*/
|
||||
module.exports.detectArchitecture = function (target) {
|
||||
return Adb.shell(target, 'cat /proc/cpuinfo').then(function (output) {
|
||||
return /intel/i.exec(output) ? 'x86' : 'arm';
|
||||
function helper () {
|
||||
return Adb.shell(target, 'cat /proc/cpuinfo').then(function (output) {
|
||||
return /intel/i.exec(output) ? 'x86' : 'arm';
|
||||
});
|
||||
}
|
||||
function timeout (ms, err) {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => reject(err), ms);
|
||||
});
|
||||
}
|
||||
// It sometimes happens (at least on OS X), that this command will hang forever.
|
||||
// To fix it, either unplug & replug device, or restart adb server.
|
||||
return Promise.race([
|
||||
helper(),
|
||||
timeout(5000, new CordovaError(
|
||||
'Device communication timed out. Try unplugging & replugging the device.'
|
||||
))
|
||||
]).catch(err => {
|
||||
if (/timed out/.exec('' + err)) {
|
||||
// adb kill-server doesn't seem to do the trick.
|
||||
// Could probably find a x-platform version of killall, but I'm not actually
|
||||
// sure that this scenario even happens on non-OSX machines.
|
||||
events.emit('verbose', 'adb timed out while detecting device/emulator architecture. Killing adb and trying again.');
|
||||
return execa('killall', ['adb']).then(function () {
|
||||
return helper().then(null, function () {
|
||||
// The double kill is sadly often necessary, at least on mac.
|
||||
events.emit('warn', 'adb timed out a second time while detecting device/emulator architecture. Killing adb and trying again.');
|
||||
return execa('killall', ['adb']).then(function () {
|
||||
return helper().then(null, function () {
|
||||
return Promise.reject(new CordovaError('adb timed out a third time while detecting device/emulator architecture. Try unplugging & replugging the device.'));
|
||||
});
|
||||
});
|
||||
});
|
||||
}, function () {
|
||||
// For non-killall OS's.
|
||||
return Promise.reject(err);
|
||||
});
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -20,12 +20,10 @@
|
||||
var fs = require('fs-extra');
|
||||
var path = require('path');
|
||||
const execa = require('execa');
|
||||
const glob = require('fast-glob');
|
||||
var events = require('cordova-common').events;
|
||||
var CordovaError = require('cordova-common').CordovaError;
|
||||
var check_reqs = require('../check_reqs');
|
||||
var PackageType = require('../PackageType');
|
||||
const { compareByAll } = require('../utils');
|
||||
const { createEditor } = require('properties-parser');
|
||||
|
||||
const MARKER = 'YOUR CHANGES WILL BE ERASED!';
|
||||
@@ -34,46 +32,83 @@ const TEMPLATE =
|
||||
'# This file is automatically generated.\n' +
|
||||
'# Do not modify this file -- ' + MARKER + '\n';
|
||||
|
||||
const isPathArchSpecific = p => /-x86|-arm/.test(path.basename(p));
|
||||
const archSpecificRegex = /-x86|-arm/;
|
||||
const unsignedBuildRegex = /-unsigned/;
|
||||
|
||||
const outputFileComparator = compareByAll([
|
||||
// Sort arch specific builds after generic ones
|
||||
isPathArchSpecific,
|
||||
const fileSorter = (filePathA, filePathB) => {
|
||||
const archSpecificA = archSpecificRegex.test(filePathA);
|
||||
const archSpecificB = archSpecificRegex.test(filePathB);
|
||||
|
||||
// Sort unsigned builds after signed ones
|
||||
filePath => /-unsigned/.test(path.basename(filePath)),
|
||||
// If they are not equal, then sort by specific archs after generic ones
|
||||
if (archSpecificA !== archSpecificB) {
|
||||
return archSpecificA < archSpecificB ? -1 : 1;
|
||||
}
|
||||
|
||||
// Sort by file modification time, latest first
|
||||
filePath => -fs.statSync(filePath).mtime.getTime(),
|
||||
// Otherwise, move onto the next sort item, which is by sorting unsigned bulds after signed ones
|
||||
const unsignedA = unsignedBuildRegex.test(filePathA);
|
||||
const unsignedB = unsignedBuildRegex.test(filePathB);
|
||||
|
||||
// Sort by file name length, ascending
|
||||
filePath => filePath.length
|
||||
]);
|
||||
if (unsignedA !== unsignedB) {
|
||||
return unsignedA < unsignedB ? -1 : 1;
|
||||
}
|
||||
|
||||
// Then, sort by modification time, latest first
|
||||
const modTimeA = fs.statSync(filePathA).mtime.getTime();
|
||||
const modTimeB = fs.statSync(filePathB).mtime.getTime();
|
||||
|
||||
if (modTimeA !== modTimeB) {
|
||||
return modTimeA < modTimeB ? 1 : -1;
|
||||
}
|
||||
|
||||
// Finally, if all above is the same, sort by file name length, ascending
|
||||
return filePathB.length < filePathA.length ? -1 : 1;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {'apk' | 'aab'} bundleType
|
||||
* @param {'debug' | 'release'} buildType
|
||||
* @param {{arch?: string}} options
|
||||
* If the provided directory does not exist or extension is missing, return an empty array.
|
||||
* If the director exists, loop the directories and collect list of files matching the extension.
|
||||
*
|
||||
* @param {String} dir Directory to scan
|
||||
* @param {String} extension
|
||||
*/
|
||||
function findOutputFiles (bundleType, buildType, { arch } = {}) {
|
||||
let files = glob.sync(`**/*.${bundleType}`, {
|
||||
absolute: true,
|
||||
cwd: path.resolve(this[`${bundleType}Dir`], buildType)
|
||||
}).map(path.normalize);
|
||||
function recursivelyFindFiles (dir, extension) {
|
||||
if (!fs.existsSync(dir) || !extension) return [];
|
||||
|
||||
const files = fs.readdirSync(dir, { withFileTypes: true })
|
||||
.map(entry => {
|
||||
const item = path.resolve(dir, entry.name);
|
||||
|
||||
if (entry.isDirectory()) return recursivelyFindFiles(item, extension);
|
||||
if (path.extname(entry.name) === `.${extension}`) return item;
|
||||
return false;
|
||||
});
|
||||
|
||||
return Array.prototype.concat(...files)
|
||||
.filter(file => file !== false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} dir
|
||||
* @param {String} build_type
|
||||
* @param {String} arch
|
||||
* @param {String} extension
|
||||
*/
|
||||
function findOutputFilesHelper (dir, build_type, arch, extension) {
|
||||
let files = recursivelyFindFiles(path.resolve(dir, build_type), extension);
|
||||
|
||||
if (files.length === 0) return files;
|
||||
|
||||
// Assume arch-specific build if newest apk has -x86 or -arm.
|
||||
const archSpecific = isPathArchSpecific(files[0]);
|
||||
const archSpecific = !!/-x86|-arm/.exec(path.basename(files[0]));
|
||||
|
||||
// And show only arch-specific ones (or non-arch-specific)
|
||||
files = files.filter(p => isPathArchSpecific(p) === archSpecific);
|
||||
files = files.filter(p => !!/-x86|-arm/.exec(path.basename(p)) === archSpecific);
|
||||
|
||||
if (archSpecific && files.length > 1 && arch) {
|
||||
files = files.filter(p => path.basename(p).includes('-' + arch));
|
||||
files = files.filter(p => path.basename(p).indexOf('-' + arch) !== -1);
|
||||
}
|
||||
|
||||
return files.sort(outputFileComparator);
|
||||
return files;
|
||||
}
|
||||
|
||||
class ProjectBuilder {
|
||||
@@ -295,7 +330,7 @@ class ProjectBuilder {
|
||||
var wrapper = path.join(this.root, 'gradlew');
|
||||
var args = this.getArgs(opts.buildType === 'debug' ? 'debug' : 'release', opts);
|
||||
|
||||
return execa(wrapper, args, { stdio: 'inherit', cwd: path.resolve(this.root) })
|
||||
return execa(wrapper, args, { stdio: 'inherit' })
|
||||
.catch(function (error) {
|
||||
if (error.toString().indexOf('failed to find target with hash string') >= 0) {
|
||||
return check_reqs.check_android_target(error).then(function () {
|
||||
@@ -311,7 +346,7 @@ class ProjectBuilder {
|
||||
clean (opts) {
|
||||
const wrapper = path.join(this.root, 'gradlew');
|
||||
const args = this.getArgs('clean', opts);
|
||||
return execa(wrapper, args, { stdio: 'inherit', cwd: path.resolve(this.root) })
|
||||
return execa(wrapper, args, { stdio: 'inherit' })
|
||||
.then(() => {
|
||||
fs.removeSync(path.join(this.root, 'out'));
|
||||
|
||||
@@ -327,11 +362,11 @@ class ProjectBuilder {
|
||||
}
|
||||
|
||||
findOutputApks (build_type, arch) {
|
||||
return findOutputFiles.call(this, 'apk', build_type, { arch });
|
||||
return findOutputFilesHelper(this.apkDir, build_type, arch, 'apk').sort(fileSorter);
|
||||
}
|
||||
|
||||
findOutputBundles (build_type) {
|
||||
return findOutputFiles.call(this, 'aab', build_type);
|
||||
return findOutputFilesHelper(this.aabDir, build_type, false, 'aab').sort(fileSorter);
|
||||
}
|
||||
|
||||
fetchBuildResults (build_type, arch) {
|
||||
|
||||
137
bin/templates/cordova/lib/check_reqs.js
vendored
137
bin/templates/cordova/lib/check_reqs.js
vendored
@@ -20,20 +20,43 @@
|
||||
const execa = require('execa');
|
||||
var path = require('path');
|
||||
var fs = require('fs-extra');
|
||||
const { forgivingWhichSync, isWindows, isDarwin } = require('./utils');
|
||||
const java = require('./env/java');
|
||||
var os = require('os');
|
||||
var which = require('which');
|
||||
var REPO_ROOT = path.join(__dirname, '..', '..', '..', '..');
|
||||
var PROJECT_ROOT = path.join(__dirname, '..', '..');
|
||||
const { CordovaError, ConfigParser, events } = require('cordova-common');
|
||||
var android_sdk = require('./android_sdk');
|
||||
const { createEditor } = require('properties-parser');
|
||||
const semver = require('semver');
|
||||
|
||||
const EXPECTED_JAVA_VERSION = '1.8.x';
|
||||
function forgivingWhichSync (cmd) {
|
||||
const whichResult = which.sync(cmd, { nothrow: true });
|
||||
|
||||
// Re-exporting these for backwards compatibility and for unit testing.
|
||||
// TODO: Remove uses and use the ./utils module directly.
|
||||
Object.assign(module.exports, { isWindows, isDarwin });
|
||||
// On null, returns empty string to maintain backwards compatibility
|
||||
// realpathSync follows symlinks
|
||||
return whichResult === null ? '' : fs.realpathSync(whichResult);
|
||||
}
|
||||
|
||||
function getJDKDirectory (directory) {
|
||||
const p = path.resolve(directory, 'java');
|
||||
if (fs.existsSync(p)) {
|
||||
const directories = fs.readdirSync(p);
|
||||
for (let i = 0; i < directories.length; i++) {
|
||||
const dir = directories[i];
|
||||
if (/^(jdk)+./.test(dir)) {
|
||||
return path.resolve(directory, 'java', dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports.isWindows = function () {
|
||||
return (os.platform() === 'win32');
|
||||
};
|
||||
|
||||
module.exports.isDarwin = function () {
|
||||
return (os.platform() === 'darwin');
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Get valid target from framework/project.properties if run from this repo
|
||||
@@ -75,6 +98,18 @@ module.exports.get_target = function () {
|
||||
return target;
|
||||
};
|
||||
|
||||
// Returns a promise. Called only by build and clean commands.
|
||||
module.exports.check_ant = function () {
|
||||
return execa('ant', ['-version']).then(({ stdout: output }) => {
|
||||
// Parse Ant version from command output
|
||||
return /version ((?:\d+\.)+(?:\d+))/i.exec(output)[1];
|
||||
}).catch(function (err) {
|
||||
if (err) {
|
||||
throw new CordovaError('Failed to run `ant -version`. Make sure you have `ant` on your $PATH.');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.get_gradle_wrapper = function () {
|
||||
var androidStudioPath;
|
||||
var i = 0;
|
||||
@@ -133,22 +168,75 @@ module.exports.check_gradle = function () {
|
||||
'in your path, or install Android Studio'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks for the java installation and correct version
|
||||
*
|
||||
* Despite the name, it should return the Java version value, it's used by the Cordova CLI.
|
||||
*/
|
||||
module.exports.check_java = async function () {
|
||||
const javaVersion = await java.getVersion();
|
||||
// Returns a promise.
|
||||
module.exports.check_java = function () {
|
||||
var javacPath = forgivingWhichSync('javac');
|
||||
var hasJavaHome = !!process.env.JAVA_HOME;
|
||||
return Promise.resolve().then(function () {
|
||||
if (hasJavaHome) {
|
||||
// Windows java installer doesn't add javac to PATH, nor set JAVA_HOME (ugh).
|
||||
if (!javacPath) {
|
||||
process.env.PATH += path.delimiter + path.join(process.env.JAVA_HOME, 'bin');
|
||||
}
|
||||
} else {
|
||||
if (javacPath) {
|
||||
// OS X has a command for finding JAVA_HOME.
|
||||
var find_java = '/usr/libexec/java_home';
|
||||
var default_java_error_msg = 'Failed to find \'JAVA_HOME\' environment variable. Try setting it manually.';
|
||||
if (fs.existsSync(find_java)) {
|
||||
return execa(find_java).then(({ stdout }) => {
|
||||
process.env.JAVA_HOME = stdout;
|
||||
}).catch(function (err) {
|
||||
if (err) {
|
||||
throw new CordovaError(default_java_error_msg);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// See if we can derive it from javac's location.
|
||||
var maybeJavaHome = path.dirname(path.dirname(javacPath));
|
||||
if (fs.existsSync(path.join(maybeJavaHome, 'lib', 'tools.jar'))) {
|
||||
process.env.JAVA_HOME = maybeJavaHome;
|
||||
} else {
|
||||
throw new CordovaError(default_java_error_msg);
|
||||
}
|
||||
}
|
||||
} else if (module.exports.isWindows()) {
|
||||
const programFilesEnv = path.resolve(process.env.ProgramFiles);
|
||||
const programFiles = 'C:\\Program Files\\';
|
||||
const programFilesx86 = 'C:\\Program Files (x86)\\';
|
||||
|
||||
if (!semver.satisfies(javaVersion.version, EXPECTED_JAVA_VERSION)) {
|
||||
throw new CordovaError(
|
||||
`Requirements check failed for JDK ${EXPECTED_JAVA_VERSION}! Detected version: ${javaVersion.version}\n` +
|
||||
'Check your ANDROID_SDK_ROOT / JAVA_HOME / PATH environment variables.'
|
||||
);
|
||||
}
|
||||
let firstJdkDir =
|
||||
getJDKDirectory(programFilesEnv) ||
|
||||
getJDKDirectory(programFiles) ||
|
||||
getJDKDirectory(programFilesx86);
|
||||
|
||||
return javaVersion;
|
||||
if (firstJdkDir) {
|
||||
firstJdkDir = firstJdkDir.replace(/\//g, path.sep);
|
||||
if (!javacPath) {
|
||||
process.env.PATH += path.delimiter + path.join(firstJdkDir, 'bin');
|
||||
}
|
||||
process.env.JAVA_HOME = firstJdkDir;
|
||||
}
|
||||
}
|
||||
}
|
||||
}).then(function () {
|
||||
return execa('javac', ['-version'], { all: true })
|
||||
.then(({ all: output }) => {
|
||||
// Java <= 8 writes version info to stderr, Java >= 9 to stdout
|
||||
const match = /javac\s+([\d.]+)/i.exec(output);
|
||||
return match && match[1];
|
||||
}, () => {
|
||||
var msg =
|
||||
'Failed to run "javac -version", make sure that you have a JDK version 8 installed.\n' +
|
||||
'You can get it from the following location:\n' +
|
||||
'https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html';
|
||||
if (process.env.JAVA_HOME) {
|
||||
msg += '\n\n';
|
||||
msg += 'Your JAVA_HOME is invalid: ' + process.env.JAVA_HOME;
|
||||
}
|
||||
throw new CordovaError(msg);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Returns a promise.
|
||||
@@ -324,6 +412,13 @@ module.exports.run = function () {
|
||||
return Promise.all([this.check_java(), this.check_android()]).then(function (values) {
|
||||
console.log('Using Android SDK: ' + process.env.ANDROID_SDK_ROOT);
|
||||
|
||||
if (!String(values[0]).startsWith('1.8.')) {
|
||||
throw new CordovaError(
|
||||
'Requirements check failed for JDK 8 (\'1.8.*\')! Detected version: ' + values[0] + '\n' +
|
||||
'Check your ANDROID_SDK_ROOT / JAVA_HOME / PATH environment variables.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!values[1]) {
|
||||
throw new CordovaError('Requirements check failed for Android SDK! Android SDK was not detected.');
|
||||
}
|
||||
|
||||
109
bin/templates/cordova/lib/device.js
vendored
Normal file
109
bin/templates/cordova/lib/device.js
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
const execa = require('execa');
|
||||
var build = require('./build');
|
||||
var path = require('path');
|
||||
var Adb = require('./Adb');
|
||||
var AndroidManifest = require('./AndroidManifest');
|
||||
var CordovaError = require('cordova-common').CordovaError;
|
||||
var events = require('cordova-common').events;
|
||||
|
||||
/**
|
||||
* Returns a promise for the list of the device ID's found
|
||||
* @param lookHarder When true, try restarting adb if no devices are found.
|
||||
*/
|
||||
module.exports.list = function (lookHarder) {
|
||||
return Adb.devices().then(function (list) {
|
||||
if (list.length === 0 && lookHarder) {
|
||||
// adb kill-server doesn't seem to do the trick.
|
||||
// Could probably find a x-platform version of killall, but I'm not actually
|
||||
// sure that this scenario even happens on non-OSX machines.
|
||||
return execa('killall', ['adb']).then(function () {
|
||||
events.emit('verbose', 'Restarting adb to see if more devices are detected.');
|
||||
return Adb.devices();
|
||||
}, function () {
|
||||
// For non-killall OS's.
|
||||
return list;
|
||||
});
|
||||
}
|
||||
return list;
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.resolveTarget = function (target) {
|
||||
return this.list(true).then(function (device_list) {
|
||||
if (!device_list || !device_list.length) {
|
||||
return Promise.reject(new CordovaError('Failed to deploy to device, no devices found.'));
|
||||
}
|
||||
// default device
|
||||
target = target || device_list[0];
|
||||
|
||||
if (device_list.indexOf(target) < 0) {
|
||||
return Promise.reject(new CordovaError('ERROR: Unable to find target \'' + target + '\'.'));
|
||||
}
|
||||
|
||||
return build.detectArchitecture(target).then(function (arch) {
|
||||
return { target: target, arch: arch, isEmulator: false };
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* Installs a previously built application on the device
|
||||
* and launches it.
|
||||
* Returns a promise.
|
||||
*/
|
||||
module.exports.install = function (target, buildResults) {
|
||||
return Promise.resolve().then(function () {
|
||||
if (target && typeof target === 'object') {
|
||||
return target;
|
||||
}
|
||||
return module.exports.resolveTarget(target);
|
||||
}).then(function (resolvedTarget) {
|
||||
var apk_path = build.findBestApkForArchitecture(buildResults, resolvedTarget.arch);
|
||||
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);
|
||||
events.emit('log', 'Package name: ' + pkgName);
|
||||
|
||||
return Adb.install(resolvedTarget.target, apk_path, { replace: true }).catch(function (error) {
|
||||
// CB-9557 CB-10157 only uninstall and reinstall app if the one that
|
||||
// is already installed on device was signed w/different certificate
|
||||
if (!/INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES/.test(error.toString())) { throw error; }
|
||||
|
||||
events.emit('warn', 'Uninstalling app from device and reinstalling it again because the ' +
|
||||
'installed app already signed with different key');
|
||||
|
||||
// This promise is always resolved, even if 'adb uninstall' fails to uninstall app
|
||||
// or the app doesn't installed at all, so no error catching needed.
|
||||
return Adb.uninstall(resolvedTarget.target, pkgName).then(function () {
|
||||
return Adb.install(resolvedTarget.target, apk_path, { replace: true });
|
||||
});
|
||||
}).then(function () {
|
||||
// unlock screen
|
||||
return Adb.shell(resolvedTarget.target, 'input keyevent 82');
|
||||
}).then(function () {
|
||||
return Adb.start(resolvedTarget.target, launchName);
|
||||
}).then(function () {
|
||||
events.emit('log', 'LAUNCH SUCCESS');
|
||||
});
|
||||
});
|
||||
};
|
||||
173
bin/templates/cordova/lib/emulator.js
vendored
173
bin/templates/cordova/lib/emulator.js
vendored
@@ -20,17 +20,25 @@
|
||||
const execa = require('execa');
|
||||
const fs = require('fs-extra');
|
||||
var android_versions = require('android-versions');
|
||||
var retry = require('./retry');
|
||||
var build = require('./build');
|
||||
var path = require('path');
|
||||
var Adb = require('./Adb');
|
||||
var AndroidManifest = require('./AndroidManifest');
|
||||
var events = require('cordova-common').events;
|
||||
var CordovaError = require('cordova-common').CordovaError;
|
||||
var android_sdk = require('./android_sdk');
|
||||
var check_reqs = require('./check_reqs');
|
||||
var which = require('which');
|
||||
var os = require('os');
|
||||
|
||||
// constants
|
||||
const ONE_SECOND = 1000; // in milliseconds
|
||||
const ONE_MINUTE = 60 * ONE_SECOND; // in milliseconds
|
||||
const INSTALL_COMMAND_TIMEOUT = 5 * ONE_MINUTE; // in milliseconds
|
||||
const NUM_INSTALL_RETRIES = 3;
|
||||
const CHECK_BOOTED_INTERVAL = 3 * ONE_SECOND; // in milliseconds
|
||||
const EXEC_KILL_SIGNAL = 'SIGKILL';
|
||||
|
||||
function forgivingWhichSync (cmd) {
|
||||
const whichResult = which.sync(cmd, { nothrow: true });
|
||||
@@ -207,9 +215,24 @@ module.exports.best_image = function () {
|
||||
});
|
||||
};
|
||||
|
||||
exports.list_started = async () => {
|
||||
return (await Adb.devices())
|
||||
.filter(id => id.startsWith('emulator-'));
|
||||
// Returns a promise.
|
||||
module.exports.list_started = function () {
|
||||
return Adb.devices({ emulators: true });
|
||||
};
|
||||
|
||||
// Returns a promise.
|
||||
// TODO: we should remove this, there's a more robust method under android_sdk.js
|
||||
module.exports.list_targets = function () {
|
||||
return execa('android', ['list', 'targets'], { cwd: os.tmpdir() }).then(({ stdout: output }) => {
|
||||
var target_out = output.split('\n');
|
||||
var targets = [];
|
||||
for (var i = target_out.length; i >= 0; i--) {
|
||||
if (target_out[i].match(/id:/)) {
|
||||
targets.push(targets[i].split(' ')[1]);
|
||||
}
|
||||
}
|
||||
return targets;
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -348,3 +371,147 @@ module.exports.wait_for_boot = function (emulator_id, time_remaining) {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* Create avd
|
||||
* TODO : Enter the stdin input required to complete the creation of an avd.
|
||||
* Returns a promise.
|
||||
*/
|
||||
module.exports.create_image = function (name, target) {
|
||||
console.log('Creating new avd named ' + name);
|
||||
if (target) {
|
||||
return execa('android', ['create', 'avd', '--name', name, '--target', target]).then(null, function (error) {
|
||||
console.error('ERROR : Failed to create emulator image : ');
|
||||
console.error(' Do you have the latest android targets including ' + target + '?');
|
||||
console.error(error.message);
|
||||
});
|
||||
} else {
|
||||
console.log('WARNING : Project target not found, creating avd with a different target but the project may fail to install.');
|
||||
// TODO: there's a more robust method for finding targets in android_sdk.js
|
||||
return execa('android', ['create', 'avd', '--name', name, '--target', this.list_targets()[0]]).then(function () {
|
||||
// TODO: This seems like another error case, even though it always happens.
|
||||
console.error('ERROR : Unable to create an avd emulator, no targets found.');
|
||||
console.error('Ensure you have targets available by running the "android" command');
|
||||
return Promise.reject(new CordovaError());
|
||||
}, function (error) {
|
||||
console.error('ERROR : Failed to create emulator image : ');
|
||||
console.error(error.message);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.resolveTarget = function (target) {
|
||||
return this.list_started().then(function (emulator_list) {
|
||||
if (emulator_list.length < 1) {
|
||||
return Promise.reject(new CordovaError('No running Android emulators found, please start an emulator before deploying your project.'));
|
||||
}
|
||||
|
||||
// default emulator
|
||||
target = target || emulator_list[0];
|
||||
if (emulator_list.indexOf(target) < 0) {
|
||||
return Promise.reject(new CordovaError('Unable to find target \'' + target + '\'. Failed to deploy to emulator.'));
|
||||
}
|
||||
|
||||
return build.detectArchitecture(target).then(function (arch) {
|
||||
return { target: target, arch: arch, isEmulator: true };
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* Installs a previously built application on the emulator and launches it.
|
||||
* If no target is specified, then it picks one.
|
||||
* If no started emulators are found, error out.
|
||||
* Returns a promise.
|
||||
*/
|
||||
module.exports.install = function (givenTarget, buildResults) {
|
||||
var target;
|
||||
// We need to find the proper path to the Android Manifest
|
||||
const manifestPath = path.join(__dirname, '..', '..', 'app', 'src', 'main', 'AndroidManifest.xml');
|
||||
const manifest = new AndroidManifest(manifestPath);
|
||||
const pkgName = manifest.getPackageId();
|
||||
|
||||
// resolve the target emulator
|
||||
return Promise.resolve().then(function () {
|
||||
if (givenTarget && typeof givenTarget === 'object') {
|
||||
return givenTarget;
|
||||
} else {
|
||||
return module.exports.resolveTarget(givenTarget);
|
||||
}
|
||||
|
||||
// set the resolved target
|
||||
}).then(function (resolvedTarget) {
|
||||
target = resolvedTarget;
|
||||
|
||||
// install the app
|
||||
}).then(function () {
|
||||
// This promise is always resolved, even if 'adb uninstall' fails to uninstall app
|
||||
// or the app doesn't installed at all, so no error catching needed.
|
||||
return Promise.resolve().then(function () {
|
||||
var apk_path = build.findBestApkForArchitecture(buildResults, target.arch);
|
||||
var execOptions = {
|
||||
cwd: os.tmpdir(),
|
||||
timeout: INSTALL_COMMAND_TIMEOUT, // in milliseconds
|
||||
killSignal: EXEC_KILL_SIGNAL
|
||||
};
|
||||
|
||||
events.emit('log', 'Using apk: ' + apk_path);
|
||||
events.emit('log', 'Package name: ' + pkgName);
|
||||
events.emit('verbose', 'Installing app on emulator...');
|
||||
|
||||
// A special function to call adb install in specific environment w/ specific options.
|
||||
// Introduced as a part of fix for http://issues.apache.org/jira/browse/CB-9119
|
||||
// to workaround sporadic emulator hangs
|
||||
function adbInstallWithOptions (target, apk, opts) {
|
||||
events.emit('verbose', 'Installing apk ' + apk + ' on ' + target + '...');
|
||||
|
||||
const args = ['-s', target, 'install', '-r', apk];
|
||||
return execa('adb', args, opts).then(({ stdout }) => {
|
||||
// adb does not return an error code even if installation fails. Instead it puts a specific
|
||||
// message to stdout, so we have to use RegExp matching to detect installation failure.
|
||||
if (/Failure/.test(stdout)) {
|
||||
if (stdout.match(/INSTALL_PARSE_FAILED_NO_CERTIFICATES/)) {
|
||||
stdout += 'Sign the build using \'-- --keystore\' or \'--buildConfig\'' +
|
||||
' or sign and deploy the unsigned apk manually using Android tools.';
|
||||
} else if (stdout.match(/INSTALL_FAILED_VERSION_DOWNGRADE/)) {
|
||||
stdout += 'You\'re trying to install apk with a lower versionCode that is already installed.' +
|
||||
'\nEither uninstall an app or increment the versionCode.';
|
||||
}
|
||||
|
||||
throw new CordovaError('Failed to install apk to emulator: ' + stdout);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function installPromise () {
|
||||
return adbInstallWithOptions(target.target, apk_path, execOptions).catch(function (error) {
|
||||
// CB-9557 CB-10157 only uninstall and reinstall app if the one that
|
||||
// is already installed on device was signed w/different certificate
|
||||
if (!/INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES/.test(error.toString())) { throw error; }
|
||||
|
||||
events.emit('warn', 'Uninstalling app from device and reinstalling it because the ' +
|
||||
'currently installed app was signed with different key');
|
||||
|
||||
// This promise is always resolved, even if 'adb uninstall' fails to uninstall app
|
||||
// or the app doesn't installed at all, so no error catching needed.
|
||||
return Adb.uninstall(target.target, pkgName).then(function () {
|
||||
return adbInstallWithOptions(target.target, apk_path, execOptions);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return retry.retryPromise(NUM_INSTALL_RETRIES, installPromise).then(function (output) {
|
||||
events.emit('log', 'INSTALL SUCCESS');
|
||||
});
|
||||
});
|
||||
// unlock screen
|
||||
}).then(function () {
|
||||
events.emit('verbose', 'Unlocking screen...');
|
||||
return Adb.shell(target.target, 'input keyevent 82');
|
||||
}).then(function () {
|
||||
Adb.start(target.target, pkgName + '/.' + manifest.getActivity().getName());
|
||||
// report success or failure
|
||||
}).then(function (output) {
|
||||
events.emit('log', 'LAUNCH SUCCESS');
|
||||
});
|
||||
};
|
||||
|
||||
123
bin/templates/cordova/lib/env/java.js
vendored
123
bin/templates/cordova/lib/env/java.js
vendored
@@ -1,123 +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.
|
||||
*/
|
||||
|
||||
const execa = require('execa');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const glob = require('fast-glob');
|
||||
const { CordovaError, events } = require('cordova-common');
|
||||
const utils = require('../utils');
|
||||
const semver = require('semver');
|
||||
|
||||
/**
|
||||
* Will be set to true on successful ensureness.
|
||||
* If true, skips the expensive java checks.
|
||||
*/
|
||||
let javaIsEnsured = false;
|
||||
|
||||
const java = {
|
||||
/**
|
||||
* Gets the version from the javac executable.
|
||||
*
|
||||
* @returns {semver.SemVer}
|
||||
*/
|
||||
getVersion: async () => {
|
||||
await java._ensure(process.env);
|
||||
|
||||
// Java <= 8 writes version info to stderr, Java >= 9 to stdout
|
||||
let version = null;
|
||||
try {
|
||||
version = (await execa('javac', ['-version'], { all: true })).all;
|
||||
} catch (ex) {
|
||||
events.emit('verbose', ex.shortMessage);
|
||||
|
||||
let msg =
|
||||
'Failed to run "javac -version", make sure that you have a JDK version 8 installed.\n' +
|
||||
'You can get it from the following location:\n' +
|
||||
'https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html';
|
||||
if (process.env.JAVA_HOME) {
|
||||
msg += '\n\n';
|
||||
msg += 'Your JAVA_HOME is invalid: ' + process.env.JAVA_HOME;
|
||||
}
|
||||
throw new CordovaError(msg);
|
||||
}
|
||||
|
||||
return semver.coerce(version);
|
||||
},
|
||||
|
||||
/**
|
||||
* Ensures that Java is installed. Will throw exception if not.
|
||||
* Will set JAVA_HOME and PATH environment variables.
|
||||
*
|
||||
* This function becomes a no-op if already ran previously.
|
||||
*/
|
||||
_ensure: async (environment) => {
|
||||
if (javaIsEnsured) {
|
||||
return;
|
||||
}
|
||||
|
||||
const javacPath = utils.forgivingWhichSync('javac');
|
||||
const hasJavaHome = !!environment.JAVA_HOME;
|
||||
if (hasJavaHome) {
|
||||
// Windows java installer doesn't add javac to PATH, nor set JAVA_HOME (ugh).
|
||||
if (!javacPath) {
|
||||
environment.PATH += path.delimiter + path.join(environment.JAVA_HOME, 'bin');
|
||||
}
|
||||
} else {
|
||||
if (javacPath) {
|
||||
// OS X has a command for finding JAVA_HOME.
|
||||
const find_java = '/usr/libexec/java_home';
|
||||
const default_java_error_msg = 'Failed to find \'JAVA_HOME\' environment variable. Try setting it manually.';
|
||||
if (fs.existsSync(find_java)) {
|
||||
try {
|
||||
environment.JAVA_HOME = (await execa(find_java)).stdout;
|
||||
} catch (ex) {
|
||||
events.emit('verbose', ex.shortMessage);
|
||||
throw new CordovaError(default_java_error_msg);
|
||||
}
|
||||
} else {
|
||||
// See if we can derive it from javac's location.
|
||||
var maybeJavaHome = path.dirname(path.dirname(javacPath));
|
||||
if (fs.existsSync(path.join(maybeJavaHome, 'lib', 'tools.jar'))) {
|
||||
environment.JAVA_HOME = maybeJavaHome;
|
||||
} else {
|
||||
throw new CordovaError(default_java_error_msg);
|
||||
}
|
||||
}
|
||||
} else if (utils.isWindows()) {
|
||||
const baseDirs = [environment.ProgramFiles, environment['ProgramFiles(x86)']];
|
||||
const globOpts = { absolute: true, onlyDirectories: true };
|
||||
const flatMap = (arr, f) => [].concat(...arr.map(f));
|
||||
const jdkDir = flatMap(baseDirs, cwd => {
|
||||
return glob.sync('java/jdk*', { cwd, ...globOpts });
|
||||
}
|
||||
)[0];
|
||||
|
||||
if (jdkDir) {
|
||||
environment.PATH += path.delimiter + path.join(jdkDir, 'bin');
|
||||
environment.JAVA_HOME = path.normalize(jdkDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
javaIsEnsured = true;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = java;
|
||||
@@ -19,21 +19,24 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
const { resolve, install } = require('./target');
|
||||
|
||||
var device = require('./device');
|
||||
var args = process.argv;
|
||||
const targetSpec = { type: 'device' };
|
||||
|
||||
if (args.length > 2) {
|
||||
var install_target;
|
||||
if (args[2].substring(0, 9) === '--target=') {
|
||||
targetSpec.id = args[2].substring(9, args[2].length);
|
||||
install_target = args[2].substring(9, args[2].length);
|
||||
device.install(install_target).catch(function (err) {
|
||||
console.error('ERROR: ' + err);
|
||||
process.exit(2);
|
||||
});
|
||||
} else {
|
||||
console.error('ERROR : argument \'' + args[2] + '\' not recognized.');
|
||||
process.exit(2);
|
||||
}
|
||||
} else {
|
||||
device.install().catch(function (err) {
|
||||
console.error('ERROR: ' + err);
|
||||
process.exit(2);
|
||||
});
|
||||
}
|
||||
|
||||
resolve(targetSpec).then(install).catch(err => {
|
||||
console.error('ERROR: ' + err);
|
||||
process.exit(2);
|
||||
});
|
||||
|
||||
@@ -19,21 +19,20 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
const { resolve, install } = require('./target');
|
||||
|
||||
var emulator = require('./emulator');
|
||||
var args = process.argv;
|
||||
const targetSpec = { type: 'emulator' };
|
||||
|
||||
var install_target;
|
||||
if (args.length > 2) {
|
||||
if (args[2].substring(0, 9) === '--target=') {
|
||||
targetSpec.id = args[2].substring(9, args[2].length);
|
||||
install_target = args[2].substring(9, args[2].length);
|
||||
} else {
|
||||
console.error('ERROR : argument \'' + args[2] + '\' not recognized.');
|
||||
process.exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
resolve(targetSpec).then(install).catch(err => {
|
||||
emulator.install(install_target).catch(function (err) {
|
||||
console.error('ERROR: ' + err);
|
||||
process.exit(2);
|
||||
});
|
||||
|
||||
@@ -19,16 +19,14 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
const { list } = require('./target');
|
||||
var devices = require('./device');
|
||||
|
||||
// Usage support for when args are given
|
||||
require('./check_reqs').check_android().then(function () {
|
||||
list().then(targets => {
|
||||
const deviceIds = targets
|
||||
.filter(({ type }) => type === 'device')
|
||||
.map(({ id }) => id);
|
||||
|
||||
console.log(deviceIds.join('\n'));
|
||||
devices.list().then(function (device_list) {
|
||||
device_list && device_list.forEach(function (dev) {
|
||||
console.log(dev);
|
||||
});
|
||||
}, function (err) {
|
||||
console.error('ERROR: ' + err);
|
||||
process.exit(2);
|
||||
|
||||
5
bin/templates/cordova/lib/pluginHandlers.js
vendored
5
bin/templates/cordova/lib/pluginHandlers.js
vendored
@@ -16,7 +16,6 @@
|
||||
|
||||
var fs = require('fs-extra');
|
||||
var path = require('path');
|
||||
var isPathInside = require('is-path-inside');
|
||||
var events = require('cordova-common').events;
|
||||
var CordovaError = require('cordova-common').CordovaError;
|
||||
|
||||
@@ -210,12 +209,12 @@ function copyFile (plugin_dir, src, project_dir, dest, link) {
|
||||
// check that src path is inside plugin directory
|
||||
var real_path = fs.realpathSync(src);
|
||||
var real_plugin_path = fs.realpathSync(plugin_dir);
|
||||
if (!isPathInside(real_path, real_plugin_path)) { throw new CordovaError('File "' + src + '" is located outside the plugin directory "' + plugin_dir + '"'); }
|
||||
if (real_path.indexOf(real_plugin_path) !== 0) { throw new CordovaError('File "' + src + '" is located outside the plugin directory "' + plugin_dir + '"'); }
|
||||
|
||||
dest = path.resolve(project_dir, dest);
|
||||
|
||||
// check that dest path is located in project directory
|
||||
if (!isPathInside(dest, project_dir)) { throw new CordovaError('Destination "' + dest + '" for source file "' + src + '" is located outside the project'); }
|
||||
if (dest.indexOf(project_dir) !== 0) { throw new CordovaError('Destination "' + dest + '" for source file "' + src + '" is located outside the project'); }
|
||||
|
||||
fs.ensureDirSync(path.dirname(dest));
|
||||
if (link) {
|
||||
|
||||
57
bin/templates/cordova/lib/prepare.js
vendored
57
bin/templates/cordova/lib/prepare.js
vendored
@@ -20,7 +20,6 @@
|
||||
var fs = require('fs-extra');
|
||||
var path = require('path');
|
||||
const nopt = require('nopt');
|
||||
const glob = require('fast-glob');
|
||||
var events = require('cordova-common').events;
|
||||
var AndroidManifest = require('./AndroidManifest');
|
||||
var checkReqs = require('./check_reqs');
|
||||
@@ -238,9 +237,9 @@ function updateProjectAccordingTo (platformConfig, locations) {
|
||||
|
||||
// Java file paths shouldn't be hard coded
|
||||
const javaDirectory = path.join(locations.javaSrc, manifestId.replace(/\./g, '/'));
|
||||
const java_files = glob.sync('**/*.java', { cwd: javaDirectory, absolute: true }).filter(f => {
|
||||
const contents = fs.readFileSync(f, 'utf-8');
|
||||
return /extends\s+CordovaActivity/.test(contents);
|
||||
const javaPattern = /\.java$/;
|
||||
const java_files = utils.scanDirectory(javaDirectory, javaPattern, true).filter(function (f) {
|
||||
return utils.grep(f, /extends\s+CordovaActivity/g) !== null;
|
||||
});
|
||||
|
||||
if (java_files.length === 0) {
|
||||
@@ -302,12 +301,11 @@ function default_versionCode (version) {
|
||||
}
|
||||
|
||||
function getImageResourcePath (resourcesDir, type, density, name, sourceName) {
|
||||
// Use same extension as source with special case for 9-Patch files
|
||||
const ext = sourceName.endsWith('.9.png')
|
||||
? '.9.png' : path.extname(sourceName).toLowerCase();
|
||||
|
||||
const subDir = density ? `${type}-${density}` : type;
|
||||
return path.join(resourcesDir, subDir, name + ext);
|
||||
if (/\.9\.png$/.test(sourceName)) {
|
||||
name = name.replace(/\.png$/, '.9.png');
|
||||
}
|
||||
var resourcePath = path.join(resourcesDir, (density ? type + '-' + density : type), name);
|
||||
return resourcePath;
|
||||
}
|
||||
|
||||
function getAdaptiveImageResourcePath (resourcesDir, type, density, name, sourceName) {
|
||||
@@ -318,15 +316,6 @@ function getAdaptiveImageResourcePath (resourcesDir, type, density, name, source
|
||||
return resourcePath;
|
||||
}
|
||||
|
||||
function makeSplashCleanupMap (projectRoot, resourcesDir) {
|
||||
// Build an initial resource map that deletes all existing splash screens
|
||||
const existingSplashPaths = glob.sync(
|
||||
`${resourcesDir.replace(/\\/g, '/')}/drawable-*/screen.{png,9.png,webp,jpg,jpeg}`,
|
||||
{ cwd: projectRoot }
|
||||
);
|
||||
return makeCleanResourceMap(existingSplashPaths);
|
||||
}
|
||||
|
||||
function updateSplashes (cordovaProject, platformResourcesDir) {
|
||||
var resources = cordovaProject.projectConfig.getSplashScreens('android');
|
||||
|
||||
@@ -336,8 +325,7 @@ function updateSplashes (cordovaProject, platformResourcesDir) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Build an initial resource map that deletes all existing splash screens
|
||||
const resourceMap = makeSplashCleanupMap(cordovaProject.root, platformResourcesDir);
|
||||
var resourceMap = mapImageResources(cordovaProject.root, platformResourcesDir, 'drawable', 'screen.png');
|
||||
|
||||
var hadMdpi = false;
|
||||
resources.forEach(function (resource) {
|
||||
@@ -348,14 +336,14 @@ function updateSplashes (cordovaProject, platformResourcesDir) {
|
||||
hadMdpi = true;
|
||||
}
|
||||
var targetPath = getImageResourcePath(
|
||||
platformResourcesDir, 'drawable', resource.density, 'screen', path.basename(resource.src));
|
||||
platformResourcesDir, 'drawable', resource.density, 'screen.png', path.basename(resource.src));
|
||||
resourceMap[targetPath] = resource.src;
|
||||
});
|
||||
|
||||
// There's no "default" drawable, so assume default == mdpi.
|
||||
if (!hadMdpi && resources.defaultResource) {
|
||||
var targetPath = getImageResourcePath(
|
||||
platformResourcesDir, 'drawable', 'mdpi', 'screen', path.basename(resources.defaultResource.src));
|
||||
platformResourcesDir, 'drawable', 'mdpi', 'screen.png', path.basename(resources.defaultResource.src));
|
||||
resourceMap[targetPath] = resources.defaultResource.src;
|
||||
}
|
||||
|
||||
@@ -367,8 +355,7 @@ function updateSplashes (cordovaProject, platformResourcesDir) {
|
||||
function cleanSplashes (projectRoot, projectConfig, platformResourcesDir) {
|
||||
var resources = projectConfig.getSplashScreens('android');
|
||||
if (resources.length > 0) {
|
||||
const resourceMap = makeSplashCleanupMap(projectRoot, platformResourcesDir);
|
||||
|
||||
var resourceMap = mapImageResources(projectRoot, platformResourcesDir, 'drawable', 'screen.png');
|
||||
events.emit('verbose', 'Cleaning splash screens at ' + platformResourcesDir);
|
||||
|
||||
// No source paths are specified in the map, so updatePaths() will delete the target files.
|
||||
@@ -558,13 +545,13 @@ function updateIconResourceForLegacy (preparedIcons, resourceMap, platformResour
|
||||
// The source paths for icons and splashes are relative to
|
||||
// project's config.xml location, so we use it as base path.
|
||||
for (var density in android_icons) {
|
||||
var targetPath = getImageResourcePath(platformResourcesDir, 'mipmap', density, 'ic_launcher', path.basename(android_icons[density].src));
|
||||
var targetPath = getImageResourcePath(platformResourcesDir, 'mipmap', density, 'ic_launcher.png', path.basename(android_icons[density].src));
|
||||
resourceMap[targetPath] = android_icons[density].src;
|
||||
}
|
||||
|
||||
// There's no "default" drawable, so assume default == mdpi.
|
||||
if (default_icon && !android_icons.mdpi) {
|
||||
var defaultTargetPath = getImageResourcePath(platformResourcesDir, 'mipmap', 'mdpi', 'ic_launcher', path.basename(default_icon.src));
|
||||
var defaultTargetPath = getImageResourcePath(platformResourcesDir, 'mipmap', 'mdpi', 'ic_launcher.png', path.basename(default_icon.src));
|
||||
resourceMap[defaultTargetPath] = default_icon.src;
|
||||
}
|
||||
|
||||
@@ -677,24 +664,14 @@ function cleanIcons (projectRoot, projectConfig, platformResourcesDir) {
|
||||
*/
|
||||
function mapImageResources (rootDir, subDir, type, resourceName) {
|
||||
const pathMap = {};
|
||||
const globOptions = { cwd: path.join(rootDir, subDir), onlyDirectories: true };
|
||||
glob.sync(type + '-*', globOptions).forEach(drawableFolder => {
|
||||
const imagePath = path.join(subDir, drawableFolder, resourceName);
|
||||
const pattern = new RegExp(type + '+-.+');
|
||||
utils.scanDirectory(path.join(rootDir, subDir), pattern).forEach(function (drawableFolder) {
|
||||
const imagePath = path.join(subDir, path.basename(drawableFolder), resourceName);
|
||||
pathMap[imagePath] = null;
|
||||
});
|
||||
return pathMap;
|
||||
}
|
||||
|
||||
/** Returns resource map that deletes all given paths */
|
||||
function makeCleanResourceMap (resourcePaths) {
|
||||
const pathMap = {};
|
||||
resourcePaths.map(path.normalize)
|
||||
.forEach(resourcePath => {
|
||||
pathMap[resourcePath] = null;
|
||||
});
|
||||
return pathMap;
|
||||
}
|
||||
|
||||
function updateFileResources (cordovaProject, platformDir) {
|
||||
var files = cordovaProject.projectConfig.getFileResources('android');
|
||||
|
||||
|
||||
34
bin/templates/cordova/lib/retry.js
vendored
34
bin/templates/cordova/lib/retry.js
vendored
@@ -21,7 +21,7 @@
|
||||
|
||||
var events = require('cordova-common').events;
|
||||
|
||||
/**
|
||||
/*
|
||||
* Retry a promise-returning function a number of times, propagating its
|
||||
* results on success or throwing its error on a failed final attempt.
|
||||
*
|
||||
@@ -31,13 +31,31 @@ var events = require('cordova-common').events;
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
module.exports.retryPromise = async function (attemptsLeft, promiseFunction, ...args) {
|
||||
while (true) {
|
||||
try {
|
||||
return await promiseFunction(...args);
|
||||
} catch (error) {
|
||||
if (--attemptsLeft < 1) throw error;
|
||||
module.exports.retryPromise = function (attemptsLeft, promiseFunction) {
|
||||
// NOTE:
|
||||
// get all trailing arguments, by skipping the first two (attemptsLeft and
|
||||
// promiseFunction) because they shouldn't get passed to promiseFunction
|
||||
var promiseFunctionArguments = Array.prototype.slice.call(arguments, 2);
|
||||
|
||||
return promiseFunction.apply(undefined, promiseFunctionArguments).then(
|
||||
// on success pass results through
|
||||
function onFulfilled (value) {
|
||||
return value;
|
||||
},
|
||||
|
||||
// on rejection either retry, or throw the error
|
||||
function onRejected (error) {
|
||||
attemptsLeft -= 1;
|
||||
|
||||
if (attemptsLeft < 1) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
events.emit('verbose', 'A retried call failed. Retrying ' + attemptsLeft + ' more time(s).');
|
||||
|
||||
// retry call self again with the same arguments, except attemptsLeft is now lower
|
||||
var fullArguments = [attemptsLeft, promiseFunction].concat(promiseFunctionArguments);
|
||||
return module.exports.retryPromise.apply(undefined, fullArguments);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
90
bin/templates/cordova/lib/run.js
vendored
90
bin/templates/cordova/lib/run.js
vendored
@@ -19,30 +19,21 @@
|
||||
|
||||
var path = require('path');
|
||||
var emulator = require('./emulator');
|
||||
const target = require('./target');
|
||||
var device = require('./device');
|
||||
var PackageType = require('./PackageType');
|
||||
const { events } = require('cordova-common');
|
||||
const { CordovaError, events } = require('cordova-common');
|
||||
|
||||
/**
|
||||
* Builds a target spec from a runOptions object
|
||||
*
|
||||
* @param {{target?: string, device?: boolean, emulator?: boolean}} runOptions
|
||||
* @return {target.TargetSpec}
|
||||
*/
|
||||
function buildTargetSpec (runOptions) {
|
||||
const spec = {};
|
||||
function getInstallTarget (runOptions) {
|
||||
var install_target;
|
||||
if (runOptions.target) {
|
||||
spec.id = runOptions.target;
|
||||
install_target = runOptions.target;
|
||||
} else if (runOptions.device) {
|
||||
spec.type = 'device';
|
||||
install_target = '--device';
|
||||
} else if (runOptions.emulator) {
|
||||
spec.type = 'emulator';
|
||||
install_target = '--emulator';
|
||||
}
|
||||
return spec;
|
||||
}
|
||||
|
||||
function formatResolvedTarget ({ id, type }) {
|
||||
return `${type} ${id}`;
|
||||
return install_target;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,12 +50,57 @@ module.exports.run = function (runOptions) {
|
||||
runOptions = runOptions || {};
|
||||
|
||||
var self = this;
|
||||
const spec = buildTargetSpec(runOptions);
|
||||
|
||||
return target.resolve(spec).then(function (resolvedTarget) {
|
||||
events.emit('log', `Deploying to ${formatResolvedTarget(resolvedTarget)}`);
|
||||
var install_target = getInstallTarget(runOptions);
|
||||
|
||||
return Promise.resolve().then(function () {
|
||||
if (!install_target) {
|
||||
// no target given, deploy to device if available, otherwise use the emulator.
|
||||
return device.list().then(function (device_list) {
|
||||
if (device_list.length > 0) {
|
||||
events.emit('warn', 'No target specified, deploying to device \'' + device_list[0] + '\'.');
|
||||
install_target = device_list[0];
|
||||
} else {
|
||||
events.emit('warn', 'No target specified and no devices found, deploying to emulator');
|
||||
install_target = '--emulator';
|
||||
}
|
||||
});
|
||||
}
|
||||
}).then(function () {
|
||||
if (install_target === '--device') {
|
||||
return device.resolveTarget(null);
|
||||
} else if (install_target === '--emulator') {
|
||||
// Give preference to any already started emulators. Else, start one.
|
||||
return emulator.list_started().then(function (started) {
|
||||
return started && started.length > 0 ? started[0] : emulator.start();
|
||||
}).then(function (emulatorId) {
|
||||
return emulator.resolveTarget(emulatorId);
|
||||
});
|
||||
}
|
||||
// They specified a specific device/emulator ID.
|
||||
return device.list().then(function (devices) {
|
||||
if (devices.indexOf(install_target) > -1) {
|
||||
return device.resolveTarget(install_target);
|
||||
}
|
||||
return emulator.list_started().then(function (started_emulators) {
|
||||
if (started_emulators.indexOf(install_target) > -1) {
|
||||
return emulator.resolveTarget(install_target);
|
||||
}
|
||||
return emulator.list_images().then(function (avds) {
|
||||
// if target emulator isn't started, then start it.
|
||||
for (var avd in avds) {
|
||||
if (avds[avd].name === install_target) {
|
||||
return emulator.start(install_target).then(function (emulatorId) {
|
||||
return emulator.resolveTarget(emulatorId);
|
||||
});
|
||||
}
|
||||
}
|
||||
return Promise.reject(new CordovaError(`Target '${install_target}' not found, unable to run project`));
|
||||
});
|
||||
});
|
||||
});
|
||||
}).then(function (resolvedTarget) {
|
||||
return new Promise((resolve) => {
|
||||
const builder = require('./builders/builders').getBuilder();
|
||||
const buildOptions = require('./build').parseBuildOptions(runOptions, null, self.root);
|
||||
|
||||
// Android app bundles cannot be deployed directly to the device
|
||||
@@ -74,13 +110,15 @@ module.exports.run = function (runOptions) {
|
||||
throw packageTypeErrorMessage;
|
||||
}
|
||||
|
||||
resolve(self._builder.fetchBuildResults(buildOptions.buildType, buildOptions.arch));
|
||||
}).then(async function (buildResults) {
|
||||
if (resolvedTarget.type === 'emulator') {
|
||||
await emulator.wait_for_boot(resolvedTarget.id);
|
||||
resolve(builder.fetchBuildResults(buildOptions.buildType, buildOptions.arch));
|
||||
}).then(function (buildResults) {
|
||||
if (resolvedTarget && resolvedTarget.isEmulator) {
|
||||
return emulator.wait_for_boot(resolvedTarget.target).then(function () {
|
||||
return emulator.install(resolvedTarget, buildResults);
|
||||
});
|
||||
}
|
||||
|
||||
return target.install(resolvedTarget, buildResults);
|
||||
return device.install(resolvedTarget, buildResults);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
155
bin/templates/cordova/lib/target.js
vendored
155
bin/templates/cordova/lib/target.js
vendored
@@ -1,155 +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.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const { inspect } = require('util');
|
||||
const Adb = require('./Adb');
|
||||
const build = require('./build');
|
||||
const emulator = require('./emulator');
|
||||
const AndroidManifest = require('./AndroidManifest');
|
||||
const { compareBy } = require('./utils');
|
||||
const { retryPromise } = require('./retry');
|
||||
const { events, CordovaError } = require('cordova-common');
|
||||
|
||||
const INSTALL_COMMAND_TIMEOUT = 5 * 60 * 1000;
|
||||
const NUM_INSTALL_RETRIES = 3;
|
||||
const EXEC_KILL_SIGNAL = 'SIGKILL';
|
||||
|
||||
/**
|
||||
* @typedef { 'device' | 'emulator' } TargetType
|
||||
* @typedef { { id: string, type: TargetType } } Target
|
||||
* @typedef { { id?: string, type?: TargetType } } TargetSpec
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns a list of available targets (connected devices & started emulators)
|
||||
*
|
||||
* @return {Promise<Target[]>}
|
||||
*/
|
||||
exports.list = async () => {
|
||||
return (await Adb.devices())
|
||||
.map(id => ({
|
||||
id,
|
||||
type: id.startsWith('emulator-') ? 'emulator' : 'device'
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {TargetSpec?} spec
|
||||
* @return {Promise<Target>}
|
||||
*/
|
||||
async function resolveToOnlineTarget (spec = {}) {
|
||||
const targetList = await exports.list();
|
||||
if (targetList.length === 0) return null;
|
||||
|
||||
// Sort by type: devices first, then emulators.
|
||||
targetList.sort(compareBy(t => t.type));
|
||||
|
||||
// Find first matching target for spec. {} matches any target.
|
||||
return targetList.find(target =>
|
||||
Object.keys(spec).every(k => spec[k] === target[k])
|
||||
) || null;
|
||||
}
|
||||
|
||||
async function isEmulatorName (name) {
|
||||
const emus = await emulator.list_images();
|
||||
return emus.some(avd => avd.name === name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {TargetSpec?} spec
|
||||
* @return {Promise<Target>}
|
||||
*/
|
||||
async function resolveToOfflineEmulator (spec = {}) {
|
||||
if (spec.type === 'device') return null;
|
||||
if (spec.id && !(await isEmulatorName(spec.id))) return null;
|
||||
|
||||
// try to start an emulator with name spec.id
|
||||
// if spec.id is undefined, picks best match regarding target API
|
||||
const emulatorId = await emulator.start(spec.id);
|
||||
|
||||
return { id: emulatorId, type: 'emulator' };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {TargetSpec?} spec
|
||||
* @return {Promise<Target & {arch: string}>}
|
||||
*/
|
||||
exports.resolve = async (spec = {}) => {
|
||||
events.emit('verbose', `Trying to find target matching ${inspect(spec)}`);
|
||||
|
||||
const resolvedTarget =
|
||||
(await resolveToOnlineTarget(spec)) ||
|
||||
(await resolveToOfflineEmulator(spec));
|
||||
|
||||
if (!resolvedTarget) {
|
||||
throw new CordovaError(`Could not find target matching ${inspect(spec)}`);
|
||||
}
|
||||
|
||||
return {
|
||||
...resolvedTarget,
|
||||
arch: await build.detectArchitecture(resolvedTarget.id)
|
||||
};
|
||||
};
|
||||
|
||||
exports.install = async function ({ id: target, arch, type }, buildResults) {
|
||||
const apk_path = build.findBestApkForArchitecture(buildResults, arch);
|
||||
const manifest = new AndroidManifest(path.join(__dirname, '../../app/src/main/AndroidManifest.xml'));
|
||||
const pkgName = manifest.getPackageId();
|
||||
const launchName = pkgName + '/.' + manifest.getActivity().getName();
|
||||
|
||||
events.emit('log', 'Using apk: ' + apk_path);
|
||||
events.emit('log', 'Package name: ' + pkgName);
|
||||
events.emit('verbose', `Installing app on target ${target}`);
|
||||
|
||||
async function doInstall (execOptions = {}) {
|
||||
try {
|
||||
await Adb.install(target, apk_path, { replace: true, execOptions });
|
||||
} catch (error) {
|
||||
// CB-9557 CB-10157 only uninstall and reinstall app if the one that
|
||||
// is already installed on device was signed w/different certificate
|
||||
if (!/INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES/.test(error.toString())) throw error;
|
||||
|
||||
events.emit('warn', 'Uninstalling app from device and reinstalling it again because the ' +
|
||||
'installed app already signed with different key');
|
||||
|
||||
// This promise is always resolved, even if 'adb uninstall' fails to uninstall app
|
||||
// or the app doesn't installed at all, so no error catching needed.
|
||||
await Adb.uninstall(target, pkgName);
|
||||
await Adb.install(target, apk_path, { replace: true });
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'emulator') {
|
||||
// Work around sporadic emulator hangs: http://issues.apache.org/jira/browse/CB-9119
|
||||
await retryPromise(NUM_INSTALL_RETRIES, () => doInstall({
|
||||
timeout: INSTALL_COMMAND_TIMEOUT,
|
||||
killSignal: EXEC_KILL_SIGNAL
|
||||
}));
|
||||
} else {
|
||||
await doInstall();
|
||||
}
|
||||
events.emit('log', 'INSTALL SUCCESS');
|
||||
|
||||
events.emit('verbose', 'Unlocking screen...');
|
||||
await Adb.shell(target, 'input keyevent 82');
|
||||
|
||||
await Adb.start(target, launchName);
|
||||
events.emit('log', 'LAUNCH SUCCESS');
|
||||
};
|
||||
71
bin/templates/cordova/lib/utils.js
vendored
71
bin/templates/cordova/lib/utils.js
vendored
@@ -24,8 +24,7 @@
|
||||
// TODO: Perhaps this should live in cordova-common?
|
||||
|
||||
const fs = require('fs-extra');
|
||||
const which = require('which');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* Reads, searches, and replaces the found occurences with replacementString and then writes the file back out.
|
||||
@@ -42,27 +41,57 @@ exports.replaceFileContents = function (file, searchRegex, replacementString) {
|
||||
fs.writeFileSync(file, contents);
|
||||
};
|
||||
|
||||
// Some helpers for easier sorting
|
||||
exports.compare = (a, b) => (a < b && -1) || +(a > b);
|
||||
exports.compareBy = f => (a, b) => exports.compare(f(a), f(b));
|
||||
exports.compareByAll = fns => {
|
||||
const comparators = fns.map(exports.compareBy);
|
||||
return (a, b) => {
|
||||
for (const cmp of comparators) {
|
||||
const result = cmp(a, b);
|
||||
if (result) return result;
|
||||
/**
|
||||
* Reads a file and scans for regex. Returns the line of the first occurence or null if no occurences are found.
|
||||
*
|
||||
* @param {string} file A file path
|
||||
* @param {RegExp} regex A search regex
|
||||
* @returns string|null
|
||||
*/
|
||||
exports.grep = function (file, regex) {
|
||||
const contents = fs.readFileSync(file).toString().replace(/\\r/g, '').split('\n');
|
||||
for (let i = 0; i < contents.length; i++) {
|
||||
const line = contents[i];
|
||||
if (regex.test(line)) {
|
||||
return line;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
exports.forgivingWhichSync = (cmd) => {
|
||||
const whichResult = which.sync(cmd, { nothrow: true });
|
||||
/**
|
||||
* Scans directories and outputs a list of found paths that matches the regex
|
||||
*
|
||||
* @param {string} directory The starting directory
|
||||
* @param {RegExp} regex The search regex
|
||||
* @param {boolean} recursive Enables recursion
|
||||
* @returns Array<string>
|
||||
*/
|
||||
exports.scanDirectory = function (directory, regex, recursive) {
|
||||
let output = [];
|
||||
|
||||
// On null, returns empty string to maintain backwards compatibility
|
||||
// realpathSync follows symlinks
|
||||
return whichResult === null ? '' : fs.realpathSync(whichResult);
|
||||
if (fs.existsSync(directory)) {
|
||||
const items = fs.readdirSync(directory);
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
const itemPath = path.join(directory, item);
|
||||
const stats = fs.statSync(itemPath);
|
||||
|
||||
if (regex.test(itemPath)) {
|
||||
output.push(itemPath);
|
||||
}
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
if (recursive) {
|
||||
output = output.concat(exports.scanDirectory(itemPath, regex, recursive));
|
||||
} else {
|
||||
// Move onto the next item
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
exports.isWindows = () => os.platform() === 'win32';
|
||||
exports.isDarwin = () => os.platform() === 'darwin';
|
||||
|
||||
@@ -44,8 +44,11 @@ buildscript {
|
||||
ext.kotlin_version = gradlePluginKotlinVersion
|
||||
}
|
||||
|
||||
apply from: 'repositories.gradle'
|
||||
repositories repos
|
||||
repositories {
|
||||
mavenCentral()
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
apply from: '../CordovaLib/cordova.gradle'
|
||||
@@ -79,8 +82,10 @@ buildscript {
|
||||
|
||||
// Allow plugins to declare Maven dependencies via build-extras.gradle.
|
||||
allprojects {
|
||||
apply from: 'repositories.gradle'
|
||||
repositories repos
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
|
||||
@@ -1,23 +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.
|
||||
*/
|
||||
|
||||
ext.repos = {
|
||||
mavenCentral()
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
4
bin/templates/project/assets/www/cordova.js
vendored
4
bin/templates/project/assets/www/cordova.js
vendored
@@ -1,5 +1,5 @@
|
||||
// Platform: android
|
||||
// cordova-js rel/6.0.0-10-g07379820
|
||||
// 538a985db128858c0a0eb4dd40fb9c8e5433fc94
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
@@ -19,7 +19,7 @@
|
||||
under the License.
|
||||
*/
|
||||
;(function() {
|
||||
var PLATFORM_VERSION_BUILD_LABEL = '9.1.0';
|
||||
var PLATFORM_VERSION_BUILD_LABEL = '9.0.0';
|
||||
// file: src/scripts/require.js
|
||||
var require;
|
||||
var define;
|
||||
|
||||
@@ -20,9 +20,10 @@
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.3.50'
|
||||
apply from: 'repositories.gradle'
|
||||
repositories repos
|
||||
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.0.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
@@ -32,8 +33,10 @@ buildscript {
|
||||
}
|
||||
|
||||
allprojects {
|
||||
apply from: 'repositories.gradle'
|
||||
repositories repos
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
//This replaces project.properties w.r.t. build settings
|
||||
project.ext {
|
||||
|
||||
@@ -1,22 +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.
|
||||
*/
|
||||
|
||||
ext.repos = {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
@@ -20,17 +20,13 @@ ext {
|
||||
apply from: 'cordova.gradle'
|
||||
cdvCompileSdkVersion = privateHelpers.getProjectTarget()
|
||||
cdvBuildToolsVersion = privateHelpers.findLatestInstalledBuildTools()
|
||||
if (project.hasProperty('cdvMinSdkVersion') && cdvMinSdkVersion.isInteger()) {
|
||||
cdvMinSdkVersion = cdvMinSdkVersion as int
|
||||
println '[Cordova] cdvMinSdkVersion is overridden, try it at your own risk.'
|
||||
} else {
|
||||
cdvMinSdkVersion = 22; // current Cordova's default
|
||||
}
|
||||
}
|
||||
|
||||
buildscript {
|
||||
apply from: 'repositories.gradle'
|
||||
repositories repos
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// The gradle plugin and the maven plugin have to be updated after each version of Android
|
||||
@@ -42,8 +38,10 @@ buildscript {
|
||||
}
|
||||
|
||||
allprojects {
|
||||
apply from: 'repositories.gradle'
|
||||
repositories repos
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
@@ -51,7 +49,7 @@ apply plugin: 'com.github.dcendents.android-maven'
|
||||
apply plugin: 'com.jfrog.bintray'
|
||||
|
||||
group = 'org.apache.cordova'
|
||||
version = '9.1.0'
|
||||
version = '9.0.0'
|
||||
|
||||
android {
|
||||
compileSdkVersion cdvCompileSdkVersion
|
||||
@@ -62,9 +60,9 @@ android {
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
// For the Android Cordova Lib, we allow changing the minSdkVersion, but it is at the users own risk
|
||||
// For the Android Cordova Lib, we will hardcode the minSdkVersion and not allow changes.
|
||||
defaultConfig {
|
||||
minSdkVersion cdvMinSdkVersion
|
||||
minSdkVersion 22
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
@@ -142,9 +140,9 @@ bintray {
|
||||
licenses = ['Apache-2.0']
|
||||
labels = ['android', 'cordova', 'phonegap']
|
||||
version {
|
||||
name = '9.1.0'
|
||||
released = new Date()
|
||||
vcsTag = '9.1.0'
|
||||
name = '9.0.0'
|
||||
released = new Date()
|
||||
vcsTag = '9.0.0'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +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.
|
||||
*/
|
||||
|
||||
ext.repos = {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
@@ -125,9 +125,6 @@ public class CordovaActivity extends Activity {
|
||||
// (as was the case in previous cordova versions)
|
||||
if (!preferences.getBoolean("FullscreenNotImmersive", false)) {
|
||||
immersiveMode = true;
|
||||
// The splashscreen plugin needs the flags set before we're focused to prevent
|
||||
// the nav and title bars from flashing in.
|
||||
setImmersiveUiVisibility();
|
||||
} else {
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
@@ -324,26 +321,22 @@ public class CordovaActivity extends Activity {
|
||||
/**
|
||||
* Called when view focus is changed
|
||||
*/
|
||||
@SuppressLint("InlinedApi")
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
if (hasFocus && immersiveMode) {
|
||||
setImmersiveUiVisibility();
|
||||
final int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
||||
|
||||
getWindow().getDecorView().setSystemUiVisibility(uiOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
protected void setImmersiveUiVisibility() {
|
||||
final int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
||||
|
||||
getWindow().getDecorView().setSystemUiVisibility(uiOptions);
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
|
||||
|
||||
@@ -114,6 +114,7 @@ public class CordovaBridge {
|
||||
/** 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);
|
||||
|
||||
@@ -41,7 +41,6 @@ import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.Locale;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
* What this class provides:
|
||||
@@ -287,19 +286,13 @@ public class CordovaResourceApi {
|
||||
case URI_TYPE_HTTP:
|
||||
case URI_TYPE_HTTPS: {
|
||||
HttpURLConnection conn = (HttpURLConnection)new URL(uri.toString()).openConnection();
|
||||
conn.setRequestProperty("Accept-Encoding", "gzip");
|
||||
conn.setDoInput(true);
|
||||
String mimeType = conn.getHeaderField("Content-Type");
|
||||
if (mimeType != null) {
|
||||
mimeType = mimeType.split(";")[0];
|
||||
}
|
||||
int length = conn.getContentLength();
|
||||
InputStream inputStream;
|
||||
if ("gzip".equals(conn.getContentEncoding())) {
|
||||
inputStream = new GZIPInputStream(conn.getInputStream());
|
||||
} else {
|
||||
inputStream = conn.getInputStream();
|
||||
}
|
||||
InputStream inputStream = conn.getInputStream();
|
||||
return new OpenForReadResult(uri, inputStream, mimeType, length, null);
|
||||
}
|
||||
case URI_TYPE_PLUGIN: {
|
||||
|
||||
@@ -31,7 +31,7 @@ import android.webkit.WebChromeClient.CustomViewCallback;
|
||||
* are not expected to implement it.
|
||||
*/
|
||||
public interface CordovaWebView {
|
||||
public static final String CORDOVA_VERSION = "9.1.0";
|
||||
public static final String CORDOVA_VERSION = "9.0.0";
|
||||
|
||||
void init(CordovaInterface cordova, List<PluginEntry> pluginEntries, CordovaPreferences preferences);
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
package org.apache.cordova;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
@@ -35,7 +34,6 @@ import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -178,28 +176,22 @@ public class CordovaWebViewImpl implements CordovaWebView {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// If timeout, then stop loading and handle error (if activity still exists)
|
||||
if (loadUrlTimeout == currentLoadUrlTimeout && cordova.getActivity() != null) {
|
||||
// If timeout, then stop loading and handle error
|
||||
if (loadUrlTimeout == currentLoadUrlTimeout) {
|
||||
cordova.getActivity().runOnUiThread(loadError);
|
||||
} else if (cordova.getActivity() == null) {
|
||||
LOG.d(TAG, "Cordova activity does not exist.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (cordova.getActivity() != null) {
|
||||
final boolean _recreatePlugins = recreatePlugins;
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
if (loadUrlTimeoutValue > 0) {
|
||||
cordova.getThreadPool().execute(timeoutCheck);
|
||||
}
|
||||
engine.loadUrl(url, _recreatePlugins);
|
||||
final boolean _recreatePlugins = recreatePlugins;
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
if (loadUrlTimeoutValue > 0) {
|
||||
cordova.getThreadPool().execute(timeoutCheck);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
LOG.d(TAG, "Cordova activity does not exist.");
|
||||
}
|
||||
engine.loadUrl(url, _recreatePlugins);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -234,37 +226,21 @@ public class CordovaWebViewImpl implements CordovaWebView {
|
||||
LOG.w(TAG, "showWebPage: Refusing to send intent for URL since it is not in the <allow-intent> whitelist. URL=" + url);
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = null;
|
||||
try {
|
||||
if (url.startsWith("intent://")) {
|
||||
intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
// To send an intent without CATEGORY_BROWSER, a custom plugin should be used.
|
||||
intent.addCategory(Intent.CATEGORY_BROWSABLE);
|
||||
Uri uri = Uri.parse(url);
|
||||
// Omitting the MIME type for file: URLs causes "No Activity found to handle Intent".
|
||||
// Adding the MIME type to http: URLs causes them to not be handled by the downloader.
|
||||
if ("file".equals(uri.getScheme())) {
|
||||
intent.setDataAndType(uri, resourceApi.getMimeType(uri));
|
||||
} else {
|
||||
intent = new Intent(Intent.ACTION_VIEW);
|
||||
// To send an intent without CATEGORY_BROWSER, a custom plugin should be used.
|
||||
intent.addCategory(Intent.CATEGORY_BROWSABLE);
|
||||
Uri uri = Uri.parse(url);
|
||||
// Omitting the MIME type for file: URLs causes "No Activity found to handle Intent".
|
||||
// Adding the MIME type to http: URLs causes them to not be handled by the downloader.
|
||||
if ("file".equals(uri.getScheme())) {
|
||||
intent.setDataAndType(uri, resourceApi.getMimeType(uri));
|
||||
} else {
|
||||
intent.setData(uri);
|
||||
}
|
||||
}
|
||||
if (cordova.getActivity() != null) {
|
||||
cordova.getActivity().startActivity(intent);
|
||||
} else {
|
||||
LOG.d(TAG, "Cordova activity does not exist.");
|
||||
}
|
||||
} catch (URISyntaxException e) {
|
||||
LOG.e(TAG, "Error parsing url " + url, e);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
if (url.startsWith("intent://") && intent != null && intent.getStringExtra("browser_fallback_url") != null) {
|
||||
showWebPage(intent.getStringExtra("browser_fallback_url"), openExternal, clearHistory, params);
|
||||
} else {
|
||||
LOG.e(TAG, "Error loading url " + url, e);
|
||||
intent.setData(uri);
|
||||
}
|
||||
cordova.getActivity().startActivity(intent);
|
||||
} catch (android.content.ActivityNotFoundException e) {
|
||||
LOG.e(TAG, "Error loading url " + url, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,12 +255,7 @@ public class CordovaWebViewImpl implements CordovaWebView {
|
||||
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
boolean ret = engine.getView().dispatchKeyEvent(event);
|
||||
if (!ret) {
|
||||
// If the engine didn't handle the event, handle it normally.
|
||||
ret = super.dispatchKeyEvent(event);
|
||||
}
|
||||
return ret;
|
||||
return engine.getView().dispatchKeyEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -582,15 +553,11 @@ public class CordovaWebViewImpl implements CordovaWebView {
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
if (cordova.getActivity() != null) {
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
pluginManager.postMessage("spinner", "stop");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
LOG.d(TAG, "Cordova activity does not exist.");
|
||||
}
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
pluginManager.postMessage("spinner", "stop");
|
||||
}
|
||||
});
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,9 +19,7 @@
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
@@ -30,7 +28,6 @@ import android.content.res.Configuration;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Debug;
|
||||
import android.os.Build;
|
||||
|
||||
/**
|
||||
* PluginManager is exposed to JavaScript in the Cordova WebView.
|
||||
@@ -43,8 +40,8 @@ public class PluginManager {
|
||||
private static final int SLOW_EXEC_WARNING_THRESHOLD = Debug.isDebuggerConnected() ? 60 : 16;
|
||||
|
||||
// List of service entries
|
||||
private final Map<String, CordovaPlugin> pluginMap = Collections.synchronizedMap(new LinkedHashMap<String, CordovaPlugin>());
|
||||
private final Map<String, PluginEntry> entryMap = Collections.synchronizedMap(new LinkedHashMap<String, PluginEntry>());
|
||||
private final LinkedHashMap<String, CordovaPlugin> pluginMap = new LinkedHashMap<String, CordovaPlugin>();
|
||||
private final LinkedHashMap<String, PluginEntry> entryMap = new LinkedHashMap<String, PluginEntry>();
|
||||
|
||||
private final CordovaInterface ctx;
|
||||
private final CordovaWebView app;
|
||||
@@ -93,17 +90,13 @@ public class PluginManager {
|
||||
* Create plugins objects that have onload set.
|
||||
*/
|
||||
private void startupPlugins() {
|
||||
synchronized (entryMap) {
|
||||
for (PluginEntry entry : entryMap.values()) {
|
||||
// Add a null entry to for each non-startup plugin to avoid ConcurrentModificationException
|
||||
// When iterating plugins.
|
||||
if (entry.onload) {
|
||||
getPlugin(entry.service);
|
||||
}
|
||||
else {
|
||||
LOG.d(TAG, "startupPlugins: put - " + entry.service);
|
||||
pluginMap.put(entry.service, null);
|
||||
}
|
||||
for (PluginEntry entry : entryMap.values()) {
|
||||
// Add a null entry to for each non-startup plugin to avoid ConcurrentModificationException
|
||||
// When iterating plugins.
|
||||
if (entry.onload) {
|
||||
getPlugin(entry.service);
|
||||
} else {
|
||||
pluginMap.put(entry.service, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -176,7 +169,6 @@ public class PluginManager {
|
||||
ret = instantiatePlugin(pe.pluginClass);
|
||||
}
|
||||
ret.privateInitialize(service, ctx, app, app.getPreferences());
|
||||
LOG.d(TAG, "getPlugin - put: " + service);
|
||||
pluginMap.put(service, ret);
|
||||
}
|
||||
return ret;
|
||||
@@ -204,7 +196,6 @@ public class PluginManager {
|
||||
this.entryMap.put(entry.service, entry);
|
||||
if (entry.plugin != null) {
|
||||
entry.plugin.privateInitialize(entry.service, ctx, app, app.getPreferences());
|
||||
LOG.d(TAG, "addService: put - " + entry.service);
|
||||
pluginMap.put(entry.service, entry.plugin);
|
||||
}
|
||||
}
|
||||
@@ -215,11 +206,9 @@ public class PluginManager {
|
||||
* @param multitasking Flag indicating if multitasking is turned on for app
|
||||
*/
|
||||
public void onPause(boolean multitasking) {
|
||||
synchronized (this.pluginMap) {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onPause(multitasking);
|
||||
}
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onPause(multitasking);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -237,11 +226,9 @@ public class PluginManager {
|
||||
*
|
||||
*/
|
||||
public boolean onReceivedHttpAuthRequest(CordovaWebView view, ICordovaHttpAuthHandler handler, String host, String realm) {
|
||||
synchronized (this.pluginMap) {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null && plugin.onReceivedHttpAuthRequest(app, handler, host, realm)) {
|
||||
return true;
|
||||
}
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null && plugin.onReceivedHttpAuthRequest(app, handler, host, realm)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -258,11 +245,9 @@ public class PluginManager {
|
||||
*
|
||||
*/
|
||||
public boolean onReceivedClientCertRequest(CordovaWebView view, ICordovaClientCertRequest request) {
|
||||
synchronized (this.pluginMap) {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null && plugin.onReceivedClientCertRequest(app, request)) {
|
||||
return true;
|
||||
}
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null && plugin.onReceivedClientCertRequest(app, request)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -274,11 +259,9 @@ public class PluginManager {
|
||||
* @param multitasking Flag indicating if multitasking is turned on for app
|
||||
*/
|
||||
public void onResume(boolean multitasking) {
|
||||
synchronized (this.pluginMap) {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onResume(multitasking);
|
||||
}
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onResume(multitasking);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -287,11 +270,9 @@ public class PluginManager {
|
||||
* Called when the activity is becoming visible to the user.
|
||||
*/
|
||||
public void onStart() {
|
||||
synchronized (this.pluginMap) {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onStart();
|
||||
}
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onStart();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -300,11 +281,9 @@ public class PluginManager {
|
||||
* Called when the activity is no longer visible to the user.
|
||||
*/
|
||||
public void onStop() {
|
||||
synchronized (this.pluginMap) {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onStop();
|
||||
}
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onStop();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -313,11 +292,9 @@ public class PluginManager {
|
||||
* The final call you receive before your activity is destroyed.
|
||||
*/
|
||||
public void onDestroy() {
|
||||
synchronized (this.pluginMap) {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onDestroy();
|
||||
}
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -330,22 +307,11 @@ public class PluginManager {
|
||||
* @return Object to stop propagation or null
|
||||
*/
|
||||
public Object postMessage(String id, Object data) {
|
||||
LOG.d(TAG, "postMessage: " + id);
|
||||
synchronized (this.pluginMap) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
this.pluginMap.forEach((s, plugin) -> {
|
||||
if (plugin != null) {
|
||||
plugin.onMessage(id, data);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
Object obj = plugin.onMessage(id, data);
|
||||
if (obj != null) {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
Object obj = plugin.onMessage(id, data);
|
||||
if (obj != null) {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -356,11 +322,9 @@ public class PluginManager {
|
||||
* Called when the activity receives a new intent.
|
||||
*/
|
||||
public void onNewIntent(Intent intent) {
|
||||
synchronized (this.pluginMap) {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onNewIntent(intent);
|
||||
}
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onNewIntent(intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -377,14 +341,12 @@ public class PluginManager {
|
||||
* false to block the resource.
|
||||
*/
|
||||
public boolean shouldAllowRequest(String url) {
|
||||
synchronized (this.entryMap) {
|
||||
for (PluginEntry entry : this.entryMap.values()) {
|
||||
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||
if (plugin != null) {
|
||||
Boolean result = plugin.shouldAllowRequest(url);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
for (PluginEntry entry : this.entryMap.values()) {
|
||||
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||
if (plugin != null) {
|
||||
Boolean result = plugin.shouldAllowRequest(url);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -417,14 +379,12 @@ public class PluginManager {
|
||||
* false to block the navigation.
|
||||
*/
|
||||
public boolean shouldAllowNavigation(String url) {
|
||||
synchronized (this.entryMap) {
|
||||
for (PluginEntry entry : this.entryMap.values()) {
|
||||
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||
if (plugin != null) {
|
||||
Boolean result = plugin.shouldAllowNavigation(url);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
for (PluginEntry entry : this.entryMap.values()) {
|
||||
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||
if (plugin != null) {
|
||||
Boolean result = plugin.shouldAllowNavigation(url);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -438,14 +398,12 @@ public class PluginManager {
|
||||
* Called when the webview is requesting the exec() bridge be enabled.
|
||||
*/
|
||||
public boolean shouldAllowBridgeAccess(String url) {
|
||||
synchronized (this.entryMap) {
|
||||
for (PluginEntry entry : this.entryMap.values()) {
|
||||
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||
if (plugin != null) {
|
||||
Boolean result = plugin.shouldAllowBridgeAccess(url);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
for (PluginEntry entry : this.entryMap.values()) {
|
||||
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||
if (plugin != null) {
|
||||
Boolean result = plugin.shouldAllowBridgeAccess(url);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -467,14 +425,12 @@ public class PluginManager {
|
||||
* false to block the intent.
|
||||
*/
|
||||
public Boolean shouldOpenExternalUrl(String url) {
|
||||
synchronized (this.entryMap) {
|
||||
for (PluginEntry entry : this.entryMap.values()) {
|
||||
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||
if (plugin != null) {
|
||||
Boolean result = plugin.shouldOpenExternalUrl(url);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
for (PluginEntry entry : this.entryMap.values()) {
|
||||
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||
if (plugin != null) {
|
||||
Boolean result = plugin.shouldOpenExternalUrl(url);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -490,38 +446,32 @@ public class PluginManager {
|
||||
* @return Return false to allow the URL to load, return true to prevent the URL from loading.
|
||||
*/
|
||||
public boolean onOverrideUrlLoading(String url) {
|
||||
synchronized (this.entryMap) {
|
||||
for (PluginEntry entry : this.entryMap.values()) {
|
||||
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||
if (plugin != null && plugin.onOverrideUrlLoading(url)) {
|
||||
return true;
|
||||
}
|
||||
for (PluginEntry entry : this.entryMap.values()) {
|
||||
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||
if (plugin != null && plugin.onOverrideUrlLoading(url)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the app navigates or refreshes.
|
||||
*/
|
||||
public void onReset() {
|
||||
synchronized (this.pluginMap) {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onReset();
|
||||
}
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onReset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Uri remapUri(Uri uri) {
|
||||
synchronized (this.pluginMap) {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
Uri ret = plugin.remapUri(uri);
|
||||
if (ret != null) {
|
||||
return ret;
|
||||
}
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
Uri ret = plugin.remapUri(uri);
|
||||
if (ret != null) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -554,24 +504,20 @@ public class PluginManager {
|
||||
* @param newConfig The new device configuration
|
||||
*/
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
synchronized (this.pluginMap) {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onConfigurationChanged(newConfig);
|
||||
}
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onConfigurationChanged(newConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Bundle onSaveInstanceState() {
|
||||
Bundle state = new Bundle();
|
||||
synchronized (this.pluginMap) {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
Bundle pluginState = plugin.onSaveInstanceState();
|
||||
if (pluginState != null) {
|
||||
state.putBundle(plugin.getServiceName(), pluginState);
|
||||
}
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
Bundle pluginState = plugin.onSaveInstanceState();
|
||||
if(pluginState != null) {
|
||||
state.putBundle(plugin.getServiceName(), pluginState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,15 +152,6 @@ public class SystemWebViewEngine implements CordovaWebViewEngine {
|
||||
settings.setJavaScriptCanOpenWindowsAutomatically(true);
|
||||
settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
|
||||
|
||||
/**
|
||||
* https://developer.android.com/reference/android/webkit/WebSettings#setAllowFileAccess(boolean)
|
||||
*
|
||||
* SDK >= 30 has recently set this value to false by default.
|
||||
* It is recommended to turn off this settings To prevent possible security issues targeting Build.VERSION_CODES.Q and earlier.
|
||||
* For existing functionality, this setting is set to true. In a future release, this should be defaulted to false.
|
||||
*/
|
||||
settings.setAllowFileAccess(true);
|
||||
|
||||
String manufacturer = android.os.Build.MANUFACTURER;
|
||||
LOG.d(TAG, "CordovaWebView is running on device made by: " + manufacturer);
|
||||
|
||||
|
||||
1197
package-lock.json
generated
1197
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cordova-android",
|
||||
"version": "9.1.0",
|
||||
"version": "9.0.0",
|
||||
"description": "cordova-android release",
|
||||
"bin": {
|
||||
"create": "bin/create"
|
||||
@@ -15,12 +15,12 @@
|
||||
],
|
||||
"scripts": {
|
||||
"test": "npm run lint && npm run cover && npm run java-unit-tests",
|
||||
"lint": "eslint . \"bin/**/!(*.*|gitignore)\"",
|
||||
"unit-tests": "jasmine --config=spec/unit/jasmine.json",
|
||||
"cover": "nyc jasmine --config=spec/coverage.json",
|
||||
"e2e-tests": "jasmine --config=spec/e2e/jasmine.json",
|
||||
"java-unit-tests": "node test/run_java_unit_tests.js",
|
||||
"clean:java-unit-tests": "node test/clean.js"
|
||||
"lint": "eslint . \"bin/**/!(*.*|gitignore)\"",
|
||||
"clean-tests": "node bin/clean_test.js"
|
||||
},
|
||||
"author": "Apache Software Foundation",
|
||||
"license": "Apache-2.0",
|
||||
@@ -28,12 +28,9 @@
|
||||
"android-versions": "^1.5.0",
|
||||
"cordova-common": "^4.0.1",
|
||||
"execa": "^4.0.2",
|
||||
"fast-glob": "^3.2.4",
|
||||
"fs-extra": "^9.0.1",
|
||||
"is-path-inside": "^3.0.2",
|
||||
"nopt": "^4.0.3",
|
||||
"properties-parser": "^0.3.1",
|
||||
"semver": "^7.3.4",
|
||||
"which": "^2.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -44,8 +41,9 @@
|
||||
"rewire": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.10.0"
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"engineStrict": true,
|
||||
"nyc": {
|
||||
"include": [
|
||||
"bin/lib/**",
|
||||
|
||||
149
spec/e2e/helpers/projectActions.js
Normal file
149
spec/e2e/helpers/projectActions.js
Normal file
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
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 PluginInfoProvider = require('cordova-common').PluginInfoProvider;
|
||||
const fs = require('fs-extra');
|
||||
var cp = require('child_process');
|
||||
var path = require('path');
|
||||
var util = require('util');
|
||||
|
||||
var cordova_bin = path.join(__dirname, '../../../bin');
|
||||
|
||||
/**
|
||||
* Creates a project using platform create script with given parameters
|
||||
* @param {string} projectname - name of the project
|
||||
* @param {string} projectid - id of the project
|
||||
* @param {string} platformpath - path to the platform
|
||||
* @param {function} callback - function which is called (without arguments) when the project is created or (with error object) when error occurs
|
||||
*/
|
||||
module.exports.createProject = function (projectname, projectid, platformpath, callback) {
|
||||
// platformpath is optional
|
||||
if (!callback && typeof platformpath === 'function') {
|
||||
callback = platformpath;
|
||||
platformpath = null;
|
||||
}
|
||||
var projectDirName = getDirName(projectid);
|
||||
var createScriptPath = platformpath ? path.join(platformpath, 'bin/create') : path.join(cordova_bin, 'create');
|
||||
|
||||
// remove existing folder
|
||||
module.exports.removeProject(projectid);
|
||||
|
||||
// create the project
|
||||
var command = util.format('"%s" %s %s "%s"', createScriptPath, projectDirName, projectid, projectname);
|
||||
cp.exec(command, function (error, stdout, stderr) {
|
||||
if (error) {
|
||||
console.log(stdout);
|
||||
console.error(stderr);
|
||||
}
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates a project using platform update script with given parameters
|
||||
* @param {string} projectid - id of the project
|
||||
* @param {string} platformpath - path to the platform
|
||||
* @param {function} callback - function which is called (without arguments) when the project is updated or (with error object) when error occurs
|
||||
*/
|
||||
module.exports.updateProject = function (projectid, platformpath, callback) {
|
||||
// platformpath is optional
|
||||
if (!callback && typeof platformpath === 'function') {
|
||||
callback = platformpath;
|
||||
platformpath = null;
|
||||
}
|
||||
var projectDirName = getDirName(projectid);
|
||||
var updateScriptPath = platformpath ? path.join(platformpath, 'bin/update') : path.join(cordova_bin, 'update');
|
||||
var command = util.format('"%s" %s', updateScriptPath, projectDirName);
|
||||
cp.exec(command, function (error, stdout, stderr) {
|
||||
if (error) {
|
||||
console.log(stdout);
|
||||
console.error(stderr);
|
||||
}
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds a project using platform build script with given parameters
|
||||
* @param {string} projectid - id of the project
|
||||
* @param {function} callback - function which is called (without arguments) when the project is built or (with error object) when error occurs
|
||||
*/
|
||||
module.exports.buildProject = function (projectid, callback) {
|
||||
var projectDirName = getDirName(projectid);
|
||||
var command = path.join(projectDirName, 'cordova/build');
|
||||
|
||||
cp.exec(command, function (error, stdout, stderr) {
|
||||
if (error) {
|
||||
console.log(stdout);
|
||||
console.error(stderr);
|
||||
}
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a project
|
||||
* @param {string} projectid - id of the project
|
||||
*/
|
||||
module.exports.removeProject = function (projectid) {
|
||||
var projectDirName = getDirName(projectid);
|
||||
fs.removeSync(projectDirName);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a plugin to a project using platform api
|
||||
* @param {string} projectid - id of the project
|
||||
* @param {string} plugindir - path to a plugin
|
||||
* @param {function} callback - function which is called (without arguments) when the plugin is added or (with error object) when error occurs
|
||||
*/
|
||||
module.exports.addPlugin = function (projectid, plugindir, callback) {
|
||||
var projectDirName = getDirName(projectid);
|
||||
var pip = new PluginInfoProvider();
|
||||
var pluginInfo = pip.get(plugindir);
|
||||
var Api = require(path.join(__dirname, '../../..', projectDirName, 'cordova', 'Api.js'));
|
||||
var api = new Api('android', projectDirName);
|
||||
|
||||
api.addPlugin(pluginInfo).then(function () {
|
||||
callback(null);
|
||||
}, function (error) {
|
||||
console.error(error);
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a version number from project using platform script
|
||||
* @param {string} projectid - id of the project
|
||||
* @param {function} callback - function which is called with platform version as an argument
|
||||
*/
|
||||
module.exports.getPlatformVersion = function (projectid, callback) {
|
||||
var command = path.join(getDirName(projectid), 'cordova/version');
|
||||
|
||||
cp.exec(command, function (error, stdout, stderr) {
|
||||
if (error) {
|
||||
console.log(stdout);
|
||||
console.error(stderr);
|
||||
}
|
||||
callback(stdout.trim());
|
||||
});
|
||||
};
|
||||
|
||||
function getDirName (projectid) {
|
||||
return 'test-' + projectid;
|
||||
}
|
||||
@@ -26,6 +26,6 @@ jasmine.getEnv().addReporter(new SpecReporter({
|
||||
},
|
||||
summary: {
|
||||
displayDuration: true,
|
||||
displayStacktrace: 'raw'
|
||||
displayStacktrace: true
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -20,15 +20,13 @@
|
||||
const CordovaError = require('cordova-common').CordovaError;
|
||||
const rewire = require('rewire');
|
||||
|
||||
const adbOutput = `List of devices attached
|
||||
emulator-5554\tdevice
|
||||
emulator-5556\toffline
|
||||
123a76565509e124\tdevice
|
||||
123a76565509e123\tbootloader
|
||||
`;
|
||||
|
||||
describe('Adb', () => {
|
||||
const deviceId = '123a76565509e124';
|
||||
const adbOutput = `List of devices attached
|
||||
emulator-5554\tdevice
|
||||
123a76565509e124\tdevice`;
|
||||
const [, emulatorLine, deviceLine] = adbOutput.split('\n');
|
||||
const emulatorId = emulatorLine.split('\t')[0];
|
||||
const deviceId = deviceLine.split('\t')[0];
|
||||
|
||||
const alreadyExistsError = 'adb: failed to install app.apk: Failure[INSTALL_FAILED_ALREADY_EXISTS]';
|
||||
const certificateError = 'adb: failed to install app.apk: Failure[INSTALL_PARSE_FAILED_NO_CERTIFICATES]';
|
||||
@@ -43,15 +41,40 @@ describe('Adb', () => {
|
||||
Adb.__set__('execa', execaSpy);
|
||||
});
|
||||
|
||||
describe('devices', () => {
|
||||
it('should return the IDs of all fully booted devices & emulators', () => {
|
||||
execaSpy.and.resolveTo({ stdout: adbOutput });
|
||||
describe('isDevice', () => {
|
||||
it('should return true for a real device', () => {
|
||||
const isDevice = Adb.__get__('isDevice');
|
||||
|
||||
expect(isDevice(deviceLine)).toBeTruthy();
|
||||
expect(isDevice(emulatorLine)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('isEmulator', () => {
|
||||
it('should return true for an emulator', () => {
|
||||
const isEmulator = Adb.__get__('isEmulator');
|
||||
|
||||
expect(isEmulator(emulatorLine)).toBeTruthy();
|
||||
expect(isEmulator(deviceLine)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('devices', () => {
|
||||
beforeEach(() => {
|
||||
execaSpy.and.returnValue(Promise.resolve({ stdout: adbOutput }));
|
||||
});
|
||||
|
||||
it('should return only devices if no options are specified', () => {
|
||||
return Adb.devices().then(devices => {
|
||||
expect(devices).toEqual([
|
||||
'emulator-5554',
|
||||
'123a76565509e124'
|
||||
]);
|
||||
expect(devices.length).toBe(1);
|
||||
expect(devices[0]).toBe(deviceId);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return only emulators if opts.emulators is true', () => {
|
||||
return Adb.devices({ emulators: true }).then(devices => {
|
||||
expect(devices.length).toBe(1);
|
||||
expect(devices[0]).toBe(emulatorId);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,44 +20,51 @@
|
||||
var os = require('os');
|
||||
var path = require('path');
|
||||
var common = require('cordova-common');
|
||||
const EventEmitter = require('events');
|
||||
var rewire = require('rewire');
|
||||
|
||||
var Api = require('../../bin/templates/cordova/Api');
|
||||
var AndroidProject = require('../../bin/templates/cordova/lib/AndroidProject');
|
||||
var builders = require('../../bin/templates/cordova/lib/builders/builders');
|
||||
|
||||
var PluginInfo = common.PluginInfo;
|
||||
|
||||
var FIXTURES = path.join(__dirname, '../e2e/fixtures');
|
||||
var FAKE_PROJECT_DIR = path.join(os.tmpdir(), 'plugin-test-project');
|
||||
|
||||
describe('Api', () => {
|
||||
describe('addPlugin method', function () {
|
||||
var api;
|
||||
describe('addPlugin method', function () {
|
||||
var api, Api, gradleBuilder;
|
||||
|
||||
beforeEach(function () {
|
||||
var pluginManager = jasmine.createSpyObj('pluginManager', ['addPlugin']);
|
||||
pluginManager.addPlugin.and.resolveTo();
|
||||
spyOn(common.PluginManager, 'get').and.returnValue(pluginManager);
|
||||
beforeEach(function () {
|
||||
Api = rewire('../../bin/templates/cordova/Api');
|
||||
|
||||
var projectSpy = jasmine.createSpyObj('AndroidProject', ['getPackageName', 'write', 'isClean']);
|
||||
spyOn(AndroidProject, 'getProjectFile').and.returnValue(projectSpy);
|
||||
var pluginManager = jasmine.createSpyObj('pluginManager', ['addPlugin']);
|
||||
pluginManager.addPlugin.and.resolveTo();
|
||||
spyOn(common.PluginManager, 'get').and.returnValue(pluginManager);
|
||||
|
||||
api = new Api('android', FAKE_PROJECT_DIR, new EventEmitter());
|
||||
spyOn(api._builder, 'prepBuildFiles');
|
||||
var projectSpy = jasmine.createSpyObj('AndroidProject', ['getPackageName', 'write', 'isClean']);
|
||||
spyOn(AndroidProject, 'getProjectFile').and.returnValue(projectSpy);
|
||||
|
||||
Api.__set__('Api.prototype.clean', async () => {});
|
||||
|
||||
// Prevent logging to avoid polluting the test reports
|
||||
Api.__set__('selfEvents.emit', jasmine.createSpy());
|
||||
|
||||
api = new Api('android', FAKE_PROJECT_DIR);
|
||||
|
||||
gradleBuilder = jasmine.createSpyObj('gradleBuilder', ['prepBuildFiles']);
|
||||
spyOn(builders, 'getBuilder').and.returnValue(gradleBuilder);
|
||||
});
|
||||
|
||||
const getPluginFixture = name => new PluginInfo(path.join(FIXTURES, name));
|
||||
|
||||
it('Test#001 : should call gradleBuilder.prepBuildFiles for every plugin with frameworks', () => {
|
||||
return api.addPlugin(getPluginFixture('cordova-plugin-fake')).then(() => {
|
||||
expect(gradleBuilder.prepBuildFiles).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
const getPluginFixture = name => new PluginInfo(path.join(FIXTURES, name));
|
||||
|
||||
it('Test#001 : should call gradleBuilder.prepBuildFiles for every plugin with frameworks', () => {
|
||||
return api.addPlugin(getPluginFixture('cordova-plugin-fake')).then(() => {
|
||||
expect(api._builder.prepBuildFiles).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('Test#002 : shouldn\'t trigger gradleBuilder.prepBuildFiles for plugins without android frameworks', () => {
|
||||
return api.addPlugin(getPluginFixture('cordova-plugin-fake-ios-frameworks')).then(() => {
|
||||
expect(api._builder.prepBuildFiles).not.toHaveBeenCalled();
|
||||
});
|
||||
it('Test#002 : shouldn\'t trigger gradleBuilder.prepBuildFiles for plugins without android frameworks', () => {
|
||||
return api.addPlugin(getPluginFixture('cordova-plugin-fake-ios-frameworks')).then(() => {
|
||||
expect(gradleBuilder.prepBuildFiles).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -289,7 +289,7 @@ describe('ProjectBuilder', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('outputFileComparator', () => {
|
||||
describe('fileSorter', () => {
|
||||
it('should sort APKs from most recent to oldest, deprioritising unsigned arch-specific builds', () => {
|
||||
const APKs = {
|
||||
'app-debug.apk': new Date('2018-04-20'),
|
||||
@@ -309,7 +309,7 @@ describe('ProjectBuilder', () => {
|
||||
});
|
||||
|
||||
const apkArray = Object.keys(APKs);
|
||||
const sortedApks = apkArray.sort(ProjectBuilder.__get__('outputFileComparator'));
|
||||
const sortedApks = apkArray.sort(ProjectBuilder.__get__('fileSorter'));
|
||||
|
||||
expect(sortedApks).toEqual(expectedResult);
|
||||
});
|
||||
|
||||
@@ -18,54 +18,26 @@
|
||||
*/
|
||||
|
||||
var rewire = require('rewire');
|
||||
var check_reqs = rewire('../../bin/templates/cordova/lib/check_reqs');
|
||||
var android_sdk = require('../../bin/templates/cordova/lib/android_sdk');
|
||||
var fs = require('fs-extra');
|
||||
var path = require('path');
|
||||
var events = require('cordova-common').events;
|
||||
var which = require('which');
|
||||
const { CordovaError } = require('cordova-common');
|
||||
|
||||
// This should match /bin/templates/project/build.gradle
|
||||
const DEFAULT_TARGET_API = 29;
|
||||
|
||||
describe('check_reqs', function () {
|
||||
let check_reqs;
|
||||
beforeEach(() => {
|
||||
check_reqs = rewire('../../bin/templates/cordova/lib/check_reqs');
|
||||
});
|
||||
|
||||
var original_env;
|
||||
beforeAll(function () {
|
||||
original_env = Object.assign({}, process.env);
|
||||
original_env = Object.create(process.env);
|
||||
});
|
||||
afterEach(function () {
|
||||
// process.env has some special behavior, so we do not
|
||||
// replace it but only restore its original properties
|
||||
Object.keys(process.env).forEach(k => {
|
||||
delete process.env[k];
|
||||
});
|
||||
Object.assign(process.env, original_env);
|
||||
});
|
||||
|
||||
describe('check_java', () => {
|
||||
it('detects if unexpected JDK version is installed', async () => {
|
||||
check_reqs.__set__({
|
||||
EXPECTED_JAVA_VERSION: '9999.9999.9999',
|
||||
java: { getVersion: async () => ({ version: '1.8.0' }) }
|
||||
});
|
||||
|
||||
await expectAsync(check_reqs.check_java()).toBeRejectedWithError(CordovaError, /Requirements check failed for JDK 9999.9999.9999! Detected version: 1.8.0/);
|
||||
});
|
||||
|
||||
it('should return the version', async () => {
|
||||
check_reqs.__set__({
|
||||
java: { getVersion: async () => ({ version: '1.8.0' }) }
|
||||
});
|
||||
|
||||
await expectAsync(check_reqs.check_java()).toBeResolvedTo({ version: '1.8.0' });
|
||||
Object.keys(original_env).forEach(function (k) {
|
||||
process.env[k] = original_env[k];
|
||||
});
|
||||
});
|
||||
|
||||
describe('check_android', function () {
|
||||
describe('find and set ANDROID_HOME when ANDROID_HOME and ANDROID_SDK_ROOT is not set', function () {
|
||||
beforeEach(function () {
|
||||
@@ -83,6 +55,9 @@ describe('check_reqs', function () {
|
||||
process.env.ProgramFiles = 'windows-program-files';
|
||||
return check_reqs.check_android().then(function () {
|
||||
expect(process.env.ANDROID_SDK_ROOT).toContain('windows-local-app-data');
|
||||
}).finally(function () {
|
||||
delete process.env.LOCALAPPDATA;
|
||||
delete process.env.ProgramFiles;
|
||||
});
|
||||
});
|
||||
it('it should set ANDROID_SDK_ROOT on Darwin', () => {
|
||||
@@ -91,6 +66,8 @@ describe('check_reqs', function () {
|
||||
process.env.HOME = 'home is where the heart is';
|
||||
return check_reqs.check_android().then(function () {
|
||||
expect(process.env.ANDROID_SDK_ROOT).toContain('home is where the heart is');
|
||||
}).finally(function () {
|
||||
delete process.env.HOME;
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -243,6 +220,9 @@ describe('check_reqs', function () {
|
||||
process.env.ANDROID_SDK_ROOT = 'let the children play';
|
||||
spyOn(fs, 'existsSync').and.returnValue(true);
|
||||
});
|
||||
afterEach(function () {
|
||||
delete process.env.ANDROID_SDK_ROOT;
|
||||
});
|
||||
it('should add tools/bin,tools,platform-tools to PATH if `avdmanager`,`android`,`adb` is not found', () => {
|
||||
return check_reqs.check_android().then(function () {
|
||||
expect(process.env.PATH).toContain('let the children play' + path.sep + 'tools');
|
||||
@@ -263,20 +243,20 @@ describe('check_reqs', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('with ANDROID_SDK_ROOT / without ANDROID_HOME', async () => {
|
||||
it('with ANDROID_SDK_ROOT / without ANDROID_HOME', () => {
|
||||
process.env.ANDROID_SDK_ROOT = '/android/sdk/root';
|
||||
await expectAsync(check_reqs.check_gradle()).toBeResolvedTo('/android/sdk/root/bin/gradle');
|
||||
expectAsync(check_reqs.check_gradle()).toBeResolvedTo('/android/sdk/root/bin/gradle');
|
||||
});
|
||||
|
||||
it('with ANDROID_SDK_ROOT / with ANDROID_HOME', async () => {
|
||||
it('with ANDROID_SDK_ROOT / with ANDROID_HOME', () => {
|
||||
process.env.ANDROID_SDK_ROOT = '/android/sdk/root';
|
||||
process.env.ANDROID_HOME = '/android/sdk/home';
|
||||
await expectAsync(check_reqs.check_gradle()).toBeResolvedTo('/android/sdk/root/bin/gradle');
|
||||
expectAsync(check_reqs.check_gradle()).toBeResolvedTo('/android/sdk/root/bin/gradle');
|
||||
});
|
||||
|
||||
it('without ANDROID_SDK_ROOT / with ANDROID_HOME', async () => {
|
||||
it('without ANDROID_SDK_ROOT / with ANDROID_HOME', () => {
|
||||
process.env.ANDROID_HOME = '/android/sdk/home';
|
||||
await expectAsync(check_reqs.check_gradle()).toBeResolvedTo('/android/sdk/home/bin/gradle');
|
||||
expectAsync(check_reqs.check_gradle()).toBeResolvedTo('/android/sdk/home/bin/gradle');
|
||||
});
|
||||
|
||||
it('without ANDROID_SDK_ROOT / without ANDROID_HOME', () => {
|
||||
@@ -368,8 +348,6 @@ describe('check_reqs', function () {
|
||||
}
|
||||
});
|
||||
|
||||
spyOn(events, 'emit');
|
||||
|
||||
getPreferenceSpy.and.returnValue(DEFAULT_TARGET_API - 1);
|
||||
|
||||
var target = check_reqs.get_target();
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
|
||||
var rewire = require('rewire');
|
||||
var utils = require('../../bin/templates/cordova/lib/utils');
|
||||
var utils = require('../../bin/lib/utils');
|
||||
var create = rewire('../../bin/lib/create');
|
||||
var check_reqs = require('../../bin/templates/cordova/lib/check_reqs');
|
||||
var fs = require('fs-extra');
|
||||
@@ -132,7 +132,6 @@ describe('create', function () {
|
||||
spyOn(create, 'copyBuildRules');
|
||||
spyOn(create, 'writeProjectProperties');
|
||||
spyOn(create, 'prepBuildFiles');
|
||||
spyOn(create, 'writeNameForAndroidStudio');
|
||||
revert_manifest_mock = create.__set__('AndroidManifest', Manifest_mock);
|
||||
spyOn(fs, 'existsSync').and.returnValue(false);
|
||||
spyOn(fs, 'copySync');
|
||||
@@ -301,24 +300,4 @@ describe('create', function () {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('writeNameForAndroidStudio', () => {
|
||||
const project_path = path.join('some', 'path');
|
||||
const appName = 'Test Cordova';
|
||||
|
||||
beforeEach(function () {
|
||||
spyOn(fs, 'ensureDirSync');
|
||||
spyOn(fs, 'writeFileSync');
|
||||
});
|
||||
|
||||
it('should call ensureDirSync with path', () => {
|
||||
create.writeNameForAndroidStudio(project_path, appName);
|
||||
expect(fs.ensureDirSync).toHaveBeenCalledWith(path.join(project_path, '.idea'));
|
||||
});
|
||||
|
||||
it('should call writeFileSync with content', () => {
|
||||
create.writeNameForAndroidStudio(project_path, appName);
|
||||
expect(fs.writeFileSync).toHaveBeenCalledWith(path.join(project_path, '.idea', '.name'), appName);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
233
spec/unit/device.spec.js
Normal file
233
spec/unit/device.spec.js
Normal file
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
const rewire = require('rewire');
|
||||
|
||||
const CordovaError = require('cordova-common').CordovaError;
|
||||
|
||||
describe('device', () => {
|
||||
const DEVICE_LIST = ['device1', 'device2', 'device3'];
|
||||
let AdbSpy;
|
||||
let device;
|
||||
|
||||
beforeEach(() => {
|
||||
device = rewire('../../bin/templates/cordova/lib/device');
|
||||
AdbSpy = jasmine.createSpyObj('Adb', ['devices', 'install', 'shell', 'start', 'uninstall']);
|
||||
device.__set__('Adb', AdbSpy);
|
||||
});
|
||||
|
||||
describe('list', () => {
|
||||
it('should return the list from adb devices', () => {
|
||||
AdbSpy.devices.and.returnValue(Promise.resolve(DEVICE_LIST));
|
||||
|
||||
return device.list().then(list => {
|
||||
expect(list).toEqual(DEVICE_LIST);
|
||||
});
|
||||
});
|
||||
|
||||
it('should kill adb and try to get devices again if none are found the first time, and `lookHarder` is set', () => {
|
||||
const execaSpy = jasmine.createSpy('execa').and.returnValue(Promise.resolve());
|
||||
device.__set__('execa', execaSpy);
|
||||
AdbSpy.devices.and.returnValues(Promise.resolve([]), Promise.resolve(DEVICE_LIST));
|
||||
|
||||
return device.list(true).then(list => {
|
||||
expect(execaSpy).toHaveBeenCalledWith('killall', ['adb']);
|
||||
expect(list).toBe(DEVICE_LIST);
|
||||
expect(AdbSpy.devices).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the empty list if killing adb fails', () => {
|
||||
const emptyDevices = [];
|
||||
const execaSpy = jasmine.createSpy('execa').and.returnValue(Promise.reject());
|
||||
device.__set__('execa', execaSpy);
|
||||
AdbSpy.devices.and.returnValues(Promise.resolve(emptyDevices));
|
||||
|
||||
return device.list(true).then(list => {
|
||||
expect(execaSpy).toHaveBeenCalledWith('killall', ['adb']);
|
||||
expect(list).toBe(emptyDevices);
|
||||
expect(AdbSpy.devices).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveTarget', () => {
|
||||
let buildSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
buildSpy = jasmine.createSpyObj('build', ['detectArchitecture']);
|
||||
buildSpy.detectArchitecture.and.returnValue(Promise.resolve());
|
||||
device.__set__('build', buildSpy);
|
||||
|
||||
spyOn(device, 'list').and.returnValue(Promise.resolve(DEVICE_LIST));
|
||||
});
|
||||
|
||||
it('should select the first device to be the target if none is specified', () => {
|
||||
return device.resolveTarget().then(deviceInfo => {
|
||||
expect(deviceInfo.target).toBe(DEVICE_LIST[0]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should use the given target instead of the default', () => {
|
||||
return device.resolveTarget(DEVICE_LIST[2]).then(deviceInfo => {
|
||||
expect(deviceInfo.target).toBe(DEVICE_LIST[2]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should set emulator to false', () => {
|
||||
return device.resolveTarget().then(deviceInfo => {
|
||||
expect(deviceInfo.isEmulator).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if there are no devices', () => {
|
||||
device.list.and.returnValue(Promise.resolve([]));
|
||||
|
||||
return device.resolveTarget().then(
|
||||
() => fail('Unexpectedly resolved'),
|
||||
err => {
|
||||
expect(err).toEqual(jasmine.any(CordovaError));
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if the specified target does not exist', () => {
|
||||
return device.resolveTarget('nonexistent-target').then(
|
||||
() => fail('Unexpectedly resolved'),
|
||||
err => {
|
||||
expect(err).toEqual(jasmine.any(CordovaError));
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should detect the architecture and return it with the device info', () => {
|
||||
const target = DEVICE_LIST[1];
|
||||
const arch = 'unittestarch';
|
||||
|
||||
buildSpy.detectArchitecture.and.returnValue(Promise.resolve(arch));
|
||||
|
||||
return device.resolveTarget(target).then(deviceInfo => {
|
||||
expect(buildSpy.detectArchitecture).toHaveBeenCalledWith(target);
|
||||
expect(deviceInfo.arch).toBe(arch);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('install', () => {
|
||||
let AndroidManifestSpy;
|
||||
let AndroidManifestFns;
|
||||
let AndroidManifestGetActivitySpy;
|
||||
let buildSpy;
|
||||
let target;
|
||||
|
||||
beforeEach(() => {
|
||||
target = { target: DEVICE_LIST[0], arch: 'arm7', isEmulator: false };
|
||||
|
||||
buildSpy = jasmine.createSpyObj('build', ['findBestApkForArchitecture']);
|
||||
device.__set__('build', buildSpy);
|
||||
|
||||
AndroidManifestFns = jasmine.createSpyObj('AndroidManifestFns', ['getPackageId', 'getActivity']);
|
||||
AndroidManifestGetActivitySpy = jasmine.createSpyObj('getActivity', ['getName']);
|
||||
AndroidManifestFns.getActivity.and.returnValue(AndroidManifestGetActivitySpy);
|
||||
AndroidManifestSpy = jasmine.createSpy('AndroidManifest').and.returnValue(AndroidManifestFns);
|
||||
device.__set__('AndroidManifest', AndroidManifestSpy);
|
||||
|
||||
AdbSpy.install.and.returnValue(Promise.resolve());
|
||||
AdbSpy.shell.and.returnValue(Promise.resolve());
|
||||
AdbSpy.start.and.returnValue(Promise.resolve());
|
||||
});
|
||||
|
||||
it('should get the full target object if only id is specified', () => {
|
||||
const targetId = DEVICE_LIST[0];
|
||||
spyOn(device, 'resolveTarget').and.returnValue(Promise.resolve(target));
|
||||
|
||||
return device.install(targetId).then(() => {
|
||||
expect(device.resolveTarget).toHaveBeenCalledWith(targetId);
|
||||
});
|
||||
});
|
||||
|
||||
it('should install to the passed target', () => {
|
||||
return device.install(target).then(() => {
|
||||
expect(AdbSpy.install).toHaveBeenCalledWith(target.target, undefined, jasmine.anything());
|
||||
});
|
||||
});
|
||||
|
||||
it('should install the correct apk based on the architecture and build results', () => {
|
||||
const buildResults = {
|
||||
apkPaths: 'path/to/apks',
|
||||
buildType: 'debug',
|
||||
buildMethod: 'foo'
|
||||
};
|
||||
|
||||
const apkPath = 'my/apk/path/app.apk';
|
||||
buildSpy.findBestApkForArchitecture.and.returnValue(apkPath);
|
||||
|
||||
return device.install(target, buildResults).then(() => {
|
||||
expect(buildSpy.findBestApkForArchitecture).toHaveBeenCalledWith(buildResults, target.arch);
|
||||
expect(AdbSpy.install).toHaveBeenCalledWith(jasmine.anything(), apkPath, jasmine.anything());
|
||||
});
|
||||
});
|
||||
|
||||
it('should uninstall and reinstall app if failure is due to different certificates', () => {
|
||||
AdbSpy.install.and.returnValues(
|
||||
Promise.reject('Failed to install: INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES'),
|
||||
Promise.resolve()
|
||||
);
|
||||
|
||||
AdbSpy.uninstall.and.callFake(() => {
|
||||
expect(AdbSpy.install).toHaveBeenCalledTimes(1);
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
return device.install(target).then(() => {
|
||||
expect(AdbSpy.install).toHaveBeenCalledTimes(2);
|
||||
expect(AdbSpy.uninstall).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw any error not caused by different certificates', () => {
|
||||
const errorMsg = new CordovaError('Failed to install');
|
||||
AdbSpy.install.and.returnValues(Promise.reject(errorMsg));
|
||||
|
||||
return device.install(target).then(
|
||||
() => fail('Unexpectedly resolved'),
|
||||
err => {
|
||||
expect(err).toBe(errorMsg);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should unlock the screen on device', () => {
|
||||
return device.install(target).then(() => {
|
||||
expect(AdbSpy.shell).toHaveBeenCalledWith(target.target, 'input keyevent 82');
|
||||
});
|
||||
});
|
||||
|
||||
it('should start the newly installed app on the device', () => {
|
||||
const packageId = 'unittestapp';
|
||||
const activityName = 'TestActivity';
|
||||
AndroidManifestFns.getPackageId.and.returnValue(packageId);
|
||||
AndroidManifestGetActivitySpy.getName.and.returnValue(activityName);
|
||||
|
||||
return device.install(target).then(() => {
|
||||
expect(AdbSpy.start).toHaveBeenCalledWith(target.target, `${packageId}/.${activityName}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -26,6 +26,7 @@ const CordovaError = require('cordova-common').CordovaError;
|
||||
const check_reqs = require('../../bin/templates/cordova/lib/check_reqs');
|
||||
|
||||
describe('emulator', () => {
|
||||
const EMULATOR_LIST = ['emulator-5555', 'emulator-5556', 'emulator-5557'];
|
||||
let emu;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -204,13 +205,13 @@ describe('emulator', () => {
|
||||
});
|
||||
|
||||
describe('list_started', () => {
|
||||
it('should return a list of all online emulators', () => {
|
||||
it('should call adb devices with the emulators flag', () => {
|
||||
const AdbSpy = jasmine.createSpyObj('Adb', ['devices']);
|
||||
AdbSpy.devices.and.resolveTo(['emulator-5556', '123a76565509e124']);
|
||||
AdbSpy.devices.and.returnValue(Promise.resolve());
|
||||
emu.__set__('Adb', AdbSpy);
|
||||
|
||||
return emu.list_started().then(emus => {
|
||||
expect(emus).toEqual(['emulator-5556']);
|
||||
return emu.list_started().then(() => {
|
||||
expect(AdbSpy.devices).toHaveBeenCalledWith({ emulators: true });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -528,4 +529,158 @@ describe('emulator', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveTarget', () => {
|
||||
const arch = 'arm7-test';
|
||||
|
||||
beforeEach(() => {
|
||||
const buildSpy = jasmine.createSpyObj('build', ['detectArchitecture']);
|
||||
buildSpy.detectArchitecture.and.returnValue(Promise.resolve(arch));
|
||||
emu.__set__('build', buildSpy);
|
||||
|
||||
spyOn(emu, 'list_started').and.returnValue(Promise.resolve(EMULATOR_LIST));
|
||||
});
|
||||
|
||||
it('should throw an error if there are no running emulators', () => {
|
||||
emu.list_started.and.returnValue(Promise.resolve([]));
|
||||
|
||||
return emu.resolveTarget().then(
|
||||
() => fail('Unexpectedly resolved'),
|
||||
err => {
|
||||
expect(err).toEqual(jasmine.any(CordovaError));
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if the requested emulator is not running', () => {
|
||||
const targetEmulator = 'unstarted-emu';
|
||||
|
||||
return emu.resolveTarget(targetEmulator).then(
|
||||
() => fail('Unexpectedly resolved'),
|
||||
err => {
|
||||
expect(err.message).toContain(targetEmulator);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should return info on the first running emulator if none is specified', () => {
|
||||
return emu.resolveTarget().then(emulatorInfo => {
|
||||
expect(emulatorInfo.target).toBe(EMULATOR_LIST[0]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the emulator info', () => {
|
||||
return emu.resolveTarget(EMULATOR_LIST[1]).then(emulatorInfo => {
|
||||
expect(emulatorInfo).toEqual({ target: EMULATOR_LIST[1], arch, isEmulator: true });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('install', () => {
|
||||
let AndroidManifestSpy;
|
||||
let AndroidManifestFns;
|
||||
let AndroidManifestGetActivitySpy;
|
||||
let AdbSpy;
|
||||
let buildSpy;
|
||||
let execaSpy;
|
||||
let target;
|
||||
|
||||
beforeEach(() => {
|
||||
target = { target: EMULATOR_LIST[1], arch: 'arm7', isEmulator: true };
|
||||
|
||||
buildSpy = jasmine.createSpyObj('build', ['findBestApkForArchitecture']);
|
||||
emu.__set__('build', buildSpy);
|
||||
|
||||
AndroidManifestFns = jasmine.createSpyObj('AndroidManifestFns', ['getPackageId', 'getActivity']);
|
||||
AndroidManifestGetActivitySpy = jasmine.createSpyObj('getActivity', ['getName']);
|
||||
AndroidManifestFns.getActivity.and.returnValue(AndroidManifestGetActivitySpy);
|
||||
AndroidManifestSpy = jasmine.createSpy('AndroidManifest').and.returnValue(AndroidManifestFns);
|
||||
emu.__set__('AndroidManifest', AndroidManifestSpy);
|
||||
|
||||
AdbSpy = jasmine.createSpyObj('Adb', ['shell', 'start', 'uninstall']);
|
||||
AdbSpy.shell.and.returnValue(Promise.resolve());
|
||||
AdbSpy.start.and.returnValue(Promise.resolve());
|
||||
AdbSpy.uninstall.and.returnValue(Promise.resolve());
|
||||
emu.__set__('Adb', AdbSpy);
|
||||
|
||||
execaSpy = jasmine.createSpy('execa').and.resolveTo({});
|
||||
emu.__set__('execa', execaSpy);
|
||||
});
|
||||
|
||||
it('should get the full target object if only id is specified', () => {
|
||||
const targetId = target.target;
|
||||
spyOn(emu, 'resolveTarget').and.returnValue(Promise.resolve(target));
|
||||
|
||||
return emu.install(targetId, {}).then(() => {
|
||||
expect(emu.resolveTarget).toHaveBeenCalledWith(targetId);
|
||||
});
|
||||
});
|
||||
|
||||
it('should install to the passed target', () => {
|
||||
return emu.install(target, {}).then(() => {
|
||||
const execCmd = execaSpy.calls.argsFor(0)[1].join(' ');
|
||||
expect(execCmd).toContain(`-s ${target.target} install`);
|
||||
});
|
||||
});
|
||||
|
||||
it('should install the correct apk based on the architecture and build results', () => {
|
||||
const buildResults = {
|
||||
apkPaths: 'path/to/apks',
|
||||
buildType: 'debug',
|
||||
buildMethod: 'foo'
|
||||
};
|
||||
|
||||
const apkPath = 'my/apk/path/app.apk';
|
||||
buildSpy.findBestApkForArchitecture.and.returnValue(apkPath);
|
||||
|
||||
return emu.install(target, buildResults).then(() => {
|
||||
expect(buildSpy.findBestApkForArchitecture).toHaveBeenCalledWith(buildResults, target.arch);
|
||||
|
||||
const execCmd = execaSpy.calls.argsFor(0)[1].join(' ');
|
||||
expect(execCmd).toContain(`install -r ${apkPath}`);
|
||||
});
|
||||
});
|
||||
|
||||
it('should uninstall and reinstall app if failure is due to different certificates', () => {
|
||||
execaSpy.and.returnValues(
|
||||
...['Failure: INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES', '']
|
||||
.map(out => Promise.resolve({ stdout: out }))
|
||||
);
|
||||
|
||||
return emu.install(target, {}).then(() => {
|
||||
expect(execaSpy).toHaveBeenCalledTimes(2);
|
||||
expect(AdbSpy.uninstall).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw any error not caused by different certificates', () => {
|
||||
const errorMsg = 'Failure: Failed to install';
|
||||
execaSpy.and.resolveTo({ stdout: errorMsg });
|
||||
|
||||
return emu.install(target, {}).then(
|
||||
() => fail('Unexpectedly resolved'),
|
||||
err => {
|
||||
expect(err).toEqual(jasmine.any(CordovaError));
|
||||
expect(err.message).toContain(errorMsg);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should unlock the screen on device', () => {
|
||||
return emu.install(target, {}).then(() => {
|
||||
expect(AdbSpy.shell).toHaveBeenCalledWith(target.target, 'input keyevent 82');
|
||||
});
|
||||
});
|
||||
|
||||
it('should start the newly installed app on the device', () => {
|
||||
const packageId = 'unittestapp';
|
||||
const activityName = 'TestActivity';
|
||||
AndroidManifestFns.getPackageId.and.returnValue(packageId);
|
||||
AndroidManifestGetActivitySpy.getName.and.returnValue(activityName);
|
||||
|
||||
return emu.install(target, {}).then(() => {
|
||||
expect(AdbSpy.start).toHaveBeenCalledWith(target.target, `${packageId}/.${activityName}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,177 +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.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const rewire = require('rewire');
|
||||
const { CordovaError } = require('cordova-common');
|
||||
const utils = require('../../bin/templates/cordova/lib/utils');
|
||||
const glob = require('fast-glob');
|
||||
|
||||
describe('Java', () => {
|
||||
const Java = rewire('../../bin/templates/cordova/lib/env/java');
|
||||
|
||||
describe('getVersion', () => {
|
||||
let originalEnsureFunc = null;
|
||||
|
||||
beforeEach(() => {
|
||||
/*
|
||||
This is to avoid changing/polluting the
|
||||
process environment variables
|
||||
as a result of running these java tests; which could produce
|
||||
unexpected side effects to other tests.
|
||||
*/
|
||||
originalEnsureFunc = Java._ensure;
|
||||
spyOn(Java, '_ensure').and.callFake(() => {
|
||||
return originalEnsureFunc({});
|
||||
});
|
||||
});
|
||||
|
||||
it('runs', async () => {
|
||||
Java.__set__('execa', () => Promise.resolve({
|
||||
all: 'javac 1.8.0_275'
|
||||
}));
|
||||
|
||||
console.log('BEFORE', process.env.JAVA_HOME);
|
||||
const result = await Java.getVersion();
|
||||
console.log('AFTER', process.env.JAVA_HOME);
|
||||
expect(result.major).toBe(1);
|
||||
expect(result.minor).toBe(8);
|
||||
expect(result.patch).toBe(0);
|
||||
expect(result.version).toBe('1.8.0');
|
||||
});
|
||||
|
||||
it('produces a CordovaError on error', async () => {
|
||||
Java.__set__('execa', () => Promise.reject({
|
||||
shortMessage: 'test error'
|
||||
}));
|
||||
const emitSpy = jasmine.createSpy('events.emit');
|
||||
Java.__set__('events', {
|
||||
emit: emitSpy
|
||||
});
|
||||
|
||||
await expectAsync(Java.getVersion())
|
||||
.toBeRejectedWithError(CordovaError, /Failed to run "javac -version"/);
|
||||
expect(emitSpy).toHaveBeenCalledWith('verbose', 'test error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('_ensure', () => {
|
||||
beforeEach(() => {
|
||||
Java.__set__('javaIsEnsured', false);
|
||||
});
|
||||
|
||||
it('with JAVA_HOME / without javac', async () => {
|
||||
spyOn(utils, 'forgivingWhichSync').and.returnValue('');
|
||||
|
||||
const env = {
|
||||
JAVA_HOME: '/tmp/jdk'
|
||||
};
|
||||
|
||||
await Java._ensure(env);
|
||||
|
||||
expect(env.PATH.split(path.delimiter))
|
||||
.toContain(path.join(env.JAVA_HOME, 'bin'));
|
||||
});
|
||||
|
||||
it('detects JDK in default location on windows', async () => {
|
||||
spyOn(utils, 'forgivingWhichSync').and.returnValue('');
|
||||
spyOn(utils, 'isWindows').and.returnValue(true);
|
||||
|
||||
const root = 'C:\\Program Files';
|
||||
const env = {
|
||||
ProgramFiles: root
|
||||
};
|
||||
|
||||
spyOn(glob, 'sync').and.returnValue(`${root}\\java\\jdk1.8.0_275`);
|
||||
|
||||
const jdkDir = `${root}\\java\\jdk1.8.0_275`;
|
||||
|
||||
await Java._ensure(env);
|
||||
|
||||
expect(env.JAVA_HOME).withContext('JAVA_HOME').toBe(jdkDir);
|
||||
expect(env.PATH).toContain(jdkDir);
|
||||
});
|
||||
|
||||
it('detects JDK in default location on windows (x86)', async () => {
|
||||
spyOn(utils, 'forgivingWhichSync').and.returnValue('');
|
||||
spyOn(utils, 'isWindows').and.returnValue(true);
|
||||
|
||||
const root = 'C:\\Program Files (x86)';
|
||||
const env = {
|
||||
'ProgramFiles(x86)': root
|
||||
};
|
||||
|
||||
spyOn(glob, 'sync').and.returnValue(`${root}\\java\\jdk1.8.0_275`);
|
||||
|
||||
const jdkDir = `${root}\\java\\jdk1.8.0_275`;
|
||||
|
||||
await Java._ensure(env);
|
||||
|
||||
expect(env.JAVA_HOME).withContext('JAVA_HOME').toBe(jdkDir);
|
||||
expect(env.PATH).toContain(jdkDir);
|
||||
});
|
||||
|
||||
it('without JAVA_HOME / with javac - Mac OS X - success', async () => {
|
||||
spyOn(utils, 'forgivingWhichSync').and.returnValue('/tmp/jdk/bin');
|
||||
const fsSpy = jasmine.createSpy('fs').and.returnValue(true);
|
||||
Java.__set__('fs', {
|
||||
existsSync: fsSpy
|
||||
});
|
||||
Java.__set__('execa', async () => ({ stdout: '/tmp/jdk' }));
|
||||
|
||||
const env = {};
|
||||
|
||||
await Java._ensure(env);
|
||||
|
||||
expect(fsSpy).toHaveBeenCalledWith('/usr/libexec/java_home');
|
||||
expect(env.JAVA_HOME).toBe('/tmp/jdk');
|
||||
});
|
||||
|
||||
it('without JAVA_HOME / with javac - Mac OS X - error', async () => {
|
||||
spyOn(utils, 'forgivingWhichSync').and.returnValue('/tmp/jdk/bin');
|
||||
Java.__set__('fs', { existsSync: () => true });
|
||||
Java.__set__('execa', jasmine.createSpy('execa').and.returnValue(Promise.reject({
|
||||
shortMessage: 'test error'
|
||||
})));
|
||||
const emitSpy = jasmine.createSpy('events.emit');
|
||||
Java.__set__('events', {
|
||||
emit: emitSpy
|
||||
});
|
||||
|
||||
await expectAsync(Java._ensure({}))
|
||||
.toBeRejectedWithError(CordovaError, /Failed to find 'JAVA_HOME' environment variable/);
|
||||
|
||||
expect(emitSpy).toHaveBeenCalledWith('verbose', 'test error');
|
||||
});
|
||||
|
||||
it('derive from javac location - success', async () => {
|
||||
spyOn(utils, 'forgivingWhichSync').and.returnValue('/tmp/jdk/bin');
|
||||
Java.__set__('fs', { existsSync: path => !/java_home$/.test(path) });
|
||||
const env = {};
|
||||
await Java._ensure(env);
|
||||
expect(env.JAVA_HOME).toBe('/tmp');
|
||||
});
|
||||
|
||||
it('derive from javac location - error', async () => {
|
||||
spyOn(utils, 'forgivingWhichSync').and.returnValue('/tmp/jdk/bin');
|
||||
Java.__set__('fs', { existsSync: () => false });
|
||||
await expectAsync(Java._ensure({})).toBeRejectedWithError(CordovaError, /Failed to find 'JAVA_HOME' environment variable/);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -21,7 +21,6 @@ var common = rewire('../../../bin/templates/cordova/lib/pluginHandlers');
|
||||
var path = require('path');
|
||||
var fs = require('fs-extra');
|
||||
var osenv = require('os');
|
||||
|
||||
var test_dir = path.join(osenv.tmpdir(), 'test_plugman');
|
||||
var project_dir = path.join(test_dir, 'project');
|
||||
var src = path.join(project_dir, 'src');
|
||||
@@ -36,27 +35,25 @@ var deleteJava = common.__get__('deleteJava');
|
||||
var copyNewFile = common.__get__('copyNewFile');
|
||||
|
||||
describe('common platform handler', function () {
|
||||
afterEach(() => {
|
||||
fs.removeSync(test_dir);
|
||||
fs.removeSync(non_plugin_file);
|
||||
});
|
||||
|
||||
describe('copyFile', function () {
|
||||
it('Test#001 : should throw if source path not found', function () {
|
||||
fs.removeSync(src);
|
||||
expect(function () { copyFile(test_dir, src, project_dir, dest); })
|
||||
.toThrow(new Error('"' + src + '" not found!'));
|
||||
});
|
||||
|
||||
it('Test#002 : should throw if src not in plugin directory', function () {
|
||||
fs.ensureDirSync(project_dir);
|
||||
fs.outputFileSync(non_plugin_file, 'contents');
|
||||
fs.writeFileSync(non_plugin_file, 'contents', 'utf-8');
|
||||
var outside_file = '../non_plugin_file';
|
||||
expect(function () { copyFile(test_dir, outside_file, project_dir, dest); })
|
||||
.toThrow(new Error('File "' + path.resolve(test_dir, outside_file) + '" is located outside the plugin directory "' + test_dir + '"'));
|
||||
fs.removeSync(test_dir);
|
||||
});
|
||||
|
||||
it('Test#003 : should allow symlink src, if inside plugin', function () {
|
||||
fs.outputFileSync(java_file, 'contents');
|
||||
fs.ensureDirSync(java_dir);
|
||||
fs.writeFileSync(java_file, 'contents', 'utf-8');
|
||||
|
||||
// This will fail on windows if not admin - ignore the error in that case.
|
||||
if (ignoreEPERMonWin32(java_file, symlink_file)) {
|
||||
@@ -64,11 +61,12 @@ describe('common platform handler', function () {
|
||||
}
|
||||
|
||||
copyFile(test_dir, symlink_file, project_dir, dest);
|
||||
fs.removeSync(project_dir);
|
||||
});
|
||||
|
||||
it('Test#004 : should throw if symlink is linked to a file outside the plugin', function () {
|
||||
fs.ensureDirSync(java_dir);
|
||||
fs.outputFileSync(non_plugin_file, 'contents');
|
||||
fs.writeFileSync(non_plugin_file, 'contents', 'utf-8');
|
||||
|
||||
// This will fail on windows if not admin - ignore the error in that case.
|
||||
if (ignoreEPERMonWin32(non_plugin_file, symlink_file)) {
|
||||
@@ -77,16 +75,20 @@ describe('common platform handler', function () {
|
||||
|
||||
expect(function () { copyFile(test_dir, symlink_file, project_dir, dest); })
|
||||
.toThrow(new Error('File "' + path.resolve(test_dir, symlink_file) + '" is located outside the plugin directory "' + test_dir + '"'));
|
||||
fs.removeSync(project_dir);
|
||||
});
|
||||
|
||||
it('Test#005 : should throw if dest is outside the project directory', function () {
|
||||
fs.outputFileSync(java_file, 'contents');
|
||||
fs.ensureDirSync(java_dir);
|
||||
fs.writeFileSync(java_file, 'contents', 'utf-8');
|
||||
expect(function () { copyFile(test_dir, java_file, project_dir, non_plugin_file); })
|
||||
.toThrow(new Error('Destination "' + path.resolve(project_dir, non_plugin_file) + '" for source file "' + path.resolve(test_dir, java_file) + '" is located outside the project'));
|
||||
fs.removeSync(project_dir);
|
||||
});
|
||||
|
||||
it('Test#006 : should call mkdir -p on target path', function () {
|
||||
fs.outputFileSync(java_file, 'contents');
|
||||
fs.ensureDirSync(java_dir);
|
||||
fs.writeFileSync(java_file, 'contents', 'utf-8');
|
||||
|
||||
var s = spyOn(fs, 'ensureDirSync').and.callThrough();
|
||||
var resolvedDest = path.resolve(project_dir, dest);
|
||||
@@ -95,10 +97,12 @@ describe('common platform handler', function () {
|
||||
|
||||
expect(s).toHaveBeenCalled();
|
||||
expect(s).toHaveBeenCalledWith(path.dirname(resolvedDest));
|
||||
fs.removeSync(project_dir);
|
||||
});
|
||||
|
||||
it('Test#007 : should call cp source/dest paths', function () {
|
||||
fs.outputFileSync(java_file, 'contents');
|
||||
fs.ensureDirSync(java_dir);
|
||||
fs.writeFileSync(java_file, 'contents', 'utf-8');
|
||||
|
||||
var s = spyOn(fs, 'copySync').and.callThrough();
|
||||
var resolvedDest = path.resolve(project_dir, dest);
|
||||
@@ -107,15 +111,8 @@ describe('common platform handler', function () {
|
||||
|
||||
expect(s).toHaveBeenCalled();
|
||||
expect(s).toHaveBeenCalledWith(java_file, resolvedDest);
|
||||
});
|
||||
|
||||
it('should handle relative paths when checking for sub paths', () => {
|
||||
fs.outputFileSync(java_file, 'contents');
|
||||
const relativeProjectPath = path.relative(process.cwd(), project_dir);
|
||||
|
||||
expect(() => {
|
||||
copyFile(test_dir, java_file, relativeProjectPath, dest);
|
||||
}).not.toThrow();
|
||||
fs.removeSync(project_dir);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -124,12 +121,18 @@ describe('common platform handler', function () {
|
||||
fs.ensureDirSync(dest);
|
||||
expect(function () { copyNewFile(test_dir, src, project_dir, dest); })
|
||||
.toThrow(new Error('"' + dest + '" already exists!'));
|
||||
fs.removeSync(dest);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteJava', function () {
|
||||
beforeEach(function () {
|
||||
fs.outputFileSync(java_file, 'contents');
|
||||
fs.ensureDirSync(java_dir);
|
||||
fs.writeFileSync(java_file, 'contents', 'utf-8');
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
fs.removeSync(java_dir);
|
||||
});
|
||||
|
||||
it('Test#009 : should call fs.unlinkSync on the provided paths', function () {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,66 +18,195 @@
|
||||
*/
|
||||
|
||||
const rewire = require('rewire');
|
||||
const builders = require('../../bin/templates/cordova/lib/builders/builders');
|
||||
|
||||
describe('run', () => {
|
||||
let run;
|
||||
|
||||
beforeEach(() => {
|
||||
run = rewire('../../bin/templates/cordova/lib/run');
|
||||
run.__set__({
|
||||
events: jasmine.createSpyObj('eventsSpy', ['emit'])
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildTargetSpec', () => {
|
||||
it('Test#001 : should select correct target based on the run opts', () => {
|
||||
const buildTargetSpec = run.__get__('buildTargetSpec');
|
||||
describe('getInstallTarget', () => {
|
||||
const targetOpts = { target: 'emu' };
|
||||
const deviceOpts = { device: true };
|
||||
const emulatorOpts = { emulator: true };
|
||||
const emptyOpts = {};
|
||||
|
||||
expect(buildTargetSpec({ target: 'emu' })).toEqual({ id: 'emu' });
|
||||
expect(buildTargetSpec({ device: true })).toEqual({ type: 'device' });
|
||||
expect(buildTargetSpec({ emulator: true })).toEqual({ type: 'emulator' });
|
||||
expect(buildTargetSpec({})).toEqual({});
|
||||
it('Test#001 : should select correct target based on the run opts', () => {
|
||||
const getInstallTarget = run.__get__('getInstallTarget');
|
||||
expect(getInstallTarget(targetOpts)).toBe('emu');
|
||||
expect(getInstallTarget(deviceOpts)).toBe('--device');
|
||||
expect(getInstallTarget(emulatorOpts)).toBe('--emulator');
|
||||
expect(getInstallTarget(emptyOpts)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('run method', () => {
|
||||
let targetSpyObj, emulatorSpyObj, resolvedTarget;
|
||||
let deviceSpyObj;
|
||||
let emulatorSpyObj;
|
||||
let eventsSpyObj;
|
||||
let getInstallTargetSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
resolvedTarget = { id: 'dev1', type: 'device', arch: 'atari' };
|
||||
|
||||
targetSpyObj = jasmine.createSpyObj('deviceSpy', ['resolve', 'install']);
|
||||
targetSpyObj.resolve.and.resolveTo(resolvedTarget);
|
||||
targetSpyObj.install.and.resolveTo();
|
||||
|
||||
emulatorSpyObj = jasmine.createSpyObj('emulatorSpy', ['wait_for_boot']);
|
||||
emulatorSpyObj.wait_for_boot.and.resolveTo();
|
||||
deviceSpyObj = jasmine.createSpyObj('deviceSpy', ['install', 'list', 'resolveTarget']);
|
||||
emulatorSpyObj = jasmine.createSpyObj('emulatorSpy', ['install', 'list_images', 'list_started', 'resolveTarget', 'start', 'wait_for_boot']);
|
||||
eventsSpyObj = jasmine.createSpyObj('eventsSpy', ['emit']);
|
||||
getInstallTargetSpy = jasmine.createSpy('getInstallTargetSpy');
|
||||
|
||||
run.__set__({
|
||||
target: targetSpyObj,
|
||||
emulator: emulatorSpyObj
|
||||
});
|
||||
|
||||
// run needs `this` to behave like an Api instance
|
||||
run.run = run.run.bind({
|
||||
_builder: builders.getBuilder('FakeRootPath')
|
||||
device: deviceSpyObj,
|
||||
emulator: emulatorSpyObj,
|
||||
events: eventsSpyObj,
|
||||
getInstallTarget: getInstallTargetSpy
|
||||
});
|
||||
});
|
||||
|
||||
it('should install on target after build', () => {
|
||||
it('should run on default device when no target arguments are specified', () => {
|
||||
const deviceList = ['testDevice1', 'testDevice2'];
|
||||
|
||||
getInstallTargetSpy.and.returnValue(null);
|
||||
deviceSpyObj.list.and.returnValue(Promise.resolve(deviceList));
|
||||
|
||||
return run.run().then(() => {
|
||||
expect(targetSpyObj.install).toHaveBeenCalledWith(
|
||||
resolvedTarget,
|
||||
{ apkPaths: [], buildType: 'debug' }
|
||||
);
|
||||
expect(deviceSpyObj.resolveTarget).toHaveBeenCalledWith(deviceList[0]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should run on emulator when no target arguments are specified, and no devices are found', () => {
|
||||
const deviceList = [];
|
||||
|
||||
getInstallTargetSpy.and.returnValue(null);
|
||||
deviceSpyObj.list.and.returnValue(Promise.resolve(deviceList));
|
||||
emulatorSpyObj.list_started.and.returnValue(Promise.resolve([]));
|
||||
|
||||
return run.run().then(() => {
|
||||
expect(emulatorSpyObj.list_started).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should run on default device when device is requested, but none specified', () => {
|
||||
getInstallTargetSpy.and.returnValue('--device');
|
||||
|
||||
return run.run().then(() => {
|
||||
// Default device is selected by calling device.resolveTarget(null)
|
||||
expect(deviceSpyObj.resolveTarget).toHaveBeenCalledWith(null);
|
||||
});
|
||||
});
|
||||
|
||||
it('should run on a running emulator if one exists', () => {
|
||||
const emulatorList = ['emulator1', 'emulator2'];
|
||||
|
||||
getInstallTargetSpy.and.returnValue('--emulator');
|
||||
emulatorSpyObj.list_started.and.returnValue(Promise.resolve(emulatorList));
|
||||
|
||||
return run.run().then(() => {
|
||||
expect(emulatorSpyObj.resolveTarget).toHaveBeenCalledWith(emulatorList[0]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should start an emulator and run on that if none is running', () => {
|
||||
const emulatorList = [];
|
||||
const defaultEmulator = 'default-emu';
|
||||
|
||||
getInstallTargetSpy.and.returnValue('--emulator');
|
||||
emulatorSpyObj.list_started.and.returnValue(Promise.resolve(emulatorList));
|
||||
emulatorSpyObj.start.and.returnValue(Promise.resolve(defaultEmulator));
|
||||
|
||||
return run.run().then(() => {
|
||||
expect(emulatorSpyObj.resolveTarget).toHaveBeenCalledWith(defaultEmulator);
|
||||
});
|
||||
});
|
||||
|
||||
it('should run on a named device if it is specified', () => {
|
||||
const deviceList = ['device1', 'device2', 'device3'];
|
||||
getInstallTargetSpy.and.returnValue(deviceList[1]);
|
||||
|
||||
deviceSpyObj.list.and.returnValue(Promise.resolve(deviceList));
|
||||
|
||||
return run.run().then(() => {
|
||||
expect(deviceSpyObj.resolveTarget).toHaveBeenCalledWith(deviceList[1]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should run on a named emulator if it is specified', () => {
|
||||
const startedEmulatorList = ['emu1', 'emu2', 'emu3'];
|
||||
getInstallTargetSpy.and.returnValue(startedEmulatorList[2]);
|
||||
|
||||
deviceSpyObj.list.and.returnValue(Promise.resolve([]));
|
||||
emulatorSpyObj.list_started.and.returnValue(Promise.resolve(startedEmulatorList));
|
||||
|
||||
return run.run().then(() => {
|
||||
expect(emulatorSpyObj.resolveTarget).toHaveBeenCalledWith(startedEmulatorList[2]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should start named emulator and then run on it if it is specified', () => {
|
||||
const emulatorList = [
|
||||
{ name: 'emu1', id: 1 },
|
||||
{ name: 'emu2', id: 2 },
|
||||
{ name: 'emu3', id: 3 }
|
||||
];
|
||||
getInstallTargetSpy.and.returnValue(emulatorList[2].name);
|
||||
|
||||
deviceSpyObj.list.and.returnValue(Promise.resolve([]));
|
||||
emulatorSpyObj.list_started.and.returnValue(Promise.resolve([]));
|
||||
emulatorSpyObj.list_images.and.returnValue(Promise.resolve(emulatorList));
|
||||
emulatorSpyObj.start.and.returnValue(Promise.resolve(emulatorList[2].id));
|
||||
|
||||
return run.run().then(() => {
|
||||
expect(emulatorSpyObj.start).toHaveBeenCalledWith(emulatorList[2].name);
|
||||
expect(emulatorSpyObj.resolveTarget).toHaveBeenCalledWith(emulatorList[2].id);
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if target is specified but does not exist', () => {
|
||||
const emulatorList = [{ name: 'emu1', id: 1 }];
|
||||
const deviceList = ['device1'];
|
||||
const target = 'nonexistentdevice';
|
||||
getInstallTargetSpy.and.returnValue(target);
|
||||
|
||||
deviceSpyObj.list.and.returnValue(Promise.resolve(deviceList));
|
||||
emulatorSpyObj.list_started.and.returnValue(Promise.resolve([]));
|
||||
emulatorSpyObj.list_images.and.returnValue(Promise.resolve(emulatorList));
|
||||
|
||||
return run.run().then(
|
||||
() => fail('Expected error to be thrown'),
|
||||
err => expect(err.message).toContain(target)
|
||||
);
|
||||
});
|
||||
|
||||
it('should install on device after build', () => {
|
||||
const deviceTarget = { target: 'device1', isEmulator: false };
|
||||
getInstallTargetSpy.and.returnValue('--device');
|
||||
deviceSpyObj.resolveTarget.and.returnValue(deviceTarget);
|
||||
|
||||
return run.run().then(() => {
|
||||
expect(deviceSpyObj.install).toHaveBeenCalledWith(deviceTarget, { apkPaths: [], buildType: 'debug' });
|
||||
});
|
||||
});
|
||||
|
||||
it('should install on emulator after build', () => {
|
||||
const emulatorTarget = { target: 'emu1', isEmulator: true };
|
||||
getInstallTargetSpy.and.returnValue('--emulator');
|
||||
emulatorSpyObj.list_started.and.returnValue(Promise.resolve([emulatorTarget.target]));
|
||||
emulatorSpyObj.resolveTarget.and.returnValue(emulatorTarget);
|
||||
emulatorSpyObj.wait_for_boot.and.returnValue(Promise.resolve());
|
||||
|
||||
return run.run().then(() => {
|
||||
expect(emulatorSpyObj.install).toHaveBeenCalledWith(emulatorTarget, { apkPaths: [], buildType: 'debug' });
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail with the error message if --packageType=bundle setting is used', () => {
|
||||
targetSpyObj.resolve.and.resolveTo(resolvedTarget);
|
||||
return expectAsync(run.run({ argv: ['--packageType=bundle'] }))
|
||||
.toBeRejectedWith(jasmine.stringMatching(/Package type "bundle" is not supported/));
|
||||
const deviceList = ['testDevice1', 'testDevice2'];
|
||||
getInstallTargetSpy.and.returnValue(null);
|
||||
|
||||
deviceSpyObj.list.and.returnValue(Promise.resolve(deviceList));
|
||||
|
||||
return run.run({ argv: ['--packageType=bundle'] }).then(
|
||||
() => fail('Expected error to be thrown'),
|
||||
err => expect(err).toContain('Package type "bundle" is not supported during cordova run.')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,313 +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.
|
||||
*/
|
||||
|
||||
const rewire = require('rewire');
|
||||
const { CordovaError } = require('cordova-common');
|
||||
|
||||
describe('target', () => {
|
||||
let target;
|
||||
|
||||
beforeEach(() => {
|
||||
target = rewire('../../bin/templates/cordova/lib/target');
|
||||
});
|
||||
|
||||
describe('list', () => {
|
||||
it('should return available targets from Adb.devices', () => {
|
||||
const AdbSpy = jasmine.createSpyObj('Adb', ['devices']);
|
||||
AdbSpy.devices.and.resolveTo(['emulator-5556', '123a76565509e124']);
|
||||
target.__set__('Adb', AdbSpy);
|
||||
|
||||
return target.list().then(emus => {
|
||||
expect(emus).toEqual([
|
||||
{ id: 'emulator-5556', type: 'emulator' },
|
||||
{ id: '123a76565509e124', type: 'device' }
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveToOnlineTarget', () => {
|
||||
let resolveToOnlineTarget, emus, devs;
|
||||
|
||||
beforeEach(() => {
|
||||
resolveToOnlineTarget = target.__get__('resolveToOnlineTarget');
|
||||
|
||||
emus = [
|
||||
{ id: 'emu1', type: 'emulator' },
|
||||
{ id: 'emu2', type: 'emulator' }
|
||||
];
|
||||
devs = [
|
||||
{ id: 'dev1', type: 'device' },
|
||||
{ id: 'dev2', type: 'device' }
|
||||
];
|
||||
|
||||
spyOn(target, 'list').and.returnValue([...emus, ...devs]);
|
||||
});
|
||||
|
||||
it('should return first device when no target arguments are specified', async () => {
|
||||
return resolveToOnlineTarget().then(result => {
|
||||
expect(result.id).toBe('dev1');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return first emulator when no target arguments are specified and no devices are found', async () => {
|
||||
target.list.and.resolveTo(emus);
|
||||
return resolveToOnlineTarget().then(result => {
|
||||
expect(result.id).toBe('emu1');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return first device when type device is specified', async () => {
|
||||
return resolveToOnlineTarget({ type: 'device' }).then(result => {
|
||||
expect(result.id).toBe('dev1');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return first running emulator when type emulator is specified', async () => {
|
||||
return resolveToOnlineTarget({ type: 'emulator' }).then(result => {
|
||||
expect(result.id).toBe('emu1');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a device that matches given ID', async () => {
|
||||
return resolveToOnlineTarget({ id: 'dev2' }).then(result => {
|
||||
expect(result.id).toBe('dev2');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a running emulator that matches given ID', async () => {
|
||||
return resolveToOnlineTarget({ id: 'emu2' }).then(result => {
|
||||
expect(result.id).toBe('emu2');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return null if there are no online targets', async () => {
|
||||
target.list.and.resolveTo([]);
|
||||
return expectAsync(resolveToOnlineTarget())
|
||||
.toBeResolvedTo(null);
|
||||
});
|
||||
|
||||
it('should return null if no target matches given ID', async () => {
|
||||
return expectAsync(resolveToOnlineTarget({ id: 'foo' }))
|
||||
.toBeResolvedTo(null);
|
||||
});
|
||||
|
||||
it('should return null if no target matches given type', async () => {
|
||||
target.list.and.resolveTo(devs);
|
||||
return expectAsync(resolveToOnlineTarget({ type: 'emulator' }))
|
||||
.toBeResolvedTo(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveToOfflineEmulator', () => {
|
||||
const emuId = 'emulator-5554';
|
||||
let resolveToOfflineEmulator, emulatorSpyObj;
|
||||
|
||||
beforeEach(() => {
|
||||
resolveToOfflineEmulator = target.__get__('resolveToOfflineEmulator');
|
||||
|
||||
emulatorSpyObj = jasmine.createSpyObj('emulatorSpy', ['start']);
|
||||
emulatorSpyObj.start.and.resolveTo(emuId);
|
||||
|
||||
target.__set__({
|
||||
emulator: emulatorSpyObj,
|
||||
isEmulatorName: name => name.startsWith('emu')
|
||||
});
|
||||
});
|
||||
|
||||
it('should start an emulator and run on that if none is running', () => {
|
||||
return resolveToOfflineEmulator().then(result => {
|
||||
expect(result).toEqual({ id: emuId, type: 'emulator' });
|
||||
expect(emulatorSpyObj.start).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should start named emulator and then run on it if it is specified', () => {
|
||||
return resolveToOfflineEmulator({ id: 'emu3' }).then(result => {
|
||||
expect(result).toEqual({ id: emuId, type: 'emulator' });
|
||||
expect(emulatorSpyObj.start).toHaveBeenCalledWith('emu3');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return null if given ID is not an avd name', () => {
|
||||
return resolveToOfflineEmulator({ id: 'dev1' }).then(result => {
|
||||
expect(result).toBe(null);
|
||||
expect(emulatorSpyObj.start).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return null if given type is not emulator', () => {
|
||||
return resolveToOfflineEmulator({ type: 'device' }).then(result => {
|
||||
expect(result).toBe(null);
|
||||
expect(emulatorSpyObj.start).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolve', () => {
|
||||
let resolveToOnlineTarget, resolveToOfflineEmulator;
|
||||
|
||||
beforeEach(() => {
|
||||
resolveToOnlineTarget = jasmine.createSpy('resolveToOnlineTarget')
|
||||
.and.resolveTo(null);
|
||||
|
||||
resolveToOfflineEmulator = jasmine.createSpy('resolveToOfflineEmulator')
|
||||
.and.resolveTo(null);
|
||||
|
||||
target.__set__({
|
||||
resolveToOnlineTarget,
|
||||
resolveToOfflineEmulator,
|
||||
build: { detectArchitecture: id => id + '-arch' }
|
||||
});
|
||||
});
|
||||
|
||||
it('should delegate to resolveToOnlineTarget', () => {
|
||||
const spec = { type: 'device' };
|
||||
resolveToOnlineTarget.and.resolveTo({ id: 'dev1', type: 'device' });
|
||||
|
||||
return target.resolve(spec).then(result => {
|
||||
expect(result.id).toBe('dev1');
|
||||
expect(resolveToOnlineTarget).toHaveBeenCalledWith(spec);
|
||||
expect(resolveToOfflineEmulator).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should delegate to resolveToOfflineEmulator if resolveToOnlineTarget fails', () => {
|
||||
const spec = { type: 'emulator' };
|
||||
resolveToOfflineEmulator.and.resolveTo({ id: 'emu1', type: 'emulator' });
|
||||
|
||||
return target.resolve(spec).then(result => {
|
||||
expect(result.id).toBe('emu1');
|
||||
expect(resolveToOnlineTarget).toHaveBeenCalledWith(spec);
|
||||
expect(resolveToOfflineEmulator).toHaveBeenCalledWith(spec);
|
||||
});
|
||||
});
|
||||
|
||||
it('should add the target arch', () => {
|
||||
const spec = { type: 'device' };
|
||||
resolveToOnlineTarget.and.resolveTo({ id: 'dev1', type: 'device' });
|
||||
|
||||
return target.resolve(spec).then(result => {
|
||||
expect(result.arch).toBe('dev1-arch');
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if target cannot be resolved', () => {
|
||||
return expectAsync(target.resolve())
|
||||
.toBeRejectedWithError(/Could not find target matching/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('install', () => {
|
||||
let AndroidManifestSpy;
|
||||
let AndroidManifestFns;
|
||||
let AndroidManifestGetActivitySpy;
|
||||
let AdbSpy;
|
||||
let buildSpy;
|
||||
let installTarget;
|
||||
|
||||
beforeEach(() => {
|
||||
installTarget = { id: 'emulator-5556', type: 'emulator', arch: 'atari' };
|
||||
|
||||
buildSpy = jasmine.createSpyObj('build', ['findBestApkForArchitecture']);
|
||||
target.__set__('build', buildSpy);
|
||||
|
||||
AndroidManifestFns = jasmine.createSpyObj('AndroidManifestFns', ['getPackageId', 'getActivity']);
|
||||
AndroidManifestGetActivitySpy = jasmine.createSpyObj('getActivity', ['getName']);
|
||||
AndroidManifestFns.getActivity.and.returnValue(AndroidManifestGetActivitySpy);
|
||||
AndroidManifestSpy = jasmine.createSpy('AndroidManifest').and.returnValue(AndroidManifestFns);
|
||||
target.__set__('AndroidManifest', AndroidManifestSpy);
|
||||
|
||||
AdbSpy = jasmine.createSpyObj('Adb', ['shell', 'start', 'install', 'uninstall']);
|
||||
AdbSpy.shell.and.returnValue(Promise.resolve());
|
||||
AdbSpy.start.and.returnValue(Promise.resolve());
|
||||
AdbSpy.install.and.returnValue(Promise.resolve());
|
||||
AdbSpy.uninstall.and.returnValue(Promise.resolve());
|
||||
target.__set__('Adb', AdbSpy);
|
||||
|
||||
// Silence output during test
|
||||
spyOn(target.__get__('events'), 'emit');
|
||||
});
|
||||
|
||||
it('should install to the passed target', () => {
|
||||
return target.install(installTarget, {}).then(() => {
|
||||
expect(AdbSpy.install.calls.argsFor(0)[0]).toBe(installTarget.id);
|
||||
});
|
||||
});
|
||||
|
||||
it('should install the correct apk based on the architecture and build results', () => {
|
||||
const buildResults = {
|
||||
apkPaths: 'path/to/apks',
|
||||
buildType: 'debug',
|
||||
buildMethod: 'foo'
|
||||
};
|
||||
|
||||
const apkPath = 'my/apk/path/app.apk';
|
||||
buildSpy.findBestApkForArchitecture.and.returnValue(apkPath);
|
||||
|
||||
return target.install(installTarget, buildResults).then(() => {
|
||||
expect(buildSpy.findBestApkForArchitecture).toHaveBeenCalledWith(buildResults, installTarget.arch);
|
||||
|
||||
expect(AdbSpy.install.calls.argsFor(0)[1]).toBe(apkPath);
|
||||
});
|
||||
});
|
||||
|
||||
it('should uninstall and reinstall app if failure is due to different certificates', () => {
|
||||
AdbSpy.install.and.returnValues(
|
||||
Promise.reject('Failure: INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES'),
|
||||
Promise.resolve()
|
||||
);
|
||||
|
||||
return target.install(installTarget, {}).then(() => {
|
||||
expect(AdbSpy.install).toHaveBeenCalledTimes(2);
|
||||
expect(AdbSpy.uninstall).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw any error not caused by different certificates', () => {
|
||||
const errorMsg = 'Failure: Failed to install';
|
||||
AdbSpy.install.and.rejectWith(new CordovaError(errorMsg));
|
||||
|
||||
return target.install(installTarget, {}).then(
|
||||
() => fail('Unexpectedly resolved'),
|
||||
err => {
|
||||
expect(err).toEqual(jasmine.any(CordovaError));
|
||||
expect(err.message).toContain(errorMsg);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should unlock the screen on device', () => {
|
||||
return target.install(installTarget, {}).then(() => {
|
||||
expect(AdbSpy.shell).toHaveBeenCalledWith(installTarget.id, 'input keyevent 82');
|
||||
});
|
||||
});
|
||||
|
||||
it('should start the newly installed app on the device', () => {
|
||||
const packageId = 'unittestapp';
|
||||
const activityName = 'TestActivity';
|
||||
AndroidManifestFns.getPackageId.and.returnValue(packageId);
|
||||
AndroidManifestGetActivitySpy.getName.and.returnValue(activityName);
|
||||
|
||||
return target.install(installTarget, {}).then(() => {
|
||||
expect(AdbSpy.start).toHaveBeenCalledWith(installTarget.id, `${packageId}/.${activityName}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -45,8 +45,6 @@ import static org.apache.cordova.unittests.R.id.cordovaWebView;
|
||||
public class BackButtonMultipageTest {
|
||||
|
||||
private static final String START_URL = "file:///android_asset/www/backbuttonmultipage/index.html";
|
||||
private static final String SAMPLE3_URL = "file:///android_asset/www/backbuttonmultipage/sample3.html";
|
||||
private static final String SAMPLE2_URL = "file:///android_asset/www/backbuttonmultipage/sample2.html";
|
||||
//I have no idea why we picked 100, but we did.
|
||||
private static final int WEBVIEW_ID = 100;
|
||||
private TestActivity mActivity;
|
||||
@@ -73,19 +71,19 @@ public class BackButtonMultipageTest {
|
||||
webInterface.sendJavascript("window.location = 'sample2.html';");
|
||||
}
|
||||
});
|
||||
assertPageSample(SAMPLE2_URL);
|
||||
assertEquals("file:///android_asset/www/backbuttonmultipage/sample2.html", mActivity.onPageFinishedUrl.take());
|
||||
mActivityRule.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
webInterface.sendJavascript("window.location = 'sample3.html';");
|
||||
}
|
||||
});
|
||||
assertPageSample(SAMPLE3_URL);
|
||||
assertEquals("file:///android_asset/www/backbuttonmultipage/sample3.html", mActivity.onPageFinishedUrl.take());
|
||||
mActivityRule.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
assertTrue(webInterface.backHistory());
|
||||
}
|
||||
});
|
||||
assertPageSample(SAMPLE2_URL);
|
||||
assertEquals("file:///android_asset/www/backbuttonmultipage/sample2.html", mActivity.onPageFinishedUrl.take());
|
||||
mActivityRule.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
assertTrue(webInterface.backHistory());
|
||||
@@ -106,22 +104,22 @@ public class BackButtonMultipageTest {
|
||||
|
||||
mActivityRule.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
webInterface.loadUrl(SAMPLE2_URL);
|
||||
webInterface.loadUrl("file:///android_asset/www/backbuttonmultipage/sample2.html");
|
||||
}
|
||||
});
|
||||
assertPageSample(SAMPLE2_URL);
|
||||
assertEquals("file:///android_asset/www/backbuttonmultipage/sample2.html", mActivity.onPageFinishedUrl.take());
|
||||
mActivityRule.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
webInterface.loadUrl(SAMPLE3_URL);
|
||||
webInterface.loadUrl("file:///android_asset/www/backbuttonmultipage/sample3.html");
|
||||
}
|
||||
});
|
||||
assertPageSample(SAMPLE3_URL);
|
||||
assertEquals("file:///android_asset/www/backbuttonmultipage/sample3.html", mActivity.onPageFinishedUrl.take());
|
||||
mActivityRule.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
assertTrue(webInterface.backHistory());
|
||||
}
|
||||
});
|
||||
assertPageSample(SAMPLE2_URL);
|
||||
assertEquals("file:///android_asset/www/backbuttonmultipage/sample2.html", mActivity.onPageFinishedUrl.take());
|
||||
mActivityRule.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
assertTrue(webInterface.backHistory());
|
||||
@@ -142,24 +140,19 @@ public class BackButtonMultipageTest {
|
||||
|
||||
mActivityRule.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
webInterface.loadUrl(SAMPLE2_URL);
|
||||
webInterface.loadUrl("file:///android_asset/www/backbuttonmultipage/sample2.html");
|
||||
}
|
||||
});
|
||||
assertPageSample(SAMPLE2_URL);
|
||||
assertEquals("file:///android_asset/www/backbuttonmultipage/sample2.html", mActivity.onPageFinishedUrl.take());
|
||||
mActivityRule.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
webInterface.loadUrl(SAMPLE3_URL);
|
||||
webInterface.loadUrl("file:///android_asset/www/backbuttonmultipage/sample3.html");
|
||||
}
|
||||
});
|
||||
assertPageSample(SAMPLE3_URL);
|
||||
assertEquals("file:///android_asset/www/backbuttonmultipage/sample3.html", mActivity.onPageFinishedUrl.take());
|
||||
onView(withId(WEBVIEW_ID)).perform(pressBack());
|
||||
assertPageSample(SAMPLE2_URL);
|
||||
assertEquals("file:///android_asset/www/backbuttonmultipage/sample2.html", mActivity.onPageFinishedUrl.take());
|
||||
onView(withId(WEBVIEW_ID)).perform(pressBack());
|
||||
assertEquals(START_URL, mActivity.onPageFinishedUrl.take());
|
||||
}
|
||||
|
||||
private void assertPageSample(String url) {
|
||||
assertEquals(url, mActivity.onPageFinishedUrl.take());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,8 +45,6 @@ import static org.apache.cordova.unittests.R.id.cordovaWebView;
|
||||
public class BackButtonMultipageTest {
|
||||
|
||||
private static final String START_URL = "file:///android_asset/www/backbuttonmultipage/index.html";
|
||||
private static final String SAMPLE3_URL = "file:///android_asset/www/backbuttonmultipage/sample3.html";
|
||||
private static final String SAMPLE2_URL = "file:///android_asset/www/backbuttonmultipage/sample2.html";
|
||||
//I have no idea why we picked 100, but we did.
|
||||
private static final int WEBVIEW_ID = 100;
|
||||
private TestActivity mActivity;
|
||||
@@ -73,19 +71,19 @@ public class BackButtonMultipageTest {
|
||||
webInterface.sendJavascript("window.location = 'sample2.html';");
|
||||
}
|
||||
});
|
||||
assertPageSample(SAMPLE2_URL);
|
||||
assertEquals("file:///android_asset/www/backbuttonmultipage/sample2.html", mActivity.onPageFinishedUrl.take());
|
||||
mActivityRule.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
webInterface.sendJavascript("window.location = 'sample3.html';");
|
||||
}
|
||||
});
|
||||
assertPageSample(SAMPLE3_URL);
|
||||
assertEquals("file:///android_asset/www/backbuttonmultipage/sample3.html", mActivity.onPageFinishedUrl.take());
|
||||
mActivityRule.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
assertTrue(webInterface.backHistory());
|
||||
}
|
||||
});
|
||||
assertPageSample(SAMPLE2_URL);
|
||||
assertEquals("file:///android_asset/www/backbuttonmultipage/sample2.html", mActivity.onPageFinishedUrl.take());
|
||||
mActivityRule.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
assertTrue(webInterface.backHistory());
|
||||
@@ -106,22 +104,22 @@ public class BackButtonMultipageTest {
|
||||
|
||||
mActivityRule.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
webInterface.loadUrl(SAMPLE2_URL);
|
||||
webInterface.loadUrl("file:///android_asset/www/backbuttonmultipage/sample2.html");
|
||||
}
|
||||
});
|
||||
assertPageSample(SAMPLE2_URL);
|
||||
assertEquals("file:///android_asset/www/backbuttonmultipage/sample2.html", mActivity.onPageFinishedUrl.take());
|
||||
mActivityRule.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
webInterface.loadUrl(SAMPLE3_URL);
|
||||
webInterface.loadUrl("file:///android_asset/www/backbuttonmultipage/sample3.html");
|
||||
}
|
||||
});
|
||||
assertPageSample(SAMPLE3_URL);
|
||||
assertEquals("file:///android_asset/www/backbuttonmultipage/sample3.html", mActivity.onPageFinishedUrl.take());
|
||||
mActivityRule.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
assertTrue(webInterface.backHistory());
|
||||
}
|
||||
});
|
||||
assertPageSample(SAMPLE2_URL);
|
||||
assertEquals("file:///android_asset/www/backbuttonmultipage/sample2.html", mActivity.onPageFinishedUrl.take());
|
||||
mActivityRule.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
assertTrue(webInterface.backHistory());
|
||||
@@ -142,23 +140,19 @@ public class BackButtonMultipageTest {
|
||||
|
||||
mActivityRule.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
webInterface.loadUrl(SAMPLE2_URL);
|
||||
webInterface.loadUrl("file:///android_asset/www/backbuttonmultipage/sample2.html");
|
||||
}
|
||||
});
|
||||
assertPageSample(SAMPLE2_URL);
|
||||
assertEquals("file:///android_asset/www/backbuttonmultipage/sample2.html", mActivity.onPageFinishedUrl.take());
|
||||
mActivityRule.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
webInterface.loadUrl(SAMPLE3_URL);
|
||||
webInterface.loadUrl("file:///android_asset/www/backbuttonmultipage/sample3.html");
|
||||
}
|
||||
});
|
||||
assertPageSample(SAMPLE3_URL);
|
||||
assertEquals("file:///android_asset/www/backbuttonmultipage/sample3.html", mActivity.onPageFinishedUrl.take());
|
||||
onView(withId(WEBVIEW_ID)).perform(pressBack());
|
||||
assertPageSample(SAMPLE2_URL);
|
||||
assertEquals("file:///android_asset/www/backbuttonmultipage/sample2.html", mActivity.onPageFinishedUrl.take());
|
||||
onView(withId(WEBVIEW_ID)).perform(pressBack());
|
||||
assertEquals(START_URL, mActivity.onPageFinishedUrl.take());
|
||||
}
|
||||
|
||||
private void assertPageSample(String url) {
|
||||
assertEquals(url, mActivity.onPageFinishedUrl.take());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user