Compare commits

...

43 Commits

Author SHA1 Message Date
João Gonçalves
eaca570cb6 fix: opaque navigation bar in edge to edge (#1867) 2025-12-19 20:58:02 +09:00
Norman Breau
76bac55fba fix: cordova requirements command and SDK lookup based on tools (#1877)
* fix: cordova requirements command and SDK lookup based on tools

* Update spec/unit/AndroidCommandLineTools.spec.js

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

* Update lib/env/AndroidCommandLineTools.js

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

* Update lib/env/AndroidCommandLineTools.js

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

* Update lib/env/AndroidCommandLineTools.js

---------

Co-authored-by: エリス <erisu@users.noreply.github.com>
2025-12-18 09:23:01 -04:00
Manuel Beck
6b76757c80 CallbackContext: Add success method for boolean (#1864)
- Add helper method for success callbacks that returns `Status.OK` with a boolean
2025-12-17 10:31:26 +01:00
Norman Breau
fb562f4ed0 fix(windows): Escape back-slashes for gradle config jdk path (#1876) 2025-12-16 22:31:10 -04:00
Manuel Beck
eaf875b0d6 chore(readme): added nightly build section (#1873)
Added section on installing nightly builds of Cordova Android for testing purposes, with warnings about stability and production use.
2025-12-04 00:52:18 +01:00
エリス
d7afba0ad0 chore: update release audit workflow & license headers (#1870) 2025-11-23 13:04:10 +09:00
dependabot[bot]
ebe6890dee chore(deps-dev): bump js-yaml from 4.1.0 to 4.1.1 (#1869)
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.0 to 4.1.1.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.1.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-18 18:41:35 -08:00
dependabot[bot]
172b8448af chore(deps): bump glob from 10.4.5 to 10.5.0 (#1868)
Bumps [glob](https://github.com/isaacs/node-glob) from 10.4.5 to 10.5.0.
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v10.4.5...v10.5.0)

---
updated-dependencies:
- dependency-name: glob
  dependency-version: 10.5.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-18 18:19:35 -08:00
エリス
655aa0a5fb fix: re-expose and support pollOnce (#1854) 2025-10-27 12:40:49 +09:00
エリス
52578ae770 fix(statusbar): inject script block to compute color & replace padStart w/ logic supported on SDK 24 (#1853) 2025-10-27 10:46:24 +09:00
João Gonçalves
360be21ea5 fix: edge to edge/fullscreen margins (#1847)
Ensures that when in edge to edge or fullscreen modes the values for
margin left and right are 0

Co-authored-by: Kepa Totorica <kepa.baum.totorica@outsystems.com>
2025-10-09 11:47:23 +09:00
André Sousa
5bca218e5b feat: Allows additional settings to be included on settings.gradle (#1819) 2025-09-27 02:24:26 +09:00
Manuel Beck
eb5fe4fbda fix: Deprecation warning in PluginManager for using Class.newInstance (#1823)
- Calling Class.newInstance is deprecated. Instead Class.getDeclaredConstructor().newInstance() should be called
2025-09-27 02:23:24 +09:00
Norman Breau
46af3114b8 fix: Back button override on API 36+ (#1831) 2025-09-27 02:22:22 +09:00
エリス
76aa938002 feat!: support previous non-E2E (#1817)
* feat: support previous non-e2e (add FrameLayout wrapper)
* feat: implement internal SystemBar plugin
* feat: implement StatusBar plugin JS API (SystemBarPlugin)
* feat!: force custom statusbarView for all SDKs
* chore: various cleanup, refactors, fixes, and docs from recent changes
* feat: use getComputedStyle for setBackgroundColor
* chore: suppress deprecation warnings for method using setNavigationBarColor
* chore: return null when rootView is null
* fix: setOnApplyWindowInsetsListener to return insets
* fix: setting appearance when e2e is enabled
* fix: set statusBarColor to transparent, use new statusBar UI
2025-09-27 02:21:17 +09:00
エリス
7d7f511023 feat: replace nyc with c8 (#1844) 2025-09-12 00:46:39 +09:00
エリス
488c498733 chore!: update dependencies (#1843)
* chore!: bump rewire@9.0.1
* chore: bump jasmine@5.10.0
* chore: bump dedent@1.7.0
* chore: tmp@0.2.5
* chore: rebuilt package-lock.json
* ci: update workflow dependencies
2025-09-11 17:42:38 +09:00
エリス
e4457f7fdb chore!: update template defaults (#1837)
* chore: update default package id
* chore: sync cordova-app-hello-world defaults & modified for Android differences
2025-08-19 13:55:18 +09:00
エリス
8742cfe4a6 chore(npm)!: bump cordova-common@6.0.0 w/ rebuilt package-lock.json (#1835)
* chore(npm)!: bump cordova-common@6.0.0
* chore(npm): rebuilt package-lock.json
2025-08-09 13:01:06 +09:00
dependabot[bot]
af1ae68a97 chore(deps-dev): bump tmp from 0.2.3 to 0.2.4 (#1834)
Bumps [tmp](https://github.com/raszi/node-tmp) from 0.2.3 to 0.2.4.
- [Changelog](https://github.com/raszi/node-tmp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/raszi/node-tmp/compare/v0.2.3...v0.2.4)

---
updated-dependencies:
- dependency-name: tmp
  dependency-version: 0.2.4
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-08 12:52:32 +09:00
エリス
56afb70894 dep(npm): bump @cordova/eslint-config@6.0.0 (#1830) 2025-07-25 23:14:25 +09:00
エリス
1204a79378 ci: use macos-15 (#1829) 2025-07-25 01:16:44 +09:00
エリス
bf0ba3dd5e ci(workflow): update release-audit & license config (#1828) 2025-07-20 12:29:07 +09:00
エリス
c2cf589d84 feat: allow disabling splash screen for embedded Cordova (#1824) 2025-07-16 14:34:03 +09:00
Norman Breau
0190d2e33a feat: AndroidShowDeprecations preference flag (#1822) 2025-07-09 12:21:28 +09:00
Manuel Beck
df36c7a2c4 fix: gradle deprecation warnings about property assignment (#1821)
When building a plain Android Cordova app without plugins, there will be Gradle depraction warnings: `Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.`. Compiling it with `--warning-mode all` shows, that some properties are assigned without an `=`, which will not be supported any longer with Gradle 10.0.
2025-07-09 12:18:02 +09:00
stephan-kutzner
b79232612b fix: replace deprecated fs.F_OK with fs.constants.F_OK (#1820)
This addresses the warning DEP0176 introduces in Node.js v20.8.0
2025-07-08 01:41:36 -03:00
エリス
60d2842024 feat!: add partial night & day theme support (#1818)
* feat!: partial night & day theme support
* feat!: prefix core resource values files with cdv_
2025-07-07 22:31:54 +09:00
エリス
cab5c5b7ec fix: apply repositories.gradle for cordova.gradle dependencies (#1816) 2025-07-05 13:41:58 +09:00
エリス
484c60e483 test(plugins): remove old deprecated android_studio option (#1815) 2025-06-24 12:06:00 +09:00
エリス
72e7148884 feat!: bump kotlin@2.1.21 (#1814) 2025-06-24 11:58:49 +09:00
エリス
36bee66493 feat: androidx.appcompat:appcompat@1.7.1 (#1813) 2025-06-24 11:43:45 +09:00
エリス
4dcfc361d2 feat: androidx.webkit:webkit@1.14.0 (#1812) 2025-06-24 11:22:47 +09:00
エリス
c25ed27828 feat!: bump Gradle@8.14.2 & AGP@8.10.1 (#1811) 2025-06-24 10:57:25 +09:00
エリス
d8f6f37737 feat!: bump sdk & build tools to 36 (#1810) 2025-06-24 10:42:10 +09:00
エリス
f0e8885693 fix(plugins): remove from platformWWW by default (#1807) 2025-06-16 21:29:33 +09:00
エリス
5dc9c72821 fix(plugins): rename & match removeFileF logic with other platforms (#1806) 2025-06-16 21:29:02 +09:00
エリス
7a47fe01dc feat!: remove unused getASPath.bat file (#1808) 2025-06-16 13:13:39 +09:00
エリス
08b8a95416 fix(plugins): handle uninstalling multiple plugin assets (#1805) 2025-06-16 13:11:04 +09:00
エリス
6024465814 ci: update workflow - added node 24, permission scoping & codecov pinning (#1804) 2025-06-16 12:35:09 +09:00
エリス
6b8e819f21 ci(release-audit): use latest apache-rat-action (#1809) 2025-06-16 12:34:39 +09:00
エリス
00744c4f71 chore: bump 15.0.0-dev (#1803) 2025-06-16 12:34:19 +09:00
Erisu
872d98876e chore: bump version 14.0.2-dev 2025-04-24 13:05:52 +09:00
57 changed files with 2109 additions and 2446 deletions

View File

@@ -1,3 +0,0 @@
templates/project/assets/www/cordova.js
test/android/app
test/androidx/app

View File

@@ -1,27 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
extends: '@cordova/eslint-config/node'
overrides:
- files: [spec/**/*.js]
extends: '@cordova/eslint-config/node-tests'
rules:
prefer-promise-reject-errors: off
- files: [cordova-js-src/**/*.js]
extends: '@cordova/eslint-config/browser'

17
.gitattributes vendored
View File

@@ -1,3 +1,20 @@
# 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.
* text eol=lf
# source code

View File

@@ -6,12 +6,12 @@
# "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
# 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
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
@@ -25,6 +25,10 @@ on:
branches:
- '*'
permissions:
contents: read
security-events: write
jobs:
test:
name: NodeJS ${{ matrix.node-version }} on ${{ matrix.os }}
@@ -33,16 +37,16 @@ jobs:
strategy:
matrix:
node-version: [20.x, 22.x]
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [20.x, 22.x, 24.x]
os: [ubuntu-latest, windows-latest, macos-15]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: ${{ matrix.node-version }}
- uses: actions/setup-java@v4
- uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '17'
@@ -77,7 +81,8 @@ jobs:
- uses: github/codeql-action/analyze@v3
- uses: codecov/codecov-action@v4
# v4.6.0
- uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238
if: success()
with:
name: ${{ runner.os }} node.js ${{ matrix.node-version }}

View File

@@ -6,12 +6,12 @@
# "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
# 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
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
@@ -25,27 +25,31 @@ on:
branches:
- '*'
permissions:
contents: read
jobs:
test:
name: Audit Licenses
runs-on: ubuntu-latest
steps:
# Checkout project
- uses: actions/checkout@v4
- uses: actions/checkout@v6
# Check license headers
- uses: erisu/apache-rat-action@555ae80334a535eb6c1f8920b121563a5a985a75
# Check license headers (v2.0.0)
- uses: erisu/apache-rat-action@46fb01ce7d8f76bdcd7ab10e7af46e1ea95ca01c
# Setup environment with node
- uses: actions/setup-node@v4
- uses: actions/setup-node@v6
with:
node-version: 20
node-version: 24
# Install node packages
- name: npm install packages
run: npm i
run: npm ci
# Check node package licenses
- uses: erisu/license-checker-action@e929758f9416f30234ac454fc9054ca4b803871d
# Check node package licenses (v2.0.1)
- uses: erisu/license-checker-action@99cffa11264fe545fd0baa6c13bca5a00ae608f2
with:
license-config: 'licence_checker.yml'
include-asf-category-a: true

20
.gitignore vendored
View File

@@ -1,3 +1,20 @@
# 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.
.DS_Store
.gradle
.metadata
@@ -30,6 +47,8 @@ example
/test/.externalNativeBuild
/test/androidx/cdv-gradle-config.json
/test/androidx/repositories.gradle
/test/androidx/app/repositories.gradle
/test/androidx/tools
/test/androidx/build
/test/assets/www/.tmp*
@@ -47,7 +66,6 @@ tmp/**/*
npm-debug.log
node_modules/
coverage/
.nyc_output/
# Eclipse Buildship files
.project
.settings

View File

@@ -1,3 +1,20 @@
# 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.
.*
coverage
test

18
.npmrc
View File

@@ -1,2 +1,18 @@
registry=https://registry.npmjs.org
# 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.
registry=https://registry.npmjs.org

View File

@@ -1,13 +1,27 @@
\.(.*)
(.*).txt
coverage
fixtures
generated
gitignore
intermediates
reports
test-results
node_modules
gradle
gradlew
gradlew.bat
# 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.
.git/
coverage/
framework/build/
node_modules/
spec/fixtures/
templates/project/gitignore
test/androidx/app/.gitignore
test/androidx/app/build/
test/androidx/build/
test/androidx/tools/

View File

@@ -1,3 +1,20 @@
# 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.
#
# Settings for post-review (used for uploading diffs to reviews.apache.org).
#

View File

@@ -66,6 +66,20 @@ npm install
npm test
```
## Install Nightly Build
```bash
cordova platform add android@nightly
```
### Notes
Nightly builds are **not recommended for production apps**. They are intended for testing purposes. This allows users either to check if recent changes in the main branch have fixed existing issues or to identify new bugs before an official release.
Nightly builds are generated daily and may be **unstable**.
See [Apache Cordova - Nightly Builds](https://cordova.apache.org/contribute/nightly_builds.html) for more details.
## Further reading
* [Apache Cordova](https://cordova.apache.org/)

View File

@@ -91,7 +91,7 @@ function androidExec (success, fail, service, action, args) {
var callbackId = service + cordova.callbackId++;
var argsJson = JSON.stringify(args);
if (success || fail) {
cordova.callbacks[callbackId] = { success: success, fail: fail };
cordova.callbacks[callbackId] = { success, fail };
}
var msgs = nativeApiProvider.get().exec(bridgeSecret, service, action, callbackId, argsJson);
@@ -131,6 +131,8 @@ function pollOnce (opt_fromOnlineEvent) {
}
}
androidExec.pollOnce = pollOnce;
function pollingTimerFunc () {
if (pollEnabled) {
pollOnce();

View File

@@ -39,6 +39,10 @@ module.exports = {
// Core Splash Screen
modulemapper.clobbers('cordova/plugin/android/splashscreen', 'navigator.splashscreen');
// Attach the internal statusBar utility to window.statusbar
// see the file under plugin/android/statusbar.js
modulemapper.clobbers('cordova/plugin/android/statusbar', 'window.statusbar');
var APP_PLUGIN_NAME = Number(cordova.platformVersion.split('.')[0]) >= 4 ? 'CoreAndroid' : 'App';
// Inject a listener for the backbutton on the document.

View File

@@ -0,0 +1,93 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var exec = require('cordova/exec');
var statusBarVisible = true;
var statusBar = {};
// This <script> element is explicitly used by Cordova's statusbar for computing color. (Do not use this element)
const statusBarScript = document.createElement('script');
document.head.appendChild(statusBarScript);
Object.defineProperty(statusBar, 'visible', {
configurable: false,
enumerable: true,
get: function () {
if (window.StatusBar) {
// try to let the StatusBar plugin handle it
return window.StatusBar.isVisible;
}
return statusBarVisible;
},
set: function (value) {
if (window.StatusBar) {
// try to let the StatusBar plugin handle it
if (value) {
window.StatusBar.show();
} else {
window.StatusBar.hide();
}
} else {
statusBarVisible = value;
exec(null, null, 'SystemBarPlugin', 'setStatusBarVisible', [!!value]);
}
}
});
Object.defineProperty(statusBar, 'setBackgroundColor', {
configurable: false,
enumerable: false,
writable: false,
value: function (value) {
statusBarScript.style.color = value;
var rgbStr = window.getComputedStyle(statusBarScript).getPropertyValue('color');
if (!rgbStr.match(/^rgb/)) { return; }
var rgbVals = rgbStr.match(/\d+/g).map(function (v) { return parseInt(v, 10); });
if (rgbVals.length < 3) {
return;
} else if (rgbVals.length === 3) {
rgbVals = [255].concat(rgbVals);
}
// TODO: Use `padStart(2, '0')` once SDK 24 is dropped.
const padRgb = (val) => {
const hex = val.toString(16);
return hex.length === 1 ? '0' + hex : hex;
};
const a = padRgb(rgbVals[0]);
const r = padRgb(rgbVals[1]);
const g = padRgb(rgbVals[2]);
const b = padRgb(rgbVals[3]);
const hexStr = '#' + a + r + g + b;
if (window.StatusBar) {
window.StatusBar.backgroundColorByHexString(hexStr);
} else {
exec(null, null, 'SystemBarPlugin', 'setStatusBarBackgroundColor', rgbVals);
}
}
});
module.exports = statusBar;

66
eslint.config.js Normal file
View File

@@ -0,0 +1,66 @@
/**
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 { defineConfig, globalIgnores } = require('eslint/config');
const nodeConfig = require('@cordova/eslint-config/node');
const nodeTestConfig = require('@cordova/eslint-config/node-tests');
const browserConfig = require('@cordova/eslint-config/browser');
module.exports = defineConfig([
globalIgnores([
'**/coverage/',
'spec/fixtures/',
'templates/project/assets/www/cordova.js',
'test/android/app',
'test/androidx/app'
]),
{
// Include these JavaScript files that do not have file extensions.
files: [
'templates/cordova/version',
'templates/cordova/android_sdk_version',
'templates/cordova/lib/list-devices',
'templates/cordova/lib/list-emulator-images'
]
},
...nodeConfig,
...nodeTestConfig.map(config => ({
files: ['spec/**/*.js'],
...config,
rules: {
...(config.rules || {}),
'prefer-promise-reject-errors': 'off'
}
})),
...browserConfig.map(config => ({
files: [
'cordova-js-src/**/*.js',
'templates/project/assets/**/*.js'
],
...config,
languageOptions: {
...(config?.languageOptions || {}),
globals: {
...(config.languageOptions?.globals || {}),
require: 'readonly',
module: 'readonly'
}
}
}))
]);

View File

@@ -44,7 +44,7 @@ allprojects {
apply plugin: 'com.android.library'
android {
namespace 'org.apache.cordova'
namespace = 'org.apache.cordova'
compileSdkVersion cordovaConfig.COMPILE_SDK_VERSION
buildToolsVersion cordovaConfig.BUILD_TOOLS_VERSION

View File

@@ -1,18 +1,18 @@
{
"MIN_SDK_VERSION": 24,
"SDK_VERSION": 35,
"SDK_VERSION": 36,
"COMPILE_SDK_VERSION": null,
"GRADLE_VERSION": "8.13",
"MIN_BUILD_TOOLS_VERSION": "35.0.0",
"AGP_VERSION": "8.7.3",
"KOTLIN_VERSION": "1.9.24",
"ANDROIDX_APP_COMPAT_VERSION": "1.7.0",
"ANDROIDX_WEBKIT_VERSION": "1.12.1",
"GRADLE_VERSION": "8.14.2",
"MIN_BUILD_TOOLS_VERSION": "36.0.0",
"AGP_VERSION": "8.10.1",
"KOTLIN_VERSION": "2.1.21",
"ANDROIDX_APP_COMPAT_VERSION": "1.7.1",
"ANDROIDX_WEBKIT_VERSION": "1.14.0",
"ANDROIDX_CORE_SPLASHSCREEN_VERSION": "1.0.1",
"GRADLE_PLUGIN_GOOGLE_SERVICES_VERSION": "4.4.2",
"IS_GRADLE_PLUGIN_GOOGLE_SERVICES_ENABLED": false,
"IS_GRADLE_PLUGIN_KOTLIN_ENABLED": false,
"PACKAGE_NAMESPACE": "io.cordova.helloCordova",
"PACKAGE_NAMESPACE": "org.apache.cordova.hellocordova",
"JAVA_SOURCE_COMPATIBILITY": 11,
"JAVA_TARGET_COMPATIBILITY": 11,
"KOTLIN_JVM_TARGET": null

View File

@@ -240,10 +240,8 @@ ext {
}
buildscript {
repositories {
google()
mavenCentral()
}
apply from: 'repositories.gradle'
repositories repos
dependencies {
classpath 'io.github.g00fy2:versioncompare:1.4.1@jar'

View File

@@ -61,6 +61,15 @@ public class CallbackContext {
webView.sendPluginResult(pluginResult, callbackId);
}
/**
* Helper for success callbacks that just returns the Status.OK by default
*
* @param message The message to add to the success result.
*/
public void success(boolean message) {
sendPluginResult(new PluginResult(PluginResult.Status.OK, message));
}
/**
* Helper for success callbacks that just returns the Status.OK by default
*

View File

@@ -77,6 +77,14 @@ public class ConfigXmlParser {
)
);
pluginEntries.add(
new PluginEntry(
SystemBarPlugin.PLUGIN_NAME,
"org.apache.cordova.SystemBarPlugin",
true
)
);
pluginEntries.add(
new PluginEntry(
SplashScreenPlugin.PLUGIN_NAME,

View File

@@ -29,9 +29,10 @@ import android.annotation.SuppressLint;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Color;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@@ -42,7 +43,11 @@ import android.webkit.WebViewClient;
import android.widget.FrameLayout;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.splashscreen.SplashScreen;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
/**
* This class is the main Android activity that represents the Cordova
@@ -100,17 +105,25 @@ public class CordovaActivity extends AppCompatActivity {
private SplashScreen splashScreen;
private boolean canEdgeToEdge = false;
private boolean isFullScreen = false;
/**
* Called when the activity is first created.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
// Handle the splash screen transition.
splashScreen = SplashScreen.installSplashScreen(this);
if (showInitialSplashScreen()) {
splashScreen = SplashScreen.installSplashScreen(this);
}
// need to activate preferences before super.onCreate to avoid "requestFeature() must be called before adding content" exception
loadConfig();
canEdgeToEdge = preferences.getBoolean("AndroidEdgeToEdge", false)
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM;
String logLevel = preferences.getString("loglevel", "ERROR");
LOG.setLogLevel(logLevel);
@@ -125,7 +138,10 @@ public class CordovaActivity extends AppCompatActivity {
LOG.d(TAG, "The SetFullscreen configuration is deprecated in favor of Fullscreen, and will be removed in a future version.");
preferences.set("Fullscreen", true);
}
if (preferences.getBoolean("Fullscreen", false)) {
isFullScreen = preferences.getBoolean("Fullscreen", false);
if (isFullScreen) {
// NOTE: use the FullscreenNotImmersive configuration key to set the activity in a REAL full screen
// (as was the case in previous cordova versions)
if (!preferences.getBoolean("FullscreenNotImmersive", false)) {
@@ -157,7 +173,9 @@ public class CordovaActivity extends AppCompatActivity {
cordovaInterface.onCordovaInit(appView.getPluginManager());
// Setup the splash screen based on preference settings
cordovaInterface.pluginManager.postMessage("setupSplashScreen", splashScreen);
if (showInitialSplashScreen()) {
cordovaInterface.pluginManager.postMessage("setupSplashScreen", splashScreen);
}
// Wire the hardware volume controls to control media if desired.
String volumePref = preferences.getString("DefaultVolumeStream", "");
@@ -180,26 +198,58 @@ public class CordovaActivity extends AppCompatActivity {
//Suppressing warnings in AndroidStudio
@SuppressWarnings({"deprecation", "ResourceType"})
protected void createViews() {
//Why are we setting a constant as the ID? This should be investigated
appView.getView().setId(100);
appView.getView().setLayoutParams(new FrameLayout.LayoutParams(
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
// Root FrameLayout
FrameLayout rootLayout = new FrameLayout(this);
rootLayout.setLayoutParams(new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
ViewGroup.LayoutParams.MATCH_PARENT
));
setContentView(appView.getView());
// WebView
View webView = appView.getView();
webView.setLayoutParams(new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
));
if (preferences.contains("BackgroundColor")) {
try {
int backgroundColor = preferences.getInteger("BackgroundColor", Color.BLACK);
// Background of activity:
appView.getView().setBackgroundColor(backgroundColor);
}
catch (NumberFormatException e){
e.printStackTrace();
}
}
// Create StatusBar view that will overlay the top inset
View statusBarView = new View(this);
statusBarView.setTag("statusBarView");
appView.getView().requestFocusFromTouch();
// Handle Window Insets
ViewCompat.setOnApplyWindowInsetsListener(rootLayout, (v, insets) -> {
Insets bars = insets.getInsets(
WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout()
);
boolean isStatusBarVisible = statusBarView.getVisibility() != View.GONE;
int top = isStatusBarVisible && !canEdgeToEdge && !isFullScreen ? bars.top : 0;
int bottom = !canEdgeToEdge && !isFullScreen ? bars.bottom : 0;
int left = !canEdgeToEdge && !isFullScreen ? bars.left : 0;
int right = !canEdgeToEdge && !isFullScreen ? bars.right : 0;
FrameLayout.LayoutParams webViewParams = (FrameLayout.LayoutParams) webView.getLayoutParams();
webViewParams.setMargins(left, top, right, bottom);
webView.setLayoutParams(webViewParams);
FrameLayout.LayoutParams statusBarParams = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
top,
Gravity.TOP
);
statusBarView.setLayoutParams(statusBarParams);
return insets;
});
rootLayout.addView(webView);
rootLayout.addView(statusBarView);
setContentView(rootLayout);
rootLayout.post(() -> ViewCompat.requestApplyInsets(rootLayout));
webView.requestFocusFromTouch();
}
/**
@@ -537,4 +587,20 @@ public class CordovaActivity extends AppCompatActivity {
}
}
/**
* Indicates whether to show the splash screen while the WebView is initially loading.
* <p>
* This method is available for native apps that embed a Cordova WebView.
* Native apps most likely already have their own splash screen setup.
* This option is not configurable for Cordova CLIcreated apps.
*
* @return {@code true}
* <p>
* To disable the initial splash screen, override this method and return {@code false}
* in your activity that extends {@link CordovaActivity}.
*/
protected boolean showInitialSplashScreen() {
return true;
}
}

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 = "14.0.1";
public static final String CORDOVA_VERSION = "15.0.0-dev";
void init(CordovaInterface cordova, List<PluginEntry> pluginEntries, CordovaPreferences preferences);

View File

@@ -28,9 +28,12 @@ import org.json.JSONObject;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.content.IntentFilter;
import android.telephony.TelephonyManager;
import android.view.KeyEvent;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
import java.util.HashMap;
@@ -45,7 +48,9 @@ public class CoreAndroid extends CordovaPlugin {
private CallbackContext messageChannel;
private PluginResult pendingResume;
private PluginResult pendingPause;
private OnBackInvokedCallback backCallback;
private final Object messageChannelLock = new Object();
private final Object backButtonHandlerLock = new Object();
/**
* Send an event to be fired on the Javascript side.
@@ -63,6 +68,7 @@ public class CoreAndroid extends CordovaPlugin {
@Override
public void pluginInitialize() {
this.initTelephonyReceiver();
backCallback = null;
}
/**
@@ -247,6 +253,29 @@ public class CoreAndroid extends CordovaPlugin {
*/
public void overrideBackbutton(boolean override) {
LOG.i("App", "WARNING: Back Button Default Behavior will be overridden. The backbutton event will be fired!");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA) {
if (override) {
synchronized (backButtonHandlerLock) {
if (backCallback == null) {
// The callback is intentionally empty. Since API 36, intercepting the back button is ignored, which means
// the onDispatchKeyEvent boolean result won't actually stop native from consuming the back button and doing
// it's own logic, UNLESS if there is an OnBackInvokedCallback registered on the dispatcher.
// The key dispatch events will still fire, which still handles propagating back button events to the webview.
// See https://developer.android.com/about/versions/16/behavior-changes-16#predictive-back for more info on the caveat.
backCallback = () -> {};
this.cordova.getActivity().getOnBackInvokedDispatcher().registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, backCallback);
}
}
} else {
synchronized (backButtonHandlerLock) {
if (backCallback != null) {
this.cordova.getActivity().getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(backCallback);
backCallback = null;
}
}
}
}
webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_BACK, override);
}

View File

@@ -204,7 +204,7 @@ public class NativeToJsMessageQueue {
}
}
if (!willSendAllMessages) {
sb.append("window.setTimeout(function(){cordova.require('cordova/plugin/android/polling').pollOnce();},0);");
sb.append("window.setTimeout(function(){cordova.require('cordova/exec').pollOnce();},0);");
}
for (int i = willSendAllMessages ? 1 : 0; i < numMessagesToSend; ++i) {
sb.append('}');

View File

@@ -564,7 +564,7 @@ public class PluginManager {
c = Class.forName(className);
}
if (c != null & CordovaPlugin.class.isAssignableFrom(c)) {
ret = (CordovaPlugin) c.newInstance();
ret = (CordovaPlugin) c.getDeclaredConstructor().newInstance();
}
} catch (Exception e) {
e.printStackTrace();

View File

@@ -155,10 +155,13 @@ public class SplashScreenPlugin extends CordovaPlugin {
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
splashScreenViewProvider.remove();
webView.getPluginManager().postMessage("updateSystemBars", null);
}
}).start();
}
});
} else {
webView.getPluginManager().postMessage("updateSystemBars", null);
}
}

View File

@@ -0,0 +1,374 @@
/*
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.
*/
package org.apache.cordova;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.os.Build;
import android.view.View;
import android.view.ViewParent;
import android.view.Window;
import android.view.WindowInsetsController;
import android.widget.FrameLayout;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsControllerCompat;
import org.json.JSONArray;
import org.json.JSONException;
public class SystemBarPlugin extends CordovaPlugin {
static final String PLUGIN_NAME = "SystemBarPlugin";
static final int INVALID_COLOR = -1;
// Internal variables
private Context context;
private Resources resources;
private int overrideStatusBarBackgroundColor = INVALID_COLOR;
private boolean canEdgeToEdge = false;
@Override
protected void pluginInitialize() {
context = cordova.getContext();
resources = context.getResources();
canEdgeToEdge = preferences.getBoolean("AndroidEdgeToEdge", false)
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM;
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
cordova.getActivity().runOnUiThread(this::updateSystemBars);
}
@Override
public void onResume(boolean multitasking) {
super.onResume(multitasking);
cordova.getActivity().runOnUiThread(this::updateSystemBars);
}
@Override
public Object onMessage(String id, Object data) {
if (id.equals("updateSystemBars")) {
cordova.getActivity().runOnUiThread(this::updateSystemBars);
}
return null;
}
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
if(canEdgeToEdge) {
return false;
}
if ("setStatusBarVisible".equals(action)) {
boolean visible = args.getBoolean(0);
cordova.getActivity().runOnUiThread(() -> setStatusBarVisible(visible));
} else if ("setStatusBarBackgroundColor".equals(action)) {
cordova.getActivity().runOnUiThread(() -> setStatusBarBackgroundColor(args));
} else {
return false;
}
callbackContext.success();
return true;
}
/**
* Allow the app to override the status bar visibility from JS API.
* If for some reason the statusBarView could not be discovered, it will silently ignore
* the change request
*
* @param visible should the status bar be visible?
*/
private void setStatusBarVisible(final boolean visible) {
View statusBar = getStatusBarView(webView);
if (statusBar != null) {
statusBar.setVisibility(visible ? View.VISIBLE : View.GONE);
FrameLayout rootLayout = getRootLayout(webView);
if (rootLayout != null) {
ViewCompat.requestApplyInsets(rootLayout);
}
}
}
/**
* Allow the app to override the status bar background color from JS API.
* If the supplied ARGB is invalid or fails to parse, it will silently ignore
* the change request.
*
* @param argbVals {A, R, G, B}
*/
private void setStatusBarBackgroundColor(JSONArray argbVals) {
try {
int a = argbVals.getInt(0);
int r = argbVals.getInt(1);
int g = argbVals.getInt(2);
int b = argbVals.getInt(3);
String hexColor = String.format("#%02X%02X%02X%02X", a, r, g, b);
int parsedColor = parseColorFromString(hexColor);
if (parsedColor == INVALID_COLOR) return;
overrideStatusBarBackgroundColor = parsedColor;
updateStatusBar(overrideStatusBarBackgroundColor);
} catch (JSONException e) {
// Silently skip
}
}
/**
* Attempt to update all system bars (status, navigation and gesture bars) in various points
* of the apps life cycle.
* For example:
* 1. Device configurations between (E.g. between dark and light mode)
* 2. User resumes the app
* 3. App transitions from SplashScreen Theme to App's Theme
*/
private void updateSystemBars() {
// Update Root View Background Color
int rootViewBackgroundColor = getPreferenceBackgroundColor();
if (rootViewBackgroundColor == INVALID_COLOR) {
rootViewBackgroundColor = canEdgeToEdge ? Color.TRANSPARENT : getUiModeColor();
}
updateRootView(rootViewBackgroundColor);
// Update StatusBar Background Color
int statusBarBackgroundColor;
if (overrideStatusBarBackgroundColor != INVALID_COLOR) {
statusBarBackgroundColor = overrideStatusBarBackgroundColor;
} else if (preferences.contains("StatusBarBackgroundColor")) {
statusBarBackgroundColor = getPreferenceStatusBarBackgroundColor();
} else if(preferences.contains("BackgroundColor")){
statusBarBackgroundColor = rootViewBackgroundColor;
} else {
statusBarBackgroundColor = canEdgeToEdge ? Color.TRANSPARENT : getUiModeColor();
}
updateStatusBar(statusBarBackgroundColor);
}
/**
* Updates the root layout's background color with the supplied color int.
* It will also determine if the background color is light or dark to properly adjust the
* appearance of the navigation/gesture bar's icons so it will not clash with the background.
* <p>
* System bars (navigation & gesture) on SDK 25 or lower is forced to black as the appearance
* of the fonts can not be updated.
* System bars (navigation & gesture) on SDK 26 or greater allows custom background color.
* <p/>
*
* @param bgColor Background color
*/
@SuppressWarnings("deprecation")
private void updateRootView(int bgColor) {
Window window = cordova.getActivity().getWindow();
// Set the root view's background color. Works on SDK 36+
View root = cordova.getActivity().findViewById(android.R.id.content);
if (root != null) root.setBackgroundColor(bgColor);
// Automatically set the font and icon color of the system bars based on background color.
boolean isBackgroundColorLight;
if(bgColor == Color.TRANSPARENT) {
isBackgroundColorLight = isColorLight(getUiModeColor());
} else {
isBackgroundColorLight = isColorLight(bgColor);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
WindowInsetsController controller = window.getInsetsController();
if (controller != null) {
int appearance = WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
if (isBackgroundColorLight) {
controller.setSystemBarsAppearance(0, appearance);
} else {
controller.setSystemBarsAppearance(appearance, appearance);
}
}
}
WindowInsetsControllerCompat controllerCompat = WindowCompat.getInsetsController(window, window.getDecorView());
controllerCompat.setAppearanceLightNavigationBars(isBackgroundColorLight);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
window.setNavigationBarColor(bgColor);
} else {
window.setNavigationBarColor(Color.BLACK);
}
}
/**
* Updates the statusBarView background color with the supplied color int.
* It will also determine if the background color is light or dark to properly adjust the
* appearance of the status bar so the font will not clash with the background.
*
* @param bgColor Background color
*/
private void updateStatusBar(int bgColor) {
Window window = cordova.getActivity().getWindow();
View statusBar = getStatusBarView(webView);
if (statusBar != null) {
statusBar.setBackgroundColor(bgColor);
}
// Automatically set the font and icon color of the system bars based on background color.
boolean isStatusBarBackgroundColorLight;
if(bgColor == Color.TRANSPARENT) {
isStatusBarBackgroundColorLight = isColorLight(getUiModeColor());
} else {
isStatusBarBackgroundColorLight = isColorLight(bgColor);
}
WindowInsetsControllerCompat controllerCompat = WindowCompat.getInsetsController(window, window.getDecorView());
controllerCompat.setAppearanceLightStatusBars(isStatusBarBackgroundColorLight);
}
/**
* Determines if the supplied color's appearance is light.
*
* @param color color
* @return boolean value true is returned when the color is light.
*/
private static boolean isColorLight(int color) {
double r = Color.red(color) / 255.0;
double g = Color.green(color) / 255.0;
double b = Color.blue(color) / 255.0;
double luminance = 0.299 * r + 0.587 * g + 0.114 * b;
return luminance > 0.5;
}
/**
* Returns the StatusBarBackgroundColor preference value.
* If the value is missing or fails to parse, it will attempt to try to guess the background
* color by extracting from the apps R.color.cdv_background_color or determine from the uiModes.
* If all fails, the color normally used in light mode is returned.
*
* @return int
*/
private int getPreferenceStatusBarBackgroundColor() {
String colorString = preferences.getString("StatusBarBackgroundColor", null);
int parsedColor = parseColorFromString(colorString);
if (parsedColor != INVALID_COLOR) return parsedColor;
return getUiModeColor(); // fallback
}
/**
* Returns the BackgroundColor preference value.
* If missing or fails to decode, it will return INVALID_COLOR (-1).
*
* @return int
*/
private int getPreferenceBackgroundColor() {
try {
return preferences.getInteger("BackgroundColor", INVALID_COLOR);
} catch (NumberFormatException e) {
LOG.e(PLUGIN_NAME, "Invalid background color argument. Example valid string: '0x00000000'");
return INVALID_COLOR;
}
}
/**
* Tries to find and return the rootLayout.
*
* @param webView CordovaWebView
* @return FrameLayout|null
*/
private FrameLayout getRootLayout(CordovaWebView webView) {
ViewParent parent = webView.getView().getParent();
if (parent instanceof FrameLayout) {
return (FrameLayout) parent;
}
return null;
}
/**
* Tries to find and return the statusBarView.
*
* @param webView CordovaWebView
* @return View|null
*/
private View getStatusBarView(CordovaWebView webView) {
FrameLayout rootView = getRootLayout(webView);
if (rootView == null) {
return null;
}
for (int i = 0; i < rootView.getChildCount(); i++) {
View child = rootView.getChildAt(i);
Object tag = child.getTag();
if ("statusBarView".equals(tag)) {
return child;
}
}
return null;
}
/**
* Determines the background color for status bar & root layer.
* The color will come from the app's R.color.cdv_background_color.
* If for some reason the resource is missing, it will try to fallback on the uiMode.
* <p>
* The uiMode as follows.
* If night mode: "#121318" (android.R.color.system_background_dark)
* If day mode: "#FAF8FF" (android.R.color.system_background_light)
* If all fails, light mode will be returned.
* </p>
* The hex values are supplied instead of "android.R.color" for backwards compatibility.
*
* @return int color
*/
@SuppressLint("DiscouragedApi")
private int getUiModeColor() {
boolean isNightMode = (resources.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
String fallbackColor = isNightMode ? "#121318" : "#FAF8FF";
int colorResId = resources.getIdentifier("cdv_background_color", "color", context.getPackageName());
return colorResId != 0
? ContextCompat.getColor(context, colorResId)
: Color.parseColor(fallbackColor);
}
/**
* Parse color string that would be provided by app developers.
* If the color string is empty or unable to parse, it will return INVALID_COLOR (-1).
*
* @param colorPref hex string value, #AARRGGBB or #RRGGBB
* @return int
*/
private int parseColorFromString(final String colorPref) {
if (colorPref.isEmpty()) return INVALID_COLOR;
try {
return Color.parseColor(colorPref);
} catch (IllegalArgumentException ignore) {
LOG.e(PLUGIN_NAME, "Invalid color hex code. Valid format: #RRGGBB or #AARRGGBB");
return INVALID_COLOR;
}
}
}

View File

@@ -72,9 +72,9 @@ class Api {
platformWww: path.join(this.root, 'platform_www'),
configXml: path.join(appRes, 'xml', 'config.xml'),
defaultConfigXml: path.join(this.root, 'cordova', 'defaults.xml'),
strings: path.join(appRes, 'values', 'strings.xml'),
themes: path.join(appRes, 'values', 'themes.xml'),
colors: path.join(appRes, 'values', 'colors.xml'),
strings: path.join(appRes, 'values', 'cdv_strings.xml'),
themes: path.join(appRes, 'values', 'cdv_themes.xml'),
colors: path.join(appRes, 'values', 'cdv_colors.xml'),
manifest: path.join(appMain, 'AndroidManifest.xml'),
build: path.join(this.root, 'build'),
javaSrc: path.join(appMain, 'java')
@@ -179,10 +179,6 @@ class Api {
removePlugin (plugin, uninstallOptions) {
const project = AndroidProject.getProjectFile(this.root);
if (uninstallOptions && uninstallOptions.usePlatformWww === true) {
uninstallOptions.usePlatformWww = false;
}
return PluginManager.get(this.platform, this.locations, project)
.removePlugin(plugin, uninstallOptions)
.then(function () {

View File

@@ -173,7 +173,7 @@ class ProjectBuilder {
// This is the future-proof way of checking if a file exists
// This must be synchronous to satisfy a Travis test
try {
fs.accessSync(subProjectGradle, fs.F_OK);
fs.accessSync(subProjectGradle, fs.constants.F_OK);
} catch (e) {
fs.cpSync(pluginBuildGradle, subProjectGradle);
}
@@ -205,7 +205,8 @@ class ProjectBuilder {
'// GENERATED FILE - DO NOT EDIT\n' +
'apply from: "cdv-gradle-name.gradle"\n' +
'include ":"\n' +
settingsGradlePaths.join(''));
settingsGradlePaths.join('') +
'\nif (file("settings-extras.gradle").exists()) {\n apply from: "settings-extras.gradle"\n}\n');
// Touch empty cdv-gradle-name.gradle file if missing.
if (!fs.existsSync(path.join(this.root, 'cdv-gradle-name.gradle'))) {
@@ -318,7 +319,9 @@ class ProjectBuilder {
*/
const javaHome = process.env.CORDOVA_JAVA_HOME || process.env.JAVA_HOME || false;
if (javaHome) {
configProperties.set('java.home', javaHome);
// Double escape back-slashes so that it is written as escaped back-slashes
// in the gradle config. Primary an issue in window environments.
configProperties.set('java.home', javaHome.replace(/\\/g, '\\\\'));
} else {
configProperties.unset('java.home');
}

View File

@@ -24,6 +24,7 @@ const java = require('./env/java');
const { CordovaError, ConfigParser, events } = require('cordova-common');
const android_sdk = require('./android_sdk');
const { SDK_VERSION } = require('./gradle-config-defaults');
const AndroidCommandLineTools = require('./env/AndroidCommandLineTools');
// Re-exporting these for backwards compatibility and for unit testing.
// TODO: Remove uses and use the ./utils module directly.
@@ -216,14 +217,14 @@ module.exports.check_android = function () {
}
}
if (avdmanagerInPath) {
parentDir = path.dirname(avdmanagerInPath);
grandParentDir = path.dirname(parentDir);
if (path.basename(parentDir) === 'bin' && path.basename(grandParentDir) === 'tools') {
maybeSetAndroidHome(path.dirname(grandParentDir));
let sdkPath = null;
if (/cmdline-tools/.test(avdmanagerInPath)) {
sdkPath = path.resolve(avdmanagerInPath, '../../../..');
maybeSetAndroidHome(sdkPath);
} else {
throw new CordovaError('Failed to find \'ANDROID_HOME\' environment variable. Try setting it manually.\n' +
'Detected \'avdmanager\' command at ' + parentDir + ' but no \'tools' + path.sep + 'bin\' directory found near.\n' +
'Try reinstall Android SDK or update your PATH to include valid path to SDK' + path.sep + 'tools' + path.sep + 'bin directory.');
'Detected \'avdmanager\' command at ' + parentDir + ' but does not appear to be within an Android SDK installation.\n' +
'Try reinstall Android SDK or update your PATH to include valid path to SDK');
}
}
}
@@ -240,7 +241,10 @@ module.exports.check_android = function () {
process.env.PATH += path.delimiter + path.join(process.env.ANDROID_HOME, 'platform-tools');
}
if (hasAndroidHome && !avdmanagerInPath) {
process.env.PATH += path.delimiter + path.join(process.env.ANDROID_HOME, 'tools', 'bin');
const cmdLineToolsBin = AndroidCommandLineTools.getBinPath();
if (cmdLineToolsBin) {
process.env.PATH += path.delimiter + cmdLineToolsBin;
}
}
return hasAndroidHome;
});

View File

@@ -205,7 +205,7 @@ exports.create = function (project_path, config, options, events) {
return Promise.reject(new CordovaError('Project already exists! Delete and recreate'));
}
const package_name = config.android_packageName() || config.packageName() || 'io.cordova.helloCordova';
const package_name = config.android_packageName() || config.packageName() || 'org.apache.cordova.hellocordova';
const project_name = config.name() || 'Hello Cordova';
const safe_activity_name = config.android_activityName() || options.activityName || 'MainActivity';
@@ -264,7 +264,7 @@ exports.create = function (project_path, config, options, events) {
fs.mkdirSync(activity_dir, { recursive: true });
fs.cpSync(path.join(project_template_dir, 'Activity.java'), activity_path);
utils.replaceFileContents(activity_path, /__ACTIVITY__/, safe_activity_name);
utils.replaceFileContents(path.join(app_path, 'res', 'values', 'strings.xml'), /__NAME__/, utils.escape(project_name));
utils.replaceFileContents(path.join(app_path, 'res', 'values', 'cdv_strings.xml'), /__NAME__/, utils.escape(project_name));
utils.replaceFileContents(activity_path, /__ID__/, package_name);
const manifest = new AndroidManifest(path.join(project_template_dir, 'AndroidManifest.xml'));

102
lib/env/AndroidCommandLineTools.js vendored Normal file
View File

@@ -0,0 +1,102 @@
/**
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 { events } = require('cordova-common');
const fs = require('node:fs');
const path = require('node:path');
const semver = require('semver');
/**
* Utility collection for resolving the Android SDK command line tools installed
* on the workstation.
*/
const AndroidCommandLineTools = {
/**
* Gets a sorted list of available versions found on the system.
*
* If the command line tools is not resolvable, then an empty array will be returned.
*
* This function depends on ANDROID_HOME environment variable.
*
* @returns {String[]}
*/
getAvailableVersions: () => {
const androidHome = path.resolve(AndroidCommandLineTools.__getAndroidHome());
if (!fs.existsSync(androidHome)) {
events.emit('warn', 'ANDROID_HOME is not resolvable.');
return [];
}
const cmdLineToolsContainer = path.join(androidHome, 'cmdline-tools');
if (!fs.existsSync(cmdLineToolsContainer)) {
events.emit('warn', 'Android SDK is missing cmdline-tools directory.');
return [];
}
const cmdLineVersions = fs.readdirSync(cmdLineToolsContainer)
.filter((value) => {
// expected directory paths are semver-like version strings or literally "latest"
return value === 'latest' || semver.coerce(value) !== null;
})
.sort((a, b) => {
// "latest" directory always comes first
if (a === 'latest') return -1;
if (b === 'latest') return 1;
const av = semver.coerce(a, {
includePrerelease: true
});
const bv = semver.coerce(b, {
includePrerelease: true
});
// Descending (highest version first)
return semver.rcompare(av, bv);
});
return cmdLineVersions;
},
/**
* Gets the bin path of the cmd line tools using the latest available that
* is installed on the workstation.
*
* Returns null if there are no versions fond
*
* @returns {String | null}
*/
getBinPath: () => {
const versions = AndroidCommandLineTools.getAvailableVersions();
if (versions.length === 0) {
return null;
}
const version = versions[0];
return path.resolve(AndroidCommandLineTools.__getAndroidHome(), 'cmdline-tools', version, 'bin');
},
/**
* @internal
*/
__getAndroidHome: () => process.env.ANDROID_HOME
};
module.exports = AndroidCommandLineTools;

View File

@@ -1,20 +0,0 @@
:: Licensed to the Apache Software Foundation (ASF) under one
:: or more contributor license agreements. See the NOTICE file
:: distributed with this work for additional information
:: regarding copyright ownership. The ASF licenses this file
:: to you under the Apache License, Version 2.0 (the
:: "License"); you may not use this file except in compliance
:: with the License. You may obtain a copy of the License at
::
:: http://www.apache.org/licenses/LICENSE-2.0
::
:: Unless required by applicable law or agreed to in writing,
:: software distributed under the License is distributed on an
:: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
:: KIND, either express or implied. See the License for the
:: specific language governing permissions and limitations
:: under the License.
@ECHO OFF
for /f "tokens=2*" %%a in ('REG QUERY "HKEY_LOCAL_MACHINE\SOFTWARE\Android Studio" /v Path') do set "ASPath=%%~b"
ECHO %ASPath%

View File

@@ -42,7 +42,7 @@ const handlers = {
deleteJava(project.projectDir, dest);
} else {
// Just remove the file, not the whole parent directory
removeFile(path.resolve(project.projectDir, dest));
removeFileF(path.resolve(project.projectDir, dest));
}
}
},
@@ -53,7 +53,7 @@ const handlers = {
},
uninstall: function (obj, plugin, project, options) {
const dest = path.join('app/libs', path.basename(obj.src));
removeFile(path.resolve(project.projectDir, dest));
removeFileF(path.resolve(project.projectDir, dest));
}
},
'resource-file': {
@@ -63,7 +63,7 @@ const handlers = {
},
uninstall: function (obj, plugin, project, options) {
const dest = path.join('app', 'src', 'main', obj.target);
removeFile(path.resolve(project.projectDir, dest));
removeFileF(path.resolve(project.projectDir, dest));
}
},
framework: {
@@ -102,7 +102,7 @@ const handlers = {
if (obj.custom) {
const subRelativeDir = project.getCustomSubprojectRelativeDir(plugin.id, src);
removeFile(path.resolve(project.projectDir, subRelativeDir));
removeFileF(path.resolve(project.projectDir, subRelativeDir));
subDir = path.resolve(project.projectDir, subRelativeDir);
// If it's the last framework in the plugin, remove the parent directory.
const parDir = path.dirname(subDir);
@@ -141,14 +141,13 @@ const handlers = {
uninstall: function (obj, plugin, project, options) {
const target = obj.target || obj.src;
if (!target) throw new CordovaError(generateAttributeError('target', 'asset', plugin.id));
if (!target) {
throw new CordovaError(generateAttributeError('target', 'asset', plugin.id));
}
removeFile(path.resolve(project.www, target));
removeFile(path.resolve(project.www, 'plugins', plugin.id));
removeFileAndParents(project.www, target);
if (options && options.usePlatformWww) {
// CB-11022 remove file from both directories if usePlatformWww is specified
removeFile(path.resolve(project.platformWww, target));
removeFile(path.resolve(project.platformWww, 'plugins', plugin.id));
removeFileAndParents(project.platformWww, target);
}
}
},
@@ -248,8 +247,8 @@ function symlinkFileOrDirTree (src, dest) {
}
}
function removeFile (file) {
fs.rmSync(file);
function removeFileF (file) {
fs.rmSync(file, { recursive: true, force: true });
}
// Sometimes we want to remove some java, and prune any unnecessary empty directories
@@ -262,7 +261,7 @@ function removeFileAndParents (baseDir, destFile, stopper) {
const file = path.resolve(baseDir, destFile);
if (!fs.existsSync(file)) return;
removeFile(file);
removeFileF(file);
// check if directory is empty
let curDir = path.dirname(file);

View File

@@ -113,7 +113,8 @@ function getUserGradleConfig (configXml) {
{ xmlKey: 'GradlePluginKotlinEnabled', gradleKey: 'IS_GRADLE_PLUGIN_KOTLIN_ENABLED', type: Boolean },
{ xmlKey: 'AndroidJavaSourceCompatibility', gradleKey: 'JAVA_SOURCE_COMPATIBILITY', type: Number },
{ xmlKey: 'AndroidJavaTargetCompatibility', gradleKey: 'JAVA_TARGET_COMPATIBILITY', type: Number },
{ xmlKey: 'AndroidKotlinJVMTarget', gradleKey: 'KOTLIN_JVM_TARGET', type: String }
{ xmlKey: 'AndroidKotlinJVMTarget', gradleKey: 'KOTLIN_JVM_TARGET', type: String },
{ xmlKey: 'AndroidShowDeprecations', gradleKey: 'JAVA_SHOW_DEPRECATIONS', type: Boolean }
];
return configXmlToGradleMapping.reduce((config, mapping) => {
@@ -346,7 +347,7 @@ function updateProjectAccordingTo (platformConfig, locations) {
* @param {Object} locations A map of locations for this platform
*/
function updateProjectStrings (platformConfig, locations) {
// Update app name by editing res/values/strings.xml
// Update app name by editing res/values/cdv_strings.xml
const strings = xmlHelpers.parseElementtreeSync(locations.strings);
const name = platformConfig.name();
@@ -377,30 +378,10 @@ function warnForDeprecatedSplashScreen (cordovaProject) {
* @param {Object} locations A map of locations for this platform
*/
function updateProjectTheme (platformConfig, locations) {
// res/values/themes.xml
// res/values/cdv_themes.xml
const themes = xmlHelpers.parseElementtreeSync(locations.themes);
const splashScreenTheme = themes.find('style[@name="Theme.App.SplashScreen"]');
// Update edge-to-edge settings in app theme.
let hasE2E = false; // default case
const preferenceE2E = platformConfig.getPreference('AndroidEdgeToEdge', this.platform);
if (!preferenceE2E) {
events.emit('verbose', 'The preference name "AndroidEdgeToEdge" was not set. Defaulting to "false".');
} else {
const hasInvalidPreferenceE2E = preferenceE2E !== 'true' && preferenceE2E !== 'false';
if (hasInvalidPreferenceE2E) {
events.emit('verbose', 'Preference name "AndroidEdgeToEdge" has an invalid value. Valid values are "true" or "false". Defaulting to "false"');
}
hasE2E = hasInvalidPreferenceE2E ? false : preferenceE2E === 'true';
}
const optOutE2EKey = 'android:windowOptOutEdgeToEdgeEnforcement';
const optOutE2EItem = splashScreenTheme.find(`item[@name="${optOutE2EKey}"]`);
const optOutE2EValue = !hasE2E ? 'true' : 'false';
optOutE2EItem.text = optOutE2EValue;
events.emit('verbose', `Updating theme item "${optOutE2EKey}" with value "${optOutE2EValue}"`);
let splashBg = platformConfig.getPreference('AndroidWindowSplashScreenBackground', this.platform);
if (!splashBg) {
splashBg = platformConfig.getPreference('SplashScreenBackgroundColor', this.platform);
@@ -408,13 +389,17 @@ function updateProjectTheme (platformConfig, locations) {
if (!splashBg) {
splashBg = platformConfig.getPreference('BackgroundColor', this.platform);
}
if (!splashBg) {
splashBg = '@color/cdv_splashscreen_background';
}
// use the user defined value for "colors.xml"
updateProjectSplashScreenBackgroundColor(splashBg, locations);
events.emit('verbose', 'The Android Splash Screen background color was set to: ' +
(splashBg === '@color/cdv_splashscreen_background' ? 'Default' : splashBg)
);
// force the themes value to `@color/cdv_splashscreen_background`
const splashBgNode = splashScreenTheme.find('item[@name="windowSplashScreenBackground"]');
splashBgNode.text = '@color/cdv_splashscreen_background';
splashBgNode.text = splashBg;
[
// Splash Screen
@@ -471,7 +456,7 @@ function updateProjectTheme (platformConfig, locations) {
break;
case 'windowSplashScreenIconBackgroundColor':
// use the user defined value for "colors.xml"
// use the user defined value for "cdv_colors.xml"
updateProjectSplashScreenIconBackgroundColor(cdvConfigPrefValue, locations);
// force the themes value to `@color/cdv_splashscreen_icon_background`
@@ -495,7 +480,7 @@ function updateProjectTheme (platformConfig, locations) {
break;
case 'postSplashScreenTheme':
themeTargetNode.text = cdvConfigPrefValue || '@style/Theme.AppCompat.NoActionBar';
themeTargetNode.text = cdvConfigPrefValue || '@style/Theme.Cordova.App.DayNight';
break;
default:
@@ -507,29 +492,13 @@ function updateProjectTheme (platformConfig, locations) {
events.emit('verbose', 'Wrote out Android application themes to ' + locations.themes);
}
/**
* @param {String} splashBackgroundColor SplashScreen Background Color Hex Code
* be used to update project
* @param {Object} locations A map of locations for this platform
*/
function updateProjectSplashScreenBackgroundColor (splashBackgroundColor, locations) {
if (!splashBackgroundColor) { splashBackgroundColor = '#FFFFFF'; }
// res/values/colors.xml
const colors = xmlHelpers.parseElementtreeSync(locations.colors);
colors.find('color[@name="cdv_splashscreen_background"]').text = splashBackgroundColor.replace(/'/g, '\\\'');
fs.writeFileSync(locations.colors, colors.write({ indent: 4 }), 'utf-8');
events.emit('verbose', 'Wrote out Android application SplashScreen Color to ' + locations.colors);
}
/**
* @param {String} splashIconBackgroundColor SplashScreen Icon Background Color Hex Code
* be used to update project
* @param {Object} locations A map of locations for this platform
*/
function updateProjectSplashScreenIconBackgroundColor (splashIconBackgroundColor, locations) {
// res/values/colors.xml
// res/values/cdv_colors.xml
const colors = xmlHelpers.parseElementtreeSync(locations.colors);
// node name
const name = 'cdv_splashscreen_icon_background';
@@ -627,12 +596,12 @@ function updateProjectSplashScreenImage (locations, themeKey, cdvConfigPrefKey,
// copy the png to correct mipmap folder with name of ic_cdv_splashscreen.png
// delete ic_cdv_splashscreen.xml from drawable folder
// update themes.xml windowSplashScreenAnimatedIcon value to @mipmap/ic_cdv_splashscreen
// update cdv_themes.xml windowSplashScreenAnimatedIcon value to @mipmap/ic_cdv_splashscreen
cleanupAndSetProjectSplashScreenImage(cdvConfigPrefValue, destFilePath, possiblePreviousDestFilePath);
} else if (iconExtension === '.xml') {
// copy the xml to drawable folder with name of ic_cdv_splashscreen.xml
// delete ic_cdv_splashscreen.png from mipmap folder
// update themes.xml windowSplashScreenAnimatedIcon value to @drawable/ic_cdv_splashscreen
// update cdv_themes.xml windowSplashScreenAnimatedIcon value to @drawable/ic_cdv_splashscreen
cleanupAndSetProjectSplashScreenImage(cdvConfigPrefValue, destFilePath, possiblePreviousDestFilePath);
} else {
// use the default destFilePath & possiblePreviousDestFilePath, no update require.

View File

@@ -6,55 +6,14 @@
# "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
# 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
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# Compiled list of allowed 3RD PARTY LICENSES from:
#
# ASF CATEGORY A: WHAT CAN WE INCLUDE IN AN ASF PROJECT
# https://www.apache.org/legal/resolved.html#category-a
#
# Licenses converted into the SPDX standardized short identifier format.
# https://spdx.org/licenses/
allowed-licenses:
- 0BSD
- AFL-3.0
- Apache-1.1
- Apache-2.0
- APAFML
- BlueOak-1.0.0
- BSD-2-Clause
- BSD-3-Clause
- BSD-3-Clause-LBNL
- BSL-1.0
- CC-PDDC
- CC0-1.0
- EPICS
- HPND
- ICU
- ISC
- MIT
- MIT-0
- MS-PL
- MulanPSL-2.0
- NCSA
- OGL-UK-3.0
- PHP-3.01
- PostgreSQL
- PSF-2.0
- SMLNJ
- Unicode-DFS-2016
- Unlicense
- UPL-1.0
- W3C
- WTFPL
- X11
- Xnet
- Zlib
- ZPL-2.0
# Empty for the release audit workflow.
# The `license-config` is required even if there are no custom configs

2811
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "cordova-android",
"version": "14.0.1",
"version": "15.0.0-dev",
"description": "cordova-android release",
"types": "./types/index.d.ts",
"main": "lib/Api.js",
@@ -14,10 +14,10 @@
"scripts": {
"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": "eslint .",
"lint:fix": "npm run lint -- --fix",
"unit-tests": "jasmine --config=spec/unit/jasmine.json",
"cover": "nyc jasmine --config=spec/coverage.json",
"cover": "c8 jasmine --config=spec/coverage.json",
"e2e-tests": "jasmine --config=spec/e2e/jasmine.json",
"java-unit-tests": "node test/run_java_unit_tests.js"
},
@@ -25,8 +25,8 @@
"license": "Apache-2.0",
"dependencies": {
"android-versions": "^2.1.0",
"cordova-common": "^5.0.1",
"dedent": "^1.5.3",
"cordova-common": "^6.0.0",
"dedent": "^1.7.0",
"execa": "^5.1.1",
"fast-glob": "^3.3.3",
"is-path-inside": "^3.0.3",
@@ -38,19 +38,19 @@
"which": "^5.0.0"
},
"devDependencies": {
"@cordova/eslint-config": "^5.1.0",
"@cordova/eslint-config": "^6.0.0",
"c8": "^10.1.3",
"cordova-js": "^6.1.0",
"elementtree": "^0.1.7",
"jasmine": "^5.6.0",
"jasmine": "^5.10.0",
"jasmine-spec-reporter": "^7.0.0",
"nyc": "^17.1.0",
"rewire": "^7.0.0",
"tmp": "^0.2.3"
"rewire": "^9.0.1",
"tmp": "^0.2.5"
},
"engines": {
"node": ">=20.5.0"
"node": ">=20.9.0"
},
"nyc": {
"c8": {
"include": [
"lib"
],

View File

@@ -0,0 +1,105 @@
/**
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
const fs = require('node:fs');
const path = require('node:path');
const AndroidCommandLineTools = require('../../lib/env/AndroidCommandLineTools');
describe('AndroidCommandLineTools', () => {
beforeAll(() => {
// For the purposes of these test, we will assume ANDROID_HOME is proper.
spyOn(fs, 'existsSync').and.returnValue(true);
});
describe('getAvailableVersions', () => {
describe('should return a list of command line versions', () => {
it('in descending order', () => {
spyOn(fs, 'readdirSync').and.returnValue([
'10.0',
'15.0',
'13'
]);
expect(AndroidCommandLineTools.getAvailableVersions()).toEqual([
'15.0',
'13',
'10.0'
]);
});
it('stable releases appear before prereleases', () => {
spyOn(fs, 'readdirSync').and.returnValue([
'15.0-rc01',
'15.0-alpha01',
'15.0'
]);
expect(AndroidCommandLineTools.getAvailableVersions()).toEqual([
'15.0',
'15.0-rc01',
'15.0-alpha01'
]);
});
it('"latest" should take all precedence', () => {
spyOn(fs, 'readdirSync').and.returnValue([
'15.0-rc01',
'15.0-alpha01',
'15.0',
'latest'
]);
expect(AndroidCommandLineTools.getAvailableVersions()).toEqual([
'latest',
'15.0',
'15.0-rc01',
'15.0-alpha01'
]);
});
it('invalid versions are ignored', () => {
spyOn(fs, 'readdirSync').and.returnValue([
'15.0-rc01',
'xyz',
'15.0'
]);
expect(AndroidCommandLineTools.getAvailableVersions()).toEqual([
'15.0',
'15.0-rc01'
]);
});
});
});
describe('getBinPath', () => {
beforeEach(() => {
spyOn(AndroidCommandLineTools, '__getAndroidHome').and.returnValue('/Android/Sdk');
});
it('should return the bin path of the latest version', () => {
spyOn(AndroidCommandLineTools, 'getAvailableVersions').and.returnValue([
'19.0',
'18.0'
]);
expect(AndroidCommandLineTools.getBinPath()).toBe(path.resolve('/Android/Sdk/cmdline-tools/19.0/bin'));
});
});
});

View File

@@ -27,6 +27,7 @@ const which = require('which');
const {
SDK_VERSION: DEFAULT_TARGET_API
} = require('../../lib/gradle-config-defaults');
const AndroidCommandLineTools = require('../../lib/env/AndroidCommandLineTools');
describe('check_reqs', function () {
let check_reqs;
@@ -58,6 +59,10 @@ describe('check_reqs', function () {
});
describe('check_android', function () {
beforeAll(() => {
spyOn(AndroidCommandLineTools, 'getAvailableVersions').and.returnValue(['latest']);
});
describe('find and set ANDROID_HOME when neither ANDROID_HOME nor ANDROID_SDK_ROOT is set', function () {
beforeEach(function () {
delete process.env.ANDROID_HOME;
@@ -123,13 +128,13 @@ describe('check_reqs', function () {
spyOn(fs, 'existsSync').and.returnValue(true);
spyOn(which, 'sync').and.callFake(function (cmd) {
if (cmd === 'avdmanager') {
return path.normalize('/android/sdk/tools/bin/avdmanager');
return path.resolve('/android/sdk/cmdline-tools/latest/bin/avdmanager');
} else {
return null;
}
});
return check_reqs.check_android().then(function () {
expect(process.env.ANDROID_HOME).toEqual(path.normalize('/android/sdk'));
expect(process.env.ANDROID_HOME).toEqual(path.resolve('/android/sdk'));
});
});
it('should error out if `avdmanager` command exists in a non-SDK-like directory structure', () => {
@@ -208,9 +213,8 @@ describe('check_reqs', function () {
});
it('should add tools/bin,tools,platform-tools to PATH if `avdmanager`,`android`,`adb` is not found', () => {
return check_reqs.check_android().then(function () {
expect(process.env.PATH).toContain('let the children play' + path.sep + 'tools');
expect(process.env.PATH).toContain('let the children play' + path.sep + 'platform-tools');
expect(process.env.PATH).toContain('let the children play' + path.sep + 'tools' + path.sep + 'bin');
expect(process.env.PATH).toContain('let the children play' + path.sep + 'cmdline-tools' + path.sep + 'latest' + path.sep + 'bin');
});
});
});

View File

@@ -155,10 +155,10 @@ describe('create', function () {
});
describe('parameter values and defaults', function () {
it('should have a default package name of io.cordova.helloCordova', () => {
it('should have a default package name of org.apache.cordova.hellocordova', () => {
config_mock.packageName.and.returnValue(undefined);
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(create.validatePackageName).toHaveBeenCalledWith('io.cordova.helloCordova');
expect(create.validatePackageName).toHaveBeenCalledWith('org.apache.cordova.hellocordova');
});
});
@@ -276,17 +276,17 @@ describe('create', function () {
});
});
it('should interpolate the project name into strings.xml', () => {
it('should interpolate the project name into cdv_strings.xml', () => {
config_mock.name.and.returnValue('IncredibleApp');
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(utils.replaceFileContents).toHaveBeenCalledWith(path.join(app_path, 'res', 'values', 'strings.xml'), /__NAME__/, 'IncredibleApp');
expect(utils.replaceFileContents).toHaveBeenCalledWith(path.join(app_path, 'res', 'values', 'cdv_strings.xml'), /__NAME__/, 'IncredibleApp');
});
});
it('should interpolate the escaped project name into strings.xml', () => {
it('should interpolate the escaped project name into cdv_strings.xml', () => {
config_mock.name.and.returnValue('<Incredible&App>');
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(utils.replaceFileContents).toHaveBeenCalledWith(path.join(app_path, 'res', 'values', 'strings.xml'), /__NAME__/, '&lt;Incredible&amp;App&gt;');
expect(utils.replaceFileContents).toHaveBeenCalledWith(path.join(app_path, 'res', 'values', 'cdv_strings.xml'), /__NAME__/, '&lt;Incredible&amp;App&gt;');
});
});

View File

@@ -150,7 +150,7 @@ describe('common platform handler', function () {
const s = spyOn(fs, 'rmSync').and.callThrough();
deleteJava(project_dir, java_file);
expect(s).toHaveBeenCalled();
expect(s).toHaveBeenCalledWith(path.resolve(project_dir, java_file));
expect(s).toHaveBeenCalledWith(path.resolve(project_dir, java_file), { recursive: true, force: true });
});
it('Test#010 : should delete empty directories after removing source code in a java src path hierarchy', function () {

View File

@@ -113,73 +113,73 @@ describe('android project handler', function () {
// TODO: renumber these tests and other tests below
it('Test#00a6 : should allow installing sources with new app target-dir scheme', function () {
android['source-file'].install(valid_source[1], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].install(valid_source[1], dummyPluginInfo, dummyProject);
expect(copyFileSpy)
.toHaveBeenCalledWith(dummyplugin, 'src/android/DummyPlugin2.java', temp, path.join('app/src/main/src/com/phonegap/plugins/dummyplugin/DummyPlugin2.java'), false);
});
it('Test#006b : should allow installing jar lib file from sources with new app target-dir scheme', function () {
android['source-file'].install(valid_source[2], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].install(valid_source[2], dummyPluginInfo, dummyProject);
expect(copyFileSpy)
.toHaveBeenCalledWith(dummyplugin, 'src/android/TestLib.jar', temp, path.join('app/libs/TestLib.jar'), false);
});
it('Test#006c : should allow installing aar lib file from sources with new app target-dir scheme', function () {
android['source-file'].install(valid_source[3], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].install(valid_source[3], dummyPluginInfo, dummyProject);
expect(copyFileSpy)
.toHaveBeenCalledWith(dummyplugin, 'src/android/TestAar.aar', temp, path.join('app/libs/TestAar.aar'), false);
});
it('Test#006d : should allow installing xml file from sources with old target-dir scheme', function () {
android['source-file'].install(valid_source[4], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].install(valid_source[4], dummyPluginInfo, dummyProject);
expect(copyFileSpy).toHaveBeenCalledWith(dummyplugin,
'src/android/mysettings.xml', temp,
path.join('app/src/main/res/xml/mysettings.xml'), false);
});
it('Test#006e : should allow installing file with other extension from sources with old target-dir scheme', function () {
android['source-file'].install(valid_source[5], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].install(valid_source[5], dummyPluginInfo, dummyProject);
expect(copyFileSpy).toHaveBeenCalledWith(dummyplugin,
'src/android/other.extension', temp,
path.join('app/src/main/res/values/other.extension'), false);
});
it('Test#006f : should allow installing aidl file from sources with old target-dir scheme (GH-547)', function () {
android['source-file'].install(valid_source[6], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].install(valid_source[6], dummyPluginInfo, dummyProject);
expect(copyFileSpy).toHaveBeenCalledWith(dummyplugin,
'src/android/myapi.aidl', temp,
path.join('app/src/main/aidl/com/mytest/myapi.aidl'), false);
});
it('Test#006g : should allow installing aar lib file from sources with old target-dir scheme (GH-547)', function () {
android['source-file'].install(valid_source[7], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].install(valid_source[7], dummyPluginInfo, dummyProject);
expect(copyFileSpy).toHaveBeenCalledWith(dummyplugin,
'src/android/testaar2.aar', temp,
path.join('app/libs/testaar2.aar'), false);
});
it('Test#006h : should allow installing jar lib file from sources with old target-dir scheme (GH-547)', function () {
android['source-file'].install(valid_source[8], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].install(valid_source[8], dummyPluginInfo, dummyProject);
expect(copyFileSpy).toHaveBeenCalledWith(dummyplugin,
'src/android/testjar2.jar', temp,
path.join('app/libs/testjar2.jar'), false);
});
it('Test#006i : should allow installing .so lib file from sources with old target-dir scheme (GH-547)', function () {
android['source-file'].install(valid_source[9], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].install(valid_source[9], dummyPluginInfo, dummyProject);
expect(copyFileSpy).toHaveBeenCalledWith(dummyplugin,
'src/android/jniLibs/x86/libnative.so', temp,
path.join('app/src/main/jniLibs/x86/libnative.so'), false);
});
it('Test#006j : should allow installing sources with target-dir that includes "app"', function () {
android['source-file'].install(valid_source[10], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].install(valid_source[10], dummyPluginInfo, dummyProject);
expect(copyFileSpy)
.toHaveBeenCalledWith(dummyplugin, 'src/android/DummyPlugin2.java', temp, path.join('app/src/main/java/com/appco/DummyPlugin2.java'), false);
});
it('Test#006k : should allow installing sources with target-dir that includes "app" in its first directory', function () {
android['source-file'].install(valid_source[11], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].install(valid_source[11], dummyPluginInfo, dummyProject);
expect(copyFileSpy)
.toHaveBeenCalledWith(dummyplugin, 'src/android/DummyPlugin2.java', temp, path.join('app/src/main/java/appco/src/DummyPlugin2.java'), false);
});
@@ -313,7 +313,7 @@ describe('android project handler', function () {
it('Test#017 : should remove jar files for Android Studio projects', function () {
android['lib-file'].install(valid_libs[0], dummyPluginInfo, dummyProject);
android['lib-file'].uninstall(valid_libs[0], dummyPluginInfo, dummyProject);
expect(rmSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/libs/TestLib.jar'));
expect(rmSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/libs/TestLib.jar'), { recursive: true, force: true });
});
});
@@ -321,7 +321,7 @@ describe('android project handler', function () {
it('Test#018 : should remove files for Android Studio projects', function () {
android['resource-file'].install(valid_resources[0], dummyPluginInfo, dummyProject);
android['resource-file'].uninstall(valid_resources[0], dummyPluginInfo, dummyProject);
expect(rmSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app', 'src', 'main', 'res', 'xml', 'dummy.xml'));
expect(rmSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app', 'src', 'main', 'res', 'xml', 'dummy.xml'), { recursive: true, force: true });
});
});
@@ -333,62 +333,62 @@ describe('android project handler', function () {
});
it('Test#019a : should remove stuff by calling common.deleteJava for Android Studio projects, with specific app target-dir', function () {
android['source-file'].install(valid_source[1], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(valid_source[1], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].install(valid_source[1], dummyPluginInfo, dummyProject);
android['source-file'].uninstall(valid_source[1], dummyPluginInfo, dummyProject);
expect(deleteJavaSpy).toHaveBeenCalledWith(temp, path.join('app/src/main/src/com/phonegap/plugins/dummyplugin/DummyPlugin2.java'));
});
it('Test#019b : should remove stuff by calling common.removeFile for Android Studio projects, of jar with new app target-dir scheme', function () {
android['source-file'].install(valid_source[2], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(valid_source[2], dummyPluginInfo, dummyProject, { android_studio: true });
expect(rmSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/libs/TestLib.jar'));
it('Test#019b : should remove stuff by calling removeFileF for Android Studio projects, of jar with new app target-dir scheme', function () {
android['source-file'].install(valid_source[2], dummyPluginInfo, dummyProject);
android['source-file'].uninstall(valid_source[2], dummyPluginInfo, dummyProject);
expect(rmSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/libs/TestLib.jar'), { recursive: true, force: true });
});
it('Test#019c : should remove stuff by calling common.removeFile for Android Studio projects, of aar with new app target-dir scheme', function () {
android['source-file'].install(valid_source[3], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(valid_source[3], dummyPluginInfo, dummyProject, { android_studio: true });
expect(rmSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/libs/TestAar.aar'));
it('Test#019c : should remove stuff by calling removeFileF for Android Studio projects, of aar with new app target-dir scheme', function () {
android['source-file'].install(valid_source[3], dummyPluginInfo, dummyProject);
android['source-file'].uninstall(valid_source[3], dummyPluginInfo, dummyProject);
expect(rmSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/libs/TestAar.aar'), { recursive: true, force: true });
});
it('Test#019d : should remove stuff by calling common.removeFile for Android Studio projects, of xml with old target-dir scheme', function () {
android['source-file'].install(valid_source[4], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(valid_source[4], dummyPluginInfo, dummyProject, { android_studio: true });
expect(rmSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/src/main/res/xml/mysettings.xml'));
it('Test#019d : should remove stuff by calling removeFileF for Android Studio projects, of xml with old target-dir scheme', function () {
android['source-file'].install(valid_source[4], dummyPluginInfo, dummyProject);
android['source-file'].uninstall(valid_source[4], dummyPluginInfo, dummyProject);
expect(rmSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/src/main/res/xml/mysettings.xml'), { recursive: true, force: true });
});
it('Test#019e : should remove stuff by calling common.removeFile for Android Studio projects, of file with other extension with old target-dir scheme', function () {
android['source-file'].install(valid_source[5], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(valid_source[5], dummyPluginInfo, dummyProject, { android_studio: true });
expect(rmSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/src/main/res/values/other.extension'));
it('Test#019e : should remove stuff by calling removeFileF for Android Studio projects, of file with other extension with old target-dir scheme', function () {
android['source-file'].install(valid_source[5], dummyPluginInfo, dummyProject);
android['source-file'].uninstall(valid_source[5], dummyPluginInfo, dummyProject);
expect(rmSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/src/main/res/values/other.extension'), { recursive: true, force: true });
});
it('Test#019f : should remove stuff by calling common.removeFile for Android Studio projects, of aidl with old target-dir scheme (GH-547)', function () {
android['source-file'].install(valid_source[6], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(valid_source[6], dummyPluginInfo, dummyProject, { android_studio: true });
expect(rmSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/src/main/aidl/com/mytest/myapi.aidl'));
it('Test#019f : should remove stuff by calling removeFileF for Android Studio projects, of aidl with old target-dir scheme (GH-547)', function () {
android['source-file'].install(valid_source[6], dummyPluginInfo, dummyProject);
android['source-file'].uninstall(valid_source[6], dummyPluginInfo, dummyProject);
expect(rmSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/src/main/aidl/com/mytest/myapi.aidl'), { recursive: true, force: true });
});
it('Test#019g : should remove stuff by calling common.removeFile for Android Studio projects, of aar with old target-dir scheme (GH-547)', function () {
android['source-file'].install(valid_source[7], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(valid_source[7], dummyPluginInfo, dummyProject, { android_studio: true });
expect(rmSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/libs/testaar2.aar'));
it('Test#019g : should remove stuff by calling removeFileF for Android Studio projects, of aar with old target-dir scheme (GH-547)', function () {
android['source-file'].install(valid_source[7], dummyPluginInfo, dummyProject);
android['source-file'].uninstall(valid_source[7], dummyPluginInfo, dummyProject);
expect(rmSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/libs/testaar2.aar'), { recursive: true, force: true });
});
it('Test#019h : should remove stuff by calling common.removeFile for Android Studio projects, of jar with old target-dir scheme (GH-547)', function () {
android['source-file'].install(valid_source[8], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(valid_source[8], dummyPluginInfo, dummyProject, { android_studio: true });
expect(rmSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/libs/testjar2.jar'));
it('Test#019h : should remove stuff by calling removeFileF for Android Studio projects, of jar with old target-dir scheme (GH-547)', function () {
android['source-file'].install(valid_source[8], dummyPluginInfo, dummyProject);
android['source-file'].uninstall(valid_source[8], dummyPluginInfo, dummyProject);
expect(rmSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/libs/testjar2.jar'), { recursive: true, force: true });
});
it('Test#019i : should remove stuff by calling common.removeFile for Android Studio projects, of .so lib file with old target-dir scheme (GH-547)', function () {
android['source-file'].install(valid_source[9], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(valid_source[9], dummyPluginInfo, dummyProject, { android_studio: true });
expect(rmSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/src/main/jniLibs/x86/libnative.so'));
it('Test#019i : should remove stuff by calling removeFileF for Android Studio projects, of .so lib file with old target-dir scheme (GH-547)', function () {
android['source-file'].install(valid_source[9], dummyPluginInfo, dummyProject);
android['source-file'].uninstall(valid_source[9], dummyPluginInfo, dummyProject);
expect(rmSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/src/main/jniLibs/x86/libnative.so'), { recursive: true, force: true });
});
it('Test#019j : should remove stuff by calling common.deleteJava for Android Studio projects, with target-dir that includes "app"', function () {
android['source-file'].install(valid_source[10], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(valid_source[10], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].install(valid_source[10], dummyPluginInfo, dummyProject);
android['source-file'].uninstall(valid_source[10], dummyPluginInfo, dummyProject);
expect(deleteJavaSpy).toHaveBeenCalledWith(dummyProject.projectDir, path.join('app/src/main/java/com/appco/DummyPlugin2.java'));
});
});
@@ -424,13 +424,13 @@ describe('android project handler', function () {
const framework = { src: 'plugin-lib', custom: true };
android.framework.uninstall(framework, dummyPluginInfo, dummyProject);
expect(dummyProject.removeSubProject).toHaveBeenCalledWith(dummyProject.projectDir, someString);
expect(rmSyncSpy).toHaveBeenCalledWith(someString);
expect(rmSyncSpy).toHaveBeenCalledWith(someString, { recursive: true, force: true });
});
it('Test#24 : should install gradleReference using project.removeGradleReference', function () {
const framework = { src: 'plugin-lib', custom: true, type: 'gradleReference' };
android.framework.uninstall(framework, dummyPluginInfo, dummyProject);
expect(rmSyncSpy).toHaveBeenCalledWith(someString);
expect(rmSyncSpy).toHaveBeenCalledWith(someString, { recursive: true, force: true });
expect(dummyProject.removeGradleReference).toHaveBeenCalledWith(dummyProject.projectDir, someString);
});
});
@@ -453,14 +453,14 @@ describe('android project handler', function () {
it('Test#025 : should put module to both www and platform_www when options.usePlatformWww flag is specified', function () {
android['js-module'].uninstall(jsModule, dummyPluginInfo, dummyProject, { usePlatformWww: true });
expect(rmSyncSpy).toHaveBeenCalledWith(wwwDest);
expect(rmSyncSpy).toHaveBeenCalledWith(platformWwwDest);
expect(rmSyncSpy).toHaveBeenCalledWith(wwwDest, { recursive: true, force: true });
expect(rmSyncSpy).toHaveBeenCalledWith(platformWwwDest, { recursive: true, force: true });
});
it('Test#026 : should put module to www only when options.usePlatformWww flag is not specified', function () {
android['js-module'].uninstall(jsModule, dummyPluginInfo, dummyProject);
expect(rmSyncSpy).toHaveBeenCalledWith(wwwDest);
expect(rmSyncSpy).not.toHaveBeenCalledWith(platformWwwDest);
expect(rmSyncSpy).toHaveBeenCalledWith(wwwDest, { recursive: true, force: true });
expect(rmSyncSpy).not.toHaveBeenCalledWith(platformWwwDest, { recursive: true, force: true });
});
});
@@ -481,14 +481,14 @@ describe('android project handler', function () {
it('Test#027 : should put module to both www and platform_www when options.usePlatformWww flag is specified', function () {
android.asset.uninstall(asset, dummyPluginInfo, dummyProject, { usePlatformWww: true });
expect(rmSyncSpy).toHaveBeenCalledWith(wwwDest);
expect(rmSyncSpy).toHaveBeenCalledWith(platformWwwDest);
expect(rmSyncSpy).toHaveBeenCalledWith(wwwDest, { recursive: true, force: true });
expect(rmSyncSpy).toHaveBeenCalledWith(platformWwwDest, { recursive: true, force: true });
});
it('Test#028 : should put module to www only when options.usePlatformWww flag is not specified', function () {
android.asset.uninstall(asset, dummyPluginInfo, dummyProject);
expect(rmSyncSpy).toHaveBeenCalledWith(wwwDest);
expect(rmSyncSpy).not.toHaveBeenCalledWith(platformWwwDest);
expect(rmSyncSpy).toHaveBeenCalledWith(wwwDest, { recursive: true, force: true });
expect(rmSyncSpy).not.toHaveBeenCalledWith(platformWwwDest, { recursive: true, force: true });
});
});
});

View File

@@ -930,7 +930,7 @@ describe('prepare', () => {
locations: {
plugins: '/mock/plugins',
www: '/mock/www',
strings: '/mock/res/values/strings.xml'
strings: '/mock/res/values/cdv_strings.xml'
}
};

View File

@@ -1,25 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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
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
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.
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.
-->
<widget xmlns = "http://www.w3.org/ns/widgets"
id = "io.cordova.helloCordova"
version = "2.0.0">
<widget xmlns="http://www.w3.org/ns/widgets"
xmlns:cdv="http://cordova.apache.org/ns/1.0"
id="org.apache.cordova.hellocordova"
version="1.0.0">
<!-- Preferences for Android -->
<preference name="loglevel" value="DEBUG" />

View File

@@ -183,10 +183,10 @@ task cdvPrintProps {
}
android {
namespace cordovaConfig.PACKAGE_NAMESPACE
namespace = cordovaConfig.PACKAGE_NAMESPACE
buildFeatures {
buildConfig true
buildConfig = true
}
defaultConfig {
@@ -202,7 +202,7 @@ android {
}
lintOptions {
abortOnError false
abortOnError = false
}
buildToolsVersion cordovaConfig.BUILD_TOOLS_VERSION

View File

@@ -40,6 +40,18 @@ allprojects {
}
repositories repos
subprojects {
afterEvaluate {
tasks.withType(JavaCompile).tap {
configureEach {
if (cordovaConfig.JAVA_SHOW_DEPRECATIONS == true) {
options.compilerArgs += "-Xlint:deprecation"
}
}
}
}
}
}
task clean(type: Delete) {

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<resources>
<color name="cdv_background_color">@android:color/system_background_dark</color>
<color name="cdv_splashscreen_background">@color/cdv_background_color</color>
</resources>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<resources>
<color name="cdv_background_color">#121318</color>
<color name="cdv_splashscreen_background">@color/cdv_background_color</color>
</resources>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<resources>
<color name="cdv_background_color">@android:color/system_background_light</color>
<color name="cdv_splashscreen_background">@color/cdv_background_color</color>
</resources>

View File

@@ -18,5 +18,6 @@
under the License.
-->
<resources xmlns:tools="http://schemas.android.com/tools">
<color name="cdv_splashscreen_background">#FFFFFFFF</color>
<color name="cdv_background_color">#FAF8FF</color>
<color name="cdv_splashscreen_background">@color/cdv_background_color</color>
</resources>

View File

@@ -19,19 +19,21 @@
-->
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="Theme.App.SplashScreen" parent="Theme.SplashScreen.IconBackground">
<!-- Optional: Set the splash screen background. (Default: #FFFFFF) -->
<item name="windowSplashScreenBackground">@color/cdv_splashscreen_background</item>
<!-- Optional: Set the splash screen background. (Default: @color/cdv_splashscreen_background) -->
<item name="windowSplashScreenBackground">@color/cdv_splashscreen_background</item>
<!-- Required: Add either a drawable or an animated drawable -->
<item name="windowSplashScreenAnimatedIcon">@drawable/ic_cdv_splashscreen</item>
<!-- Required: Add either a drawable or an animated drawable -->
<item name="windowSplashScreenAnimatedIcon">@drawable/ic_cdv_splashscreen</item>
<!-- Required: For animated icons -->
<item name="windowSplashScreenAnimationDuration">200</item>
<!-- Required: For animated icons -->
<item name="windowSplashScreenAnimationDuration">200</item>
<!-- Required: Set the theme of the Activity that directly follows your splash screen. -->
<item name="postSplashScreenTheme">@style/Theme.AppCompat.NoActionBar</item>
<!-- Required: Set the theme of the Activity that directly follows your splash screen. -->
<item name="postSplashScreenTheme">@style/Theme.Cordova.App.DayNight</item>
</style>
<!-- Disable Edge-to-Edge for SDK 35 -->
<item name="android:windowOptOutEdgeToEdgeEnforcement" tools:targetApi="35">true</item>
<style name="Theme.Cordova.App.DayNight" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="android:colorBackground">@color/cdv_background_color</item>
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
</resources>

View File

@@ -1,57 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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
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
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.
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.
-->
<widget xmlns = "http://www.w3.org/ns/widgets"
id = "io.cordova.helloCordova"
version = "2.0.0">
<widget xmlns="http://www.w3.org/ns/widgets"
xmlns:cdv="http://cordova.apache.org/ns/1.0"
id="org.apache.cordova.hellocordova"
version="1.0.0">
<name>Hello Cordova</name>
<description>
A sample Apache Cordova application that responds to the deviceready event.
</description>
<author href="http://cordova.io" email="dev@cordova.apache.org">
<author email="dev@cordova.apache.org" href="https://cordova.apache.org">
Apache Cordova Team
</author>
<!-- <content src="http://mysite.com/myapp.html" /> for external pages -->
<!-- https://s.apache.org/cdv-content-config -->
<content src="index.html" />
<!-- Allow List docs: https://cordova.apache.org/docs/en/latest/ -->
<access origin="*" />
<!-- Grant certain URLs the ability to launch external applications. This
behaviour is set to match that of Cordova versions before 3.6.0, and
should be reviewed before launching an application in production. It
may be changed in the future. -->
<!-- To allow connections to other resources, you must explicitly permit them using `access` tags. -->
<!-- https://s.apache.org/cdv-network-request-access -->
<!-- Example:
<access origin="https://cordova.apache.org" />
-->
<!-- To control which URLs the WebView itself can be navigated to, use the `allow-navigation` tags. -->
<!-- https://s.apache.org/cdv-allow-navigation -->
<!-- Example:
<allow-navigation href="https://cordova.apache.org/*" />
-->
<!-- To control which URLs the app is allowed to ask the system to open, use the `allow-intent` tags. -->
<!-- https://s.apache.org/cdv-allow-intent -->
<allow-intent href="http://*/*" />
<allow-intent href="https://*/*" />
<allow-intent href="tel:*" />
<allow-intent href="sms:*" />
<allow-intent href="mailto:*" />
<allow-intent href="geo:*" />
<allow-intent href="market:*" />
<preference name="loglevel" value="DEBUG" />
<!--
<preference name="backgroundColor" value="0xFFF" />
<preference name="loadUrlTimeoutValue" value="20000" />
<preference name="InAppBrowserStorageEnabled" value="true" />
<preference name="disallowOverscroll" value="true" />
-->
</widget>

View File

@@ -24,7 +24,7 @@ android {
compileSdkVersion cordovaConfig.COMPILE_SDK_VERSION
buildToolsVersion cordovaConfig.BUILD_TOOLS_VERSION
namespace 'org.apache.cordova.unittests'
namespace = 'org.apache.cordova.unittests'
defaultConfig {
applicationId "org.apache.cordova.unittests"

View File

@@ -17,7 +17,7 @@
specific language governing permissions and limitations
under the License.
-->
<widget id="io.cordova.helloCordova" version="2.0.0" xmlns="http://www.w3.org/ns/widgets">
<widget id="org.apache.cordova.hellocordova" version="2.0.0" xmlns="http://www.w3.org/ns/widgets">
<name>Hello Cordova</name>
<description>
A sample Apache Cordova application that responds to the deviceready event.

View File

@@ -62,6 +62,8 @@ class AndroidTestRunner {
.then(_ => {
// TODO we should probably not only copy these files, but instead create a new project from scratch
fs.copyFileSync(path.resolve(this.projectDir, '../../framework/cdv-gradle-config-defaults.json'), path.resolve(this.projectDir, 'cdv-gradle-config.json'));
fs.copyFileSync(path.resolve(this.projectDir, '../../framework/repositories.gradle'), path.resolve(this.projectDir, 'repositories.gradle'));
fs.copyFileSync(path.resolve(this.projectDir, '../../framework/repositories.gradle'), path.resolve(this.projectDir, 'app', 'repositories.gradle'));
fs.cpSync(path.resolve(this.projectDir, '../../templates/project/tools'), path.resolve(this.projectDir, 'tools'), { recursive: true });
fs.copyFileSync(
path.join(__dirname, '../templates/project/assets/www/cordova.js'),