Compare commits

...

39 Commits

Author SHA1 Message Date
Erisu
b104554877 release(android-v12.0.0): updated version and RELEASENOTES.md 2023-05-20 00:47:09 +09:00
エリス
7da13ccf77 feat: add listTarget api (#1602)
* feat: add listTarget api
* test: write Platform API target list specs
2023-05-08 22:27:17 +09:00
Philip Peitsch
cb48147398 feat: add plugin hooks for WebViewClient.onRenderProcessGone (#1574)
* feat: add plugin hooks for WebViewClient.onRenderProcessGone

* Update framework/src/org/apache/cordova/CordovaPlugin.java

Co-authored-by: Norman Breau <norman@nbsolutions.ca>

---------

Co-authored-by: Norman Breau <norman@nbsolutions.ca>
2023-04-23 23:20:48 -03:00
Norman Breau
6f6717afbd ci: Added NodeJS 20.x to the workflow matrix (#1607) 2023-04-22 17:01:24 -03:00
Norman Breau
3343c3bb34 fix: Gradle Args parsing (#1606)
* fix: Gradle Args parsing

* refactor: Applied ARGVParser.parseArgsStringToArgv -> parseArgsStringToArgv suggestion

* test: Added deeper testing for gradle argument parsing
2023-04-22 17:00:51 -03:00
Norman Breau
a62f699380 fix!: Make CoreAndroid plugin instantiate on load (#1605)
I don't anticipate breaking changes from this change, however it is a difference
in behaviour since CoreAndroid won't be lazily loaded, therefore I've marked this
commit has a breaking change.
2023-04-21 16:00:11 -03:00
喻志强
7efe90faac fix(BuildHelper): get package name from ApplicationInfo (#1575) 2023-04-14 10:15:55 -03:00
Norman Breau
5b546a27e6 deprecate: CoreAndroid.getBuildConfigValue (#1597) 2023-04-14 10:15:09 -03:00
Norman Breau
2252c09a49 feat: bump default kotlin to version 1.7.21 (#1594)
The rationale is based on the Gradle Compatibility Matrix: https://docs.gradle.org/current/userguide/compatibility.html

1.7.21 is the current latest version of the 1.7 Kotlin release.
2023-04-14 07:17:15 -03:00
Norman Breau
3a9c87d3b8 fix(test): Native test namespace refactor (#1595) 2023-04-14 18:47:03 +09:00
エリス
a9d4d4ebd2 feat!: bump Gradle 7.6 & AGP 7.4.2 (#1539)
* feat: bump gradle 7.6
* feat: bump android gradle plugin 7.3.1
* feat: bump android gradle plugin 7.4.2
* fix!: move android package name to build.gradle namespace
* fix!: remove deprecated package name from AndroidManifest
* fix: package name
* fix: rename CordovaGradleConfigParser's _save to write
* test: fix CordovaGradleConfigParser related specs
* fix: test refactoring for gradle namespace
* fix: accidental variable naming mixing

---------

Co-authored-by: Norman Breau <norman@nbsolutions.ca>
2023-04-12 14:39:47 +09:00
Alexis THOMAS
841710edf7 fix: ANDROID_HOME is the new default, to check first and give advice (#1471) 2023-04-09 20:43:11 -03:00
Mahendra Liya
016018513e feat: add monochrome app icon support (#1550)
* Added the monochrome version for Cordova's icon

* android: modified the ic_launcher.xml to include the monochrome drawable

* android: replaced monochrome image with  rasterized images

* android: Added support for custom monochrome icons defined in config.xml

* android: Updated Tests

* android: wrapped inside if(monochrome)

* android: Update templates/project/res/mipmap-hdpi-v26/ic_launcher.xml

Co-authored-by: エリス <erisu@users.noreply.github.com>

* Update lib/prepare.js

Co-authored-by: エリス <erisu@users.noreply.github.com>

* android: Update templates/project/res/mipmap-ldpi-v26/ic_launcher.xml

Co-authored-by: エリス <erisu@users.noreply.github.com>

* android: Update templates/project/res/mipmap-mdpi-v26/ic_launcher.xml

Co-authored-by: エリス <erisu@users.noreply.github.com>

* android Update templates/project/res/mipmap-xhdpi-v26/ic_launcher.xml

Co-authored-by: エリス <erisu@users.noreply.github.com>

* android: Update templates/project/res/mipmap-xxhdpi-v26/ic_launcher.xml

Co-authored-by: エリス <erisu@users.noreply.github.com>

* android: Update templates/project/res/mipmap-xxxhdpi-v26/ic_launcher.xml

Co-authored-by: エリス <erisu@users.noreply.github.com>

* android: Update lib/prepare.js

Co-authored-by: エリス <erisu@users.noreply.github.com>

* Resolved lint errors

* fix: test failure

---------

Co-authored-by: エリス <erisu@users.noreply.github.com>
2023-04-09 20:41:38 -03:00
Norman Breau
a78fad1783 feat: InspectableWebview preference (#1589) 2023-04-09 20:41:00 -03:00
Norman Breau
b91639dbb5 refactor: Removed obsolete version code checks (#1588)
Now that our Min SDK is 24, testing for >= N (API 24) and >= M (API 22) is obsolete as it will always be true.
Simplify the codebase by removing the conditions and keeping only the API 24 or later codepath.
2023-04-08 15:32:19 -03:00
エリス
c2013439bc dep: bump npm dependencies (#1587)
* dep: bump npm dependencies
  - fs-extra@11.1.1
  - nopt@7.1.0
  - @cordova/eslint-config@5.0.0
  - jasmine@4.6.0
* chore: apply eslint fix
2023-04-06 12:35:00 +09:00
Norman Breau
d4bfd5079b fix(GH-1432): Default content src when content tag is missing (#1573) 2023-03-21 13:24:13 -03:00
Norman Breau
dbddbf253b feat!: Bump min SDK to 24 (#1571)
Rationale:
API 22 & API 23 both account for an insignificant part of the market share.
While API 24 - API 26 has similar market share, we felt that bumping to API 26
from API 22 is too large of a jump.

Legacy devices may be completely out of support by Google and may not be able
to receive the latest webview version. As of writing, Chromium's latest
tag shows they are using a Min SDK version of 24. (Ref: https://chromium.googlesource.com/chromium/src/+/refs/tags/113.0.5653.1/build/config/android/config.gni#46)

Based on AOSP emulators, API 24 (Android 7.0) will ship with
Chrome 52 webview, which has good support for ECMAscript 2015 (ES6) (Ref: https://caniuse.com/?search=es6)

While in most cases, app users will likely have a modern webview vesion installed, this means
you can be confident that the app user will have a chrome webview version with good ES6 support,
even if they happen to be running on a factory versioned device.

See the mailing thread for the full discussion:
https://lists.apache.org/thread/zcgof080hdzzo2j96mjz0qpj0gotmn57
2023-03-16 19:45:01 -03:00
エリス
04723eb8f3 feat: bump androidx.appcompat.appcompat 1.6.1 (#1568) 2023-03-11 16:41:38 +09:00
エリス
862d33694e feat: bump androidx.webkit.webkit 1.6.0 (#1567) 2023-03-11 12:19:16 +09:00
エリス
fe3940a73c feat: bump androidx.webkit.webkit 1.5.0 (#1545) 2023-03-11 03:07:19 +09:00
エリス
81c678c58d feat: bump com.google.gms.google-services 4.3.15 (#1547)
* feat: bump com.google.gms.google-services 4.3.14
* feat: bump com.google.gms.google-services 4.3.15
Co-authored-by: jcesarmobile <jcesarmobile@gmail.com>
2023-03-11 03:06:56 +09:00
エリス
bfe086a2d7 feat: bump androidx.core.core-splashscreen 1.0.0 (#1546) 2023-03-11 02:51:33 +09:00
エリス
8fb707567a feat: bump androidx.appcompat.appcompat 1.5.1 (#1544) 2023-03-11 02:45:42 +09:00
エリス
992a60a434 feat!: bump target sdk & build tools for SDK 33 support (#1538)
* feat!: bump target sdk version 33
* feat!: bump minimum build tools version 33.0.1
* feat!: bump minimum build tools version 33.0.2
2023-03-10 12:12:17 +09:00
エリス
2318ef58ad dep(npm)!: bump acceptable modules w/ rebuilt package-lock (#1541)
* dep(npm)!: bump acceptable modules
* chore: rebuilt package-lock
2023-03-09 18:57:19 +09:00
エリス
968bd85cc3 feat!: bump node engine requirement >=16.13.0 (#1540) 2023-03-09 18:40:16 +09:00
エリス
9ef3ee9539 dep(npm)!: bump cordova-common@5.0.0 (#1566) 2023-03-09 18:34:31 +09:00
エリス
5347054efb chore: prepare package version for next major 12.0.0-dev (#1537) 2023-03-09 18:33:28 +09:00
エリス
3340e98519 ci(workflow): update codecov/codecov-action@v3 (#1542) 2023-01-18 23:53:36 +09:00
jcesarmobile
ce19a3b445 chore: add lint:fix script for fixing lint errors (#1493)
* chore: Add fmt command for fixing lint errors
* rename to lint:fix
2023-01-11 10:06:16 +09:00
Jesse MacFadyen
d02f8eafe8 Update codecoverage reporting (#1532) 2022-12-30 12:18:54 -08:00
jcesarmobile
56d4b8312b fix: only do fadeout animation if FadeSplashScreen is true (#1506) 2022-10-27 23:38:30 +02:00
Darryl Pogue
80f232aa79 fix: correctly flag API dependency on AppCompat for Maven (#1505)
* Correctly flag API dependency on AppCompat for Maven

Currently when cordova-android is published to Maven, it lists no
dependencies. However, `CordovaActivity` extends `AppCompatActivity`
which requires that the AndroidX AppCompat library be available.

Marking this as an API dependency (rather than an implementation/compile
dependency) should cause the AndroidX AppCompat library to be installed
when the cordova-android framework is added to the build.gradle of an
existing Android application.

* Publish to Maven with proper metadata

This allows the Maven publish to pick up information from the android
library component and include things like dependencies in the pom.xml
file.
2022-10-15 13:39:47 +09:00
jcesarmobile
954d3e0e75 fix: Add android prefix to windowSplashScreenBrandingImage (#1487) 2022-09-16 09:34:20 +02:00
jcesarmobile
8a1ffeeafd chore: Use gradle 7.4.2 distribution url (#1491) 2022-09-16 09:33:15 +02:00
jcesarmobile
7793db97cc refactor: replace deprecated Handler constructor (#1492) 2022-09-16 09:32:38 +02:00
Philip Peitsch
248257bd37 fix: import type definitions from obsolete cordova-plugin-splashscreen (#1489) 2022-09-13 10:03:53 -03:00
Erisu
60e3803c67 chore: bump version 11.0.1-dev (2) 2022-07-04 23:12:43 +09:00
61 changed files with 3148 additions and 1671 deletions

View File

@@ -27,7 +27,7 @@ jobs:
strategy:
matrix:
node-version: [14.x, 16.x, 18.x]
node-version: [16.x, 18.x, 20.x]
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
@@ -57,6 +57,8 @@ jobs:
env:
CI: true
- uses: codecov/codecov-action@v3
- name: upload coverage
if: success()
uses: codecov/codecov-action@v3
with:
fail_ci_if_error: true
name: ${{ runner.os }} node.js ${{ matrix.node-version }}

View File

@@ -20,6 +20,60 @@
-->
## Release Notes for Cordova (Android)
### 12.0.0 (May 20, 2023)
**Breaking:**
* [GH-1605](https://github.com/apache/cordova-android/pull/1605) fix!: Make `CoreAndroid` plugin instantiate on load
* [GH-1539](https://github.com/apache/cordova-android/pull/1539) feat!: bump Gradle 7.6 & AGP 7.4.2
* [GH-1571](https://github.com/apache/cordova-android/pull/1571) feat!: bump min SDK to 24
* [GH-1538](https://github.com/apache/cordova-android/pull/1538) feat!: bump target sdk & build tools for SDK 33 support
* [GH-1540](https://github.com/apache/cordova-android/pull/1540) feat!: bump node engine requirement `>=16.13.0`
* [GH-1597](https://github.com/apache/cordova-android/pull/1597) deprecate: `CoreAndroid.getBuildConfigValue`
* [GH-1541](https://github.com/apache/cordova-android/pull/1541) dep(npm)!: bump acceptable modules w/ rebuilt `package-lock`
* [GH-1566](https://github.com/apache/cordova-android/pull/1566) dep(npm)!: bump `cordova-common@5.0.0`
**Features:**
* [GH-1602](https://github.com/apache/cordova-android/pull/1602) feat: add `listTarget` api
* [GH-1574](https://github.com/apache/cordova-android/pull/1574) feat: add plugin hooks for `WebViewClient.onRenderProcessGone`
* [GH-1594](https://github.com/apache/cordova-android/pull/1594) feat: bump default `kotlin` to version 1.7.21
* [GH-1550](https://github.com/apache/cordova-android/pull/1550) feat: add `monochrome` app icon support
* [GH-1589](https://github.com/apache/cordova-android/pull/1589) feat: `InspectableWebview` preference
* [GH-1568](https://github.com/apache/cordova-android/pull/1568) feat: bump `androidx.appcompat.appcompat` 1.6.1
* [GH-1567](https://github.com/apache/cordova-android/pull/1567) feat: bump `androidx.webkit.webkit` 1.6.0
* [GH-1545](https://github.com/apache/cordova-android/pull/1545) feat: bump `androidx.webkit.webkit` 1.5.0
* [GH-1547](https://github.com/apache/cordova-android/pull/1547) feat: bump `com.google.gms.google-services` 4.3.15
* [GH-1546](https://github.com/apache/cordova-android/pull/1546) feat: bump `androidx.core.core-splashscreen` 1.0.0
* [GH-1544](https://github.com/apache/cordova-android/pull/1544) feat: bump `androidx.appcompat.appcompat` 1.5.1
**Fixes:**
* [GH-1606](https://github.com/apache/cordova-android/pull/1606) fix: Gradle Args parsing
* [GH-1575](https://github.com/apache/cordova-android/pull/1575) fix(`BuildHelper`): get package name from `ApplicationInfo`
* [GH-1595](https://github.com/apache/cordova-android/pull/1595) fix(test): Native test namespace refactor
* [GH-1471](https://github.com/apache/cordova-android/pull/1471) fix: `ANDROID_HOME` is the new default, to check first and give advice
* [GH-1573](https://github.com/apache/cordova-android/pull/1573) fix(GH-1432): Default `content` `src` when content tag is missing
* [GH-1506](https://github.com/apache/cordova-android/pull/1506) fix: only do fadeout animation if `FadeSplashScreen` is true
* [GH-1505](https://github.com/apache/cordova-android/pull/1505) fix: correctly flag API dependency on `AppCompat` for Maven
* [GH-1487](https://github.com/apache/cordova-android/pull/1487) fix: Add **Android** prefix to `WindowSplashScreenBrandingImage`
* [GH-1489](https://github.com/apache/cordova-android/pull/1489) fix: import type definitions from obsolete `cordova-plugin-splashscreen`
**Chores, Refactor, Dependencies & CI:**
* [GH-1493](https://github.com/apache/cordova-android/pull/1493) chore: add `lint:fix` script for fixing lint errors
* [GH-1491](https://github.com/apache/cordova-android/pull/1491) chore: Use gradle 7.4.2 distribution url
* [GH-1588](https://github.com/apache/cordova-android/pull/1588) refactor: Removed obsolete version code checks
* [GH-1492](https://github.com/apache/cordova-android/pull/1492) refactor: replace deprecated `Handler` constructor
* [GH-1587](https://github.com/apache/cordova-android/pull/1587) dep: bump npm dependencies
* `fs-extra@11.1.1`
* `nopt@7.1.0`
* `@cordova/eslint-config@5.0.0`
* `jasmine@4.6.0`
* [GH-1607](https://github.com/apache/cordova-android/pull/1607) ci: Added NodeJS 20.x to the workflow matrix
* [GH-1542](https://github.com/apache/cordova-android/pull/1542) ci(workflow): update `codecov/codecov-action@v3`
* [GH-1532](https://github.com/apache/cordova-android/pull/1532) ci: update `codecov/codecov-action` reporting format
### 11.0.0 (Jul 04, 2022)
**Breaking:**

View File

@@ -18,5 +18,6 @@
under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.apache.cordova" android:versionName="1.0" android:versionCode="1">
android:versionName="1.0"
android:versionCode="1">
</manifest>

View File

@@ -44,6 +44,8 @@ allprojects {
apply plugin: 'com.android.library'
android {
namespace 'org.apache.cordova'
compileSdkVersion cordovaConfig.COMPILE_SDK_VERSION
buildToolsVersion cordovaConfig.BUILD_TOOLS_VERSION
@@ -75,10 +77,16 @@ android {
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/NOTICE'
}
publishing {
singleVariant('release') {
withSourcesJar()
}
}
}
dependencies {
implementation "androidx.appcompat:appcompat:${cordovaConfig.ANDROIDX_APP_COMPAT_VERSION}"
api "androidx.appcompat:appcompat:${cordovaConfig.ANDROIDX_APP_COMPAT_VERSION}"
implementation "androidx.webkit:webkit:${cordovaConfig.ANDROIDX_WEBKIT_VERSION}"
implementation "androidx.core:core-splashscreen:${cordovaConfig.ANDROIDX_CORE_SPLASHSCREEN_VERSION}"
}

View File

@@ -1,15 +1,16 @@
{
"MIN_SDK_VERSION": 22,
"SDK_VERSION": 32,
"MIN_SDK_VERSION": 24,
"SDK_VERSION": 33,
"COMPILE_SDK_VERSION": null,
"GRADLE_VERSION": "7.4.2",
"MIN_BUILD_TOOLS_VERSION": "32.0.0",
"AGP_VERSION": "7.2.1",
"KOTLIN_VERSION": "1.5.21",
"ANDROIDX_APP_COMPAT_VERSION": "1.4.2",
"ANDROIDX_WEBKIT_VERSION": "1.4.0",
"ANDROIDX_CORE_SPLASHSCREEN_VERSION": "1.0.0-rc01",
"GRADLE_PLUGIN_GOOGLE_SERVICES_VERSION": "4.3.10",
"GRADLE_VERSION": "7.6",
"MIN_BUILD_TOOLS_VERSION": "33.0.2",
"AGP_VERSION": "7.4.2",
"KOTLIN_VERSION": "1.7.21",
"ANDROIDX_APP_COMPAT_VERSION": "1.6.1",
"ANDROIDX_WEBKIT_VERSION": "1.6.0",
"ANDROIDX_CORE_SPLASHSCREEN_VERSION": "1.0.0",
"GRADLE_PLUGIN_GOOGLE_SERVICES_VERSION": "4.3.15",
"IS_GRADLE_PLUGIN_GOOGLE_SERVICES_ENABLED": false,
"IS_GRADLE_PLUGIN_KOTLIN_ENABLED": false
"IS_GRADLE_PLUGIN_KOTLIN_ENABLED": false,
"PACKAGE_NAMESPACE": "io.cordova.helloCordova"
}

View File

@@ -46,73 +46,69 @@ if (project.hasProperty('signEnabled')) {
}
}
task sourcesJar(type: Jar) {
from android.sourceSets.main.java.srcDirs
classifier = 'sources'
}
afterEvaluate {
publishing {
publications {
mavenJava(MavenPublication) {
groupId = 'org.apache.cordova'
artifactId = 'framework'
version = getCordovaAndroidVersion()
publishing {
publications {
mavenJava(MavenPublication) {
groupId = 'org.apache.cordova'
artifactId = 'framework'
version = getCordovaAndroidVersion()
from components.release
artifact(sourcesJar)
artifact("$buildDir/outputs/aar/framework-release.aar")
pom {
name = 'Cordova'
description = 'A library to build Cordova-based projects for the Android platform.'
url = 'https://cordova.apache.org'
pom {
name = 'Cordova'
description = 'A library to build Cordova-based projects for the Android platform.'
url = 'https://cordova.apache.org'
licenses {
license {
name = 'Apache License, Version 2.0'
url = 'https://www.apache.org/licenses/LICENSE-2.0.txt'
licenses {
license {
name = 'Apache License, Version 2.0'
url = 'https://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
}
developers {
developer {
id = 'stevengill'
name = 'Steve Gill'
developers {
developer {
id = 'stevengill'
name = 'Steve Gill'
}
developer {
id = 'erisu'
name = 'Bryan Ellis'
email = 'erisu@apache.org'
}
}
developer {
id = 'erisu'
name = 'Bryan Ellis'
email = 'erisu@apache.org'
}
}
scm {
connection = 'scm:git:https://github.com/apache/cordova-android.git'
developerConnection = 'scm:git:git@github.com:apache/cordova-android.git'
url = 'https://github.com/apache/cordova-android'
scm {
connection = 'scm:git:https://github.com/apache/cordova-android.git'
developerConnection = 'scm:git:git@github.com:apache/cordova-android.git'
url = 'https://github.com/apache/cordova-android'
}
}
}
}
}
repositories {
maven {
def releasesRepoUrl = 'https://repository.apache.org/content/repositories/releases'
def snapshotsRepoUrl = 'https://repository.apache.org/content/repositories/snapshots'
repositories {
maven {
def releasesRepoUrl = 'https://repository.apache.org/content/repositories/releases'
def snapshotsRepoUrl = 'https://repository.apache.org/content/repositories/snapshots'
url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
credentials {
if (project.hasProperty('apacheUsername') && project.hasProperty('apachePassword')) {
username apacheUsername
password apachePassword
credentials {
if (project.hasProperty('apacheUsername') && project.hasProperty('apachePassword')) {
username apacheUsername
password apachePassword
}
}
}
}
}
signing {
if (Boolean.valueOf(cdvEnableSigning)) {
sign publishing.publications.mavenJava
signing {
if (Boolean.valueOf(cdvEnableSigning)) {
sign publishing.publications.mavenJava
}
}
}
}

View File

@@ -83,9 +83,9 @@ String doFindLatestInstalledBuildTools(String minBuildToolsVersionString) {
String getAndroidSdkDir() {
def rootDir = project.rootDir
def androidSdkDir = null
String envVar = System.getenv("ANDROID_SDK_ROOT")
String envVar = System.getenv("ANDROID_HOME")
if (envVar == null) {
envVar = System.getenv("ANDROID_HOME")
envVar = System.getenv("ANDROID_SDK_ROOT")
}
def localProperties = new File(rootDir, 'local.properties')
@@ -125,14 +125,6 @@ def doExtractIntFromManifest(name) {
return new BigInteger(matcher.group(1))
}
def doExtractStringFromManifest(name) {
def manifestFile = file(android.sourceSets.main.manifest.srcFile)
def pattern = Pattern.compile(name + "=\"(\\S+)\"")
def matcher = pattern.matcher(manifestFile.getText())
matcher.find()
return matcher.group(1)
}
def doGetConfigXml() {
def xml = file("src/main/res/xml/config.xml").getText()
// Disable namespace awareness since Cordova doesn't use them properly
@@ -231,7 +223,6 @@ ext {
privateHelpers.getProjectTarget = { doGetProjectTarget() }
privateHelpers.applyCordovaConfigCustomization = { doApplyCordovaConfigCustomization() }
privateHelpers.extractIntFromManifest = { name -> doExtractIntFromManifest(name) }
privateHelpers.extractStringFromManifest = { name -> doExtractStringFromManifest(name) }
privateHelpers.ensureValueExists = { filePath, props, key -> doEnsureValueExists(filePath, props, key) }
// These helpers can be used by plugins / projects and will not change.

View File

@@ -51,7 +51,8 @@ public class BuildHelper {
{
try
{
Class<?> clazz = Class.forName(ctx.getClass().getPackage().getName() + ".BuildConfig");
String packageName = ctx.getApplicationInfo().packageName;
Class<?> clazz = Class.forName(packageName + ".BuildConfig");
Field field = clazz.getField(key);
return field.get(null);
} catch (ClassNotFoundException e) {

View File

@@ -34,6 +34,7 @@ public class ConfigXmlParser {
private static String SCHEME_HTTP = "http";
private static String SCHEME_HTTPS = "https";
private static String DEFAULT_HOSTNAME = "localhost";
private static final String DEFAULT_CONTENT_SRC = "index.html";
private String launchUrl;
private String contentSrc;
@@ -110,6 +111,18 @@ public class ConfigXmlParser {
e.printStackTrace();
}
}
onPostParse();
}
private void onPostParse() {
// After parsing, if contentSrc is still null, it signals
// that <content> tag was completely missing. In this case,
// default it.
// https://github.com/apache/cordova-android/issues/1432
if (contentSrc == null) {
contentSrc = DEFAULT_CONTENT_SRC;
}
}
public void handleStartTag(XmlPullParser xml) {
@@ -140,7 +153,7 @@ public class ConfigXmlParser {
contentSrc = src;
} else {
// Default
contentSrc = "index.html";
contentSrc = DEFAULT_CONTENT_SRC;
}
}
}

View File

@@ -237,14 +237,6 @@ public class CordovaInterfaceImpl implements CordovaInterface {
public boolean hasPermission(String permission)
{
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
{
int result = activity.checkSelfPermission(permission);
return PackageManager.PERMISSION_GRANTED == result;
}
else
{
return true;
}
return PackageManager.PERMISSION_GRANTED == activity.checkSelfPermission(permission);
}
}

View File

@@ -31,6 +31,8 @@ import android.content.res.Configuration;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.webkit.RenderProcessGoneDetail;
import android.webkit.WebView;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -442,4 +444,19 @@ public class CordovaPlugin {
public CordovaPluginPathHandler getPathHandler() {
return null;
}
/**
* Called when the WebView's render process has exited. Can be used to collect information regarding the crash for crashlytics or optionally attempt to gracefully handle/recover the crashed webview by recreating it.
*
* See <a href="https://developer.android.com/reference/android/webkit/WebViewClient#onRenderProcessGone(android.webkit.WebView,%20android.webkit.RenderProcessGoneDetail)">WebViewClient#onRenderProcessGone</a>
*
* Note: A plugin must not attempt to recover a webview that it does not own/manage.
*
* @return true if the host application handled the situation that process has exited,
* otherwise, application will crash if render process crashed, or be killed
* if render process was killed by the system.
*/
public boolean onRenderProcessGone(final WebView view, RenderProcessGoneDetail detail) {
return false;
}
}

View File

@@ -31,7 +31,7 @@ import android.webkit.WebChromeClient.CustomViewCallback;
* are not expected to implement it.
*/
public interface CordovaWebView {
public static final String CORDOVA_VERSION = "11.0.0";
public static final String CORDOVA_VERSION = "12.0.0";
void init(CordovaInterface cordova, List<PluginEntry> pluginEntries, CordovaPreferences preferences);

View File

@@ -115,7 +115,7 @@ public class CordovaWebViewImpl implements CordovaWebView {
// This isn't enforced by the compiler, so assert here.
assert engine.getView() instanceof CordovaWebViewEngine.EngineView;
pluginManager.addService(CoreAndroid.PLUGIN_NAME, "org.apache.cordova.CoreAndroid");
pluginManager.addService(CoreAndroid.PLUGIN_NAME, "org.apache.cordova.CoreAndroid", true);
pluginManager.init();
}

View File

@@ -19,6 +19,8 @@
package org.apache.cordova;
import org.apache.cordova.BuildHelper;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -30,7 +32,6 @@ import android.content.IntentFilter;
import android.telephony.TelephonyManager;
import android.view.KeyEvent;
import java.lang.reflect.Field;
import java.util.HashMap;
/**
@@ -376,35 +377,19 @@ public class CoreAndroid extends CordovaPlugin {
}
}
/*
/*
* This needs to be implemented if you wish to use the Camera Plugin or other plugins
* that read the Build Configuration.
*
* Thanks to Phil@Medtronic and Graham Borland for finding the answer and posting it to
* StackOverflow. This is annoying as hell!
*
* @deprecated Use {@link BuildHelper#getBuildConfigValue} instead.
*/
@Deprecated
public static Object getBuildConfigValue(Context ctx, String key)
{
try
{
Class<?> clazz = Class.forName(ctx.getClass().getPackage().getName() + ".BuildConfig");
Field field = clazz.getField(key);
return field.get(null);
} catch (ClassNotFoundException e) {
LOG.d(TAG, "Unable to get the BuildConfig, is this built with ANT?");
e.printStackTrace();
} catch (NoSuchFieldException e) {
LOG.d(TAG, key + " is not a valid field. Check your build.gradle");
} catch (IllegalAccessException e) {
LOG.d(TAG, "Illegal Access Exception: Let's print a stack trace.");
e.printStackTrace();
} catch (NullPointerException e) {
LOG.d(TAG, "Null Pointer Exception: Let's print a stack trace.");
e.printStackTrace();
}
return null;
LOG.w(TAG, "CoreAndroid.getBuildConfigValue is deprecated and will be removed in a future release. Use BuildHelper.getBuildConfigValue instead.");
return BuildHelper.getBuildConfigValue(ctx, key);
}
}

View File

@@ -32,6 +32,8 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Debug;
import android.os.Build;
import android.webkit.RenderProcessGoneDetail;
import android.webkit.WebView;
/**
* PluginManager is exposed to JavaScript in the Cordova WebView.
@@ -197,7 +199,18 @@ public class PluginManager {
* @param className The plugin class name
*/
public void addService(String service, String className) {
PluginEntry entry = new PluginEntry(service, className, false);
addService(service, className, false);
}
/**
* Add a plugin class that implements a service to the service entry table.
*
* @param service The service name
* @param className The plugin class name
* @param onload If true, the plugin will be instantiated immediately
*/
public void addService(String service, String className, boolean onload) {
PluginEntry entry = new PluginEntry(service, className, onload);
this.addService(entry);
}
@@ -339,22 +352,11 @@ public class PluginManager {
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;
}
}
this.pluginMap.forEach((s, plugin) -> {
if (plugin != null) {
plugin.onMessage(id, data);
}
}
});
}
return ctx.onMessage(id, data);
}
@@ -617,4 +619,29 @@ public class PluginManager {
}
return handlers;
}
/**
* Called when the WebView's render process has exited.
*
* See https://developer.android.com/reference/android/webkit/WebViewClient#onRenderProcessGone(android.webkit.WebView,%20android.webkit.RenderProcessGoneDetail)
*
* @return true if the host application handled the situation that process has exited,
* otherwise, application will crash if render process crashed, or be killed
* if render process was killed by the system.
*/
public boolean onRenderProcessGone(final WebView view, RenderProcessGoneDetail detail) {
boolean result = false;
synchronized (this.entryMap) {
for (PluginEntry entry : this.entryMap.values()) {
CordovaPlugin plugin = pluginMap.get(entry.service);
if (plugin != null) {
if (plugin.onRenderProcessGone(view, detail)) {
result = true;
}
}
}
}
return result;
}
}

View File

@@ -127,7 +127,7 @@ public class SplashScreenPlugin extends CordovaPlugin {
// auto hide splash screen when custom delay is defined.
if (autoHide && delayTime != DEFAULT_DELAY_TIME) {
Handler splashScreenDelayHandler = new Handler();
Handler splashScreenDelayHandler = new Handler(cordova.getContext().getMainLooper());
splashScreenDelayHandler.postDelayed(() -> keepOnScreen = false, delayTime);
}
@@ -137,27 +137,29 @@ public class SplashScreenPlugin extends CordovaPlugin {
// If auto hide is disabled (false), the hiding of the splash screen must be determined &
// triggered by the front-end code with the `navigator.splashscreen.hide()` method.
// Setup the fade
splashScreen.setOnExitAnimationListener(new SplashScreen.OnExitAnimationListener() {
@Override
public void onSplashScreenExit(@NonNull SplashScreenViewProvider splashScreenViewProvider) {
View splashScreenView = splashScreenViewProvider.getView();
if (isFadeEnabled) {
// Setup the fade
splashScreen.setOnExitAnimationListener(new SplashScreen.OnExitAnimationListener() {
@Override
public void onSplashScreenExit(@NonNull SplashScreenViewProvider splashScreenViewProvider) {
View splashScreenView = splashScreenViewProvider.getView();
splashScreenView
.animate()
.alpha(0.0f)
.setDuration(isFadeEnabled ? fadeDuration : 0)
.setStartDelay(isFadeEnabled ? 0 : fadeDuration)
.setInterpolator(new AccelerateInterpolator())
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
splashScreenViewProvider.remove();
}
}).start();
}
});
splashScreenView
.animate()
.alpha(0.0f)
.setDuration(fadeDuration)
.setStartDelay(0)
.setInterpolator(new AccelerateInterpolator())
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
splashScreenViewProvider.remove();
}
}).start();
}
});
}
}
private void attemptCloseOnPageFinished() {

View File

@@ -28,6 +28,7 @@ import android.net.http.SslError;
import android.webkit.ClientCertRequest;
import android.webkit.HttpAuthHandler;
import android.webkit.MimeTypeMap;
import android.webkit.RenderProcessGoneDetail;
import android.webkit.SslErrorHandler;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
@@ -422,4 +423,15 @@ public class SystemWebViewClient extends WebViewClient {
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
return this.assetLoader.shouldInterceptRequest(request.getUrl());
}
@Override
public boolean onRenderProcessGone(final WebView view, RenderProcessGoneDetail detail) {
// Check if there is some plugin which can handle this event
PluginManager pluginManager = this.parentEngine.pluginManager;
if (pluginManager != null && pluginManager.onRenderProcessGone(view, detail)) {
return true;
}
return super.onRenderProcessGone(view, detail);
}
}

View File

@@ -175,9 +175,20 @@ public class SystemWebViewEngine implements CordovaWebViewEngine {
String databasePath = webView.getContext().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
settings.setDatabaseEnabled(true);
//Determine whether we're in debug or release mode, and turn on Debugging!
ApplicationInfo appInfo = webView.getContext().getApplicationContext().getApplicationInfo();
if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
// The default is to use the module's debuggable state to decide if the webview inspecter
// should be enabled. However, users can configure InspectableWebview preference to forcefully enable
// or disable the webview inspecter.
String inspectableWebview = preferences.getString("InspectableWebview", null);
boolean shouldEnableInspector = false;
if (inspectableWebview == null) {
ApplicationInfo appInfo = webView.getContext().getApplicationContext().getApplicationInfo();
shouldEnableInspector = (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}
else if ("true".equals(inspectableWebview)) {
shouldEnableInspector = true;
}
if (shouldEnableInspector) {
enableRemoteDebugging();
}

View File

@@ -50,15 +50,6 @@ class AndroidManifest {
return this;
}
getPackageId () {
return this.doc.getroot().attrib.package;
}
setPackageId (pkgId) {
this.doc.getroot().attrib.package = pkgId;
return this;
}
getActivity () {
const activity = this.doc.getroot().find('./application/activity');
return {

View File

@@ -20,8 +20,8 @@
const fs = require('fs');
const path = require('path');
const properties_parser = require('properties-parser');
const AndroidManifest = require('./AndroidManifest');
const pluginHandlers = require('./pluginHandlers');
const CordovaGradleConfigParserFactory = require('./config/CordovaGradleConfigParserFactory');
let projectFileCache = {};
@@ -63,17 +63,17 @@ class AndroidProject {
this.projectDir = projectDir;
this.platformWww = path.join(this.projectDir, 'platform_www');
this.www = path.join(this.projectDir, 'app/src/main/assets/www');
this.cordovaGradleConfigParser = CordovaGradleConfigParserFactory.create(this.projectDir);
}
/**
* Reads the package name out of the Android Manifest file
* Reads the package name out of the Cordova's Gradle Config file
*
* @param {String} projectDir The absolute path to the directory containing the project
* @return {String} The name of the package
*/
getPackageName () {
const manifestPath = path.join(this.projectDir, 'app/src/main/AndroidManifest.xml');
return new AndroidManifest(manifestPath).getPackageId();
return this.cordovaGradleConfigParser.getPackageName();
}
getCustomSubprojectRelativeDir (plugin_id, src) {

View File

@@ -277,6 +277,12 @@ class Api {
});
}
listTargets (options) {
return require('./check_reqs').check_android().then(() => {
return require('./run').runListDevices.call(this, options);
});
}
/**
* Cleans out the build artifacts from platform's directory, and also
* cleans out the platform www directory if called without options specified.

View File

@@ -21,6 +21,7 @@ const path = require('path');
const fs = require('fs');
const nopt = require('nopt');
const untildify = require('untildify');
const { parseArgsStringToArgv } = require('string-argv');
const Adb = require('./Adb');
@@ -36,7 +37,11 @@ function parseOpts (options, resolvedTarget, projectRoot) {
minSdkVersion: String,
maxSdkVersion: String,
targetSdkVersion: String,
// This needs to be an array since nopts will parse its entries as further options for this process
// It will be an array of 1 string: [ "string args" ]
gradleArg: [String, Array],
keystore: path,
alias: String,
storePassword: String,
@@ -58,7 +63,8 @@ function parseOpts (options, resolvedTarget, projectRoot) {
if (options.argv.maxSdkVersion) { ret.extraArgs.push('-PcdvMaxSdkVersion=' + options.argv.maxSdkVersion); }
if (options.argv.targetSdkVersion) { ret.extraArgs.push('-PcdvTargetSdkVersion=' + options.argv.targetSdkVersion); }
if (options.argv.gradleArg) {
ret.extraArgs = ret.extraArgs.concat(options.argv.gradleArg);
const gradleArgs = parseArgsStringToArgv(options.argv.gradleArg[0]);
ret.extraArgs = ret.extraArgs.concat(gradleArgs);
}
const packageArgs = {};
@@ -184,7 +190,7 @@ module.exports.run = function (options, optResolvedTarget) {
}
return {
paths: paths,
paths,
buildType: opts.buildType
};
});

View File

@@ -27,6 +27,7 @@ const check_reqs = require('../check_reqs');
const PackageType = require('../PackageType');
const { compareByAll } = require('../utils');
const { createEditor } = require('properties-parser');
const CordovaGradleConfigParserFactory = require('../config/CordovaGradleConfigParserFactory');
const MARKER = 'YOUR CHANGES WILL BE ERASED!';
const SIGNING_PROPERTIES = '-signing.properties';
@@ -84,7 +85,13 @@ class ProjectBuilder {
}
getArgs (cmd, opts) {
let args;
let args = [
'-b', path.join(this.root, 'build.gradle')
];
if (opts.extraArgs) {
args = args.concat(opts.extraArgs);
}
let buildCmd = cmd;
if (opts.packageType === PackageType.BUNDLE) {
if (cmd === 'release') {
@@ -92,8 +99,6 @@ class ProjectBuilder {
} else if (cmd === 'debug') {
buildCmd = ':app:bundleDebug';
}
args = [buildCmd, '-b', path.join(this.root, 'build.gradle')];
} else {
if (cmd === 'release') {
buildCmd = 'cdvBuildRelease';
@@ -101,14 +106,12 @@ class ProjectBuilder {
buildCmd = 'cdvBuildDebug';
}
args = [buildCmd, '-b', path.join(this.root, 'build.gradle')];
if (opts.arch) {
args.push('-PcdvBuildArch=' + opts.arch);
}
}
args.push.apply(args, opts.extraArgs);
args.push(buildCmd);
return args;
}
@@ -145,19 +148,6 @@ class ProjectBuilder {
};
}
extractRealProjectNameFromManifest () {
const manifestPath = path.join(this.root, 'app', 'src', 'main', 'AndroidManifest.xml');
const manifestData = fs.readFileSync(manifestPath, 'utf8');
const m = /<manifest[\s\S]*?package\s*=\s*"(.*?)"/i.exec(manifestData);
if (!m) {
throw new CordovaError('Could not find package name in ' + manifestPath);
}
const packageName = m[1];
const lastDotIndex = packageName.lastIndexOf('.');
return packageName.substring(lastDotIndex + 1);
}
// Makes the project buildable, minus the gradle wrapper.
prepBuildFiles () {
// Update the version of build.gradle in each dependent library.
@@ -184,7 +174,11 @@ class ProjectBuilder {
checkAndCopy(subProjects[i], this.root);
}
}
const projectName = this.extractRealProjectNameFromManifest();
// get project name cdv-gradle-config.
const cdvGradleConfig = CordovaGradleConfigParserFactory.create(this.root);
const projectName = cdvGradleConfig.getProjectNameFromPackageName();
// Remove the proj.id/name- prefix from projects: https://issues.apache.org/jira/browse/CB-9149
const settingsGradlePaths = subProjects.map(function (p) {
const realDir = p.replace(/[/\\]/g, ':');
@@ -326,6 +320,8 @@ class ProjectBuilder {
const wrapper = path.join(this.root, 'gradlew');
const args = this.getArgs(opts.buildType === 'debug' ? 'debug' : 'release', opts);
events.emit('verbose', `Running Gradle Build: ${wrapper} ${args.join(' ')}`);
try {
return await execa(wrapper, args, { stdio: 'inherit', cwd: path.resolve(this.root) });
} catch (error) {

View File

@@ -149,7 +149,7 @@ module.exports.check_gradle = function () {
const sdkDir = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT;
if (!sdkDir) {
return Promise.reject(new CordovaError('Could not find gradle wrapper within Android SDK. Could not find Android SDK directory.\n' +
'Might need to install Android SDK or set up \'ANDROID_SDK_ROOT\' env variable.'));
'Might need to install Android SDK or set up \'ANDROID_HOME\' env variable.'));
}
const gradlePath = module.exports.get_gradle_wrapper();
@@ -270,7 +270,7 @@ module.exports.check_android = function () {
'Failed to find \'android\' command in your \'PATH\'. Try update your \'PATH\' to include path to valid SDK directory.');
}
if (!fs.existsSync(process.env.ANDROID_HOME)) {
throw new CordovaError('\'ANDROID_HOME\' environment variable is set to non-existent path: ' + process.env.ANDROID_SDK_ROOT +
throw new CordovaError('\'ANDROID_HOME\' environment variable is set to non-existent path: ' + process.env.ANDROID_HOME +
'\nTry update it manually to point to valid SDK directory.');
}
// Next let's make sure relevant parts of the SDK tooling is in our PATH
@@ -306,7 +306,7 @@ module.exports.run = function () {
console.log('ANDROID_SDK_ROOT=' + process.env.ANDROID_SDK_ROOT + ' (DEPRECATED)');
return Promise.all([this.check_java(), this.check_android()]).then(function (values) {
console.log('Using Android SDK: ' + process.env.ANDROID_SDK_ROOT);
console.log('Using Android SDK: ' + (process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT));
if (!values[1]) {
throw new CordovaError('Requirements check failed for Android SDK! Android SDK was not detected.');
@@ -327,7 +327,7 @@ const Requirement = function (id, name, version, installed) {
this.name = name;
this.installed = installed || false;
this.metadata = {
version: version
version
};
};

View File

@@ -0,0 +1,71 @@
/**
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 fs = require('fs-extra');
const path = require('path');
const events = require('cordova-common').events;
class CordovaGradleConfigParser {
/**
* Loads and Edits Gradle Properties File.
*
* Do not construct this directly. Use CordovaGradleConfigParserFactory instead.
*
* @param {String} platformDir is the path of the Android platform directory
*/
constructor (platformDir) {
this._cdvGradleConfigFilePath = path.join(platformDir, 'cdv-gradle-config.json');
this._cdvGradleConfig = this._readConfig(this._cdvGradleConfigFilePath);
}
/**
* Reads and parses the configuration JSON file
*
* @param {String} configPath
* @returns {Record<any, any>} The parsed JSON object representing the gradle config.
*/
_readConfig (configPath) {
return fs.readJSONSync(configPath, 'utf-8');
}
setPackageName (packageName) {
events.emit('verbose', '[Cordova Gradle Config] Setting "PACKAGE_NAMESPACE" to ' + packageName);
this._cdvGradleConfig.PACKAGE_NAMESPACE = packageName;
return this;
}
getPackageName () {
return this._cdvGradleConfig.PACKAGE_NAMESPACE;
}
getProjectNameFromPackageName () {
const packageName = this._cdvGradleConfig.PACKAGE_NAMESPACE;
return packageName.substring(packageName.lastIndexOf('.') + 1);
}
/**
* Saves any changes that has been made to the properties file.
*/
write () {
events.emit('verbose', '[Cordova Gradle Config] Saving File');
fs.writeJSONSync(this._cdvGradleConfigFilePath, this._cdvGradleConfig, 'utf-8');
}
}
module.exports = CordovaGradleConfigParser;

View File

@@ -0,0 +1,35 @@
/**
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 CordovaGradleConfigParser = require('./CordovaGradleConfigParser');
/**
* Builds new gradle config parsers
*/
module.exports = class CordovaGradleConfigParserFactory {
/**
* Loads and Edits Gradle Properties File.
*
* @param {String} platformDir is the path of the Android platform directory
*/
static create (platformDir) {
return new CordovaGradleConfigParser(platformDir);
}
};

View File

@@ -23,6 +23,7 @@ const utils = require('./utils');
const check_reqs = require('./check_reqs');
const ROOT = path.join(__dirname, '..');
const { createEditor } = require('properties-parser');
const CordovaGradleConfigParserFactory = require('./config/CordovaGradleConfigParserFactory');
const CordovaError = require('cordova-common').CordovaError;
const AndroidManifest = require('./AndroidManifest');
@@ -249,6 +250,11 @@ exports.create = function (project_path, config, options, events) {
fs.ensureDirSync(assets_path);
fs.ensureDirSync(resource_path);
// store package name in cdv-gradle-config
const cdvGradleConfig = CordovaGradleConfigParserFactory.create(project_path);
cdvGradleConfig.setPackageName(package_name)
.write();
// interpolate the activity name and package
const packagePath = package_name.replace(/\./g, path.sep);
const activity_dir = path.join(java_path, packagePath);
@@ -261,8 +267,7 @@ exports.create = function (project_path, config, options, events) {
utils.replaceFileContents(activity_path, /__ID__/, package_name);
const manifest = new AndroidManifest(path.join(project_template_dir, 'AndroidManifest.xml'));
manifest.setPackageId(package_name)
.getActivity().setName(safe_activity_name);
manifest.getActivity().setName(safe_activity_name);
const manifest_path = path.join(app_path, 'AndroidManifest.xml');
manifest.write(manifest_path);

View File

@@ -34,6 +34,7 @@ const utils = require('./utils');
const gradleConfigDefaults = require('./gradle-config-defaults');
const checkReqs = require('./check_reqs');
const GradlePropertiesParser = require('./config/GradlePropertiesParser');
const CordovaGradleConfigParserFactory = require('./config/CordovaGradleConfigParserFactory');
function parseArguments (argv) {
return nopt({
@@ -278,20 +279,22 @@ function updateProjectAccordingTo (platformConfig, locations) {
// Java packages cannot support dashes
const androidPkgName = (platformConfig.android_packageName() || platformConfig.packageName()).replace(/-/g, '_');
const manifest = new AndroidManifest(locations.manifest);
const manifestId = manifest.getPackageId();
// updating cdv-gradle-config with new androidPkgName.
const cdvGradleConfig = CordovaGradleConfigParserFactory.create(locations.root);
cdvGradleConfig.setPackageName(androidPkgName)
.write();
const manifest = new AndroidManifest(locations.manifest);
manifest.getActivity()
.setOrientation(platformConfig.getPreference('orientation'))
.setLaunchMode(findAndroidLaunchModePreference(platformConfig));
manifest.setVersionName(platformConfig.version())
.setVersionCode(platformConfig.android_versionCode() || default_versionCode(platformConfig.version()))
.setPackageId(androidPkgName)
.write();
// Java file paths shouldn't be hard coded
const javaDirectory = path.join(locations.javaSrc, manifestId.replace(/\./g, '/'));
const javaDirectory = path.join(locations.javaSrc, androidPkgName.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);
@@ -378,11 +381,12 @@ function updateProjectSplashScreen (platformConfig, locations) {
'windowSplashScreenAnimatedIcon',
'windowSplashScreenAnimationDuration',
'windowSplashScreenBackground',
'windowSplashScreenBrandingImage',
'android:windowSplashScreenBrandingImage',
'windowSplashScreenIconBackgroundColor',
'postSplashScreenTheme'
].forEach(themeKey => {
const cdvConfigPrefKey = 'Android' + themeKey.charAt(0).toUpperCase() + themeKey.slice(1);
const index = themeKey.indexOf(':') + 1;
const cdvConfigPrefKey = 'Android' + themeKey.charAt(index).toUpperCase() + themeKey.slice(index + 1);
const cdvConfigPrefValue = platformConfig.getPreference(cdvConfigPrefKey, this.platform);
let themeTargetNode = splashScreenTheme.find(`item[@name="${themeKey}"]`);
@@ -411,7 +415,7 @@ function updateProjectSplashScreen (platformConfig, locations) {
updateProjectSplashScreenImage(locations, themeKey, cdvConfigPrefKey, cdvConfigPrefValue);
break;
case 'windowSplashScreenBrandingImage':
case 'android:windowSplashScreenBrandingImage':
// display warning only when set.
if (cdvConfigPrefValue) {
events.emit('warn', `"${themeKey}" is currently not supported by the splash screen compatibility library. https://issuetracker.google.com/issues/194301890`);
@@ -422,13 +426,14 @@ function updateProjectSplashScreen (platformConfig, locations) {
// force the themes value to `@color/cdv_splashscreen_icon_background`
if (!cdvConfigPrefValue && themeTargetNode) {
splashScreenTheme.remove(themeTargetNode);
delete themes.getroot().attrib['xmlns:tools'];
} else if (cdvConfigPrefValue) {
// if there is no current node, create a new node.
if (!themeTargetNode) {
themeTargetNode = themes.getroot().makeelement('item', { name: themeKey });
themeTargetNode = themes.getroot().makeelement('item', { name: themeKey, 'tools:targetApi': '31' });
splashScreenTheme.append(themeTargetNode);
themes.getroot().attrib['xmlns:tools'] = 'http://schemas.android.com/tools';
}
// set the user defined color.
themeTargetNode.text = '@drawable/ic_cdv_splashscreen_branding';
}
@@ -537,7 +542,7 @@ function cleanupAndSetProjectSplashScreenImage (srcFile, destFilePath, possibleP
function updateProjectSplashScreenImage (locations, themeKey, cdvConfigPrefKey, cdvConfigPrefValue = '') {
const SPLASH_SCREEN_IMAGE_BY_THEME_KEY = {
windowSplashScreenAnimatedIcon: 'ic_cdv_splashscreen',
windowSplashScreenBrandingImage: 'ic_cdv_splashscreen_branding'
'android:windowSplashScreenBrandingImage': 'ic_cdv_splashscreen_branding'
};
const destFileName = SPLASH_SCREEN_IMAGE_BY_THEME_KEY[themeKey] || null;
@@ -555,7 +560,7 @@ function updateProjectSplashScreenImage (locations, themeKey, cdvConfigPrefKey,
// Default Drawable Source File
let defaultSrcFilePath = null;
if (themeKey !== 'windowSplashScreenBrandingImage') {
if (themeKey !== 'android:windowSplashScreenBrandingImage') {
try {
// coming from user project
defaultSrcFilePath = require.resolve('cordova-android/templates/project/res/drawable/' + destFileNameExt);
@@ -575,7 +580,7 @@ function updateProjectSplashScreenImage (locations, themeKey, cdvConfigPrefKey,
}
events.emit(emitType, emmitMessage);
const cleanupOnly = themeKey === 'windowSplashScreenBrandingImage';
const cleanupOnly = themeKey === 'android:windowSplashScreenBrandingImage';
cleanupAndSetProjectSplashScreenImage(defaultSrcFilePath, destFilePath, possiblePreviousDestFilePath, cleanupOnly);
return;
}
@@ -701,8 +706,10 @@ function updateIcons (cordovaProject, platformResourcesDir) {
mapImageResources(cordovaProject.root, platformResourcesDir, 'mipmap', 'ic_launcher.png'),
mapImageResources(cordovaProject.root, platformResourcesDir, 'mipmap', 'ic_launcher_foreground.png'),
mapImageResources(cordovaProject.root, platformResourcesDir, 'mipmap', 'ic_launcher_background.png'),
mapImageResources(cordovaProject.root, platformResourcesDir, 'mipmap', 'ic_launcher_monochrome.png'),
mapImageResources(cordovaProject.root, platformResourcesDir, 'mipmap', 'ic_launcher_foreground.xml'),
mapImageResources(cordovaProject.root, platformResourcesDir, 'mipmap', 'ic_launcher_background.xml'),
mapImageResources(cordovaProject.root, platformResourcesDir, 'mipmap', 'ic_launcher_monochrome.xml'),
mapImageResources(cordovaProject.root, platformResourcesDir, 'mipmap', 'ic_launcher.xml')
);
@@ -726,17 +733,23 @@ function updateIconResourceForAdaptive (preparedIcons, resourceMap, platformReso
// project's config.xml location, so we use it as base path.
let background;
let foreground;
let monochrome;
let targetPathBackground;
let targetPathForeground;
let targetPathMonochrome;
for (const density in android_icons) {
let backgroundVal = '@mipmap/ic_launcher_background';
let foregroundVal = '@mipmap/ic_launcher_foreground';
const monochromeVal = '@mipmap/ic_launcher_monochrome';
background = android_icons[density].background;
foreground = android_icons[density].foreground;
monochrome = android_icons[density].monochrome;
if (!background || !foreground) {
const isAdaptiveIcon = background && foreground;
const isMonochromeIcon = monochrome && isAdaptiveIcon;
if (!isMonochromeIcon || !isAdaptiveIcon) {
// This icon isn't an adaptive icon, so skip it
continue;
}
@@ -767,12 +780,34 @@ function updateIconResourceForAdaptive (preparedIcons, resourceMap, platformReso
resourceMap[targetPathForeground] = android_icons[density].foreground;
}
if (monochrome) {
if (path.extname(path.basename(monochrome)) === '.xml') {
// Vector Use Case
targetPathMonochrome = getAdaptiveImageResourcePath(platformResourcesDir, 'mipmap', density, 'ic_launcher_monochrome.xml', path.basename(android_icons[density].monochrome));
resourceMap[targetPathMonochrome] = android_icons[density].monochrome;
} else if (path.extname(path.basename(monochrome)) === '.png') {
// Images Use Case
targetPathMonochrome = getAdaptiveImageResourcePath(platformResourcesDir, 'mipmap', density, 'ic_launcher_monochrome.png', path.basename(android_icons[density].monochrome));
resourceMap[targetPathMonochrome] = android_icons[density].monochrome;
}
}
// create an XML for DPI and set color
const icLauncherTemplate = `<?xml version="1.0" encoding="utf-8"?>
let icLauncherTemplate = '';
if (monochrome) {
icLauncherTemplate = `<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="` + backgroundVal + `" />
<foreground android:drawable="` + foregroundVal + `" />
<monochrome android:drawable="` + monochromeVal + `" />
</adaptive-icon>`;
} else {
icLauncherTemplate = `<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="` + backgroundVal + `" />
<foreground android:drawable="` + foregroundVal + `" />
</adaptive-icon>`;
}
const launcherXmlPath = path.join(platformResourcesDir, 'mipmap-' + density + '-v26', 'ic_launcher.xml');
@@ -786,6 +821,7 @@ function updateIconResourceForAdaptive (preparedIcons, resourceMap, platformReso
if (default_icon && !android_icons.mdpi) {
let defaultTargetPathBackground;
let defaultTargetPathForeground;
let defaultTargetPathMonochrome;
if (background.startsWith('@color')) {
// Colors Use Case
@@ -812,6 +848,18 @@ function updateIconResourceForAdaptive (preparedIcons, resourceMap, platformReso
defaultTargetPathForeground = getAdaptiveImageResourcePath(platformResourcesDir, 'mipmap', 'mdpi', 'ic_launcher_foreground.png', path.basename(default_icon.foreground));
resourceMap[defaultTargetPathForeground] = default_icon.foreground;
}
if (monochrome) {
if (path.extname(path.basename(monochrome)) === '.xml') {
// Vector Use Case
defaultTargetPathMonochrome = getAdaptiveImageResourcePath(platformResourcesDir, 'mipmap', 'mdpi', 'ic_launcher_monochrome.xml', path.basename(default_icon.monochrome));
resourceMap[defaultTargetPathMonochrome] = default_icon.monochrome;
} else if (path.extname(path.basename(monochrome)) === '.png') {
// Images Use Case
defaultTargetPathMonochrome = getAdaptiveImageResourcePath(platformResourcesDir, 'mipmap', 'mdpi', 'ic_launcher_monochrome.png', path.basename(default_icon.monochrome));
resourceMap[defaultTargetPathMonochrome] = default_icon.monochrome;
}
}
}
return resourceMap;
@@ -882,6 +930,11 @@ function prepareIcons (icons) {
const favor = {};
// populating found icon.
if (icon.background && icon.foreground && icon.monochrome) {
found.background = icon.background;
found.foreground = icon.foreground;
found.monochrome = icon.monochrome;
}
if (icon.background && icon.foreground) {
found.background = icon.background;
found.foreground = icon.foreground;
@@ -890,6 +943,11 @@ function prepareIcons (icons) {
found.src = icon.src;
}
if (default_icon.background && default_icon.foreground && default_icon.monochrome) {
favor.background = default_icon.background;
favor.foreground = default_icon.foreground;
favor.monochrome = default_icon.monochrome;
}
if (default_icon.background && default_icon.foreground) {
favor.background = default_icon.background;
favor.foreground = default_icon.foreground;
@@ -908,8 +966,8 @@ function prepareIcons (icons) {
}
return {
android_icons: android_icons,
default_icon: default_icon
android_icons,
default_icon
};
}
@@ -927,8 +985,10 @@ function cleanIcons (projectRoot, projectConfig, platformResourcesDir) {
mapImageResources(projectRoot, platformResourcesDir, 'mipmap', 'ic_launcher.png'),
mapImageResources(projectRoot, platformResourcesDir, 'mipmap', 'ic_launcher_foreground.png'),
mapImageResources(projectRoot, platformResourcesDir, 'mipmap', 'ic_launcher_background.png'),
mapImageResources(projectRoot, platformResourcesDir, 'mipmap', 'ic_launcher_monochrome.png'),
mapImageResources(projectRoot, platformResourcesDir, 'mipmap', 'ic_launcher_foreground.xml'),
mapImageResources(projectRoot, platformResourcesDir, 'mipmap', 'ic_launcher_background.xml'),
mapImageResources(projectRoot, platformResourcesDir, 'mipmap', 'ic_launcher_monochrome.xml'),
mapImageResources(projectRoot, platformResourcesDir, 'mipmap', 'ic_launcher.xml')
);

View File

@@ -23,6 +23,7 @@ const build = require('./build');
const PackageType = require('./PackageType');
const AndroidManifest = require('./AndroidManifest');
const { CordovaError, events } = require('cordova-common');
const CordovaGradleConfigParserFactory = require('./config/CordovaGradleConfigParserFactory');
/**
* Builds a target spec from a runOptions object
@@ -78,6 +79,53 @@ module.exports.run = async function (runOptions = {}) {
}
const manifest = new AndroidManifest(this.locations.manifest);
const cordovaGradleConfigParser = CordovaGradleConfigParserFactory.create(this.locations.root);
return target.install(resolvedTarget, { manifest, buildResults });
return target.install(resolvedTarget, { manifest, buildResults, cordovaGradleConfigParser });
};
module.exports.listDevices = async function () {
events.emit('log', `\nAvailable ${this.platform} devices:`);
const { list } = require('./target');
await list().then(targets => {
const deviceIds = targets
.filter(({ type }) => type === 'device')
.map(({ id }) => id);
console.log(deviceIds.join('\n'));
}, function (err) {
console.error('ERROR: ' + err);
process.exit(2);
});
};
module.exports.listEmulators = async function () {
events.emit('log', `\nAvailable ${this.platform} virtual devices:`);
const emulators = require('./emulator');
await emulators.list_images().then(function (emulator_list) {
emulator_list && emulator_list.forEach(function (emu) {
console.log(emu.name);
});
}, function (err) {
console.error('ERROR: ' + err);
process.exit(2);
});
};
module.exports.runListDevices = async function (options = {}) {
const { options: cliArgs = {} } = options;
if (cliArgs?.device) {
await module.exports.listDevices.call(this);
} else if (cliArgs?.emulator) {
await module.exports.listEmulators.call(this);
} else {
await module.exports.listDevices.call(this);
await module.exports.listEmulators.call(this);
}
return true;
};

View File

@@ -127,9 +127,9 @@ exports.resolve = async (spec, buildResults) => {
};
};
exports.install = async function ({ id: target, arch, type }, { manifest, buildResults }) {
exports.install = async function ({ id: target, arch, type }, { manifest, buildResults, cordovaGradleConfigParser }) {
const apk_path = build.findBestApkForArchitecture(buildResults, arch);
const pkgName = manifest.getPackageId();
const pkgName = cordovaGradleConfigParser.getPackageName();
const launchName = pkgName + '/.' + manifest.getActivity().getName();
events.emit('log', 'Using apk: ' + apk_path);

3565
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,8 @@
{
"name": "cordova-android",
"version": "11.0.0",
"version": "12.0.0",
"description": "cordova-android release",
"types": "./types/index.d.ts",
"main": "lib/Api.js",
"repository": "github:apache/cordova-android",
"bugs": "https://github.com/apache/cordova-android/issues",
@@ -14,6 +15,7 @@
"prepare": "cordova-js build > templates/project/assets/www/cordova.js",
"test": "npm run lint && npm run cover && npm run java-unit-tests",
"lint": "eslint lib spec test \"templates/cordova/**/!(*.*)\"",
"lint:fix": "npm run lint -- --fix",
"unit-tests": "jasmine --config=spec/unit/jasmine.json",
"cover": "nyc jasmine --config=spec/coverage.json",
"e2e-tests": "jasmine --config=spec/e2e/jasmine.json",
@@ -23,29 +25,30 @@
"author": "Apache Software Foundation",
"license": "Apache-2.0",
"dependencies": {
"android-versions": "^1.7.0",
"cordova-common": "^4.0.2",
"android-versions": "^1.8.1",
"cordova-common": "^5.0.0",
"execa": "^5.1.1",
"fast-glob": "^3.2.11",
"fs-extra": "^10.1.0",
"fast-glob": "^3.2.12",
"fs-extra": "^11.1.1",
"is-path-inside": "^3.0.3",
"nopt": "^5.0.0",
"nopt": "^7.1.0",
"properties-parser": "^0.3.1",
"semver": "^7.3.7",
"semver": "^7.3.8",
"string-argv": "^0.3.1",
"untildify": "^4.0.0",
"which": "^2.0.2"
"which": "^3.0.0"
},
"devDependencies": {
"@cordova/eslint-config": "^4.0.0",
"@cordova/eslint-config": "^5.0.0",
"cordova-js": "^6.1.0",
"elementtree": "^0.1.7",
"jasmine": "^4.2.1",
"jasmine": "^4.6.0",
"jasmine-spec-reporter": "^7.0.0",
"nyc": "^15.1.0",
"rewire": "^6.0.0"
},
"engines": {
"node": ">=14.0.0"
"node": ">=16.13.0"
},
"nyc": {
"include": [

View File

@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip

View File

@@ -110,18 +110,6 @@ describe('AndroidManifest', () => {
});
});
describe('packageId', () => {
it('should get the package ID', () => {
expect(manifest.getPackageId()).toBe(PACKAGE_ID);
});
it('should set the package ID', () => {
const newPackageId = `${PACKAGE_ID}new`;
manifest.setPackageId(newPackageId);
expect(manifest.getPackageId()).toBe(newPackageId);
});
});
describe('activity', () => {
let activity;

View File

@@ -19,6 +19,8 @@
const path = require('path');
const rewire = require('rewire');
const MockCordovaGradleConfigParser = require('./mocks/config/MockCordovaGradleConfigParser');
const CordovaGradleConfigParserFactory = require('../../lib/config/CordovaGradleConfigParserFactory');
describe('AndroidProject', () => {
const PROJECT_DIR = 'platforms/android';
@@ -30,6 +32,8 @@ describe('AndroidProject', () => {
AndroidStudioSpy = jasmine.createSpyObj('AndroidStudio', ['isAndroidStudioProject']);
AndroidProject.__set__('AndroidStudio', AndroidStudioSpy);
spyOn(CordovaGradleConfigParserFactory, 'create').and.returnValue(new MockCordovaGradleConfigParser(PROJECT_DIR));
});
describe('constructor', () => {
@@ -87,26 +91,20 @@ describe('AndroidProject', () => {
});
describe('getPackageName', () => {
let AndroidManifestSpy;
let AndroidManifestFns;
let androidProject;
beforeEach(() => {
AndroidManifestFns = jasmine.createSpyObj('AndroidManifestFns', ['getPackageId']);
AndroidManifestSpy = jasmine.createSpy('AndroidManifest').and.returnValue(AndroidManifestFns);
AndroidProject.__set__('AndroidManifest', AndroidManifestSpy);
androidProject = new AndroidProject(PROJECT_DIR);
});
it('should get the package name AndroidManifest', () => {
it('should get the package name Cordova Gradle Config file', () => {
spyOn(MockCordovaGradleConfigParser.prototype, 'getPackageName');
androidProject.getPackageName();
expect(AndroidManifestSpy).toHaveBeenCalledWith(path.join(PROJECT_DIR, 'app/src/main/AndroidManifest.xml'));
expect(MockCordovaGradleConfigParser.prototype.getPackageName).toHaveBeenCalled();
});
it('should return the package name', () => {
const packageName = 'io.cordova.unittest';
AndroidManifestFns.getPackageId.and.returnValue(packageName);
expect(androidProject.getPackageName()).toBe(packageName);
});

View File

@@ -24,6 +24,8 @@ const EventEmitter = require('events');
const Api = require('../../lib/Api');
const AndroidProject = require('../../lib/AndroidProject');
const check_reqs = require('../../lib/check_reqs');
const run_mod = require('../../lib/run');
const PluginInfo = common.PluginInfo;
@@ -60,4 +62,19 @@ describe('Api', () => {
});
});
});
describe('listTargets', () => {
let api;
beforeEach(() => {
api = new Api('android', FAKE_PROJECT_DIR, new EventEmitter());
spyOn(check_reqs, 'run').and.returnValue(Promise.resolve());
});
it('should call into lib/run module', () => {
spyOn(run_mod, 'runListDevices');
return api.listTargets().then(() => {
expect(run_mod.runListDevices).toHaveBeenCalled();
});
});
});
});

116
spec/unit/build.spec.js Normal file
View File

@@ -0,0 +1,116 @@
/*
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 builders = require('../../lib/builders/builders');
describe('build', () => {
let build;
const builder = builders.getBuilder('FakeRootPath');
beforeEach(() => {
build = rewire('../../lib/build');
build.__set__({
events: jasmine.createSpyObj('eventsSpy', ['emit'])
});
// run needs `this` to behave like an Api instance
build.run = build.run.bind({
_builder: builder
});
spyOn(builder, 'build').and.returnValue(Promise.resolve({
paths: ['fake.apk'],
buildtype: 'debug'
}));
});
describe('argument parsing', () => {
let prepEnvSpy;
beforeEach(() => {
prepEnvSpy = spyOn(builder, 'prepEnv').and.returnValue(Promise.resolve());
});
describe('gradleArg', () => {
const baseOptions = {
packageType: 'apk',
arch: undefined,
prepEnv: undefined,
buildType: 'debug'
};
it('can parse single gradle argument', async () => {
await build.run({
argv: [
'node',
'--gradleArg=--stacktrace'
]
});
expect(prepEnvSpy).toHaveBeenCalledWith({
...baseOptions,
extraArgs: ['--stacktrace']
});
});
it('can parse multiple gradle arguments', async () => {
await build.run({
argv: [
'node',
'--gradleArg=--stacktrace --info'
]
});
expect(prepEnvSpy).toHaveBeenCalledWith({
...baseOptions,
extraArgs: ['--stacktrace', '--info']
});
});
it('can parse multiple gradle arguments with strings', async () => {
await build.run({
argv: [
'node',
'--gradleArg=--testArg="hello world"'
]
});
expect(prepEnvSpy).toHaveBeenCalledWith({
...baseOptions,
extraArgs: ['--testArg="hello world"']
});
});
it('gradle args will split when necessary', async () => {
await build.run({
argv: [
'node',
'--gradleArg=--warning-mode all'
]
});
expect(prepEnvSpy).toHaveBeenCalledWith({
...baseOptions,
extraArgs: ['--warning-mode', 'all']
});
});
});
});
});

View File

@@ -21,8 +21,6 @@ const fs = require('fs-extra');
const path = require('path');
const rewire = require('rewire');
const CordovaError = require('cordova-common').CordovaError;
describe('ProjectBuilder', () => {
const rootDir = '/root';
@@ -55,40 +53,40 @@ describe('ProjectBuilder', () => {
it('should set release argument', () => {
const args = builder.getArgs('release', {});
expect(args[0]).toBe('cdvBuildRelease');
expect(args[args.length - 1]).toBe('cdvBuildRelease');
});
it('should set debug argument', () => {
const args = builder.getArgs('debug', {});
expect(args[0]).toBe('cdvBuildDebug');
expect(args[args.length - 1]).toBe('cdvBuildDebug');
});
it('should set apk release', () => {
const args = builder.getArgs('release', {
packageType: 'apk'
});
expect(args[0]).withContext(args).toBe('cdvBuildRelease');
expect(args[args.length - 1]).withContext(args).toBe('cdvBuildRelease');
});
it('should set apk debug', () => {
const args = builder.getArgs('debug', {
packageType: 'apk'
});
expect(args[0]).withContext(args).toBe('cdvBuildDebug');
expect(args[args.length - 1]).withContext(args).toBe('cdvBuildDebug');
});
it('should set bundle release', () => {
const args = builder.getArgs('release', {
packageType: 'bundle'
});
expect(args[0]).withContext(args).toBe(':app:bundleRelease');
expect(args[args.length - 1]).withContext(args).toBe(':app:bundleRelease');
});
it('should set bundle debug', () => {
const args = builder.getArgs('debug', {
packageType: 'bundle'
});
expect(args[0]).withContext(args).toBe(':app:bundleDebug');
expect(args[args.length - 1]).withContext(args).toBe(':app:bundleDebug');
});
it('should add architecture if it is passed', () => {
@@ -102,14 +100,14 @@ describe('ProjectBuilder', () => {
const args = builder.getArgs('clean', {
packageType: 'apk'
});
expect(args[0]).toBe('clean');
expect(args[args.length - 1]).toBe('clean');
});
it('should clean bundle', () => {
const args = builder.getArgs('clean', {
packageType: 'bundle'
});
expect(args[0]).toBe('clean');
expect(args[args.length - 1]).toBe('clean');
});
describe('should accept extra arguments', () => {
@@ -143,30 +141,6 @@ describe('ProjectBuilder', () => {
expect(execaSpy).not.toHaveBeenCalledWith('/my/sweet/gradle', jasmine.any(Array), jasmine.any(Object));
});
});
describe('extractRealProjectNameFromManifest', () => {
it('should get the project name from the Android Manifest', () => {
const projectName = 'unittestproject';
const projectId = `io.cordova.${projectName}`;
const manifest = `<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="${projectId}"></manifest>`;
spyOn(fs, 'readFileSync').and.returnValue(manifest);
expect(builder.extractRealProjectNameFromManifest()).toBe(projectName);
});
it('should throw an error if there is no package in the Android manifest', () => {
const manifest = `<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"></manifest>`;
spyOn(fs, 'readFileSync').and.returnValue(manifest);
expect(() => builder.extractRealProjectNameFromManifest()).toThrow(jasmine.any(CordovaError));
});
});
describe('build', () => {
beforeEach(() => {
spyOn(builder, 'getArgs');
@@ -202,6 +176,7 @@ describe('ProjectBuilder', () => {
it('should reject if the spawn fails', () => {
const errorMessage = 'Test error';
execaSpy.and.rejectWith(new Error(errorMessage));
builder.getArgs.and.returnValue([]);
return builder.build({}).then(
() => fail('Unexpectedly resolved'),
@@ -218,6 +193,7 @@ describe('ProjectBuilder', () => {
ProjectBuilder.__set__('check_reqs', checkReqsSpy);
checkReqsSpy.check_android_target.and.resolveTo();
execaSpy.and.rejectWith(testError);
builder.getArgs.and.returnValue([]);
return builder.build({}).then(
() => fail('Unexpectedly resolved'),

View File

@@ -58,7 +58,7 @@ describe('check_reqs', function () {
});
describe('check_android', function () {
describe('find and set ANDROID_HOME when ANDROID_HOME and ANDROID_SDK_ROOT is not set', function () {
describe('find and set ANDROID_HOME when neither ANDROID_HOME nor ANDROID_SDK_ROOT is set', function () {
beforeEach(function () {
delete process.env.ANDROID_HOME;
delete process.env.ANDROID_SDK_ROOT;
@@ -150,32 +150,24 @@ describe('check_reqs', function () {
});
});
describe('ANDROID_SDK_ROOT environment variable detection', () => {
describe('ANDROID_HOME environment variable detection', () => {
beforeEach(() => {
delete process.env.ANDROID_SDK_ROOT;
delete process.env.ANDROID_HOME;
delete process.env.ANDROID_SDK_ROOT;
check_reqs.__set__('forgivingWhichSync', jasmine.createSpy().and.returnValue(''));
});
const expectedAndroidSdkPath = path.sep + 'android' + path.sep + 'sdk';
const expectedAndroidRootSdkPath = path.sep + 'android' + path.sep + 'sdk' + path.sep + 'root';
it('should error if neither ANDROID_SDK_ROOT or ANDROID_HOME is defined', () => {
it('should error if neither ANDROID_HOME nor ANDROID_SDK_ROOT is defined', () => {
spyOn(fs, 'existsSync').and.returnValue(true);
return check_reqs.check_android().catch((error) => {
expect(error.toString()).toContain('Failed to find \'ANDROID_SDK_ROOT\' environment variable.');
expect(error.toString()).toContain('Failed to find \'ANDROID_HOME\' environment variable.');
});
});
it('should use ANDROID_SDK_ROOT if defined', () => {
spyOn(fs, 'existsSync').and.returnValue(true);
process.env.ANDROID_SDK_ROOT = path.normalize('/android/sdk');
return check_reqs.check_android().then(() => {
expect(process.env.ANDROID_SDK_ROOT).toContain(expectedAndroidSdkPath);
});
});
it('should use ANDROID_HOME if defined and ANDROID_SDK_ROOT is not defined', () => {
it('should use ANDROID_HOME if defined', () => {
spyOn(fs, 'existsSync').and.returnValue(true);
process.env.ANDROID_HOME = path.normalize('/android/sdk');
return check_reqs.check_android().then(() => {
@@ -183,15 +175,23 @@ describe('check_reqs', function () {
});
});
it('should use ANDROID_HOME if defined and ANDROID_SDK_ROOT is defined', () => {
it('should use ANDROID_SDK_ROOT if defined and ANDROID_HOME is not defined', () => {
spyOn(fs, 'existsSync').and.returnValue(true);
process.env.ANDROID_SDK_ROOT = path.normalize('/android/sdk/root');
process.env.ANDROID_HOME = path.normalize('/android/sdk');
return check_reqs.check_android().then(() => {
expect(process.env.ANDROID_SDK_ROOT).toContain(expectedAndroidRootSdkPath);
});
});
it('should use ANDROID_HOME if defined and ANDROID_SDK_ROOT is defined', () => {
spyOn(fs, 'existsSync').and.returnValue(true);
process.env.ANDROID_HOME = path.normalize('/android/sdk');
process.env.ANDROID_SDK_ROOT = path.normalize('/android/sdk/root');
return check_reqs.check_android().then(() => {
expect(process.env.ANDROID_HOME).toContain(expectedAndroidSdkPath);
});
});
it('should throw if ANDROID_HOME points to an invalid path', () => {
process.env.ANDROID_HOME = path.normalize('/android/sdk');
return check_reqs.check_android().catch((error) => {
@@ -219,30 +219,30 @@ describe('check_reqs', function () {
describe('check_gradle', () => {
describe('environment variable checks', () => {
beforeEach(() => {
delete process.env.ANDROID_SDK_ROOT;
delete process.env.ANDROID_HOME;
delete process.env.ANDROID_SDK_ROOT;
spyOn(check_reqs, 'get_gradle_wrapper').and.callFake(() => {
return path.normalize((process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT) + '/bin/gradle');
});
});
it('with ANDROID_SDK_ROOT / without ANDROID_HOME', async () => {
it('with ANDROID_HOME / without ANDROID_SDK_ROOT', async () => {
process.env.ANDROID_HOME = path.normalize('/android/sdk/home');
await expectAsync(check_reqs.check_gradle()).toBeResolvedTo(path.normalize('/android/sdk/home/bin/gradle'));
});
it('without ANDROID_HOME / with ANDROID_SDK_ROOT', async () => {
process.env.ANDROID_SDK_ROOT = path.normalize('/android/sdk/root');
await expectAsync(check_reqs.check_gradle()).toBeResolvedTo(path.normalize('/android/sdk/root/bin/gradle'));
});
it('with ANDROID_SDK_ROOT / with ANDROID_HOME', async () => {
it('with ANDROID_HOME / with ANDROID_SDK_ROOT', async () => {
process.env.ANDROID_HOME = path.normalize('/android/sdk/home');
process.env.ANDROID_SDK_ROOT = path.normalize('/android/sdk/root');
process.env.ANDROID_HOME = path.normalize('/android/sdk/home');
await expectAsync(check_reqs.check_gradle()).toBeResolvedTo(path.normalize('/android/sdk/home/bin/gradle'));
});
it('without ANDROID_SDK_ROOT / with ANDROID_HOME', async () => {
process.env.ANDROID_HOME = path.normalize('/android/sdk/home');
await expectAsync(check_reqs.check_gradle()).toBeResolvedTo(path.normalize('/android/sdk/home/bin/gradle'));
});
it('without ANDROID_SDK_ROOT / without ANDROID_HOME', () => {
it('without ANDROID_HOME / without ANDROID_SDK_ROOT', () => {
return check_reqs.check_gradle().catch((error) => {
expect(error.toString()).toContain('Could not find gradle wrapper within Android SDK. Could not find Android SDK directory.');
});
@@ -250,7 +250,7 @@ describe('check_reqs', function () {
});
it('should error if sdk is installed but no gradle found', () => {
process.env.ANDROID_SDK_ROOT = path.normalize('/android/sdk');
process.env.ANDROID_HOME = path.normalize('/android/sdk');
spyOn(check_reqs, 'get_gradle_wrapper').and.callFake(() => {
return '';
});

View File

@@ -23,8 +23,16 @@ const create = rewire('../../lib/create');
const check_reqs = require('../../lib/check_reqs');
const fs = require('fs-extra');
const path = require('path');
const MockCordovaGradleConfigParser = require('./mocks/config/MockCordovaGradleConfigParser');
const CordovaGradleConfigParserFactory = require('../../lib/config/CordovaGradleConfigParserFactory');
describe('create', function () {
const PROJECT_DIR = 'platforms/android';
beforeAll(() => {
spyOn(CordovaGradleConfigParserFactory, 'create').and.returnValue(new MockCordovaGradleConfigParser(PROJECT_DIR));
});
describe('validatePackageName helper method', function () {
describe('happy path (valid package names)', function () {
const valid = [

View File

@@ -0,0 +1,32 @@
/**
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 CordovaGradleConfigParser = require('../../../../lib/config/CordovaGradleConfigParser');
module.exports = class MockCordoCordovaGradleConfigParservaGradleConfigParser extends CordovaGradleConfigParser {
_readConfig (configPath) {
return {
PACKAGE_NAMESPACE: 'io.cordova.unittest'
};
}
write () {
// Pretend write :)
}
};

View File

@@ -32,6 +32,9 @@ const android_studio_project = path.join(__dirname, '../../fixtures/android_stud
const PluginInfo = require('cordova-common').PluginInfo;
const AndroidProject = require('../../../lib/AndroidProject');
const MockCordovaGradleConfigParser = require('../mocks/config/MockCordovaGradleConfigParser');
const CordovaGradleConfigParserFactory = require('../../../lib/config/CordovaGradleConfigParserFactory');
const dummyPluginInfo = new PluginInfo(dummyplugin);
const valid_source = dummyPluginInfo.getSourceFiles('android');
const valid_resources = dummyPluginInfo.getResourceFiles('android');
@@ -41,6 +44,12 @@ const faultyPluginInfo = new PluginInfo(faultyplugin);
const invalid_source = faultyPluginInfo.getSourceFiles('android');
describe('android project handler', function () {
const PROJECT_DIR = 'platforms/android';
beforeAll(() => {
spyOn(CordovaGradleConfigParserFactory, 'create').and.returnValue(new MockCordovaGradleConfigParser(PROJECT_DIR));
});
describe('installation', function () {
const copyFileOrig = common.__get__('copyFile');
const copyFileSpy = jasmine.createSpy('copyFile');

View File

@@ -23,6 +23,8 @@ const CordovaError = require('cordova-common').CordovaError;
const GradlePropertiesParser = require('../../lib/config/GradlePropertiesParser');
const utils = require('../../lib/utils');
const et = require('elementtree');
const MockCordovaGradleConfigParser = require('./mocks/config/MockCordovaGradleConfigParser');
const CordovaGradleConfigParserFactory = require('../../lib/config/CordovaGradleConfigParserFactory');
const PATH_RESOURCE = path.join('platforms', 'android', 'app', 'src', 'main', 'res');
@@ -51,8 +53,10 @@ function createResourceMap (target) {
if (!target || target === 'ic_launcher.png') resources[path.join(PATH_RESOURCE, mipmap, 'ic_launcher.png')] = null;
if (!target || target === 'ic_launcher_foreground.png') resources[path.join(PATH_RESOURCE, mipmap, 'ic_launcher_foreground.png')] = null;
if (!target || target === 'ic_launcher_background.png') resources[path.join(PATH_RESOURCE, mipmap, 'ic_launcher_background.png')] = null;
if (!target || target === 'ic_launcher_background.png') resources[path.join(PATH_RESOURCE, mipmap, 'ic_launcher_monochrome.png')] = null;
if (!target || target === 'ic_launcher_foreground.xml') resources[path.join(PATH_RESOURCE, mipmap, 'ic_launcher_foreground.xml')] = null;
if (!target || target === 'ic_launcher_background.xml') resources[path.join(PATH_RESOURCE, mipmap, 'ic_launcher_background.xml')] = null;
if (!target || target === 'ic_launcher_background.xml') resources[path.join(PATH_RESOURCE, mipmap, 'ic_launcher_monochrome.xml')] = null;
if (
!mipmap.includes('-v26') &&
@@ -91,6 +95,12 @@ describe('prepare', () => {
let emitSpy;
let updatePathsSpy;
const PROJECT_DIR = 'platforms/android';
beforeAll(() => {
spyOn(CordovaGradleConfigParserFactory, 'create').and.returnValue(new MockCordovaGradleConfigParser(PROJECT_DIR));
});
beforeEach(() => {
prepare = rewire('../../lib/prepare');
@@ -136,10 +146,14 @@ describe('prepare', () => {
return createResourceMap('ic_launcher_foreground.png');
} else if (resourceName.includes('ic_launcher_background.png')) {
return createResourceMap('ic_launcher_background.png');
} else if (resourceName.includes('ic_launcher_monochrome.png')) {
return createResourceMap('ic_launcher_monochrome.png');
} else if (resourceName.includes('ic_launcher_foreground.xml')) {
return createResourceMap('ic_launcher_foreground.xml');
} else if (resourceName.includes('ic_launcher_background.xml')) {
return createResourceMap('ic_launcher_background.xml');
} else if (resourceName.includes('ic_launcher_monochrome.xml')) {
return createResourceMap('ic_launcher_monochrome.xml');
} else if (resourceName.includes('ic_launcher.xml')) {
return createResourceMap('ic_launcher.xml');
}
@@ -305,7 +319,9 @@ describe('prepare', () => {
return [mockGetIconItem({
density: 'mdpi',
background: 'res/icon/android/mdpi-background.png',
foreground: 'res/icon/android/mdpi-foreground.xml'
foreground: 'res/icon/android/mdpi-foreground.xml',
monochrome: 'res/icon/android/mdpi-monochrome.png'
})];
};
@@ -343,7 +359,8 @@ describe('prepare', () => {
return [mockGetIconItem({
density: 'mdpi',
background: 'res/icon/android/mdpi-background.png',
foreground: 'res/icon/android/mdpi-foreground.png'
foreground: 'res/icon/android/mdpi-foreground.png',
monochrome: 'res/icon/android/mdpi-monochrome.png'
})];
};
@@ -352,6 +369,7 @@ describe('prepare', () => {
const phaseOneModification = {};
phaseOneModification[path.join(PATH_RESOURCE, 'mipmap-mdpi-v26', 'ic_launcher_foreground.png')] = 'res/icon/android/mdpi-foreground.png';
phaseOneModification[path.join(PATH_RESOURCE, 'mipmap-mdpi-v26', 'ic_launcher_background.png')] = 'res/icon/android/mdpi-background.png';
phaseOneModification[path.join(PATH_RESOURCE, 'mipmap-mdpi-v26', 'ic_launcher_monochrome.png')] = 'res/icon/android/mdpi-monochrome.png';
const phaseOneUpdatedIconsForAdaptive = Object.assign({}, resourceMap, phaseOneModification);
updateIconResourceForAdaptiveSpy = jasmine.createSpy('updateIconResourceForAdaptiveSpy');
@@ -363,6 +381,7 @@ describe('prepare', () => {
const phaseTwoModification = {};
phaseTwoModification[path.join(PATH_RESOURCE, 'mipmap-mdpi', 'ic_launcher.png')] = 'res/icon/android/mdpi-foreground.png';
phaseTwoModification[path.join(PATH_RESOURCE, 'mipmap-mdpi-v26', 'ic_launcher_background.png')] = 'res/icon/android/mdpi-background.png';
phaseTwoModification[path.join(PATH_RESOURCE, 'mipmap-mdpi-v26', 'ic_launcher_monochrome.png')] = 'res/icon/android/mdpi-monochrome.png';
const phaseTwoUpdatedIconsForLegacy = Object.assign({}, phaseOneUpdatedIconsForAdaptive, phaseTwoModification);
updateIconResourceForLegacySpy = jasmine.createSpy('updateIconResourceForLegacySpy');
@@ -400,7 +419,8 @@ describe('prepare', () => {
density: 'mdpi',
src: 'res/icon/android/mdpi-icon.png',
background: 'res/icon/android/mdpi-background.png',
foreground: 'res/icon/android/mdpi-foreground.png'
foreground: 'res/icon/android/mdpi-foreground.png',
monochrome: 'res/icon/android/mdpi-monochrome.png'
})];
};
@@ -409,6 +429,7 @@ describe('prepare', () => {
const phaseOneModification = {};
phaseOneModification[path.join(PATH_RESOURCE, 'mipmap-mdpi-v26', 'ic_launcher_foreground.png')] = 'res/icon/android/mdpi-foreground.png';
phaseOneModification[path.join(PATH_RESOURCE, 'mipmap-mdpi-v26', 'ic_launcher_background.png')] = 'res/icon/android/mdpi-background.png';
phaseOneModification[path.join(PATH_RESOURCE, 'mipmap-mdpi-v26', 'ic_launcher_monochrome.png')] = 'res/icon/android/mdpi-monochrome.png';
const phaseOneUpdatedIconsForAdaptive = Object.assign({}, resourceMap, phaseOneModification);
updateIconResourceForAdaptiveSpy = jasmine.createSpy('updateIconResourceForAdaptiveSpy');
@@ -420,6 +441,7 @@ describe('prepare', () => {
const phaseTwoModification = {};
phaseTwoModification[path.join(PATH_RESOURCE, 'mipmap-mdpi', 'ic_launcher.png')] = 'res/icon/android/mdpi-foreground.png';
phaseTwoModification[path.join(PATH_RESOURCE, 'mipmap-mdpi-v26', 'ic_launcher_background.png')] = 'res/icon/android/mdpi-background.png';
phaseTwoModification[path.join(PATH_RESOURCE, 'mipmap-mdpi-v26', 'ic_launcher_monochrome.png')] = 'res/icon/android/mdpi-monochrome.png';
const phaseTwoUpdatedIconsForLegacy = Object.assign({}, phaseOneUpdatedIconsForAdaptive, phaseTwoModification);
updateIconResourceForLegacySpy = jasmine.createSpy('updateIconResourceForLegacySpy');
@@ -511,13 +533,15 @@ describe('prepare', () => {
const ldpi = mockGetIconItem({
density: 'ldpi',
background: 'res/icon/android/ldpi-background.png',
foreground: 'res/icon/android/ldpi-foreground.png'
foreground: 'res/icon/android/ldpi-foreground.png',
monochrome: 'res/icon/android/ldpi-monochrome.png'
});
const mdpi = mockGetIconItem({
density: 'mdpi',
background: 'res/icon/android/mdpi-background.png',
foreground: 'res/icon/android/mdpi-foreground.png'
foreground: 'res/icon/android/mdpi-foreground.png',
monochrome: 'res/icon/android/mdpi-monochrome.png'
});
const icons = [ldpi, mdpi];
@@ -614,7 +638,8 @@ describe('prepare', () => {
mdpi: mockGetIconItem({
density: 'mdpi',
background: 'res/icon/android/mdpi-background.png',
foreground: 'res/icon/android/mdpi-foreground.png'
foreground: 'res/icon/android/mdpi-foreground.png',
monochrome: 'res/icon/android/mdpi-monochrome.png'
})
},
default_icon: undefined
@@ -636,6 +661,7 @@ describe('prepare', () => {
const expectedModification = {};
expectedModification[path.join(PATH_RESOURCE, 'mipmap-mdpi-v26', 'ic_launcher_background.png')] = 'res/icon/android/mdpi-background.png';
expectedModification[path.join(PATH_RESOURCE, 'mipmap-mdpi-v26', 'ic_launcher_foreground.png')] = 'res/icon/android/mdpi-foreground.png';
expectedModification[path.join(PATH_RESOURCE, 'mipmap-mdpi-v26', 'ic_launcher_monochrome.png')] = 'res/icon/android/mdpi-monochrome.png';
const expected = Object.assign({}, resourceMap, expectedModification);
const actual = updateIconResourceForAdaptive(preparedIcons, resourceMap, platformResourcesDir);
@@ -668,7 +694,8 @@ describe('prepare', () => {
const icons = [mockGetIconItem({
density: 'mdpi',
background: 'res/icon/android/mdpi-background.png',
foreground: 'res/icon/android/mdpi-foreground.png'
foreground: 'res/icon/android/mdpi-foreground.png',
monochrome: 'res/icon/android/mdpi-monochrome.png'
})];
const projectRoot = '/mock';
const projectConfig = {
@@ -893,9 +920,7 @@ describe('prepare', () => {
}),
setVersionName: jasmine.createSpy('setVersionName').and.returnValue({
setVersionCode: jasmine.createSpy('setVersionCode').and.returnValue({
setPackageId: jasmine.createSpy('setPackageId').and.returnValue({
write: jasmine.createSpy('write')
})
write: jasmine.createSpy('write')
})
})
}));

View File

@@ -19,10 +19,19 @@
const rewire = require('rewire');
const builders = require('../../lib/builders/builders');
const MockCordovaGradleConfigParser = require('./mocks/config/MockCordovaGradleConfigParser');
const CordovaGradleConfigParser = require('../../lib/config/CordovaGradleConfigParser');
const CordovaGradleConfigParserFactory = require('../../lib/config/CordovaGradleConfigParserFactory');
describe('run', () => {
let run;
const PROJECT_DIR = 'platforms/android';
beforeAll(() => {
spyOn(CordovaGradleConfigParserFactory, 'create').and.returnValue(new MockCordovaGradleConfigParser(PROJECT_DIR));
});
beforeEach(() => {
run = rewire('../../lib/run');
run.__set__({
@@ -84,7 +93,8 @@ describe('run', () => {
buildResults: {
buildType: 'debug',
apkPaths: ['fake.apk']
}
},
cordovaGradleConfigParser: jasmine.any(CordovaGradleConfigParser)
}
);
});
@@ -96,4 +106,32 @@ describe('run', () => {
.toBeRejectedWithError(/Package type "bundle" is not supported/);
});
});
describe('--list option', () => {
beforeEach(() => {
spyOn(run, 'listDevices').and.returnValue(Promise.resolve());
spyOn(run, 'listEmulators').and.returnValue(Promise.resolve());
});
it('should delegate to "listDevices" when the "runListDevices" method options param contains "options.device".', () => {
return run.runListDevices({ options: { device: true } }).then(() => {
expect(run.listDevices).toHaveBeenCalled();
expect(run.listEmulators).not.toHaveBeenCalled();
});
});
it('should delegate to "listDevices" when the "runListDevices" method options param contains "options.emulator".', () => {
return run.runListDevices({ options: { emulator: true } }).then(() => {
expect(run.listDevices).not.toHaveBeenCalled();
expect(run.listEmulators).toHaveBeenCalled();
});
});
it('should delegate to both "listEmulators" and "listDevices" when the "runListDevices" method does not contain "options.device" or "options.emulator".', () => {
return run.runListDevices({ options: {} }).then(() => {
expect(run.listDevices).toHaveBeenCalled();
expect(run.listEmulators).toHaveBeenCalled();
});
});
});
});

View File

@@ -19,10 +19,18 @@
const rewire = require('rewire');
const { CordovaError } = require('cordova-common');
const MockCordovaGradleConfigParser = require('./mocks/config/MockCordovaGradleConfigParser');
const CordovaGradleConfigParserFactory = require('../../lib/config/CordovaGradleConfigParserFactory');
describe('target', () => {
let target;
const PROJECT_DIR = 'platforms/android';
beforeAll(() => {
spyOn(CordovaGradleConfigParserFactory, 'create').and.returnValue(new MockCordovaGradleConfigParser(PROJECT_DIR));
});
beforeEach(() => {
target = rewire('../../lib/target');
});
@@ -228,14 +236,18 @@ describe('target', () => {
describe('install', () => {
let AdbSpy;
let buildSpy;
let installTarget, manifest, appSpec;
let installTarget, manifest, cordovaGradleConfigParser, appSpec;
beforeEach(() => {
installTarget = { id: 'emulator-5556', type: 'emulator', arch: 'atari' };
manifest = jasmine.createSpyObj('manifestStub', ['getPackageId', 'getActivity']);
manifest = jasmine.createSpyObj('manifestStub', ['getActivity']);
manifest.getActivity.and.returnValue(jasmine.createSpyObj('Activity', ['getName']));
appSpec = { manifest, buildResults: {} };
cordovaGradleConfigParser = jasmine.createSpyObj('cordovaGradleConfigParserStub', ['getPackageName']);
cordovaGradleConfigParser.getPackageName.and.returnValue('unittestapp');
appSpec = { manifest, buildResults: {}, cordovaGradleConfigParser };
buildSpy = jasmine.createSpyObj('build', ['findBestApkForArchitecture']);
target.__set__('build', buildSpy);
@@ -267,7 +279,7 @@ describe('target', () => {
const apkPath = 'my/apk/path/app.apk';
buildSpy.findBestApkForArchitecture.and.returnValue(apkPath);
return target.install(installTarget, { manifest, buildResults }).then(() => {
return target.install(installTarget, { manifest, buildResults, cordovaGradleConfigParser: CordovaGradleConfigParserFactory.create(PROJECT_DIR) }).then(() => {
expect(buildSpy.findBestApkForArchitecture).toHaveBeenCalledWith(buildResults, installTarget.arch);
expect(AdbSpy.install.calls.argsFor(0)[1]).toBe(apkPath);
@@ -308,7 +320,7 @@ describe('target', () => {
it('should start the newly installed app on the device', () => {
const packageId = 'unittestapp';
const activityName = 'TestActivity';
manifest.getPackageId.and.returnValue(packageId);
cordovaGradleConfigParser.getPackageName.and.returnValue(packageId);
manifest.getActivity().getName.and.returnValue(activityName);
return target.install(installTarget, appSpec).then(() => {

View File

@@ -18,7 +18,9 @@
under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="__PACKAGE__" android:versionName="1.0" android:versionCode="1" android:hardwareAccelerated="true">
android:versionName="1.0"
android:versionCode="1"
android:hardwareAccelerated="true">
<supports-screens
android:largeScreens="true"
android:normalScreens="true"

View File

@@ -179,9 +179,11 @@ task cdvPrintProps {
}
android {
namespace cordovaConfig.PACKAGE_NAMESPACE
defaultConfig {
versionCode cdvVersionCode ?: new BigInteger("" + privateHelpers.extractIntFromManifest("versionCode"))
applicationId privateHelpers.extractStringFromManifest("package")
applicationId cordovaConfig.PACKAGE_NAMESPACE
minSdkVersion cordovaConfig.MIN_SDK_VERSION
if (cordovaConfig.MAX_SDK_VERSION != null) {

View File

@@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background" />
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
</adaptive-icon>
<monochrome android:drawable="@mipmap/ic_launcher_monochrome" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background" />
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
</adaptive-icon>
<monochrome android:drawable="@mipmap/ic_launcher_monochrome" />
</adaptive-icon>

View File

@@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background" />
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
</adaptive-icon>
<monochrome android:drawable="@mipmap/ic_launcher_monochrome" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 916 B

View File

@@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background" />
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
</adaptive-icon>
<monochrome android:drawable="@mipmap/ic_launcher_monochrome" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background" />
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
</adaptive-icon>
<monochrome android:drawable="@mipmap/ic_launcher_monochrome" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background" />
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
</adaptive-icon>
<monochrome android:drawable="@mipmap/ic_launcher_monochrome" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -24,6 +24,8 @@ android {
compileSdkVersion cordovaConfig.COMPILE_SDK_VERSION
buildToolsVersion cordovaConfig.BUILD_TOOLS_VERSION
namespace 'org.apache.cordova.unittests'
defaultConfig {
applicationId "org.apache.cordova.unittests"
minSdkVersion cordovaConfig.MIN_SDK_VERSION

View File

@@ -17,8 +17,7 @@
specific language governing permissions and limitations
under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.apache.cordova.unittests">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"

26
types/index.d.ts vendored Normal file
View File

@@ -0,0 +1,26 @@
/**
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.
*/
interface Navigator {
/** This plugin displays and hides a splash screen during application launch. */
splashscreen: {
/** Dismiss the splash screen. */
hide(): void;
}
}