mirror of
https://github.com/apache/cordova-android.git
synced 2026-01-30 00:05:28 +08:00
Compare commits
98 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eaca570cb6 | ||
|
|
76bac55fba | ||
|
|
6b76757c80 | ||
|
|
fb562f4ed0 | ||
|
|
eaf875b0d6 | ||
|
|
d7afba0ad0 | ||
|
|
ebe6890dee | ||
|
|
172b8448af | ||
|
|
655aa0a5fb | ||
|
|
52578ae770 | ||
|
|
360be21ea5 | ||
|
|
5bca218e5b | ||
|
|
eb5fe4fbda | ||
|
|
46af3114b8 | ||
|
|
76aa938002 | ||
|
|
7d7f511023 | ||
|
|
488c498733 | ||
|
|
e4457f7fdb | ||
|
|
8742cfe4a6 | ||
|
|
af1ae68a97 | ||
|
|
56afb70894 | ||
|
|
1204a79378 | ||
|
|
bf0ba3dd5e | ||
|
|
c2cf589d84 | ||
|
|
0190d2e33a | ||
|
|
df36c7a2c4 | ||
|
|
b79232612b | ||
|
|
60d2842024 | ||
|
|
cab5c5b7ec | ||
|
|
484c60e483 | ||
|
|
72e7148884 | ||
|
|
36bee66493 | ||
|
|
4dcfc361d2 | ||
|
|
c25ed27828 | ||
|
|
d8f6f37737 | ||
|
|
f0e8885693 | ||
|
|
5dc9c72821 | ||
|
|
7a47fe01dc | ||
|
|
08b8a95416 | ||
|
|
6024465814 | ||
|
|
6b8e819f21 | ||
|
|
00744c4f71 | ||
|
|
872d98876e | ||
|
|
855fab238c | ||
|
|
2ffe68ab17 | ||
|
|
f697ca7dec | ||
|
|
1d82a3b52f | ||
|
|
688d2cf5ad | ||
|
|
f6e384a9ea | ||
|
|
839f9b878b | ||
|
|
d4eca414e3 | ||
|
|
5da9bd6d9d | ||
|
|
2258d33a72 | ||
|
|
ca4caf3fc1 | ||
|
|
7ab18487cf | ||
|
|
ff11f659f0 | ||
|
|
aad36fe565 | ||
|
|
7f9529408b | ||
|
|
bb4f86e7b9 | ||
|
|
d0b59863ac | ||
|
|
7544fdf1ed | ||
|
|
8f458b042b | ||
|
|
eb0f002112 | ||
|
|
e012478537 | ||
|
|
b623311efa | ||
|
|
1f349f2984 | ||
|
|
9f5518000f | ||
|
|
92116dee48 | ||
|
|
34220ae0e3 | ||
|
|
1fe44d71c5 | ||
|
|
58c2e3ae15 | ||
|
|
ea045dee63 | ||
|
|
cee7b0b8ac | ||
|
|
6f0efd3a0d | ||
|
|
dff2fc6331 | ||
|
|
1347e48d14 | ||
|
|
5a2c50d1ed | ||
|
|
5eddc460e4 | ||
|
|
3503bfa31b | ||
|
|
d281727113 | ||
|
|
172e947d18 | ||
|
|
2143045d4e | ||
|
|
3c5df42df5 | ||
|
|
c2f315c0ff | ||
|
|
89a0a72da5 | ||
|
|
ed8e5d2f0a | ||
|
|
7fa4a65d0a | ||
|
|
b773ae48f4 | ||
|
|
ebf0b105a3 | ||
|
|
9261b29cf2 | ||
|
|
90e74befc7 | ||
|
|
97806db463 | ||
|
|
4742358601 | ||
|
|
e61e271f5b | ||
|
|
0c805a0a8e | ||
|
|
a7cd4227a4 | ||
|
|
c9e7c59986 | ||
|
|
94234d988e |
@@ -1,3 +0,0 @@
|
||||
templates/project/assets/www/cordova.js
|
||||
test/android/app
|
||||
test/androidx/app
|
||||
20
.gitattributes
vendored
20
.gitattributes
vendored
@@ -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
|
||||
@@ -23,7 +40,7 @@
|
||||
*.scm text
|
||||
*.sql text
|
||||
*.sh text
|
||||
*.bat text
|
||||
*.bat text eol=crlf
|
||||
|
||||
# templates
|
||||
*.ejs text
|
||||
@@ -92,3 +109,4 @@ AUTHORS text
|
||||
*.woff binary
|
||||
*.pyc binary
|
||||
*.pdf binary
|
||||
*.jar binary
|
||||
|
||||
54
.github/workflows/ci.yml
vendored
54
.github/workflows/ci.yml
vendored
@@ -6,18 +6,28 @@
|
||||
# "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.
|
||||
|
||||
name: Node CI
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'dependabot/**'
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
jobs:
|
||||
test:
|
||||
@@ -27,22 +37,19 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [16.x, 18.x, 20.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@v3
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: set up JDK 11
|
||||
uses: actions/setup-java@v3
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '11'
|
||||
java-version: '17'
|
||||
|
||||
- name: Environment Information
|
||||
run: |
|
||||
@@ -50,6 +57,21 @@ jobs:
|
||||
npm --version
|
||||
gradle --version
|
||||
|
||||
# "bin/templates/platform_www/cordova.js" is ignored because it is a generated file.
|
||||
# It contains mixed content from the npm package "cordova-js" and "./cordova-js-src".
|
||||
# The report might not be resolvable because of the external package.
|
||||
# If the report is related to this repository, it would be detected when scanning "./cordova-js-src".
|
||||
- uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: javascript, java-kotlin
|
||||
queries: security-and-quality
|
||||
config: |
|
||||
paths-ignore:
|
||||
- coverage
|
||||
- node_modules
|
||||
- templates/project/assets/www/cordova.js
|
||||
- test/androidx/app/src/main/assets/www/cordova.js
|
||||
|
||||
- name: npm install and test
|
||||
run: |
|
||||
npm i
|
||||
@@ -57,8 +79,12 @@ jobs:
|
||||
env:
|
||||
CI: true
|
||||
|
||||
- name: upload coverage
|
||||
- uses: github/codeql-action/analyze@v3
|
||||
|
||||
# v4.6.0
|
||||
- uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238
|
||||
if: success()
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
name: ${{ runner.os }} node.js ${{ matrix.node-version }}
|
||||
token: ${{ secrets.CORDOVA_CODECOV_TOKEN }}
|
||||
fail_ci_if_error: false
|
||||
|
||||
55
.github/workflows/release-audit.yml
vendored
Normal file
55
.github/workflows/release-audit.yml
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
# 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.
|
||||
|
||||
name: Release Auditing
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'dependabot/**'
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Audit Licenses
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Checkout project
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
# Check license headers (v2.0.0)
|
||||
- uses: erisu/apache-rat-action@46fb01ce7d8f76bdcd7ab10e7af46e1ea95ca01c
|
||||
|
||||
# Setup environment with node
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
# Install node packages
|
||||
- name: npm install packages
|
||||
run: npm ci
|
||||
|
||||
# Check node package licenses (v2.0.1)
|
||||
- uses: erisu/license-checker-action@99cffa11264fe545fd0baa6c13bca5a00ae608f2
|
||||
with:
|
||||
license-config: 'licence_checker.yml'
|
||||
include-asf-category-a: true
|
||||
27
.gitignore
vendored
27
.gitignore
vendored
@@ -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
|
||||
@@ -29,12 +46,11 @@ example
|
||||
**/assets/www/cordova.js
|
||||
|
||||
/test/.externalNativeBuild
|
||||
|
||||
/test/androidx/gradle
|
||||
/test/androidx/gradlew
|
||||
/test/androidx/gradlew.bat
|
||||
/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*
|
||||
/test/assets/www/cordova.js
|
||||
/test/bin
|
||||
@@ -50,7 +66,6 @@ tmp/**/*
|
||||
npm-debug.log
|
||||
node_modules/
|
||||
coverage/
|
||||
.nyc_output/
|
||||
# Eclipse Buildship files
|
||||
.project
|
||||
.settings
|
||||
|
||||
17
.npmignore
17
.npmignore
@@ -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
18
.npmrc
@@ -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
|
||||
|
||||
35
.ratignore
35
.ratignore
@@ -1,8 +1,27 @@
|
||||
*.properties
|
||||
templates
|
||||
gen
|
||||
proguard-project.txt
|
||||
spec
|
||||
framework/build
|
||||
ic_launcher.png
|
||||
build
|
||||
# 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/
|
||||
|
||||
@@ -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).
|
||||
#
|
||||
|
||||
3
LICENSE
3
LICENSE
@@ -187,7 +187,7 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2015-2020 Apache Cordova
|
||||
Copyright 2015-2024 Apache Cordova
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -200,3 +200,4 @@
|
||||
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.
|
||||
|
||||
|
||||
16
README.md
16
README.md
@@ -32,7 +32,7 @@ Cordova Android is an Android application library that allows for Cordova-based
|
||||
|
||||
## Requirements
|
||||
|
||||
* Java Development Kit (JDK) 11
|
||||
* Java Development Kit (JDK) 17
|
||||
* [Android SDK](https://developer.android.com/)
|
||||
* [Node.js](https://nodejs.org)
|
||||
|
||||
@@ -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/)
|
||||
|
||||
@@ -20,6 +20,98 @@
|
||||
-->
|
||||
## Release Notes for Cordova (Android)
|
||||
|
||||
### 14.0.1 (Apr 24, 2025)
|
||||
|
||||
**Fixes:**
|
||||
|
||||
* [GH-1795](https://github.com/apache/cordova-android/pull/1795) fix: configure gradle `java.home`
|
||||
* [GH-1793](https://github.com/apache/cordova-android/pull/1793) fix(windows): get gradle path with `which` command
|
||||
|
||||
### 14.0.0 (Mar 23, 2025)
|
||||
|
||||
**Breaking Changes:**
|
||||
|
||||
* [GH-1788](https://github.com/apache/cordova-android/pull/1788) dep!: bump npm packages
|
||||
* nyc@17.1.0
|
||||
* which@5.0.0
|
||||
* semver@7.7.1
|
||||
* jasmine@5.6.0
|
||||
* android-versions@2.1.0
|
||||
* cordova-common@5.0.1
|
||||
* fast-glob@3.3.3
|
||||
* nopt@8.1.0
|
||||
* [GH-1789](https://github.com/apache/cordova-android/pull/1789) feat!: bump node engine requirement `>=20.5.0`
|
||||
* [GH-1784](https://github.com/apache/cordova-android/pull/1784) feat!: bump java default targets to 11
|
||||
* [GH-1771](https://github.com/apache/cordova-android/pull/1771) feat!: deprecate CordovaPlugin's method initialize
|
||||
* [GH-1767](https://github.com/apache/cordova-android/pull/1767) feat!: use kotlin-stdlib instead of kotlin-stdlib-jdk*
|
||||
* [GH-1763](https://github.com/apache/cordova-android/pull/1763) feat!: SDK 35 Support
|
||||
|
||||
**Features:**
|
||||
|
||||
* [GH-1785](https://github.com/apache/cordova-android/pull/1785) feat: bump gradle to 8.13
|
||||
* [GH-1779](https://github.com/apache/cordova-android/pull/1779) feat: add `AndroidEdgeToEdge` preference & theme flag
|
||||
* [GH-1778](https://github.com/apache/cordova-android/pull/1778) feat: Account for Node security patch
|
||||
* [GH-1768](https://github.com/apache/cordova-android/pull/1768) feat: `androidx.core:core-splashscreen@1.0.1`
|
||||
* [GH-1766](https://github.com/apache/cordova-android/pull/1766) feat: `com.google.gms:google-services@4.4.2`
|
||||
* [GH-1765](https://github.com/apache/cordova-android/pull/1765) feat: `androidx.webkit:webkit@1.12.1`
|
||||
* [GH-1764](https://github.com/apache/cordova-android/pull/1764) feat: `androidx.appcompat:appcompat@1.7.0`
|
||||
|
||||
**Fixes:**
|
||||
|
||||
* [GH-1790](https://github.com/apache/cordova-android/pull/1790) fix: replace fs-extra.ensureFileSync with fs.writeFileSync
|
||||
* [GH-1781](https://github.com/apache/cordova-android/pull/1781) fix: copy gradle wrapper from tools to platform root dir
|
||||
* [GH-1770](https://github.com/apache/cordova-android/pull/1770) fix: creation of cdv-gradle-config.json w/ --link flag
|
||||
* [GH-1739](https://github.com/apache/cordova-android/pull/1739) fix(docs): Incorrect JDK requirement stated in README
|
||||
* [GH-1718](https://github.com/apache/cordova-android/pull/1718) fix: app restart when BT keyboard is connected in some devices
|
||||
|
||||
**Chores & Refactoring:**
|
||||
|
||||
* [GH-1786](https://github.com/apache/cordova-android/pull/1786) chore: add AndroidX build test to gitignore
|
||||
* [GH-1774](https://github.com/apache/cordova-android/pull/1774) style: update & resolve doc block warnings
|
||||
* [GH-1772](https://github.com/apache/cordova-android/pull/1772) refactor: replace fs-extra with node:fs
|
||||
* [GH-1769](https://github.com/apache/cordova-android/pull/1769) refactor: prefix node:*
|
||||
* [GH-1748](https://github.com/apache/cordova-android/pull/1748) chore(deps): bump cross-spawn from 7.0.3 to 7.0.6
|
||||
* [GH-1750](https://github.com/apache/cordova-android/pull/1750) chore(ci): Fix dependabot PR failures
|
||||
* [GH-1736](https://github.com/apache/cordova-android/pull/1736) chore(deps): bump micromatch from 4.0.5 to 4.0.8
|
||||
* [GH-1716](https://github.com/apache/cordova-android/pull/1716) chore(deps): bump braces from 3.0.2 to 3.0.3
|
||||
|
||||
### 13.0.0 (May 15, 2024)
|
||||
|
||||
**Breaking Changes:**
|
||||
|
||||
* [GH-1678](https://github.com/apache/cordova-android/pull/1678) feat!: API 34 Support
|
||||
* [GH-1543](https://github.com/apache/cordova-android/pull/1543) feat!: bump `kotlin@1.9.24` & drop `kotlin-android-extensions` when kotlin `>=1.8.0`
|
||||
|
||||
**Features:**
|
||||
|
||||
* [GH-1700](https://github.com/apache/cordova-android/pull/1700) feat(splash): Support `SplashScreenBackgroundColor` preference
|
||||
* [GH-1609](https://github.com/apache/cordova-android/pull/1609) feat: add camera intent with file input capture
|
||||
* [GH-1696](https://github.com/apache/cordova-android/pull/1696) feat: Add `ResolveServiceWorkerRequests` preference
|
||||
|
||||
**Chores, Dependencies & CI:**
|
||||
|
||||
* [GH-1677](https://github.com/apache/cordova-android/pull/1677) chore(deps-dev): bump `@babel/traverse` from `7.22.10` to `7.23.2`
|
||||
* [GH-1713](https://github.com/apache/cordova-android/pull/1713) dep: bump npm dependencies 20240515
|
||||
* `semver@7.6.2`
|
||||
* `rewire@7.0.0`
|
||||
* `nopt@7.2.1`
|
||||
* `jasmine@5.1.0`
|
||||
* `fs-extra@11.2.0`
|
||||
* `fast-glob@3.3.2`
|
||||
* `dedent@1.5.3`
|
||||
* `@cordova/eslint-config@5.1.0`
|
||||
* `which@4.0.0`
|
||||
* `properties-parser@0.6.0`
|
||||
* `android-versions@2.0.0`
|
||||
* [GH-1711](https://github.com/apache/cordova-android/pull/1711) ci: Set up CodeQL analysis w/ fixes
|
||||
* [GH-1687](https://github.com/apache/cordova-android/pull/1687) ci(release-audit): add license header and dependency checker
|
||||
* [GH-1703](https://github.com/apache/cordova-android/pull/1703) ci: update `codecov@v4` w/ token
|
||||
|
||||
### 12.0.1 (Aug 23, 2023)
|
||||
|
||||
* [GH-1632](https://github.com/apache/cordova-android/pull/1632) fix(android): `monochrome` checks
|
||||
* [GH-1649](https://github.com/apache/cordova-android/pull/1649) chore: rebuild `package-lock` w/ lint corrections
|
||||
|
||||
### 12.0.0 (May 20, 2023)
|
||||
|
||||
**Breaking:**
|
||||
|
||||
4
cordova-js-src/exec.js
vendored
4
cordova-js-src/exec.js
vendored
@@ -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();
|
||||
|
||||
4
cordova-js-src/platform.js
vendored
4
cordova-js-src/platform.js
vendored
@@ -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.
|
||||
|
||||
4
cordova-js-src/plugin/android/app.js
vendored
4
cordova-js-src/plugin/android/app.js
vendored
@@ -31,14 +31,14 @@ module.exports = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Load the url into the webview or into new browser instance.
|
||||
* Load the url into the WebView or into new browser instance.
|
||||
*
|
||||
* @param url The URL to load
|
||||
* @param props Properties that can be passed in to the activity:
|
||||
* wait: int => wait msec before loading URL
|
||||
* loadingDialog: "Title,Message" => display a native loading dialog
|
||||
* loadUrlTimeoutValue: int => time in msec to wait before triggering a timeout error
|
||||
* clearHistory: boolean => clear webview history (default=false)
|
||||
* clearHistory: boolean => clear WebView history (default=false)
|
||||
* openExternal: boolean => open in a new browser (default=false)
|
||||
*
|
||||
* Example:
|
||||
|
||||
93
cordova-js-src/plugin/android/statusbar.js
vendored
Normal file
93
cordova-js-src/plugin/android/statusbar.js
vendored
Normal 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
66
eslint.config.js
Normal 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'
|
||||
}
|
||||
}
|
||||
}))
|
||||
]);
|
||||
@@ -44,14 +44,14 @@ 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
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
sourceCompatibility JavaLanguageVersion.of(cordovaConfig.JAVA_SOURCE_COMPATIBILITY)
|
||||
targetCompatibility JavaLanguageVersion.of(cordovaConfig.JAVA_TARGET_COMPATIBILITY)
|
||||
}
|
||||
|
||||
// For the Android Cordova Lib, we allow changing the minSdkVersion, but it is at the users own risk
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
{
|
||||
"MIN_SDK_VERSION": 24,
|
||||
"SDK_VERSION": 33,
|
||||
"SDK_VERSION": 36,
|
||||
"COMPILE_SDK_VERSION": null,
|
||||
"GRADLE_VERSION": "7.6",
|
||||
"MIN_BUILD_TOOLS_VERSION": "33.0.2",
|
||||
"AGP_VERSION": "7.4.2",
|
||||
"KOTLIN_VERSION": "1.7.21",
|
||||
"ANDROIDX_APP_COMPAT_VERSION": "1.6.1",
|
||||
"ANDROIDX_WEBKIT_VERSION": "1.6.0",
|
||||
"ANDROIDX_CORE_SPLASHSCREEN_VERSION": "1.0.0",
|
||||
"GRADLE_PLUGIN_GOOGLE_SERVICES_VERSION": "4.3.15",
|
||||
"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
|
||||
}
|
||||
|
||||
@@ -43,6 +43,10 @@ Boolean isVersionValid(version) {
|
||||
return !(new Version(version)).isEqual('0.0.0')
|
||||
}
|
||||
|
||||
Boolean isVersionGreaterThanEqual(versionX, versionY) {
|
||||
return (new Version(versionX)) >= (new Version(versionY))
|
||||
}
|
||||
|
||||
String doFindLatestInstalledBuildTools(String minBuildToolsVersionString) {
|
||||
def buildToolsDirContents
|
||||
try {
|
||||
@@ -146,7 +150,7 @@ def doGetConfigPreference(name, defaultValue) {
|
||||
}
|
||||
|
||||
def doApplyCordovaConfigCustomization() {
|
||||
// Apply user overide properties that comes from the "--gradleArg=-P" parameters
|
||||
// Apply user override properties that comes from the "--gradleArg=-P" parameters
|
||||
if (project.hasProperty('cdvMinSdkVersion')) {
|
||||
cordovaConfig.MIN_SDK_VERSION = Integer.parseInt('' + cdvMinSdkVersion)
|
||||
}
|
||||
@@ -236,10 +240,8 @@ ext {
|
||||
}
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
apply from: 'repositories.gradle'
|
||||
repositories repos
|
||||
|
||||
dependencies {
|
||||
classpath 'io.github.g00fy2:versioncompare:1.4.1@jar'
|
||||
|
||||
@@ -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.
|
||||
|
||||
# Project-wide Gradle settings.
|
||||
|
||||
# IDE (e.g. Android Studio) users:
|
||||
|
||||
@@ -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.
|
||||
|
||||
#Thu Nov 09 10:50:25 PST 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
|
||||
@@ -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.
|
||||
|
||||
# GENERATED FILE! DO NOT EDIT!
|
||||
|
||||
# This file was originally created by the Android Tools, but is now
|
||||
|
||||
@@ -39,7 +39,7 @@ public class AllowListPlugin extends CordovaPlugin {
|
||||
// Used when instantiated via reflection by PluginManager
|
||||
public AllowListPlugin() { }
|
||||
|
||||
// These can be used by embedders to allow Java-configuration of an allow list.
|
||||
// These can be used by plugin developers to allow Java-configuration of an allow list.
|
||||
public AllowListPlugin(Context context) {
|
||||
this(new AllowList(), new AllowList(), null);
|
||||
new CustomConfigXmlParser().parse(context);
|
||||
|
||||
@@ -37,8 +37,7 @@ public class AuthenticationToken {
|
||||
/**
|
||||
* Sets the user name.
|
||||
*
|
||||
* @param userName
|
||||
* the new user name
|
||||
* @param userName the new user name
|
||||
*/
|
||||
public void setUserName(String userName) {
|
||||
this.userName = userName;
|
||||
@@ -56,8 +55,7 @@ public class AuthenticationToken {
|
||||
/**
|
||||
* Sets the password.
|
||||
*
|
||||
* @param password
|
||||
* the new password
|
||||
* @param password the new password
|
||||
*/
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -54,7 +54,7 @@ public class CallbackMap {
|
||||
* obtained from registerCallback()
|
||||
*
|
||||
* @param mappedId The request code obtained from registerCallback()
|
||||
* @return The CordovaPlugin and orignal request code that correspond to the
|
||||
* @return The CordovaPlugin and original request code that correspond to the
|
||||
* given mappedCode
|
||||
*/
|
||||
public synchronized Pair<CordovaPlugin, Integer> getAndRemoveCallback(int mappedId) {
|
||||
|
||||
@@ -23,7 +23,7 @@ import java.util.List;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
@Deprecated // Use AllowList, CordovaPrefences, etc. directly.
|
||||
@Deprecated // Use AllowList, CordovaPreferences, etc. directly.
|
||||
public class Config {
|
||||
private static final String TAG = "Config";
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,14 +43,18 @@ 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
|
||||
* application. It should be extended by the user to load the specific
|
||||
* html file that contains the application.
|
||||
*
|
||||
* As an example:
|
||||
* <p>As an example:</p>
|
||||
*
|
||||
* <pre>
|
||||
* package org.apache.cordova.examples;
|
||||
@@ -68,17 +73,16 @@ import androidx.core.splashscreen.SplashScreen;
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* Cordova xml configuration: Cordova uses a configuration file at
|
||||
* res/xml/config.xml to specify its settings. See "The config.xml File"
|
||||
* guide in cordova-docs at http://cordova.apache.org/docs for the documentation
|
||||
* for the configuration. The use of the set*Property() methods is
|
||||
* deprecated in favor of the config.xml file.
|
||||
* <p>Cordova xml configuration: Cordova uses a configuration file at
|
||||
* res/xml/config.xml to specify its settings. See the "Config.xml API" documentation for
|
||||
* configuration details at <a href="https://cordova.apache.org/docs">Apache Cordova Docs</a>.</p>
|
||||
*
|
||||
* <p>The use of the set*Property() methods is deprecated in favor of the config.xml file.</p>
|
||||
*/
|
||||
public class CordovaActivity extends AppCompatActivity {
|
||||
public static String TAG = "CordovaActivity";
|
||||
|
||||
// The webview for our app
|
||||
// The WebView for our app
|
||||
protected CordovaWebView appView;
|
||||
|
||||
private static int ACTIVITY_STARTING = 0;
|
||||
@@ -101,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);
|
||||
|
||||
@@ -126,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)) {
|
||||
@@ -158,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", "");
|
||||
@@ -181,32 +198,64 @@ 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the default web view object.
|
||||
* <p/>
|
||||
* Override this to customize the webview that is used.
|
||||
* Override this to customize the WebView that is used.
|
||||
*/
|
||||
protected CordovaWebView makeWebView() {
|
||||
return new CordovaWebViewImpl(makeWebViewEngine());
|
||||
@@ -227,7 +276,7 @@ public class CordovaActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the url into the webview.
|
||||
* Load the url into the WebView.
|
||||
*/
|
||||
public void loadUrl(String url) {
|
||||
if (appView == null) {
|
||||
@@ -250,7 +299,7 @@ public class CordovaActivity extends AppCompatActivity {
|
||||
|
||||
if (this.appView != null) {
|
||||
// CB-9382 If there is an activity that started for result and main activity is waiting for callback
|
||||
// result, we shoudn't stop WebView Javascript timers, as activity for result might be using them
|
||||
// result, we shouldn't stop WebView Javascript timers, as activity for result might be using them
|
||||
boolean keepRunning = this.keepRunning || this.cordovaInterface.activityResultCallback != null;
|
||||
this.appView.handlePause(keepRunning);
|
||||
}
|
||||
@@ -391,6 +440,7 @@ public class CordovaActivity extends AppCompatActivity {
|
||||
if ((errorUrl != null) && (!failingUrl.equals(errorUrl)) && (appView != null)) {
|
||||
// Load URL on UI thread
|
||||
me.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
me.appView.showWebPage(errorUrl, false, true, null);
|
||||
}
|
||||
@@ -400,6 +450,7 @@ public class CordovaActivity extends AppCompatActivity {
|
||||
else {
|
||||
final boolean exit = !(errorCode == WebViewClient.ERROR_HOST_LOOKUP);
|
||||
me.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (exit) {
|
||||
me.appView.getView().setVisibility(View.GONE);
|
||||
@@ -416,6 +467,7 @@ public class CordovaActivity extends AppCompatActivity {
|
||||
public void displayError(final String title, final String message, final String button, final boolean exit) {
|
||||
final CordovaActivity me = this;
|
||||
me.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
AlertDialog.Builder dlg = new AlertDialog.Builder(me);
|
||||
@@ -424,6 +476,7 @@ public class CordovaActivity extends AppCompatActivity {
|
||||
dlg.setCancelable(false);
|
||||
dlg.setPositiveButton(button,
|
||||
new AlertDialog.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
if (exit) {
|
||||
@@ -488,6 +541,7 @@ public class CordovaActivity extends AppCompatActivity {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
cordovaInterface.onSaveInstanceState(outState);
|
||||
super.onSaveInstanceState(outState);
|
||||
@@ -533,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 CLI–created 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,63 +41,70 @@ public class CordovaClientCertRequest implements ICordovaClientCertRequest {
|
||||
* Cancel this request
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public void cancel()
|
||||
{
|
||||
request.cancel();
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the host name of the server requesting the certificate.
|
||||
/**
|
||||
* @return the host name of the server requesting the certificate.
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public String getHost()
|
||||
{
|
||||
return request.getHost();
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the acceptable types of asymmetric keys (can be null).
|
||||
/**
|
||||
* @return the acceptable types of asymmetric keys (can be null).
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public String[] getKeyTypes()
|
||||
{
|
||||
return request.getKeyTypes();
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the port number of the server requesting the certificate.
|
||||
/**
|
||||
* @return the port number of the server requesting the certificate.
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public int getPort()
|
||||
{
|
||||
return request.getPort();
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the acceptable certificate issuers for the certificate matching the private key (can be null).
|
||||
/**
|
||||
* @return the acceptable certificate issuers for the certificate matching the private key (can be null).
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public Principal[] getPrincipals()
|
||||
{
|
||||
return request.getPrincipals();
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* Ignore the request for now. Do not remember user's choice.
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public void ignore()
|
||||
{
|
||||
request.ignore();
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* Proceed with the specified private key and client certificate chain. Remember the user's positive choice and use it for future requests.
|
||||
*
|
||||
* @param privateKey The privateKey
|
||||
* @param chain The certificate chain
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public void proceed(PrivateKey privateKey, X509Certificate[] chain)
|
||||
{
|
||||
request.proceed(privateKey, chain);
|
||||
|
||||
@@ -43,18 +43,21 @@ public class CordovaDialogsHelper {
|
||||
dlg.setCancelable(true);
|
||||
dlg.setPositiveButton(android.R.string.ok,
|
||||
new AlertDialog.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
result.gotResult(true, null);
|
||||
}
|
||||
});
|
||||
dlg.setOnCancelListener(
|
||||
new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
result.gotResult(false, null);
|
||||
}
|
||||
});
|
||||
dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {
|
||||
//DO NOTHING
|
||||
@Override
|
||||
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK)
|
||||
{
|
||||
@@ -75,24 +78,28 @@ public class CordovaDialogsHelper {
|
||||
dlg.setCancelable(true);
|
||||
dlg.setPositiveButton(android.R.string.ok,
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
result.gotResult(true, null);
|
||||
}
|
||||
});
|
||||
dlg.setNegativeButton(android.R.string.cancel,
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
result.gotResult(false, null);
|
||||
}
|
||||
});
|
||||
dlg.setOnCancelListener(
|
||||
new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
result.gotResult(false, null);
|
||||
}
|
||||
});
|
||||
dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {
|
||||
//DO NOTHING
|
||||
@Override
|
||||
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK)
|
||||
{
|
||||
@@ -111,8 +118,8 @@ public class CordovaDialogsHelper {
|
||||
* If the client returns true, WebView will assume that the client will
|
||||
* handle the prompt dialog and call the appropriate JsPromptResult method.
|
||||
*
|
||||
* Since we are hacking prompts for our own purposes, we should not be using them for
|
||||
* this purpose, perhaps we should hack console.log to do this instead!
|
||||
* <p>Since we are hacking prompts for our own purposes, we should not be using them for
|
||||
* this purpose, perhaps we should hack console.log to do this instead!</p>
|
||||
*/
|
||||
public void showPrompt(String message, String defaultValue, final Result result) {
|
||||
// Returning false would also show a dialog, but the default one shows the origin (ugly).
|
||||
@@ -126,6 +133,7 @@ public class CordovaDialogsHelper {
|
||||
dlg.setCancelable(false);
|
||||
dlg.setPositiveButton(android.R.string.ok,
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
String userText = input.getText().toString();
|
||||
result.gotResult(true, userText);
|
||||
@@ -133,6 +141,7 @@ public class CordovaDialogsHelper {
|
||||
});
|
||||
dlg.setNegativeButton(android.R.string.cancel,
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
result.gotResult(false, null);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ public class CordovaHttpAuthHandler implements ICordovaHttpAuthHandler {
|
||||
/**
|
||||
* Instructs the WebView to cancel the authentication request.
|
||||
*/
|
||||
@Override
|
||||
public void cancel () {
|
||||
this.handler.cancel();
|
||||
}
|
||||
@@ -45,6 +46,7 @@ public class CordovaHttpAuthHandler implements ICordovaHttpAuthHandler {
|
||||
* @param username
|
||||
* @param password
|
||||
*/
|
||||
@Override
|
||||
public void proceed (String username, String password) {
|
||||
this.handler.proceed(username, password);
|
||||
}
|
||||
|
||||
@@ -51,7 +51,8 @@ public interface CordovaInterface {
|
||||
/**
|
||||
* Get the Android activity.
|
||||
*
|
||||
* If a custom engine lives outside of the Activity's lifecycle the return value may be null.
|
||||
* <p>If a custom engine lives outside of the Activity's lifecycle the return value
|
||||
* may be null.</p>
|
||||
*
|
||||
* @return the Activity
|
||||
*/
|
||||
@@ -74,7 +75,7 @@ public interface CordovaInterface {
|
||||
public Object onMessage(String id, Object data);
|
||||
|
||||
/**
|
||||
* Returns a shared thread pool that can be used for background tasks.
|
||||
* @return a shared thread pool that can be used for background tasks.
|
||||
*/
|
||||
public ExecutorService getThreadPool();
|
||||
|
||||
@@ -89,7 +90,9 @@ public interface CordovaInterface {
|
||||
public void requestPermissions(CordovaPlugin plugin, int requestCode, String [] permissions);
|
||||
|
||||
/**
|
||||
* Check for a permission. Returns true if the permission is granted, false otherwise.
|
||||
* Check for a permission.
|
||||
*
|
||||
* @return true if the permission is granted, false otherwise.
|
||||
*/
|
||||
public boolean hasPermission(String permission);
|
||||
|
||||
|
||||
@@ -135,7 +135,9 @@ public class CordovaInterfaceImpl implements CordovaInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Routes the result to the awaiting plugin. Returns false if no plugin was waiting.
|
||||
* Routes the result to the awaiting plugin.
|
||||
*
|
||||
* @return false if no plugin was waiting.
|
||||
*/
|
||||
public boolean onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||
CordovaPlugin callback = activityResultCallback;
|
||||
@@ -223,18 +225,21 @@ public class CordovaInterfaceImpl implements CordovaInterface {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestPermission(CordovaPlugin plugin, int requestCode, String permission) {
|
||||
String[] permissions = new String [1];
|
||||
permissions[0] = permission;
|
||||
requestPermissions(plugin, requestCode, permissions);
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public void requestPermissions(CordovaPlugin plugin, int requestCode, String [] permissions) {
|
||||
int mappedRequestCode = permissionResultCallbacks.registerCallback(plugin, requestCode);
|
||||
getActivity().requestPermissions(permissions, mappedRequestCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(String permission)
|
||||
{
|
||||
return PackageManager.PERMISSION_GRANTED == activity.checkSelfPermission(permission);
|
||||
|
||||
@@ -64,7 +64,11 @@ public class CordovaPlugin {
|
||||
* Called after plugin construction and fields have been initialized.
|
||||
* Prefer to use pluginInitialize instead since there is no value in
|
||||
* having parameters on the initialize() function.
|
||||
*
|
||||
* @deprecated Use {@link #pluginInitialize()} instead. This method is no longer recommended
|
||||
* and will be removed in future versions.
|
||||
*/
|
||||
@Deprecated
|
||||
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
|
||||
}
|
||||
|
||||
@@ -75,7 +79,7 @@ public class CordovaPlugin {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the plugin's service name (what you'd use when calling pluginManger.getPlugin())
|
||||
* @return the plugin's service name (what you'd use when calling pluginManger.getPlugin())
|
||||
*/
|
||||
public String getServiceName() {
|
||||
return serviceName;
|
||||
@@ -84,11 +88,14 @@ public class CordovaPlugin {
|
||||
/**
|
||||
* Executes the request.
|
||||
*
|
||||
* This method is called from the WebView thread. To do a non-trivial amount of work, use:
|
||||
* cordova.getThreadPool().execute(runnable);
|
||||
* <p>This method is called from the WebView thread. To do a non-trivial
|
||||
* amount of work, use:</p>
|
||||
*
|
||||
* To run on the UI thread, use:
|
||||
* cordova.getActivity().runOnUiThread(runnable);
|
||||
* <pre>cordova.getThreadPool().execute(runnable);</pre>
|
||||
*
|
||||
* <p>To run on the UI thread, use:</p>
|
||||
*
|
||||
* <pre>cordova.getActivity().runOnUiThread(runnable);</pre>
|
||||
*
|
||||
* @param action The action to execute.
|
||||
* @param rawArgs The exec() arguments in JSON form.
|
||||
@@ -103,11 +110,13 @@ public class CordovaPlugin {
|
||||
/**
|
||||
* Executes the request.
|
||||
*
|
||||
* This method is called from the WebView thread. To do a non-trivial amount of work, use:
|
||||
* cordova.getThreadPool().execute(runnable);
|
||||
* <p>This method is called from the WebView thread. To do a non-trivial amount of work, use:</p>
|
||||
*
|
||||
* To run on the UI thread, use:
|
||||
* cordova.getActivity().runOnUiThread(runnable);
|
||||
* <pre>cordova.getThreadPool().execute(runnable);</pre>
|
||||
*
|
||||
* <p>To run on the UI thread, use:</p>
|
||||
*
|
||||
* <pre>cordova.getActivity().runOnUiThread(runnable);</pre>
|
||||
*
|
||||
* @param action The action to execute.
|
||||
* @param args The exec() arguments.
|
||||
@@ -122,10 +131,10 @@ public class CordovaPlugin {
|
||||
/**
|
||||
* Executes the request.
|
||||
*
|
||||
* This method is called from the WebView thread. To do a non-trivial amount of work, use:
|
||||
* <p>This method is called from the WebView thread. To do a non-trivial amount of work, use:</p>
|
||||
* cordova.getThreadPool().execute(runnable);
|
||||
*
|
||||
* To run on the UI thread, use:
|
||||
* <p>To run on the UI thread, use:</p>
|
||||
* cordova.getActivity().runOnUiThread(runnable);
|
||||
*
|
||||
* @param action The action to execute.
|
||||
@@ -227,18 +236,18 @@ public class CordovaPlugin {
|
||||
/**
|
||||
* Hook for blocking the loading of external resources.
|
||||
*
|
||||
* This will be called when the WebView's shouldInterceptRequest wants to
|
||||
* <p>This will be called when the WebView's shouldInterceptRequest wants to
|
||||
* know whether to open a connection to an external resource. Return false
|
||||
* to block the request: if any plugin returns false, Cordova will block
|
||||
* the request. If all plugins return null, the default policy will be
|
||||
* enforced. If at least one plugin returns true, and no plugins return
|
||||
* false, then the request will proceed.
|
||||
* false, then the request will proceed.</p>
|
||||
*
|
||||
* Note that this only affects resource requests which are routed through
|
||||
* <p>Note that this only affects resource requests which are routed through
|
||||
* WebViewClient.shouldInterceptRequest, such as XMLHttpRequest requests and
|
||||
* img tag loads. WebSockets and media requests (such as <video> and <audio>
|
||||
* tags) are not affected by this method. Use CSP headers to control access
|
||||
* to such resources.
|
||||
* to such resources.</p>
|
||||
*/
|
||||
public Boolean shouldAllowRequest(String url) {
|
||||
return null;
|
||||
@@ -246,13 +255,13 @@ public class CordovaPlugin {
|
||||
|
||||
/**
|
||||
* Hook for blocking navigation by the Cordova WebView. This applies both to top-level and
|
||||
* iframe navigations.
|
||||
* iframe navigation.
|
||||
*
|
||||
* This will be called when the WebView's needs to know whether to navigate
|
||||
* <p>This will be called when the WebView's needs to know whether to navigate
|
||||
* to a new page. Return false to block the navigation: if any plugin
|
||||
* returns false, Cordova will block the navigation. If all plugins return
|
||||
* null, the default policy will be enforced. It at least one plugin returns
|
||||
* true, and no plugins return false, then the navigation will proceed.
|
||||
* true, and no plugins return false, then the navigation will proceed.</p>
|
||||
*/
|
||||
public Boolean shouldAllowNavigation(String url) {
|
||||
return null;
|
||||
@@ -270,12 +279,12 @@ public class CordovaPlugin {
|
||||
/**
|
||||
* Hook for blocking the launching of Intents by the Cordova application.
|
||||
*
|
||||
* This will be called when the WebView will not navigate to a page, but
|
||||
* <p>This will be called when the WebView will not navigate to a page, but
|
||||
* could launch an intent to handle the URL. Return false to block this: if
|
||||
* any plugin returns false, Cordova will block the navigation. If all
|
||||
* plugins return null, the default policy will be enforced. If at least one
|
||||
* plugin returns true, and no plugins return false, then the URL will be
|
||||
* opened.
|
||||
* opened.</p>
|
||||
*/
|
||||
public Boolean shouldOpenExternalUrl(String url) {
|
||||
return null;
|
||||
@@ -284,8 +293,8 @@ public class CordovaPlugin {
|
||||
/**
|
||||
* Allows plugins to handle a link being clicked. Return true here to cancel the navigation.
|
||||
*
|
||||
* @param url The URL that is trying to be loaded in the Cordova webview.
|
||||
* @return Return true to prevent the URL from loading. Default is false.
|
||||
* @param url The URL that is trying to be loaded in the Cordova WebView.
|
||||
* @return true to prevent the URL from loading. Default is false.
|
||||
*/
|
||||
public boolean onOverrideUrlLoading(String url) {
|
||||
return false;
|
||||
@@ -295,17 +304,20 @@ public class CordovaPlugin {
|
||||
* Hook for redirecting requests. Applies to WebView requests as well as requests made by plugins.
|
||||
* To handle the request directly, return a URI in the form:
|
||||
*
|
||||
* cdvplugin://pluginId/...
|
||||
* <pre>cdvplugin://pluginId/...</pre>
|
||||
*
|
||||
* And implement handleOpenForRead().
|
||||
* To make this easier, use the toPluginUri() and fromPluginUri() helpers:
|
||||
* <p>And implement handleOpenForRead().</p>
|
||||
*
|
||||
* <p>To make this easier, use the toPluginUri() and fromPluginUri() helpers:</p>
|
||||
*
|
||||
* <pre>
|
||||
* public Uri remapUri(Uri uri) { return toPluginUri(uri); }
|
||||
*
|
||||
* public CordovaResourceApi.OpenForReadResult handleOpenForRead(Uri uri) throws IOException {
|
||||
* Uri origUri = fromPluginUri(uri);
|
||||
* ...
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public Uri remapUri(Uri uri) {
|
||||
return null;
|
||||
@@ -343,9 +355,9 @@ public class CordovaPlugin {
|
||||
/**
|
||||
* Called when the WebView does a top-level navigation or refreshes.
|
||||
*
|
||||
* Plugins should stop any long-running processes and clean up internal state.
|
||||
* <p>Plugins should stop any long-running processes and clean up internal state.</p>
|
||||
*
|
||||
* Does nothing by default.
|
||||
* <p>Does nothing by default.</p>
|
||||
*/
|
||||
public void onReset() {
|
||||
}
|
||||
@@ -358,9 +370,7 @@ public class CordovaPlugin {
|
||||
* @param handler The HttpAuthHandler used to set the WebView's response
|
||||
* @param host The host requiring authentication
|
||||
* @param realm The realm for which authentication is required
|
||||
*
|
||||
* @return Returns True if plugin will resolve this auth challenge, otherwise False
|
||||
*
|
||||
* @return true if the plugin will resolve this auth challenge, else false
|
||||
*/
|
||||
public boolean onReceivedHttpAuthRequest(CordovaWebView view, ICordovaHttpAuthHandler handler, String host, String realm) {
|
||||
return false;
|
||||
@@ -372,9 +382,7 @@ public class CordovaPlugin {
|
||||
*
|
||||
* @param view The WebView that is initiating the callback
|
||||
* @param request The client certificate request
|
||||
*
|
||||
* @return Returns True if plugin will resolve this auth challenge, otherwise False
|
||||
*
|
||||
* @return True if plugin will resolve this auth challenge, otherwise False
|
||||
*/
|
||||
public boolean onReceivedClientCertRequest(CordovaWebView view, ICordovaClientCertRequest request) {
|
||||
return false;
|
||||
@@ -392,20 +400,17 @@ public class CordovaPlugin {
|
||||
* Called by the Plugin Manager when we need to actually request permissions
|
||||
*
|
||||
* @param requestCode Passed to the activity to track the request
|
||||
*
|
||||
* @return Returns the permission that was stored in the plugin
|
||||
* @return The permission that was stored in the plugin
|
||||
*/
|
||||
|
||||
public void requestPermissions(int requestCode) {
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* Called by the WebView implementation to check for geolocation permissions, can be used
|
||||
* by other Java methods in the event that a plugin is using this as a dependency.
|
||||
*
|
||||
* @return Returns true if the plugin has all the permissions it needs to operate.
|
||||
* @return True if the plugin has all the permissions it needs to operate.
|
||||
*/
|
||||
|
||||
public boolean hasPermisssion() {
|
||||
return true;
|
||||
}
|
||||
@@ -416,7 +421,6 @@ public class CordovaPlugin {
|
||||
* @param requestCode
|
||||
* @param permissions
|
||||
* @param grantResults
|
||||
*
|
||||
* @deprecated Use {@link #onRequestPermissionsResult} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@@ -439,6 +443,7 @@ public class CordovaPlugin {
|
||||
|
||||
/**
|
||||
* Allow plugins to supply a PathHandler for the WebViewAssetHandler
|
||||
*
|
||||
* @return a CordovaPluginPathHandler which listen for paths and returns a response
|
||||
*/
|
||||
public CordovaPluginPathHandler getPathHandler() {
|
||||
@@ -446,11 +451,13 @@ public class CordovaPlugin {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the WebView's render process has exited. Can be used to collect information regarding the crash for crashlytics or optionally attempt to gracefully handle/recover the crashed webview by recreating it.
|
||||
* Called when the WebView's render process has exited. Can be used to collect information
|
||||
* regarding the crash for crashlytics or optionally attempt to gracefully handle/recover the
|
||||
* crashed WebView by recreating it.
|
||||
*
|
||||
* See <a href="https://developer.android.com/reference/android/webkit/WebViewClient#onRenderProcessGone(android.webkit.WebView,%20android.webkit.RenderProcessGoneDetail)">WebViewClient#onRenderProcessGone</a>
|
||||
* <p>See <a href="https://developer.android.com/reference/android/webkit/WebViewClient#onRenderProcessGone(android.webkit.WebView,%20android.webkit.RenderProcessGoneDetail)">WebViewClient#onRenderProcessGone</a></p>
|
||||
*
|
||||
* Note: A plugin must not attempt to recover a webview that it does not own/manage.
|
||||
* <p>Note: A plugin must not attempt to recover a WebView that it does not own/manage.</p>
|
||||
*
|
||||
* @return true if the host application handled the situation that process has exited,
|
||||
* otherwise, application will crash if render process crashed, or be killed
|
||||
|
||||
@@ -45,21 +45,36 @@ import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
* What this class provides:
|
||||
* 1. Helpers for reading & writing to URLs.
|
||||
* - E.g. handles assets, resources, content providers, files, data URIs, http[s]
|
||||
* - E.g. Can be used to query for mime-type & content length.
|
||||
*
|
||||
* 2. To allow plugins to redirect URLs (via remapUrl).
|
||||
* - All plugins should call remapUrl() on URLs they receive from JS *before*
|
||||
* passing the URL onto other utility functions in this class.
|
||||
* - For an example usage of this, refer to the org.apache.cordova.file plugin.
|
||||
* <ol>
|
||||
* <li>
|
||||
* Helpers for reading & writing to URLs.
|
||||
* <ul>
|
||||
* <li>E.g. handles assets, resources, content providers, files, data URIs, http[s]</li>
|
||||
* <li>E.g. Can be used to query for mime-type & content length.</p></li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* <li>
|
||||
* To allow plugins to redirect URLs (via remapUrl).
|
||||
* <ul>
|
||||
* <li>
|
||||
* All plugins should call remapUrl() on URLs they receive from JS *before* passing the URL onto other utility functions in this class.
|
||||
* </li>
|
||||
* <li>For an example usage of this, refer to the org.apache.cordova.file plugin.</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* </ol>
|
||||
*
|
||||
* Future Work:
|
||||
* - Consider using a Cursor to query content URLs for their size (like the file plugin does).
|
||||
* - Allow plugins to remapUri to "cdv-plugin://plugin-name/foo", which CordovaResourceApi
|
||||
* would then delegate to pluginManager.getPlugin(plugin-name).openForRead(url)
|
||||
* - Currently, plugins *can* do this by remapping to a data: URL, but it's inefficient
|
||||
* for large payloads.
|
||||
* <p>Future Work:</p>
|
||||
* <ul>
|
||||
* <li>Consider using a Cursor to query content URLs for their size (like the file plugin does).</li>
|
||||
* <li>
|
||||
* Allow plugins to remapUri to "cdv-plugin://plugin-name/foo", which CordovaResourceApi would then delegate to pluginManager.getPlugin(plugin-name).openForRead(url)
|
||||
* <ul>
|
||||
* <li>Currently, plugins *can* do this by remapping to a data: URL, but it's inefficient for large payloads.</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* </ul>
|
||||
*/
|
||||
public class CordovaResourceApi {
|
||||
@SuppressWarnings("unused")
|
||||
@@ -143,8 +158,7 @@ public class CordovaResourceApi {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a File that points to the resource, or null if the resource
|
||||
* is not on the local filesystem.
|
||||
* @return A file that points to the resource, or null if the resource is not on the local filesystem.
|
||||
*/
|
||||
public File mapUriToFile(Uri uri) {
|
||||
assertBackgroundThread();
|
||||
@@ -223,11 +237,12 @@ public class CordovaResourceApi {
|
||||
|
||||
/**
|
||||
* Opens a stream to the given URI, also providing the MIME type & length.
|
||||
*
|
||||
* @return Never returns null.
|
||||
* @throws Throws an InvalidArgumentException for relative URIs. Relative URIs should be
|
||||
* resolved before being passed into this function.
|
||||
* @throws Throws an IOException if the URI cannot be opened.
|
||||
* @throws Throws an IllegalStateException if called on a foreground thread.
|
||||
* @throws IllegalArgumentException For relative URIs. Relative URIs should be resolved before
|
||||
* being passed into this function.
|
||||
* @throws IOException If the URI cannot be opened.
|
||||
* @throws IllegalStateException If called on a foreground thread.
|
||||
*/
|
||||
public OpenForReadResult openForRead(Uri uri) throws IOException {
|
||||
return openForRead(uri, false);
|
||||
@@ -235,11 +250,12 @@ public class CordovaResourceApi {
|
||||
|
||||
/**
|
||||
* Opens a stream to the given URI, also providing the MIME type & length.
|
||||
*
|
||||
* @return Never returns null.
|
||||
* @throws Throws an InvalidArgumentException for relative URIs. Relative URIs should be
|
||||
* resolved before being passed into this function.
|
||||
* @throws Throws an IOException if the URI cannot be opened.
|
||||
* @throws Throws an IllegalStateException if called on a foreground thread and skipThreadCheck is false.
|
||||
* @throws IllegalArgumentException For relative URIs. Relative URIs should be resolved before
|
||||
* being passed into this function.
|
||||
* @throws IOException If the URI cannot be opened.
|
||||
* @throws IllegalStateException If called on a foreground thread and skipThreadCheck is false.
|
||||
*/
|
||||
public OpenForReadResult openForRead(Uri uri, boolean skipThreadCheck) throws IOException {
|
||||
if (!skipThreadCheck) {
|
||||
@@ -320,10 +336,11 @@ public class CordovaResourceApi {
|
||||
|
||||
/**
|
||||
* Opens a stream to the given URI.
|
||||
*
|
||||
* @return Never returns null.
|
||||
* @throws Throws an InvalidArgumentException for relative URIs. Relative URIs should be
|
||||
* resolved before being passed into this function.
|
||||
* @throws Throws an IOException if the URI cannot be opened.
|
||||
* @throws IllegalArgumentException For relative URIs. Relative URIs should be resolved before
|
||||
* being passed into this function.
|
||||
* @throws IOException If the URI cannot be opened.
|
||||
*/
|
||||
public OutputStream openOutputStream(Uri uri, boolean append) throws IOException {
|
||||
assertBackgroundThread();
|
||||
|
||||
@@ -25,13 +25,13 @@ import android.view.View;
|
||||
import android.webkit.WebChromeClient.CustomViewCallback;
|
||||
|
||||
/**
|
||||
* Main interface for interacting with a Cordova webview - implemented by CordovaWebViewImpl.
|
||||
* Main interface for interacting with a Cordova WebView - implemented by CordovaWebViewImpl.
|
||||
* This is an interface so that it can be easily mocked in tests.
|
||||
* Methods may be added to this interface without a major version bump, as plugins & embedders
|
||||
* Methods may be added to this interface without a major version bump, as plugins/developer
|
||||
* are not expected to implement it.
|
||||
*/
|
||||
public interface CordovaWebView {
|
||||
public static final String CORDOVA_VERSION = "12.0.0";
|
||||
public static final String CORDOVA_VERSION = "15.0.0-dev";
|
||||
|
||||
void init(CordovaInterface cordova, List<PluginEntry> pluginEntries, CordovaPreferences preferences);
|
||||
|
||||
@@ -70,34 +70,47 @@ public interface CordovaWebView {
|
||||
/**
|
||||
* Send JavaScript statement back to JavaScript.
|
||||
*
|
||||
* Deprecated (https://issues.apache.org/jira/browse/CB-6851)
|
||||
* <p>Deprecated (<a href="https://issues.apache.org/jira/browse/CB-6851">CB-6851</a>)
|
||||
* Instead of executing snippets of JS, you should use the exec bridge
|
||||
* to create a Java->JS communication channel.
|
||||
* To do this:
|
||||
* 1. Within plugin.xml (to have your JS run before deviceready):
|
||||
* <js-module><runs/></js-module>
|
||||
* 2. Within your .js (call exec on start-up):
|
||||
* to create a Java->JS communication channel.</p>
|
||||
*
|
||||
* <p>To do this:</p>
|
||||
*
|
||||
* <p>1. Within plugin.xml (to have your JS run before deviceready):</p>
|
||||
*
|
||||
* <pre>
|
||||
* <js-module><runs/></js-module>
|
||||
* </pre>
|
||||
*
|
||||
* <p>2. Within your .js (call exec on start-up):</p>
|
||||
*
|
||||
* <pre>
|
||||
* require('cordova/channel').onCordovaReady.subscribe(function() {
|
||||
* require('cordova/exec')(win, null, 'Plugin', 'method', []);
|
||||
* function win(message) {
|
||||
* ... process message from java here ...
|
||||
* }
|
||||
* });
|
||||
* 3. Within your .java:
|
||||
* </pre>
|
||||
*
|
||||
* <p>3. Within your .java:</p>
|
||||
*
|
||||
* <pre>
|
||||
* PluginResult dataResult = new PluginResult(PluginResult.Status.OK, CODE);
|
||||
* dataResult.setKeepCallback(true);
|
||||
* savedCallbackContext.sendPluginResult(dataResult);
|
||||
* </pre>
|
||||
*/
|
||||
@Deprecated
|
||||
void sendJavascript(String statememt);
|
||||
|
||||
/**
|
||||
* Load the specified URL in the Cordova webview or a new browser instance.
|
||||
* Load the specified URL in the Cordova WebView or a new browser instance.
|
||||
*
|
||||
* NOTE: If openExternal is false, only allow listed URLs can be loaded.
|
||||
* <p>NOTE: If openExternal is false, only allow listed URLs can be loaded.</p>
|
||||
*
|
||||
* @param url The url to load.
|
||||
* @param openExternal Load url in browser instead of Cordova webview.
|
||||
* @param openExternal Load url in browser instead of Cordova WebView.
|
||||
* @param clearHistory Clear the history stack, so new page becomes top of history
|
||||
* @param params Parameters for new app
|
||||
*/
|
||||
|
||||
@@ -59,7 +59,7 @@ public interface CordovaWebViewEngine {
|
||||
/** Clean up all resources associated with the WebView. */
|
||||
void destroy();
|
||||
|
||||
/** Add the evaulate Javascript method **/
|
||||
/** Add the evaluate Javascript method **/
|
||||
void evaluateJavascript(String js, ValueCallback<String> callback);
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,7 +43,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Main class for interacting with a Cordova webview. Manages plugins, events, and a CordovaWebViewEngine.
|
||||
* Main class for interacting with a Cordova WebView. Manages plugins, events, and a CordovaWebViewEngine.
|
||||
* Class uses two-phase initialization. You must call init() before calling any other methods.
|
||||
*/
|
||||
public class CordovaWebViewImpl implements CordovaWebView {
|
||||
@@ -149,11 +149,12 @@ public class CordovaWebViewImpl implements CordovaWebView {
|
||||
|
||||
// Timeout error method
|
||||
final Runnable loadError = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
stopLoading();
|
||||
LOG.e(TAG, "CordovaWebView: TIMEOUT ERROR!");
|
||||
|
||||
// Handle other errors by passing them to the webview in JS
|
||||
// Handle other errors by passing them to the WebView in JS
|
||||
JSONObject data = new JSONObject();
|
||||
try {
|
||||
data.put("errorCode", -6);
|
||||
@@ -168,6 +169,7 @@ public class CordovaWebViewImpl implements CordovaWebView {
|
||||
|
||||
// Timeout timer method
|
||||
final Runnable timeoutCheck = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
synchronized (this) {
|
||||
@@ -189,6 +191,7 @@ public class CordovaWebViewImpl implements CordovaWebView {
|
||||
if (cordova.getActivity() != null) {
|
||||
final boolean _recreatePlugins = recreatePlugins;
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (loadUrlTimeoutValue > 0) {
|
||||
cordova.getThreadPool().execute(timeoutCheck);
|
||||
@@ -216,7 +219,7 @@ public class CordovaWebViewImpl implements CordovaWebView {
|
||||
engine.clearHistory();
|
||||
}
|
||||
|
||||
// If loading into our webview
|
||||
// If loading into our WebView
|
||||
if (!openExternal) {
|
||||
// Make sure url is in allow list
|
||||
if (pluginManager.shouldAllowNavigation(url)) {
|
||||
@@ -484,7 +487,7 @@ public class CordovaWebViewImpl implements CordovaWebView {
|
||||
|
||||
// If app doesn't want to run in background
|
||||
if (!keepRunning) {
|
||||
// Pause JavaScript timers. This affects all webviews within the app!
|
||||
// Pause JavaScript timers. This affects all WebViews within the app!
|
||||
engine.setPaused(true);
|
||||
}
|
||||
}
|
||||
@@ -494,7 +497,7 @@ public class CordovaWebViewImpl implements CordovaWebView {
|
||||
return;
|
||||
}
|
||||
|
||||
// Resume JavaScript timers. This affects all webviews within the app!
|
||||
// Resume JavaScript timers. This affects all WebViews within the app!
|
||||
engine.setPaused(false);
|
||||
this.pluginManager.onResume(keepRunning);
|
||||
|
||||
@@ -534,7 +537,7 @@ public class CordovaWebViewImpl implements CordovaWebView {
|
||||
// We should use a blank data: url instead so it's more obvious
|
||||
this.loadUrl("about:blank");
|
||||
|
||||
// TODO: Should not destroy webview until after about:blank is done loading.
|
||||
// TODO: Should not destroy WebView until after about:blank is done loading.
|
||||
engine.destroy();
|
||||
hideCustomView();
|
||||
}
|
||||
@@ -579,11 +582,13 @@ public class CordovaWebViewImpl implements CordovaWebView {
|
||||
// Make app visible after 2 sec in case there was a JS error and Cordova JS never initialized correctly
|
||||
if (engine.getView().getVisibility() != View.VISIBLE) {
|
||||
Thread t = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
if (cordova.getActivity() != null) {
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
pluginManager.postMessage("spinner", "stop");
|
||||
}
|
||||
|
||||
@@ -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,16 +68,19 @@ public class CoreAndroid extends CordovaPlugin {
|
||||
@Override
|
||||
public void pluginInitialize() {
|
||||
this.initTelephonyReceiver();
|
||||
backCallback = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the request and returns PluginResult.
|
||||
*
|
||||
* @param action The action to execute.
|
||||
* @param args JSONArry of arguments for the plugin.
|
||||
* @param args JSONArray of arguments for the plugin.
|
||||
* @param callbackContext The callback context from which we were invoked.
|
||||
*
|
||||
* @return A PluginResult object with a status and message.
|
||||
*/
|
||||
@Override
|
||||
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
|
||||
PluginResult.Status status = PluginResult.Status.OK;
|
||||
String result = "";
|
||||
@@ -82,10 +90,11 @@ public class CoreAndroid extends CordovaPlugin {
|
||||
this.clearCache();
|
||||
}
|
||||
else if (action.equals("show")) {
|
||||
// This gets called from JavaScript onCordovaReady to show the webview.
|
||||
// This gets called from JavaScript onCordovaReady to show the WebView.
|
||||
// I recommend we change the name of the Message as spinner/stop is not
|
||||
// indicative of what this actually does (shows the webview).
|
||||
// indicative of what this actually does (shows the WebView).
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
webView.getPluginManager().postMessage("spinner", "stop");
|
||||
}
|
||||
@@ -144,6 +153,7 @@ public class CoreAndroid extends CordovaPlugin {
|
||||
*/
|
||||
public void clearCache() {
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
webView.clearCache();
|
||||
}
|
||||
@@ -151,7 +161,7 @@ public class CoreAndroid extends CordovaPlugin {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the url into the webview.
|
||||
* Load the url into the WebView.
|
||||
*
|
||||
* @param url
|
||||
* @param props Properties that can be passed in to the Cordova activity (i.e. loadingDialog, wait, ...)
|
||||
@@ -215,6 +225,7 @@ public class CoreAndroid extends CordovaPlugin {
|
||||
*/
|
||||
public void clearHistory() {
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
webView.clearHistory();
|
||||
}
|
||||
@@ -227,6 +238,7 @@ public class CoreAndroid extends CordovaPlugin {
|
||||
*/
|
||||
public void backHistory() {
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
webView.backHistory();
|
||||
}
|
||||
@@ -241,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);
|
||||
}
|
||||
|
||||
@@ -349,10 +384,10 @@ public class CoreAndroid extends CordovaPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* Unregister the receiver
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public void onDestroy()
|
||||
{
|
||||
webView.getContext().unregisterReceiver(this.telephonyReceiver);
|
||||
|
||||
@@ -31,32 +31,32 @@ public interface ICordovaClientCertRequest {
|
||||
*/
|
||||
public void cancel();
|
||||
|
||||
/*
|
||||
* Returns the host name of the server requesting the certificate.
|
||||
/**
|
||||
* @return the host name of the server requesting the certificate.
|
||||
*/
|
||||
public String getHost();
|
||||
|
||||
/*
|
||||
* Returns the acceptable types of asymmetric keys (can be null).
|
||||
/**
|
||||
* @return the acceptable types of asymmetric keys (can be null).
|
||||
*/
|
||||
public String[] getKeyTypes();
|
||||
|
||||
/*
|
||||
* Returns the port number of the server requesting the certificate.
|
||||
/**
|
||||
* @return the port number of the server requesting the certificate.
|
||||
*/
|
||||
public int getPort();
|
||||
|
||||
/*
|
||||
* Returns the acceptable certificate issuers for the certificate matching the private key (can be null).
|
||||
/**
|
||||
* @return the acceptable certificate issuers for the certificate matching the private key (can be null).
|
||||
*/
|
||||
public Principal[] getPrincipals();
|
||||
|
||||
/*
|
||||
/**
|
||||
* Ignore the request for now. Do not remember user's choice.
|
||||
*/
|
||||
public void ignore();
|
||||
|
||||
/*
|
||||
/**
|
||||
* Proceed with the specified private key and client certificate chain. Remember the user's positive choice and use it for future requests.
|
||||
*
|
||||
* @param privateKey The privateKey
|
||||
|
||||
@@ -23,8 +23,8 @@ import android.util.Log;
|
||||
/**
|
||||
* Log to Android logging system.
|
||||
*
|
||||
* Log message can be a string or a printf formatted string with arguments.
|
||||
* See http://developer.android.com/reference/java/util/Formatter.html
|
||||
* <p>Log message can be a string or a printf formatted string with arguments.
|
||||
* See <a href="http://developer.android.com/reference/java/util/Formatter.html">Formatter</a></p>
|
||||
*/
|
||||
public class LOG {
|
||||
|
||||
|
||||
@@ -124,12 +124,10 @@ public class NativeToJsMessageQueue {
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines and returns queued messages combined into a single string.
|
||||
*
|
||||
* Combines as many messages as possible, without exceeding
|
||||
* COMBINED_RESPONSE_CUTOFF in case of multiple response messages.
|
||||
*
|
||||
* Returns null if the queue is empty.
|
||||
* @return a string of queued messages combined or null if the queue is empty.
|
||||
*/
|
||||
public String popAndEncode(boolean fromOnlineEvent) {
|
||||
synchronized (this) {
|
||||
@@ -206,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('}');
|
||||
@@ -302,6 +300,7 @@ public class NativeToJsMessageQueue {
|
||||
@Override
|
||||
public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) {
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String js = queue.popAndEncodeAsJs();
|
||||
if (js != null) {
|
||||
@@ -330,6 +329,7 @@ public class NativeToJsMessageQueue {
|
||||
@Override
|
||||
public void reset() {
|
||||
delegate.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
online = false;
|
||||
// If the following call triggers a notifyOfFlush, then ignore it.
|
||||
@@ -342,6 +342,7 @@ public class NativeToJsMessageQueue {
|
||||
@Override
|
||||
public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) {
|
||||
delegate.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!queue.isEmpty()) {
|
||||
ignoreNextFlush = false;
|
||||
@@ -372,6 +373,7 @@ public class NativeToJsMessageQueue {
|
||||
@Override
|
||||
public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) {
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String js = queue.popAndEncodeAsJs();
|
||||
if (js != null) {
|
||||
|
||||
@@ -66,7 +66,6 @@ public class PermissionHelper {
|
||||
*
|
||||
* @param plugin The plugin the permission is being checked against
|
||||
* @param permission The permission to be checked
|
||||
*
|
||||
* @return True if the permission has already been granted and false otherwise
|
||||
*/
|
||||
public static boolean hasPermission(CordovaPlugin plugin, String permission) {
|
||||
|
||||
@@ -48,36 +48,36 @@ public final class PluginEntry {
|
||||
/**
|
||||
* Constructs with a CordovaPlugin already instantiated.
|
||||
*
|
||||
* @param service The name of the service
|
||||
* @param pluginClass The plugin class name
|
||||
* @param service The name of the service
|
||||
* @param plugin The plugin class name
|
||||
*/
|
||||
public PluginEntry(String service, CordovaPlugin plugin) {
|
||||
this(service, plugin.getClass().getName(), true, plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param service The name of the service
|
||||
* @param plugin The CordovaPlugin already instantiated
|
||||
* @param onload Create plugin object when HTML page is loaded
|
||||
* @param service The name of the service
|
||||
* @param plugin The CordovaPlugin already instantiated
|
||||
* @param onload Create plugin object when HTML page is loaded
|
||||
*/
|
||||
public PluginEntry(String service, CordovaPlugin plugin, boolean onload) {
|
||||
this(service, plugin.getClass().getName(), onload, plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param service The name of the service
|
||||
* @param pluginClass The plugin class name
|
||||
* @param onload Create plugin object when HTML page is loaded
|
||||
* @param service The name of the service
|
||||
* @param pluginClass The plugin class name
|
||||
* @param onload Create plugin object when HTML page is loaded
|
||||
*/
|
||||
public PluginEntry(String service, String pluginClass, boolean onload) {
|
||||
this(service, pluginClass, onload, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param service The name of the service
|
||||
* @param pluginClass The plugin class name
|
||||
* @param onload Create plugin object when HTML page is loaded
|
||||
* @param plugin The CordovaPlugin already instantiated
|
||||
* @param service The name of the service
|
||||
* @param pluginClass The plugin class name
|
||||
* @param onload Create plugin object when HTML page is loaded
|
||||
* @param plugin The CordovaPlugin already instantiated
|
||||
*/
|
||||
private PluginEntry(String service, String pluginClass, boolean onload, CordovaPlugin plugin) {
|
||||
this.service = service;
|
||||
|
||||
@@ -38,8 +38,8 @@ import android.webkit.WebView;
|
||||
/**
|
||||
* PluginManager is exposed to JavaScript in the Cordova WebView.
|
||||
*
|
||||
* Calling native plugin code can be done by calling PluginManager.exec(...)
|
||||
* from JavaScript.
|
||||
* <p>Calling native plugin code can be done by calling PluginManager.exec(...)
|
||||
* from JavaScript.</p>
|
||||
*/
|
||||
public class PluginManager {
|
||||
private static String TAG = "PluginManager";
|
||||
@@ -87,7 +87,7 @@ public class PluginManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Init when loading a new HTML page into webview.
|
||||
* Init when loading a new HTML page into WebView.
|
||||
*/
|
||||
public void init() {
|
||||
LOG.d(TAG, "init()");
|
||||
@@ -121,9 +121,9 @@ public class PluginManager {
|
||||
* Receives a request for execution and fulfills it by finding the appropriate
|
||||
* Java class and calling it's execute method.
|
||||
*
|
||||
* PluginManager.exec can be used either synchronously or async. In either case, a JSON encoded
|
||||
* <p>PluginManager.exec can be used either synchronously or async. In either case, a JSON encoded
|
||||
* string is returned that will indicate if any errors have occurred when trying to find
|
||||
* or execute the class denoted by the clazz argument.
|
||||
* or execute the class denoted by the clazz argument.</p>
|
||||
*
|
||||
* @param service String containing the service to run
|
||||
* @param action String containing the action that the class is supposed to perform. This is
|
||||
@@ -252,9 +252,7 @@ public class PluginManager {
|
||||
* @param handler The HttpAuthHandler used to set the WebView's response
|
||||
* @param host The host requiring authentication
|
||||
* @param realm The realm for which authentication is required
|
||||
*
|
||||
* @return Returns True if there is a plugin which will resolve this auth challenge, otherwise False
|
||||
*
|
||||
* @return True if there is a plugin which will resolve this auth challenge, otherwise False
|
||||
*/
|
||||
public boolean onReceivedHttpAuthRequest(CordovaWebView view, ICordovaHttpAuthHandler handler, String host, String realm) {
|
||||
synchronized (this.pluginMap) {
|
||||
@@ -273,9 +271,7 @@ public class PluginManager {
|
||||
*
|
||||
* @param view The WebView that is initiating the callback
|
||||
* @param request The client certificate request
|
||||
*
|
||||
* @return Returns True if plugin will resolve this auth challenge, otherwise False
|
||||
*
|
||||
* @return True if plugin will resolve this auth challenge, otherwise False
|
||||
*/
|
||||
public boolean onReceivedClientCertRequest(CordovaWebView view, ICordovaClientCertRequest request) {
|
||||
synchronized (this.pluginMap) {
|
||||
@@ -375,12 +371,14 @@ public class PluginManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo should we move this somewhere public and accessible by all plugins?
|
||||
* For now, it is placed where it is used and kept private so we can decide later and move without causing a breaking change.
|
||||
* An ideal location might be in the "ConfigXmlParser" at the time it generates the "launchUrl".
|
||||
* TODO: should we move this somewhere public and accessible by all plugins?
|
||||
*
|
||||
* @todo should we be restrictive on the "file://" return? e.g. "file:///android_asset/www/"
|
||||
* Would be considered as a breaking change if we apply a more granular check.
|
||||
* <p>For now, it is placed where it is used and kept private so we can decide later and move without causing a breaking change.
|
||||
* An ideal location might be in the "ConfigXmlParser" at the time it generates the "launchUrl".</p>
|
||||
*
|
||||
* TODO: should we be restrictive on the "file://" return? e.g. "file:///android_asset/www/"
|
||||
*
|
||||
* <p>Would be considered as a breaking change if we apply a more granular check.</p>
|
||||
*/
|
||||
private String getLaunchUrlPrefix() {
|
||||
if (!app.getPreferences().getBoolean("AndroidInsecureFileModeEnabled", false)) {
|
||||
@@ -393,14 +391,14 @@ public class PluginManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the webview is going to request an external resource.
|
||||
* Called when the WebView is going to request an external resource.
|
||||
*
|
||||
* This delegates to the installed plugins, and returns true/false for the
|
||||
* <p>This delegates to the installed plugins, and returns true/false for the
|
||||
* first plugin to provide a non-null result. If no plugins respond, then
|
||||
* the default policy is applied.
|
||||
* the default policy is applied.</p>
|
||||
*
|
||||
* @param url The URL that is being requested.
|
||||
* @return Returns true to allow the resource to load,
|
||||
* @return true to allow the resource to load,
|
||||
* false to block the resource.
|
||||
*/
|
||||
public boolean shouldAllowRequest(String url) {
|
||||
@@ -425,7 +423,7 @@ public class PluginManager {
|
||||
return true;
|
||||
}
|
||||
if (url.startsWith("file://")) {
|
||||
//This directory on WebKit/Blink based webviews contains SQLite databases!
|
||||
//This directory on WebKit/Blink based WebViews contains SQLite databases!
|
||||
//DON'T CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING!
|
||||
return !url.contains("/app_webview/");
|
||||
}
|
||||
@@ -433,14 +431,14 @@ public class PluginManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the webview is going to change the URL of the loaded content.
|
||||
* Called when the WebView is going to change the URL of the loaded content.
|
||||
*
|
||||
* This delegates to the installed plugins, and returns true/false for the
|
||||
* <p>This delegates to the installed plugins, and returns true/false for the
|
||||
* first plugin to provide a non-null result. If no plugins respond, then
|
||||
* the default policy is applied.
|
||||
* the default policy is applied.</p>
|
||||
*
|
||||
* @param url The URL that is being requested.
|
||||
* @return Returns true to allow the navigation,
|
||||
* @return true to allow the navigation,
|
||||
* false to block the navigation.
|
||||
*/
|
||||
public boolean shouldAllowNavigation(String url) {
|
||||
@@ -462,7 +460,7 @@ public class PluginManager {
|
||||
|
||||
|
||||
/**
|
||||
* Called when the webview is requesting the exec() bridge be enabled.
|
||||
* Called when the WebView is requesting the exec() bridge be enabled.
|
||||
*/
|
||||
public boolean shouldAllowBridgeAccess(String url) {
|
||||
synchronized (this.entryMap) {
|
||||
@@ -482,15 +480,15 @@ public class PluginManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the webview is going not going to navigate, but may launch
|
||||
* Called when the WebView is going not going to navigate, but may launch
|
||||
* an Intent for an URL.
|
||||
*
|
||||
* This delegates to the installed plugins, and returns true/false for the
|
||||
* <p>This delegates to the installed plugins, and returns true/false for the
|
||||
* first plugin to provide a non-null result. If no plugins respond, then
|
||||
* the default policy is applied.
|
||||
* the default policy is applied.</p>
|
||||
*
|
||||
* @param url The URL that is being requested.
|
||||
* @return Returns true to allow the URL to launch an intent,
|
||||
* @return true to allow the URL to launch an intent,
|
||||
* false to block the intent.
|
||||
*/
|
||||
public Boolean shouldOpenExternalUrl(String url) {
|
||||
@@ -511,7 +509,7 @@ public class PluginManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the URL of the webview changes.
|
||||
* Called when the URL of the WebView changes.
|
||||
*
|
||||
* @param url The URL that is being changed to.
|
||||
* @return Return false to allow the URL to load, return true to prevent the URL from loading.
|
||||
@@ -566,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();
|
||||
@@ -623,9 +621,9 @@ public class PluginManager {
|
||||
/**
|
||||
* Called when the WebView's render process has exited.
|
||||
*
|
||||
* See https://developer.android.com/reference/android/webkit/WebViewClient#onRenderProcessGone(android.webkit.WebView,%20android.webkit.RenderProcessGoneDetail)
|
||||
* <p>See <a href="https://developer.android.com/reference/android/webkit/WebViewClient#onRenderProcessGone(android.webkit.WebView,%20android.webkit.RenderProcessGoneDetail)">WebViewClient#onRenderProcessGone</a></p>
|
||||
*
|
||||
* @return true if the host application handled the situation that process has exited,
|
||||
* @return true if the host application handled the situation that process has exited,
|
||||
* otherwise, application will crash if render process crashed, or be killed
|
||||
* if render process was killed by the system.
|
||||
*/
|
||||
|
||||
@@ -118,8 +118,7 @@ public class PluginResult {
|
||||
}
|
||||
|
||||
/**
|
||||
* If messageType == MESSAGE_TYPE_STRING, then returns the message string.
|
||||
* Otherwise, returns null.
|
||||
* @return message string when messageType is MESSAGE_TYPE_STRING otherwise null.
|
||||
*/
|
||||
public String getStrMessage() {
|
||||
return strMessage;
|
||||
|
||||
@@ -45,25 +45,25 @@ public class SplashScreenPlugin extends CordovaPlugin {
|
||||
|
||||
// Config preference values
|
||||
/**
|
||||
* @param boolean autoHide to auto splash screen (default=true)
|
||||
* Boolean flag to auto hide splash screen (default=true)
|
||||
*/
|
||||
private boolean autoHide;
|
||||
/**
|
||||
* @param int delayTime in milliseconds (default=-1)
|
||||
* Integer value of how long to delay in milliseconds (default=-1)
|
||||
*/
|
||||
private int delayTime;
|
||||
/**
|
||||
* @param int fade to fade out splash screen (default=true)
|
||||
* Boolean flag if to fade to fade out splash screen (default=true)
|
||||
*/
|
||||
private boolean isFadeEnabled;
|
||||
/**
|
||||
* @param int fadeDuration fade out duration in milliseconds (default=500)
|
||||
* Integer value of the fade duration in milliseconds (default=500)
|
||||
*/
|
||||
private int fadeDuration;
|
||||
|
||||
// Internal variables
|
||||
/**
|
||||
* @param boolean keepOnScreen flag to determine if the splash screen remains visible.
|
||||
* Boolean flag to determine if the splash screen remains visible.
|
||||
*/
|
||||
private boolean keepOnScreen = true;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
374
framework/src/org/apache/cordova/SystemBarPlugin.java
Normal file
374
framework/src/org/apache/cordova/SystemBarPlugin.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,22 +41,27 @@ class SystemCookieManager implements ICordovaCookieManager {
|
||||
cookieManager.setAcceptFileSchemeCookies(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCookiesEnabled(boolean accept) {
|
||||
cookieManager.setAcceptCookie(accept);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCookie(final String url, final String value) {
|
||||
cookieManager.setCookie(url, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCookie(final String url) {
|
||||
return cookieManager.getCookie(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearCookies() {
|
||||
cookieManager.removeAllCookies(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
cookieManager.flush();
|
||||
}
|
||||
|
||||
@@ -37,16 +37,19 @@ class SystemExposedJsApi implements ExposedJsApi {
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
@Override
|
||||
public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
|
||||
return bridge.jsExec(bridgeSecret, service, action, callbackId, arguments);
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
@Override
|
||||
public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException {
|
||||
bridge.jsSetNativeToJsBridgeMode(bridgeSecret, value);
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
@Override
|
||||
public String retrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException {
|
||||
return bridge.jsRetrieveJsMessages(bridgeSecret, fromOnlineEvent);
|
||||
}
|
||||
|
||||
@@ -18,18 +18,22 @@
|
||||
*/
|
||||
package org.apache.cordova.engine;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import android.annotation.TargetApi;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import android.app.Activity;
|
||||
import android.content.ClipData;
|
||||
import android.content.Context;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.MediaStore;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
import android.webkit.ConsoleMessage;
|
||||
import android.webkit.GeolocationPermissions.Callback;
|
||||
import android.webkit.JsPromptResult;
|
||||
import android.webkit.JsResult;
|
||||
@@ -41,6 +45,7 @@ import android.webkit.PermissionRequest;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
import androidx.core.content.FileProvider;
|
||||
|
||||
import org.apache.cordova.CordovaDialogsHelper;
|
||||
import org.apache.cordova.CordovaPlugin;
|
||||
@@ -114,8 +119,8 @@ public class SystemWebChromeClient extends WebChromeClient {
|
||||
* If the client returns true, WebView will assume that the client will
|
||||
* handle the prompt dialog and call the appropriate JsPromptResult method.
|
||||
*
|
||||
* Since we are hacking prompts for our own purposes, we should not be using them for
|
||||
* this purpose, perhaps we should hack console.log to do this instead!
|
||||
* <p>Since we are hacking prompts for our own purposes, we should not be using them for
|
||||
* this purpose, perhaps we should hack console.log to do this instead!</p>
|
||||
*/
|
||||
@Override
|
||||
public boolean onJsPrompt(WebView view, String origin, String message, String defaultValue, final JsPromptResult result) {
|
||||
@@ -150,15 +155,15 @@ public class SystemWebChromeClient extends WebChromeClient {
|
||||
quotaUpdater.updateQuota(MAX_QUOTA);
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* Instructs the client to show a prompt to ask the user to set the Geolocation permission state for the specified origin.
|
||||
*
|
||||
* This also checks for the Geolocation Plugin and requests permission from the application to use Geolocation.
|
||||
* <p>This also checks for the Geolocation Plugin and requests permission from the application to use Geolocation.</p>
|
||||
*
|
||||
* @param origin
|
||||
* @param callback
|
||||
*/
|
||||
@Override
|
||||
public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
|
||||
super.onGeolocationPermissionsShowPrompt(origin, callback);
|
||||
callback.invoke(origin, true, false);
|
||||
@@ -183,12 +188,13 @@ public class SystemWebChromeClient extends WebChromeClient {
|
||||
parentEngine.getCordovaWebView().hideCustomView();
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* Ask the host application for a custom progress view to show while
|
||||
* a <video> is loading.
|
||||
*
|
||||
* @return View The progress view.
|
||||
*/
|
||||
@Override
|
||||
public View getVideoLoadingProgressView() {
|
||||
if (mVideoProgressView == null) {
|
||||
// Create a new Loading view programmatically.
|
||||
@@ -199,7 +205,7 @@ public class SystemWebChromeClient extends WebChromeClient {
|
||||
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
||||
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
|
||||
layout.setLayoutParams(layoutParams);
|
||||
// the proress bar
|
||||
// the progress bar
|
||||
ProgressBar bar = new ProgressBar(parentEngine.getView().getContext());
|
||||
LinearLayout.LayoutParams barLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
||||
barLayoutParams.gravity = Gravity.CENTER;
|
||||
@@ -212,52 +218,110 @@ public class SystemWebChromeClient extends WebChromeClient {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onShowFileChooser(WebView webView, final ValueCallback<Uri[]> filePathsCallback, final WebChromeClient.FileChooserParams fileChooserParams) {
|
||||
public boolean onShowFileChooser(WebView webView, final ValueCallback<Uri[]> filePathsCallback,
|
||||
final WebChromeClient.FileChooserParams fileChooserParams) {
|
||||
Intent fileIntent = fileChooserParams.createIntent();
|
||||
|
||||
// Check if multiple-select is specified
|
||||
Boolean selectMultiple = false;
|
||||
if (fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE) {
|
||||
selectMultiple = true;
|
||||
}
|
||||
Intent intent = fileChooserParams.createIntent();
|
||||
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, selectMultiple);
|
||||
|
||||
fileIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, selectMultiple);
|
||||
|
||||
// Uses Intent.EXTRA_MIME_TYPES to pass multiple mime types.
|
||||
String[] acceptTypes = fileChooserParams.getAcceptTypes();
|
||||
if (acceptTypes.length > 1) {
|
||||
intent.setType("*/*"); // Accept all, filter mime types by Intent.EXTRA_MIME_TYPES.
|
||||
intent.putExtra(Intent.EXTRA_MIME_TYPES, acceptTypes);
|
||||
fileIntent.setType("*/*"); // Accept all, filter mime types by Intent.EXTRA_MIME_TYPES.
|
||||
fileIntent.putExtra(Intent.EXTRA_MIME_TYPES, acceptTypes);
|
||||
}
|
||||
|
||||
// Image from camera intent
|
||||
Uri tempUri = null;
|
||||
Intent captureIntent = null;
|
||||
if (fileChooserParams.isCaptureEnabled()) {
|
||||
captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||
Context context = parentEngine.getView().getContext();
|
||||
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
|
||||
&& captureIntent.resolveActivity(context.getPackageManager()) != null) {
|
||||
try {
|
||||
File tempFile = createTempFile(context);
|
||||
LOG.d(LOG_TAG, "Temporary photo capture file: " + tempFile);
|
||||
tempUri = createUriForFile(context, tempFile);
|
||||
LOG.d(LOG_TAG, "Temporary photo capture URI: " + tempUri);
|
||||
captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, tempUri);
|
||||
} catch (IOException e) {
|
||||
LOG.e(LOG_TAG, "Unable to create temporary file for photo capture", e);
|
||||
captureIntent = null;
|
||||
}
|
||||
} else {
|
||||
LOG.w(LOG_TAG, "Device does not support photo capture");
|
||||
captureIntent = null;
|
||||
}
|
||||
}
|
||||
final Uri captureUri = tempUri;
|
||||
|
||||
// Chooser intent
|
||||
Intent chooserIntent = Intent.createChooser(fileIntent, null);
|
||||
if (captureIntent != null) {
|
||||
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { captureIntent });
|
||||
}
|
||||
|
||||
try {
|
||||
LOG.i(LOG_TAG, "Starting intent for file chooser");
|
||||
parentEngine.cordova.startActivityForResult(new CordovaPlugin() {
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||
// Handle result
|
||||
Uri[] result = null;
|
||||
if (resultCode == Activity.RESULT_OK && intent != null) {
|
||||
if (intent.getClipData() != null) {
|
||||
// handle multiple-selected files
|
||||
final int numSelectedFiles = intent.getClipData().getItemCount();
|
||||
result = new Uri[numSelectedFiles];
|
||||
for (int i = 0; i < numSelectedFiles; i++) {
|
||||
result[i] = intent.getClipData().getItemAt(i).getUri();
|
||||
LOG.d(LOG_TAG, "Receive file chooser URL: " + result[i]);
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
List<Uri> uris = new ArrayList<Uri>();
|
||||
|
||||
if (intent != null && intent.getData() != null) { // single file
|
||||
LOG.v(LOG_TAG, "Adding file (single): " + intent.getData());
|
||||
uris.add(intent.getData());
|
||||
} else if (captureUri != null) { // camera
|
||||
LOG.v(LOG_TAG, "Adding camera capture: " + captureUri);
|
||||
uris.add(captureUri);
|
||||
} else if (intent != null && intent.getClipData() != null) { // multiple files
|
||||
ClipData clipData = intent.getClipData();
|
||||
int count = clipData.getItemCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
Uri uri = clipData.getItemAt(i).getUri();
|
||||
LOG.v(LOG_TAG, "Adding file (multiple): " + uri);
|
||||
if (uri != null) {
|
||||
uris.add(uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (intent.getData() != null) {
|
||||
// handle single-selected file
|
||||
result = WebChromeClient.FileChooserParams.parseResult(resultCode, intent);
|
||||
LOG.d(LOG_TAG, "Receive file chooser URL: " + result);
|
||||
|
||||
if (!uris.isEmpty()) {
|
||||
LOG.d(LOG_TAG, "Receive file chooser URL: " + uris.toString());
|
||||
result = uris.toArray(new Uri[uris.size()]);
|
||||
}
|
||||
}
|
||||
filePathsCallback.onReceiveValue(result);
|
||||
}
|
||||
}, intent, FILECHOOSER_RESULTCODE);
|
||||
}, chooserIntent, FILECHOOSER_RESULTCODE);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
LOG.w("No activity found to handle file chooser intent.", e);
|
||||
LOG.w(LOG_TAG, "No activity found to handle file chooser intent.", e);
|
||||
filePathsCallback.onReceiveValue(null);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private File createTempFile(Context context) throws IOException {
|
||||
// Create an image file name
|
||||
File tempFile = File.createTempFile("temp", ".jpg", context.getCacheDir());
|
||||
return tempFile;
|
||||
}
|
||||
|
||||
private Uri createUriForFile(Context context, File tempFile) throws IOException {
|
||||
String appId = context.getPackageName();
|
||||
Uri uri = FileProvider.getUriForFile(context, appId + ".cdv.core.file.provider", tempFile);
|
||||
return uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPermissionRequest(final PermissionRequest request) {
|
||||
LOG.d(LOG_TAG, "onPermissionRequest: " + Arrays.toString(request.getResources()));
|
||||
|
||||
@@ -29,6 +29,8 @@ import android.webkit.ClientCertRequest;
|
||||
import android.webkit.HttpAuthHandler;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import android.webkit.RenderProcessGoneDetail;
|
||||
import android.webkit.ServiceWorkerClient;
|
||||
import android.webkit.ServiceWorkerController;
|
||||
import android.webkit.SslErrorHandler;
|
||||
import android.webkit.WebResourceRequest;
|
||||
import android.webkit.WebResourceResponse;
|
||||
@@ -116,6 +118,18 @@ public class SystemWebViewClient extends WebViewClient {
|
||||
});
|
||||
|
||||
this.assetLoader = assetLoaderBuilder.build();
|
||||
boolean setAsServiceWorkerClient = parentEngine.preferences.getBoolean("ResolveServiceWorkerRequests", true);
|
||||
ServiceWorkerController controller = null;
|
||||
|
||||
if (setAsServiceWorkerClient) {
|
||||
controller = ServiceWorkerController.getInstance();
|
||||
controller.setServiceWorkerClient(new ServiceWorkerClient(){
|
||||
@Override
|
||||
public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) {
|
||||
return assetLoader.shouldInterceptRequest(request.getUrl());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -185,7 +199,7 @@ public class SystemWebViewClient extends WebViewClient {
|
||||
* one time for the main frame. This also means that onPageStarted will not be called when the contents of an
|
||||
* embedded frame changes, i.e. clicking a link whose target is an iframe.
|
||||
*
|
||||
* @param view The webview initiating the callback.
|
||||
* @param view The WebView initiating the callback.
|
||||
* @param url The url of the page.
|
||||
*/
|
||||
@Override
|
||||
@@ -202,7 +216,7 @@ public class SystemWebViewClient extends WebViewClient {
|
||||
* This method is called only for main frame. When onPageFinished() is called, the rendering picture may not be updated yet.
|
||||
*
|
||||
*
|
||||
* @param view The webview initiating the callback.
|
||||
* @param view The WebView initiating the callback.
|
||||
* @param url The url of the page.
|
||||
*/
|
||||
@Override
|
||||
@@ -214,7 +228,7 @@ public class SystemWebViewClient extends WebViewClient {
|
||||
}
|
||||
isCurrentlyLoading = false;
|
||||
|
||||
/**
|
||||
/*
|
||||
* Because of a timing issue we need to clear this history in onPageFinished as well as
|
||||
* onPageStarted. However we only want to do this if the doClearHistory boolean is set to
|
||||
* true. You see when you load a url with a # in it which is common in jQuery applications
|
||||
@@ -318,7 +332,6 @@ public class SystemWebViewClient extends WebViewClient {
|
||||
*
|
||||
* @param host
|
||||
* @param realm
|
||||
*
|
||||
* @return the authentication token or null if did not exist
|
||||
*/
|
||||
public AuthenticationToken removeAuthenticationToken(String host, String realm) {
|
||||
@@ -328,15 +341,16 @@ public class SystemWebViewClient extends WebViewClient {
|
||||
/**
|
||||
* Gets the authentication token.
|
||||
*
|
||||
* In order it tries:
|
||||
* 1- host + realm
|
||||
* 2- host
|
||||
* 3- realm
|
||||
* 4- no host, no realm
|
||||
* <p>In order it tries:</p>
|
||||
* <ol>
|
||||
* <li>host + realm</li>
|
||||
* <li>host</li>
|
||||
* <li>realm</li>
|
||||
* <li>no host, no realm</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param host
|
||||
* @param realm
|
||||
*
|
||||
* @return the authentication token
|
||||
*/
|
||||
public AuthenticationToken getAuthenticationToken(String host, String realm) {
|
||||
|
||||
@@ -110,7 +110,7 @@ public class SystemWebViewEngine implements CordovaWebViewEngine {
|
||||
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode.OnlineEventsBridgeModeDelegate() {
|
||||
@Override
|
||||
public void setNetworkAvailable(boolean value) {
|
||||
//sometimes this can be called after calling webview.destroy() on destroy()
|
||||
//sometimes this can be called after calling webView.destroy() on destroy()
|
||||
//thus resulting in a NullPointerException
|
||||
if(webView!=null) {
|
||||
webView.setNetworkAvailable(value);
|
||||
@@ -175,9 +175,9 @@ public class SystemWebViewEngine implements CordovaWebViewEngine {
|
||||
String databasePath = webView.getContext().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
|
||||
settings.setDatabaseEnabled(true);
|
||||
|
||||
// The default is to use the module's debuggable state to decide if the webview inspecter
|
||||
// should be enabled. However, users can configure InspectableWebview preference to forcefully enable
|
||||
// or disable the webview inspecter.
|
||||
// The default is to use the module's debuggable state to decide if the WebView inspector
|
||||
// should be enabled. However, users can configure InspectableWebView preference to forcefully enable
|
||||
// or disable the WebView inspector.
|
||||
String inspectableWebview = preferences.getString("InspectableWebview", null);
|
||||
boolean shouldEnableInspector = false;
|
||||
if (inspectableWebview == null) {
|
||||
@@ -249,7 +249,7 @@ public class SystemWebViewEngine implements CordovaWebViewEngine {
|
||||
|
||||
|
||||
/**
|
||||
* Load the url into the webview.
|
||||
* Load the url into the WebView.
|
||||
*/
|
||||
@Override
|
||||
public void loadUrl(final String url, boolean clearNavigationStack) {
|
||||
@@ -288,7 +288,7 @@ public class SystemWebViewEngine implements CordovaWebViewEngine {
|
||||
*/
|
||||
@Override
|
||||
public boolean goBack() {
|
||||
// Check webview first to see if there is a history
|
||||
// Check WebView first to see if there is a history
|
||||
// This is needed to support curPage#diffLink, since they are added to parentEngine's history, but not our history url array (JQMobile behavior)
|
||||
if (webView.canGoBack()) {
|
||||
webView.goBack();
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
const os = require('os');
|
||||
const os = require('node:os');
|
||||
const execa = require('execa');
|
||||
const events = require('cordova-common').events;
|
||||
const CordovaError = require('cordova-common').CordovaError;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const fs = require('node:fs');
|
||||
const xml = require('cordova-common').xmlHelpers;
|
||||
|
||||
const DEFAULT_ORIENTATION = 'default';
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const properties_parser = require('properties-parser');
|
||||
const pluginHandlers = require('./pluginHandlers');
|
||||
const CordovaGradleConfigParserFactory = require('./config/CordovaGradleConfigParserFactory');
|
||||
|
||||
12
lib/Api.js
12
lib/Api.js
@@ -17,7 +17,7 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const path = require('node:path');
|
||||
|
||||
const AndroidProject = require('./AndroidProject');
|
||||
const PluginManager = require('cordova-common').PluginManager;
|
||||
@@ -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 () {
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const path = require('node:path');
|
||||
const fs = require('node:fs');
|
||||
const nopt = require('nopt');
|
||||
const untildify = require('untildify');
|
||||
const { parseArgsStringToArgv } = require('string-argv');
|
||||
|
||||
@@ -17,15 +17,16 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const fs = require('node:fs');
|
||||
const fsp = require('node:fs/promises');
|
||||
const path = require('node:path');
|
||||
const execa = require('execa');
|
||||
const glob = require('fast-glob');
|
||||
const events = require('cordova-common').events;
|
||||
const CordovaError = require('cordova-common').CordovaError;
|
||||
const check_reqs = require('../check_reqs');
|
||||
const PackageType = require('../PackageType');
|
||||
const { compareByAll } = require('../utils');
|
||||
const { compareByAll, isWindows } = require('../utils');
|
||||
const { createEditor } = require('properties-parser');
|
||||
const CordovaGradleConfigParserFactory = require('../config/CordovaGradleConfigParserFactory');
|
||||
|
||||
@@ -85,9 +86,7 @@ class ProjectBuilder {
|
||||
}
|
||||
|
||||
getArgs (cmd, opts) {
|
||||
let args = [
|
||||
'-b', path.join(this.root, 'build.gradle')
|
||||
];
|
||||
let args = [];
|
||||
if (opts.extraArgs) {
|
||||
args = args.concat(opts.extraArgs);
|
||||
}
|
||||
@@ -116,16 +115,27 @@ class ProjectBuilder {
|
||||
return args;
|
||||
}
|
||||
|
||||
/*
|
||||
* This returns a promise
|
||||
*/
|
||||
runGradleWrapper (gradle_cmd) {
|
||||
const gradlePath = path.join(this.root, 'gradlew');
|
||||
const wrapperGradle = path.join(this.root, 'wrapper.gradle');
|
||||
if (fs.existsSync(gradlePath)) {
|
||||
// Literally do nothing, for some reason this works, while !fs.existsSync didn't on Windows
|
||||
getGradleWrapperPath () {
|
||||
let wrapper = path.join(this.root, 'tools', 'gradlew');
|
||||
|
||||
if (isWindows()) {
|
||||
wrapper += '.bat';
|
||||
}
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs/updates the gradle wrapper
|
||||
* @param {string} gradleVersion The gradle version to install. Ignored if CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL environment variable is defined
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async installGradleWrapper (gradleVersion) {
|
||||
if (process.env.CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL) {
|
||||
events.emit('verbose', `Overriding Gradle Version via CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL (${process.env.CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL})`);
|
||||
await execa('gradle', ['-p', path.join(this.root, 'tools'), 'wrapper', '--gradle-distribution-url', process.env.CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL], { stdio: 'inherit' });
|
||||
} else {
|
||||
return execa(gradle_cmd, ['-p', this.root, 'wrapper', '-b', wrapperGradle], { stdio: 'inherit' });
|
||||
await execa('gradle', ['-p', path.join(this.root, 'tools'), 'wrapper', '--gradle-version', gradleVersion], { stdio: 'inherit' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,9 +173,9 @@ 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.copySync(pluginBuildGradle, subProjectGradle);
|
||||
fs.cpSync(pluginBuildGradle, subProjectGradle);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -195,10 +205,11 @@ 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.pathExistsSync(path.join(this.root, 'cdv-gradle-name.gradle'))) {
|
||||
if (!fs.existsSync(path.join(this.root, 'cdv-gradle-name.gradle'))) {
|
||||
fs.writeFileSync(path.join(this.root, 'cdv-gradle-name.gradle'), '');
|
||||
}
|
||||
|
||||
@@ -275,31 +286,63 @@ class ProjectBuilder {
|
||||
|
||||
prepEnv (opts) {
|
||||
const self = this;
|
||||
const config = this._getCordovaConfig();
|
||||
const configPropertiesPath = path.join(self.root, 'tools', '.gradle', 'config.properties');
|
||||
|
||||
return check_reqs.check_gradle()
|
||||
.then(function (gradlePath) {
|
||||
return self.runGradleWrapper(gradlePath);
|
||||
.then(function () {
|
||||
events.emit('verbose', `Using Gradle: ${config.GRADLE_VERSION}`);
|
||||
return self.installGradleWrapper(config.GRADLE_VERSION);
|
||||
}).then(async function () {
|
||||
try {
|
||||
// Try to create "config.properties" file if missing
|
||||
const fd = fs.openSync(configPropertiesPath, 'wx+', 0o600);
|
||||
fs.writeFileSync(fd, '', 'utf8');
|
||||
fs.closeSync(fd);
|
||||
} catch {
|
||||
// File already existed, nothing to do.
|
||||
}
|
||||
|
||||
// Loads "config.properties"for editing
|
||||
const configProperties = createEditor(configPropertiesPath);
|
||||
/*
|
||||
* File is replaced on each build. "before_build" hook scripts can inject
|
||||
* custom settings before this step runs. This step will still overwrite
|
||||
* the "java.home" setting. Ensure environment variables are properly
|
||||
* configured if want to use custom "java.home".
|
||||
*
|
||||
* Sets "java.home" using the "CORDOVA_JAVA_HOME" environment variable.
|
||||
* If unavailable, fallback to "JAVA_HOME".
|
||||
* If neither is set, "java.home" is removed (if previously set),
|
||||
* allowing Android Studio to display a warning and auto-configure
|
||||
* to use its internal (bundled) Java.
|
||||
*/
|
||||
const javaHome = process.env.CORDOVA_JAVA_HOME || process.env.JAVA_HOME || false;
|
||||
if (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');
|
||||
}
|
||||
// Saves changes
|
||||
configProperties.save();
|
||||
}).then(async function () {
|
||||
await fsp.cp(path.join(self.root, 'tools', 'gradle'), path.join(self.root, 'gradle'), { recursive: true, force: true });
|
||||
await fsp.cp(path.join(self.root, 'tools', 'gradlew'), path.join(self.root, 'gradlew'), { recursive: true, force: true });
|
||||
await fsp.cp(path.join(self.root, 'tools', 'gradlew.bat'), path.join(self.root, 'gradlew.bat'), { recursive: true, force: true });
|
||||
await fsp.cp(path.join(self.root, 'tools', '.gradle'), path.join(self.root, '.gradle'), { recursive: true, force: true });
|
||||
}).then(function () {
|
||||
return self.prepBuildFiles();
|
||||
}).then(() => {
|
||||
const config = this._getCordovaConfig();
|
||||
// update/set the distributionUrl in the gradle-wrapper.properties
|
||||
const gradleWrapperPropertiesPath = path.join(self.root, 'gradle/wrapper/gradle-wrapper.properties');
|
||||
const gradleWrapperProperties = createEditor(gradleWrapperPropertiesPath);
|
||||
const distributionUrl = process.env.CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL || `https://services.gradle.org/distributions/gradle-${config.GRADLE_VERSION}-all.zip`;
|
||||
gradleWrapperProperties.set('distributionUrl', distributionUrl);
|
||||
gradleWrapperProperties.save();
|
||||
|
||||
events.emit('verbose', `Gradle Distribution URL: ${distributionUrl}`);
|
||||
})
|
||||
.then(() => {
|
||||
const signingPropertiesPath = path.join(self.root, `${opts.buildType}${SIGNING_PROPERTIES}`);
|
||||
|
||||
if (fs.existsSync(signingPropertiesPath)) fs.removeSync(signingPropertiesPath);
|
||||
if (opts.packageInfo) {
|
||||
fs.ensureFileSync(signingPropertiesPath);
|
||||
fs.writeFileSync(signingPropertiesPath, '', 'utf8');
|
||||
const signingProperties = createEditor(signingPropertiesPath);
|
||||
signingProperties.addHeadComment(TEMPLATE);
|
||||
opts.packageInfo.appendToProperties(signingProperties);
|
||||
} else {
|
||||
fs.rmSync(signingPropertiesPath, { force: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -309,7 +352,7 @@ class ProjectBuilder {
|
||||
* @returns The user defined configs
|
||||
*/
|
||||
_getCordovaConfig () {
|
||||
return fs.readJSONSync(path.join(this.root, 'cdv-gradle-config.json'));
|
||||
return JSON.parse(fs.readFileSync(path.join(this.root, 'cdv-gradle-config.json'), 'utf-8') || '{}');
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -317,7 +360,7 @@ class ProjectBuilder {
|
||||
* Returns a promise.
|
||||
*/
|
||||
async build (opts) {
|
||||
const wrapper = path.join(this.root, 'gradlew');
|
||||
const wrapper = this.getGradleWrapperPath();
|
||||
const args = this.getArgs(opts.buildType === 'debug' ? 'debug' : 'release', opts);
|
||||
|
||||
events.emit('verbose', `Running Gradle Build: ${wrapper} ${args.join(' ')}`);
|
||||
@@ -338,11 +381,11 @@ class ProjectBuilder {
|
||||
}
|
||||
|
||||
clean (opts) {
|
||||
const wrapper = path.join(this.root, 'gradlew');
|
||||
const wrapper = this.getGradleWrapperPath();
|
||||
const args = this.getArgs('clean', opts);
|
||||
return execa(wrapper, args, { stdio: 'inherit', cwd: path.resolve(this.root) })
|
||||
.then(() => {
|
||||
fs.removeSync(path.join(this.root, 'out'));
|
||||
fs.rmSync(path.join(this.root, 'out'), { recursive: true, force: true });
|
||||
|
||||
['debug', 'release'].map(config => path.join(this.root, `${config}${SIGNING_PROPERTIES}`))
|
||||
.forEach(file => {
|
||||
@@ -350,7 +393,7 @@ class ProjectBuilder {
|
||||
const hasMarker = hasFile && fs.readFileSync(file, 'utf8')
|
||||
.includes(MARKER);
|
||||
|
||||
if (hasFile && hasMarker) fs.removeSync(file);
|
||||
if (hasFile && hasMarker) fs.rmSync(file);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,14 +17,14 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
const execa = require('execa');
|
||||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('node:path');
|
||||
const fs = require('node:fs');
|
||||
const { forgivingWhichSync, isWindows, isDarwin } = require('./utils');
|
||||
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.
|
||||
@@ -104,44 +104,7 @@ function getUserCompileSdkVersion (projectRoot) {
|
||||
}
|
||||
|
||||
module.exports.get_gradle_wrapper = function () {
|
||||
let androidStudioPath;
|
||||
let i = 0;
|
||||
let foundStudio = false;
|
||||
let program_dir;
|
||||
// OK, This hack only works on Windows, not on Mac OS or Linux. We will be deleting this eventually!
|
||||
if (module.exports.isWindows()) {
|
||||
const result = execa.sync(path.join(__dirname, 'getASPath.bat'));
|
||||
// console.log('result.stdout =' + result.stdout.toString());
|
||||
// console.log('result.stderr =' + result.stderr.toString());
|
||||
|
||||
if (result.stderr.toString().length > 0) {
|
||||
const androidPath = path.join(process.env.ProgramFiles, 'Android') + '/';
|
||||
if (fs.existsSync(androidPath)) {
|
||||
program_dir = fs.readdirSync(androidPath);
|
||||
while (i < program_dir.length && !foundStudio) {
|
||||
if (program_dir[i].startsWith('Android Studio')) {
|
||||
foundStudio = true;
|
||||
androidStudioPath = path.join(process.env.ProgramFiles, 'Android', program_dir[i], 'gradle');
|
||||
} else { ++i; }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// console.log('got android studio path from registry');
|
||||
// remove the (os independent) new line char at the end of stdout
|
||||
// add gradle to match the above.
|
||||
androidStudioPath = path.join(result.stdout.toString().split('\r\n')[0], 'gradle');
|
||||
}
|
||||
}
|
||||
|
||||
if (androidStudioPath !== null && fs.existsSync(androidStudioPath)) {
|
||||
const dirs = fs.readdirSync(androidStudioPath);
|
||||
if (dirs[0].split('-')[0] === 'gradle') {
|
||||
return path.join(androidStudioPath, dirs[0], 'bin', 'gradle');
|
||||
}
|
||||
} else {
|
||||
// OK, let's try to check for Gradle!
|
||||
return forgivingWhichSync('gradle');
|
||||
}
|
||||
return forgivingWhichSync('gradle');
|
||||
};
|
||||
|
||||
// Returns a promise. Called only by build and clean commands.
|
||||
@@ -254,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');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -278,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;
|
||||
});
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const events = require('cordova-common').events;
|
||||
|
||||
class CordovaGradleConfigParser {
|
||||
@@ -41,7 +41,7 @@ class CordovaGradleConfigParser {
|
||||
* @returns {Record<any, any>} The parsed JSON object representing the gradle config.
|
||||
*/
|
||||
_readConfig (configPath) {
|
||||
return fs.readJSONSync(configPath, 'utf-8');
|
||||
return JSON.parse(fs.readFileSync(configPath, 'utf-8') || '{}');
|
||||
}
|
||||
|
||||
setPackageName (packageName) {
|
||||
@@ -64,7 +64,7 @@ class CordovaGradleConfigParser {
|
||||
*/
|
||||
write () {
|
||||
events.emit('verbose', '[Cordova Gradle Config] Saving File');
|
||||
fs.writeJSONSync(this._cdvGradleConfigFilePath, this._cdvGradleConfig, 'utf-8');
|
||||
fs.writeFileSync(this._cdvGradleConfigFilePath, JSON.stringify(this._cdvGradleConfig, null, 2), 'utf-8');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
/**
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const propertiesParser = require('properties-parser');
|
||||
const events = require('cordova-common').events;
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('node:path');
|
||||
const fs = require('node:fs');
|
||||
const utils = require('./utils');
|
||||
const check_reqs = require('./check_reqs');
|
||||
const ROOT = path.join(__dirname, '..');
|
||||
@@ -49,27 +49,27 @@ function copyJsAndLibrary (projectPath, shared, projectName, targetAPI) {
|
||||
const app_path = path.join(projectPath, 'app', 'src', 'main');
|
||||
const platform_www = path.join(projectPath, 'platform_www');
|
||||
|
||||
fs.copySync(srcCordovaJsPath, path.join(app_path, 'assets', 'www', 'cordova.js'));
|
||||
fs.cpSync(srcCordovaJsPath, path.join(app_path, 'assets', 'www', 'cordova.js'));
|
||||
|
||||
// Copy the cordova.js file to platforms/<platform>/platform_www/
|
||||
// The www dir is nuked on each prepare so we keep cordova.js in platform_www
|
||||
fs.ensureDirSync(platform_www);
|
||||
fs.copySync(srcCordovaJsPath, path.join(platform_www, 'cordova.js'));
|
||||
fs.mkdirSync(platform_www, { recursive: true });
|
||||
fs.cpSync(srcCordovaJsPath, path.join(platform_www, 'cordova.js'));
|
||||
fs.cpSync(path.join(ROOT, 'framework', 'cdv-gradle-config-defaults.json'), path.join(projectPath, 'cdv-gradle-config.json'));
|
||||
|
||||
if (shared) {
|
||||
const relativeFrameworkPath = path.relative(projectPath, getFrameworkDir(projectPath, true));
|
||||
fs.symlinkSync(relativeFrameworkPath, nestedCordovaLibPath, 'dir');
|
||||
} else {
|
||||
fs.ensureDirSync(nestedCordovaLibPath);
|
||||
fs.copySync(path.join(ROOT, 'framework', 'AndroidManifest.xml'), path.join(nestedCordovaLibPath, 'AndroidManifest.xml'));
|
||||
fs.mkdirSync(nestedCordovaLibPath, { recursive: true });
|
||||
fs.cpSync(path.join(ROOT, 'framework', 'AndroidManifest.xml'), path.join(nestedCordovaLibPath, 'AndroidManifest.xml'));
|
||||
const propertiesEditor = createEditor(path.join(ROOT, 'framework', 'project.properties'));
|
||||
propertiesEditor.set('target', targetAPI);
|
||||
propertiesEditor.save(path.join(nestedCordovaLibPath, 'project.properties'));
|
||||
fs.copySync(path.join(ROOT, 'framework', 'build.gradle'), path.join(nestedCordovaLibPath, 'build.gradle'));
|
||||
fs.copySync(path.join(ROOT, 'framework', 'cordova.gradle'), path.join(nestedCordovaLibPath, 'cordova.gradle'));
|
||||
fs.copySync(path.join(ROOT, 'framework', 'repositories.gradle'), path.join(nestedCordovaLibPath, 'repositories.gradle'));
|
||||
fs.copySync(path.join(ROOT, 'framework', 'src'), path.join(nestedCordovaLibPath, 'src'));
|
||||
fs.copySync(path.join(ROOT, 'framework', 'cdv-gradle-config-defaults.json'), path.join(projectPath, 'cdv-gradle-config.json'));
|
||||
fs.cpSync(path.join(ROOT, 'framework', 'build.gradle'), path.join(nestedCordovaLibPath, 'build.gradle'));
|
||||
fs.cpSync(path.join(ROOT, 'framework', 'cordova.gradle'), path.join(nestedCordovaLibPath, 'cordova.gradle'));
|
||||
fs.cpSync(path.join(ROOT, 'framework', 'repositories.gradle'), path.join(nestedCordovaLibPath, 'repositories.gradle'));
|
||||
fs.cpSync(path.join(ROOT, 'framework', 'src'), path.join(nestedCordovaLibPath, 'src'), { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,29 +113,24 @@ function prepBuildFiles (projectPath) {
|
||||
buildModule.getBuilder(projectPath).prepBuildFiles();
|
||||
}
|
||||
|
||||
function copyBuildRules (projectPath, isLegacy) {
|
||||
function copyBuildRules (projectPath) {
|
||||
const srcDir = path.join(ROOT, 'templates', 'project');
|
||||
|
||||
if (isLegacy) {
|
||||
// The project's build.gradle is identical to the earlier build.gradle, so it should still work
|
||||
fs.copySync(path.join(srcDir, 'legacy', 'build.gradle'), path.join(projectPath, 'legacy', 'build.gradle'));
|
||||
fs.copySync(path.join(srcDir, 'wrapper.gradle'), path.join(projectPath, 'wrapper.gradle'));
|
||||
} else {
|
||||
fs.copySync(path.join(srcDir, 'build.gradle'), path.join(projectPath, 'build.gradle'));
|
||||
fs.copySync(path.join(srcDir, 'app', 'build.gradle'), path.join(projectPath, 'app', 'build.gradle'));
|
||||
fs.copySync(path.join(srcDir, 'app', 'repositories.gradle'), path.join(projectPath, 'app', 'repositories.gradle'));
|
||||
fs.copySync(path.join(srcDir, 'repositories.gradle'), path.join(projectPath, 'repositories.gradle'));
|
||||
fs.copySync(path.join(srcDir, 'wrapper.gradle'), path.join(projectPath, 'wrapper.gradle'));
|
||||
}
|
||||
fs.cpSync(path.join(srcDir, 'build.gradle'), path.join(projectPath, 'build.gradle'));
|
||||
fs.cpSync(path.join(srcDir, 'app', 'build.gradle'), path.join(projectPath, 'app', 'build.gradle'));
|
||||
fs.cpSync(path.join(srcDir, 'app', 'repositories.gradle'), path.join(projectPath, 'app', 'repositories.gradle'));
|
||||
fs.cpSync(path.join(srcDir, 'repositories.gradle'), path.join(projectPath, 'repositories.gradle'));
|
||||
|
||||
copyGradleTools(projectPath);
|
||||
}
|
||||
|
||||
function copyScripts (projectPath) {
|
||||
const srcScriptsDir = path.join(ROOT, 'templates', 'cordova');
|
||||
const destScriptsDir = path.join(projectPath, 'cordova');
|
||||
// Delete old scripts directory if this is an update.
|
||||
fs.removeSync(destScriptsDir);
|
||||
fs.rmSync(destScriptsDir, { recursive: true, force: true });
|
||||
// Copy in the new ones.
|
||||
fs.copySync(srcScriptsDir, destScriptsDir);
|
||||
fs.cpSync(srcScriptsDir, destScriptsDir, { recursive: true });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,6 +171,12 @@ function validateProjectName (project_name) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
function copyGradleTools (projectPath) {
|
||||
const srcDir = path.join(ROOT, 'templates', 'project');
|
||||
|
||||
fs.cpSync(path.resolve(srcDir, 'tools'), path.resolve(projectPath, 'tools'), { recursive: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an android application with the given options.
|
||||
*
|
||||
@@ -204,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';
|
||||
@@ -231,13 +232,13 @@ exports.create = function (project_path, config, options, events) {
|
||||
const app_path = path.join(project_path, 'app', 'src', 'main');
|
||||
|
||||
// copy project template
|
||||
fs.ensureDirSync(app_path);
|
||||
fs.copySync(path.join(project_template_dir, 'assets'), path.join(app_path, 'assets'));
|
||||
fs.copySync(path.join(project_template_dir, 'res'), path.join(app_path, 'res'));
|
||||
fs.copySync(path.join(project_template_dir, 'gitignore'), path.join(project_path, '.gitignore'));
|
||||
fs.mkdirSync(app_path, { recursive: true });
|
||||
fs.cpSync(path.join(project_template_dir, 'assets'), path.join(app_path, 'assets'), { recursive: true });
|
||||
fs.cpSync(path.join(project_template_dir, 'res'), path.join(app_path, 'res'), { recursive: true });
|
||||
fs.cpSync(path.join(project_template_dir, 'gitignore'), path.join(project_path, '.gitignore'));
|
||||
|
||||
// Manually create directories that would be empty within the template (since git doesn't track directories).
|
||||
fs.ensureDirSync(path.join(app_path, 'libs'));
|
||||
fs.mkdirSync(path.join(app_path, 'libs'), { recursive: true });
|
||||
|
||||
// copy cordova.js, cordova.jar
|
||||
exports.copyJsAndLibrary(project_path, options.link, safe_activity_name, target_api);
|
||||
@@ -246,9 +247,9 @@ exports.create = function (project_path, config, options, events) {
|
||||
const java_path = path.join(app_path, 'java');
|
||||
const assets_path = path.join(app_path, 'assets');
|
||||
const resource_path = path.join(app_path, 'res');
|
||||
fs.ensureDirSync(java_path);
|
||||
fs.ensureDirSync(assets_path);
|
||||
fs.ensureDirSync(resource_path);
|
||||
fs.mkdirSync(java_path, { recursive: true });
|
||||
fs.mkdirSync(assets_path, { recursive: true });
|
||||
fs.mkdirSync(resource_path, { recursive: true });
|
||||
|
||||
// store package name in cdv-gradle-config
|
||||
const cdvGradleConfig = CordovaGradleConfigParserFactory.create(project_path);
|
||||
@@ -260,10 +261,10 @@ exports.create = function (project_path, config, options, events) {
|
||||
const activity_dir = path.join(java_path, packagePath);
|
||||
const activity_path = path.join(activity_dir, safe_activity_name + '.java');
|
||||
|
||||
fs.ensureDirSync(activity_dir);
|
||||
fs.copySync(path.join(project_template_dir, 'Activity.java'), activity_path);
|
||||
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'));
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
*/
|
||||
|
||||
const execa = require('execa');
|
||||
const fs = require('fs-extra');
|
||||
const fs = require('node:fs');
|
||||
const android_versions = require('android-versions');
|
||||
const path = require('path');
|
||||
const path = require('node:path');
|
||||
const Adb = require('./Adb');
|
||||
const events = require('cordova-common').events;
|
||||
const CordovaError = require('cordova-common').CordovaError;
|
||||
|
||||
102
lib/env/AndroidCommandLineTools.js
vendored
Normal file
102
lib/env/AndroidCommandLineTools.js
vendored
Normal 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;
|
||||
4
lib/env/java.js
vendored
4
lib/env/java.js
vendored
@@ -18,8 +18,8 @@
|
||||
*/
|
||||
|
||||
const execa = require('execa');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const glob = require('fast-glob');
|
||||
const { CordovaError, events } = require('cordova-common');
|
||||
const utils = require('../utils');
|
||||
|
||||
@@ -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%
|
||||
@@ -14,8 +14,8 @@
|
||||
*
|
||||
*/
|
||||
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const isPathInside = require('is-path-inside');
|
||||
const events = require('cordova-common').events;
|
||||
const CordovaError = require('cordova-common').CordovaError;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -166,13 +165,13 @@ const handlers = {
|
||||
scriptContent = 'cordova.define("' + moduleName + '", function(require, exports, module) {\n' + scriptContent + '\n});\n';
|
||||
|
||||
const wwwDest = path.resolve(project.www, 'plugins', plugin.id, obj.src);
|
||||
fs.ensureDirSync(path.dirname(wwwDest));
|
||||
fs.mkdirSync(path.dirname(wwwDest), { recursive: true });
|
||||
fs.writeFileSync(wwwDest, scriptContent, 'utf-8');
|
||||
|
||||
if (options && options.usePlatformWww) {
|
||||
// CB-11022 copy file to both directories if usePlatformWww is specified
|
||||
const platformWwwDest = path.resolve(project.platformWww, 'plugins', plugin.id, obj.src);
|
||||
fs.ensureDirSync(path.dirname(platformWwwDest));
|
||||
fs.mkdirSync(path.dirname(platformWwwDest), { recursive: true });
|
||||
fs.writeFileSync(platformWwwDest, scriptContent, 'utf-8');
|
||||
}
|
||||
},
|
||||
@@ -217,11 +216,11 @@ function copyFile (plugin_dir, src, project_dir, dest, link) {
|
||||
// check that dest path is located in project directory
|
||||
if (!isPathInside(dest, project_dir)) { throw new CordovaError('Destination "' + dest + '" for source file "' + src + '" is located outside the project'); }
|
||||
|
||||
fs.ensureDirSync(path.dirname(dest));
|
||||
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
||||
if (link) {
|
||||
symlinkFileOrDirTree(src, dest);
|
||||
} else {
|
||||
fs.copySync(src, dest);
|
||||
fs.cpSync(src, dest, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,11 +234,11 @@ function copyNewFile (plugin_dir, src, project_dir, dest, link) {
|
||||
|
||||
function symlinkFileOrDirTree (src, dest) {
|
||||
if (fs.existsSync(dest)) {
|
||||
fs.removeSync(dest);
|
||||
fs.rmSync(dest, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
if (fs.statSync(src).isDirectory()) {
|
||||
fs.ensureDirSync(path.dirname(dest));
|
||||
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
||||
fs.readdirSync(src).forEach(function (entry) {
|
||||
symlinkFileOrDirTree(path.join(src, entry), path.join(dest, entry));
|
||||
});
|
||||
@@ -248,8 +247,8 @@ function symlinkFileOrDirTree (src, dest) {
|
||||
}
|
||||
}
|
||||
|
||||
function removeFile (file) {
|
||||
fs.removeSync(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);
|
||||
|
||||
141
lib/prepare.js
141
lib/prepare.js
@@ -17,10 +17,11 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const nopt = require('nopt');
|
||||
const glob = require('fast-glob');
|
||||
const dedent = require('dedent');
|
||||
const events = require('cordova-common').events;
|
||||
const AndroidManifest = require('./AndroidManifest');
|
||||
const xmlHelpers = require('cordova-common').xmlHelpers;
|
||||
@@ -92,7 +93,7 @@ function updateUserProjectGradleConfig (project) {
|
||||
|
||||
// Write out changes
|
||||
const projectGradleConfigPath = path.join(project.root, 'cdv-gradle-config.json');
|
||||
fs.writeJSONSync(projectGradleConfigPath, projectGradleConfig, { spaces: 2 });
|
||||
fs.writeFileSync(projectGradleConfigPath, JSON.stringify(projectGradleConfig, null, 2), 'utf-8');
|
||||
}
|
||||
|
||||
function getUserGradleConfig (configXml) {
|
||||
@@ -109,7 +110,11 @@ function getUserGradleConfig (configXml) {
|
||||
{ xmlKey: 'AndroidXWebKitVersion', gradleKey: 'ANDROIDX_WEBKIT_VERSION', type: String },
|
||||
{ xmlKey: 'GradlePluginGoogleServicesVersion', gradleKey: 'GRADLE_PLUGIN_GOOGLE_SERVICES_VERSION', type: String },
|
||||
{ xmlKey: 'GradlePluginGoogleServicesEnabled', gradleKey: 'IS_GRADLE_PLUGIN_GOOGLE_SERVICES_ENABLED', type: Boolean },
|
||||
{ xmlKey: 'GradlePluginKotlinEnabled', gradleKey: 'IS_GRADLE_PLUGIN_KOTLIN_ENABLED', type: Boolean }
|
||||
{ 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: 'AndroidShowDeprecations', gradleKey: 'JAVA_SHOW_DEPRECATIONS', type: Boolean }
|
||||
];
|
||||
|
||||
return configXmlToGradleMapping.reduce((config, mapping) => {
|
||||
@@ -194,7 +199,7 @@ function updateConfigFilesFrom (sourceConfig, configMunger, locations) {
|
||||
|
||||
// First cleanup current config and merge project's one into own
|
||||
// Overwrite platform config.xml with defaults.xml.
|
||||
fs.copySync(locations.defaultConfigXml, locations.configXml);
|
||||
fs.cpSync(locations.defaultConfigXml, locations.configXml);
|
||||
|
||||
// Then apply config changes from global munge to all config files
|
||||
// in project (including project's config)
|
||||
@@ -267,7 +272,7 @@ function cleanWww (projectRoot, locations) {
|
||||
*/
|
||||
function updateProjectAccordingTo (platformConfig, locations) {
|
||||
updateProjectStrings(platformConfig, locations);
|
||||
updateProjectSplashScreen(platformConfig, locations);
|
||||
updateProjectTheme(platformConfig, locations);
|
||||
|
||||
const name = platformConfig.name();
|
||||
|
||||
@@ -312,14 +317,14 @@ function updateProjectAccordingTo (platformConfig, locations) {
|
||||
const newDestFile = path.join(locations.root, 'app', 'src', 'main', 'java', androidPkgName.replace(/\./g, '/'), path.basename(destFile));
|
||||
if (newDestFile.toLowerCase() !== destFile.toLowerCase()) {
|
||||
// If package was name changed we need to create new java with main activity in path matching new package name
|
||||
fs.ensureDirSync(path.dirname(newDestFile));
|
||||
fs.mkdirSync(path.dirname(newDestFile), { recursive: true });
|
||||
events.emit('verbose', `copy ${destFile} to ${newDestFile}`);
|
||||
fs.copySync(destFile, newDestFile);
|
||||
fs.cpSync(destFile, newDestFile);
|
||||
utils.replaceFileContents(newDestFile, /package [\w.]*;/, 'package ' + androidPkgName + ';');
|
||||
events.emit('verbose', 'Wrote out Android package name "' + androidPkgName + '" to ' + newDestFile);
|
||||
// If package was name changed we need to remove old java with main activity
|
||||
events.emit('verbose', `remove ${destFile}`);
|
||||
fs.removeSync(destFile);
|
||||
fs.rmSync(destFile);
|
||||
// remove any empty directories
|
||||
let currentDir = path.dirname(destFile);
|
||||
const sourcesRoot = path.resolve(locations.root, 'src');
|
||||
@@ -342,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();
|
||||
@@ -372,15 +377,34 @@ function warnForDeprecatedSplashScreen (cordovaProject) {
|
||||
* be used to update project
|
||||
* @param {Object} locations A map of locations for this platform
|
||||
*/
|
||||
function updateProjectSplashScreen (platformConfig, locations) {
|
||||
// res/values/themes.xml
|
||||
function updateProjectTheme (platformConfig, locations) {
|
||||
// res/values/cdv_themes.xml
|
||||
const themes = xmlHelpers.parseElementtreeSync(locations.themes);
|
||||
const splashScreenTheme = themes.find('style[@name="Theme.App.SplashScreen"]');
|
||||
|
||||
let splashBg = platformConfig.getPreference('AndroidWindowSplashScreenBackground', this.platform);
|
||||
if (!splashBg) {
|
||||
splashBg = platformConfig.getPreference('SplashScreenBackgroundColor', this.platform);
|
||||
}
|
||||
if (!splashBg) {
|
||||
splashBg = platformConfig.getPreference('BackgroundColor', this.platform);
|
||||
}
|
||||
if (!splashBg) {
|
||||
splashBg = '@color/cdv_splashscreen_background';
|
||||
}
|
||||
|
||||
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 = splashBg;
|
||||
|
||||
[
|
||||
// Splash Screen
|
||||
'windowSplashScreenAnimatedIcon',
|
||||
'windowSplashScreenAnimationDuration',
|
||||
'windowSplashScreenBackground',
|
||||
'android:windowSplashScreenBrandingImage',
|
||||
'windowSplashScreenIconBackgroundColor',
|
||||
'postSplashScreenTheme'
|
||||
@@ -391,14 +415,6 @@ function updateProjectSplashScreen (platformConfig, locations) {
|
||||
let themeTargetNode = splashScreenTheme.find(`item[@name="${themeKey}"]`);
|
||||
|
||||
switch (themeKey) {
|
||||
case 'windowSplashScreenBackground':
|
||||
// use the user defined value for "colors.xml"
|
||||
updateProjectSplashScreenBackgroundColor(cdvConfigPrefValue, locations);
|
||||
|
||||
// force the themes value to `@color/cdv_splashscreen_background`
|
||||
themeTargetNode.text = '@color/cdv_splashscreen_background';
|
||||
break;
|
||||
|
||||
case 'windowSplashScreenAnimatedIcon':
|
||||
// handle here the cases of "png" vs "xml" (drawable)
|
||||
// If "png":
|
||||
@@ -440,7 +456,7 @@ function updateProjectSplashScreen (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`
|
||||
@@ -464,7 +480,7 @@ function updateProjectSplashScreen (platformConfig, locations) {
|
||||
break;
|
||||
|
||||
case 'postSplashScreenTheme':
|
||||
themeTargetNode.text = cdvConfigPrefValue || '@style/Theme.AppCompat.NoActionBar';
|
||||
themeTargetNode.text = cdvConfigPrefValue || '@style/Theme.Cordova.App.DayNight';
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -476,29 +492,13 @@ function updateProjectSplashScreen (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';
|
||||
@@ -526,16 +526,16 @@ function updateProjectSplashScreenIconBackgroundColor (splashIconBackgroundColor
|
||||
|
||||
function cleanupAndSetProjectSplashScreenImage (srcFile, destFilePath, possiblePreviousDestFilePath, cleanupOnly = false) {
|
||||
if (fs.existsSync(possiblePreviousDestFilePath)) {
|
||||
fs.removeSync(possiblePreviousDestFilePath);
|
||||
fs.rmSync(possiblePreviousDestFilePath);
|
||||
}
|
||||
|
||||
if (cleanupOnly && fs.existsSync(destFilePath)) {
|
||||
// Also remove dest file path for cleanup even if previous was not use.
|
||||
fs.removeSync(destFilePath);
|
||||
fs.rmSync(destFilePath);
|
||||
}
|
||||
|
||||
if (!cleanupOnly && srcFile && fs.existsSync(srcFile)) {
|
||||
fs.copySync(srcFile, destFilePath);
|
||||
fs.cpSync(srcFile, destFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -596,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.
|
||||
@@ -747,9 +747,24 @@ function updateIconResourceForAdaptive (preparedIcons, resourceMap, platformReso
|
||||
foreground = android_icons[density].foreground;
|
||||
monochrome = android_icons[density].monochrome;
|
||||
|
||||
const isAdaptiveIcon = background && foreground;
|
||||
const isMonochromeIcon = monochrome && isAdaptiveIcon;
|
||||
if (!isMonochromeIcon || !isAdaptiveIcon) {
|
||||
const hasAdaptiveIcons = !!background && !!foreground;
|
||||
let hasMonochromeIcon = !!monochrome;
|
||||
|
||||
if (hasMonochromeIcon && !hasAdaptiveIcons) {
|
||||
// If we have a monochrome icon, but no adaptive icons,
|
||||
// then warn that in order to use monochrome, the adaptive icons
|
||||
// must be supplied. We will ignore monochrome and proceed with the
|
||||
// icon preparation however.
|
||||
hasMonochromeIcon = false;
|
||||
monochrome = undefined;
|
||||
events.emit('warn', dedent`
|
||||
Monochrome icon found but without adaptive properties.
|
||||
Monochrome icon requires the adaptive background and foreground assets.
|
||||
See https://cordova.apache.org/docs/en/latest/config_ref/images.html fore more information.
|
||||
`);
|
||||
}
|
||||
|
||||
if (!hasAdaptiveIcons) {
|
||||
// This icon isn't an adaptive icon, so skip it
|
||||
continue;
|
||||
}
|
||||
@@ -780,7 +795,7 @@ function updateIconResourceForAdaptive (preparedIcons, resourceMap, platformReso
|
||||
resourceMap[targetPathForeground] = android_icons[density].foreground;
|
||||
}
|
||||
|
||||
if (monochrome) {
|
||||
if (hasMonochromeIcon) {
|
||||
if (path.extname(path.basename(monochrome)) === '.xml') {
|
||||
// Vector Use Case
|
||||
targetPathMonochrome = getAdaptiveImageResourcePath(platformResourcesDir, 'mipmap', density, 'ic_launcher_monochrome.xml', path.basename(android_icons[density].monochrome));
|
||||
@@ -794,19 +809,23 @@ function updateIconResourceForAdaptive (preparedIcons, resourceMap, platformReso
|
||||
|
||||
// create an XML for DPI and set color
|
||||
let icLauncherTemplate = '';
|
||||
if (monochrome) {
|
||||
icLauncherTemplate = `<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="` + backgroundVal + `" />
|
||||
<foreground android:drawable="` + foregroundVal + `" />
|
||||
<monochrome android:drawable="` + monochromeVal + `" />
|
||||
</adaptive-icon>`;
|
||||
if (hasMonochromeIcon) {
|
||||
icLauncherTemplate = dedent`
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="${backgroundVal}" />
|
||||
<foreground android:drawable="${foregroundVal}" />
|
||||
<monochrome android:drawable="${monochromeVal}" />
|
||||
</adaptive-icon>
|
||||
`;
|
||||
} else {
|
||||
icLauncherTemplate = `<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="` + backgroundVal + `" />
|
||||
<foreground android:drawable="` + foregroundVal + `" />
|
||||
</adaptive-icon>`;
|
||||
icLauncherTemplate = dedent`
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="${backgroundVal}" />
|
||||
<foreground android:drawable="${foregroundVal}" />
|
||||
</adaptive-icon>
|
||||
`;
|
||||
}
|
||||
|
||||
const launcherXmlPath = path.join(platformResourcesDir, 'mipmap-' + density + '-v26', 'ic_launcher.xml');
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
const { inspect } = require('util');
|
||||
const { inspect } = require('node:util');
|
||||
const execa = require('execa');
|
||||
const Adb = require('./Adb');
|
||||
const build = require('./build');
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
|
||||
// TODO: Perhaps this should live in cordova-common?
|
||||
|
||||
const fs = require('fs-extra');
|
||||
const fs = require('node:fs');
|
||||
const which = require('which');
|
||||
const os = require('os');
|
||||
const os = require('node:os');
|
||||
|
||||
/**
|
||||
* Reads, searches, and replaces the found occurences with replacementString and then writes the file back out.
|
||||
|
||||
@@ -15,13 +15,5 @@
|
||||
# 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'
|
||||
# Empty for the release audit workflow.
|
||||
# The `license-config` is required even if there are no custom configs
|
||||
8943
package-lock.json
generated
8943
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
38
package.json
38
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cordova-android",
|
||||
"version": "12.0.0",
|
||||
"version": "15.0.0-dev",
|
||||
"description": "cordova-android release",
|
||||
"types": "./types/index.d.ts",
|
||||
"main": "lib/Api.js",
|
||||
@@ -14,43 +14,43 @@
|
||||
"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",
|
||||
"clean:java-unit-tests": "node test/clean.js"
|
||||
"java-unit-tests": "node test/run_java_unit_tests.js"
|
||||
},
|
||||
"author": "Apache Software Foundation",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"android-versions": "^1.8.1",
|
||||
"cordova-common": "^5.0.0",
|
||||
"android-versions": "^2.1.0",
|
||||
"cordova-common": "^6.0.0",
|
||||
"dedent": "^1.7.0",
|
||||
"execa": "^5.1.1",
|
||||
"fast-glob": "^3.2.12",
|
||||
"fs-extra": "^11.1.1",
|
||||
"fast-glob": "^3.3.3",
|
||||
"is-path-inside": "^3.0.3",
|
||||
"nopt": "^7.1.0",
|
||||
"properties-parser": "^0.3.1",
|
||||
"semver": "^7.3.8",
|
||||
"nopt": "^8.1.0",
|
||||
"properties-parser": "^0.6.0",
|
||||
"semver": "^7.7.1",
|
||||
"string-argv": "^0.3.1",
|
||||
"untildify": "^4.0.0",
|
||||
"which": "^3.0.0"
|
||||
"which": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cordova/eslint-config": "^5.0.0",
|
||||
"@cordova/eslint-config": "^6.0.0",
|
||||
"c8": "^10.1.3",
|
||||
"cordova-js": "^6.1.0",
|
||||
"elementtree": "^0.1.7",
|
||||
"jasmine": "^4.6.0",
|
||||
"jasmine": "^5.10.0",
|
||||
"jasmine-spec-reporter": "^7.0.0",
|
||||
"nyc": "^15.1.0",
|
||||
"rewire": "^6.0.0"
|
||||
"rewire": "^9.0.1",
|
||||
"tmp": "^0.2.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.13.0"
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"nyc": {
|
||||
"c8": {
|
||||
"include": [
|
||||
"lib"
|
||||
],
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
const os = require('os');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const os = require('node:os');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { EventEmitter } = require('events');
|
||||
const { ConfigParser, PluginInfoProvider } = require('cordova-common');
|
||||
const Api = require('../../lib/Api');
|
||||
@@ -49,14 +49,15 @@ describe('E2E', function () {
|
||||
api = await makeProject(projectPath);
|
||||
});
|
||||
afterEach(() => {
|
||||
fs.removeSync(tmpDir);
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('loads the API from a project directory', async () => {
|
||||
// Allow test project to find the `cordova-android` module
|
||||
fs.ensureSymlinkSync(
|
||||
path.join(__dirname, '../..'),
|
||||
path.join(tmpDir, 'node_modules/cordova-android'),
|
||||
fs.mkdirSync(path.join(tmpDir, 'node_modules'), { recursive: true });
|
||||
fs.symlinkSync(
|
||||
path.join(__dirname, '..', '..'),
|
||||
path.join(tmpDir, 'node_modules', 'cordova-android'),
|
||||
'junction'
|
||||
);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
package="com.example.anis.myapplication">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
|
||||
@@ -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.
|
||||
|
||||
#Mon Dec 28 10:00:20 PST 2015
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
|
||||
105
spec/unit/AndroidCommandLineTools.spec.js
Normal file
105
spec/unit/AndroidCommandLineTools.spec.js
Normal 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'));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -17,9 +17,9 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const rewire = require('rewire');
|
||||
|
||||
describe('AndroidManifest', () => {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const path = require('node:path');
|
||||
const rewire = require('rewire');
|
||||
const MockCordovaGradleConfigParser = require('./mocks/config/MockCordovaGradleConfigParser');
|
||||
const CordovaGradleConfigParserFactory = require('../../lib/config/CordovaGradleConfigParserFactory');
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const common = require('cordova-common');
|
||||
const EventEmitter = require('events');
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const rewire = require('rewire');
|
||||
|
||||
describe('android_sdk', () => {
|
||||
|
||||
@@ -17,9 +17,10 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const rewire = require('rewire');
|
||||
const { isWindows } = require('../../../lib/utils');
|
||||
|
||||
describe('ProjectBuilder', () => {
|
||||
const rootDir = '/root';
|
||||
@@ -128,19 +129,24 @@ describe('ProjectBuilder', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('runGradleWrapper', () => {
|
||||
it('should run the provided gradle command if a gradle wrapper does not already exist', () => {
|
||||
spyOn(fs, 'existsSync').and.returnValue(false);
|
||||
builder.runGradleWrapper('/my/sweet/gradle');
|
||||
expect(execaSpy).toHaveBeenCalledWith('/my/sweet/gradle', jasmine.any(Array), jasmine.any(Object));
|
||||
describe('installGradleWrapper', () => {
|
||||
beforeEach(() => {
|
||||
execaSpy.and.resolveTo();
|
||||
});
|
||||
|
||||
it('should do nothing if a gradle wrapper exists in the project directory', () => {
|
||||
spyOn(fs, 'existsSync').and.returnValue(true);
|
||||
builder.runGradleWrapper('/my/sweet/gradle');
|
||||
expect(execaSpy).not.toHaveBeenCalledWith('/my/sweet/gradle', jasmine.any(Array), jasmine.any(Object));
|
||||
it('should run gradle wrapper 8.13', async () => {
|
||||
await builder.installGradleWrapper('8.13');
|
||||
expect(execaSpy).toHaveBeenCalledWith('gradle', ['-p', path.normalize('/root/tools'), 'wrapper', '--gradle-version', '8.13'], jasmine.any(Object));
|
||||
});
|
||||
|
||||
it('CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL should override gradle version', async () => {
|
||||
process.env.CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL = 'https://dist.local';
|
||||
await builder.installGradleWrapper('8.13');
|
||||
delete process.env.CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL;
|
||||
expect(execaSpy).toHaveBeenCalledWith('gradle', ['-p', path.normalize('/root/tools'), 'wrapper', '--gradle-distribution-url', 'https://dist.local'], jasmine.any(Object));
|
||||
});
|
||||
});
|
||||
|
||||
describe('build', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(builder, 'getArgs');
|
||||
@@ -170,7 +176,12 @@ describe('ProjectBuilder', () => {
|
||||
|
||||
builder.build({});
|
||||
|
||||
expect(execaSpy).toHaveBeenCalledWith(path.join(rootDir, 'gradlew'), testArgs, jasmine.anything());
|
||||
let gradle = path.join(rootDir, 'tools', 'gradlew');
|
||||
if (isWindows()) {
|
||||
gradle += '.bat';
|
||||
}
|
||||
|
||||
expect(execaSpy).toHaveBeenCalledWith(gradle, testArgs, jasmine.anything());
|
||||
});
|
||||
|
||||
it('should reject if the spawn fails', () => {
|
||||
@@ -209,7 +220,7 @@ describe('ProjectBuilder', () => {
|
||||
beforeEach(() => {
|
||||
const marker = ProjectBuilder.__get__('MARKER');
|
||||
spyOn(fs, 'readFileSync').and.returnValue(`Some Header Here: ${marker}`);
|
||||
spyOn(fs, 'removeSync');
|
||||
spyOn(fs, 'rmSync');
|
||||
spyOn(builder, 'getArgs');
|
||||
execaSpy.and.returnValue(Promise.resolve());
|
||||
});
|
||||
@@ -227,14 +238,19 @@ describe('ProjectBuilder', () => {
|
||||
const gradleArgs = ['test', 'args', '-f'];
|
||||
builder.getArgs.and.returnValue(gradleArgs);
|
||||
|
||||
let gradle = path.join(rootDir, 'tools', 'gradlew');
|
||||
if (isWindows()) {
|
||||
gradle += '.bat';
|
||||
}
|
||||
|
||||
return builder.clean(opts).then(() => {
|
||||
expect(execaSpy).toHaveBeenCalledWith(path.join(rootDir, 'gradlew'), gradleArgs, jasmine.anything());
|
||||
expect(execaSpy).toHaveBeenCalledWith(gradle, gradleArgs, jasmine.anything());
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove "out" folder', () => {
|
||||
return builder.clean({}).then(() => {
|
||||
expect(fs.removeSync).toHaveBeenCalledWith(path.join(rootDir, 'out'));
|
||||
expect(fs.rmSync).toHaveBeenCalledWith(path.join(rootDir, 'out'), { recursive: true, force: true });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -245,8 +261,8 @@ describe('ProjectBuilder', () => {
|
||||
spyOn(fs, 'existsSync').and.returnValue(true);
|
||||
|
||||
return builder.clean({}).then(() => {
|
||||
expect(fs.removeSync).toHaveBeenCalledWith(debugSigningFile);
|
||||
expect(fs.removeSync).toHaveBeenCalledWith(releaseSigningFile);
|
||||
expect(fs.rmSync).toHaveBeenCalledWith(debugSigningFile);
|
||||
expect(fs.rmSync).toHaveBeenCalledWith(releaseSigningFile);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -257,8 +273,8 @@ describe('ProjectBuilder', () => {
|
||||
spyOn(fs, 'existsSync').and.returnValue(false);
|
||||
|
||||
return builder.clean({}).then(() => {
|
||||
expect(fs.removeSync).not.toHaveBeenCalledWith(debugSigningFile);
|
||||
expect(fs.removeSync).not.toHaveBeenCalledWith(releaseSigningFile);
|
||||
expect(fs.rmSync).not.toHaveBeenCalledWith(debugSigningFile);
|
||||
expect(fs.rmSync).not.toHaveBeenCalledWith(releaseSigningFile);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,14 +19,15 @@
|
||||
|
||||
const rewire = require('rewire');
|
||||
const android_sdk = require('../../lib/android_sdk');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const events = require('cordova-common').events;
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,8 +21,8 @@ const rewire = require('rewire');
|
||||
const utils = require('../../lib/utils');
|
||||
const create = rewire('../../lib/create');
|
||||
const check_reqs = require('../../lib/check_reqs');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const MockCordovaGradleConfigParser = require('./mocks/config/MockCordovaGradleConfigParser');
|
||||
const CordovaGradleConfigParserFactory = require('../../lib/config/CordovaGradleConfigParserFactory');
|
||||
|
||||
@@ -142,8 +142,8 @@ describe('create', function () {
|
||||
spyOn(create, 'prepBuildFiles');
|
||||
revert_manifest_mock = create.__set__('AndroidManifest', Manifest_mock);
|
||||
spyOn(fs, 'existsSync').and.returnValue(false);
|
||||
spyOn(fs, 'copySync');
|
||||
spyOn(fs, 'ensureDirSync');
|
||||
spyOn(fs, 'cpSync');
|
||||
spyOn(fs, 'mkdirSync');
|
||||
spyOn(utils, 'replaceFileContents');
|
||||
config_mock = jasmine.createSpyObj('ConfigParser mock instance', ['packageName', 'android_packageName', 'name', 'android_activityName']);
|
||||
events_mock = jasmine.createSpyObj('EventEmitter mock instance', ['emit']);
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -238,17 +238,17 @@ describe('create', function () {
|
||||
describe('happy path', function () {
|
||||
it('should copy project templates from a specified custom template', () => {
|
||||
return create.create(project_path, config_mock, { customTemplate: '/template/path' }, events_mock).then(() => {
|
||||
expect(fs.copySync).toHaveBeenCalledWith(path.join('/template/path', 'assets'), path.join(app_path, 'assets'));
|
||||
expect(fs.copySync).toHaveBeenCalledWith(path.join('/template/path', 'res'), path.join(app_path, 'res'));
|
||||
expect(fs.copySync).toHaveBeenCalledWith(path.join('/template/path', 'gitignore'), path.join(project_path, '.gitignore'));
|
||||
expect(fs.cpSync).toHaveBeenCalledWith(path.join('/template/path', 'assets'), path.join(app_path, 'assets'), { recursive: true });
|
||||
expect(fs.cpSync).toHaveBeenCalledWith(path.join('/template/path', 'res'), path.join(app_path, 'res'), { recursive: true });
|
||||
expect(fs.cpSync).toHaveBeenCalledWith(path.join('/template/path', 'gitignore'), path.join(project_path, '.gitignore'));
|
||||
});
|
||||
});
|
||||
|
||||
it('should copy project templates from the default templates location if no custom template is provided', () => {
|
||||
return create.create(project_path, config_mock, {}, events_mock).then(() => {
|
||||
expect(fs.copySync).toHaveBeenCalledWith(path.join(default_templates, 'assets'), path.join(app_path, 'assets'));
|
||||
expect(fs.copySync).toHaveBeenCalledWith(path.join(default_templates, 'res'), path.join(app_path, 'res'));
|
||||
expect(fs.copySync).toHaveBeenCalledWith(path.join(default_templates, 'gitignore'), path.join(project_path, '.gitignore'));
|
||||
expect(fs.cpSync).toHaveBeenCalledWith(path.join(default_templates, 'assets'), path.join(app_path, 'assets'), { recursive: true });
|
||||
expect(fs.cpSync).toHaveBeenCalledWith(path.join(default_templates, 'res'), path.join(app_path, 'res'), { recursive: true });
|
||||
expect(fs.cpSync).toHaveBeenCalledWith(path.join(default_templates, 'gitignore'), path.join(project_path, '.gitignore'));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -261,7 +261,7 @@ describe('create', function () {
|
||||
it('should create a java src directory based on the provided project package name', () => {
|
||||
config_mock.packageName.and.returnValue('org.apache.cordova');
|
||||
return create.create(project_path, config_mock, {}, events_mock).then(() => {
|
||||
expect(fs.ensureDirSync).toHaveBeenCalledWith(path.join(app_path, 'java', 'org', 'apache', 'cordova'));
|
||||
expect(fs.mkdirSync).toHaveBeenCalledWith(path.join(app_path, 'java', 'org', 'apache', 'cordova'), { recursive: true });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -270,23 +270,23 @@ describe('create', function () {
|
||||
config_mock.android_activityName.and.returnValue('CEEDEEVEE');
|
||||
const activity_path = path.join(app_path, 'java', 'org', 'apache', 'cordova', 'CEEDEEVEE.java');
|
||||
return create.create(project_path, config_mock, {}, events_mock).then(() => {
|
||||
expect(fs.copySync).toHaveBeenCalledWith(path.join(default_templates, 'Activity.java'), activity_path);
|
||||
expect(fs.cpSync).toHaveBeenCalledWith(path.join(default_templates, 'Activity.java'), activity_path);
|
||||
expect(utils.replaceFileContents).toHaveBeenCalledWith(activity_path, /__ACTIVITY__/, 'CEEDEEVEE');
|
||||
expect(utils.replaceFileContents).toHaveBeenCalledWith(activity_path, /__ID__/, 'org.apache.cordova');
|
||||
});
|
||||
});
|
||||
|
||||
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__/, '<Incredible&App>');
|
||||
expect(utils.replaceFileContents).toHaveBeenCalledWith(path.join(app_path, 'res', 'values', 'cdv_strings.xml'), /__NAME__/, '<Incredible&App>');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const rewire = require('rewire');
|
||||
const which = require('which');
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const path = require('node:path');
|
||||
const rewire = require('rewire');
|
||||
const { CordovaError } = require('cordova-common');
|
||||
const utils = require('../../lib/utils');
|
||||
|
||||
@@ -18,27 +18,41 @@
|
||||
|
||||
const rewire = require('rewire');
|
||||
const common = rewire('../../../lib/pluginHandlers');
|
||||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
const osenv = require('os');
|
||||
const path = require('node:path');
|
||||
const fs = require('node:fs');
|
||||
const tmp = require('tmp');
|
||||
|
||||
const test_dir = path.join(osenv.tmpdir(), 'test_plugman');
|
||||
tmp.setGracefulCleanup();
|
||||
|
||||
const tempdir = tmp.dirSync({ unsafeCleanup: true });
|
||||
const test_dir = path.join(tempdir.name, 'test_plugman');
|
||||
const project_dir = path.join(test_dir, 'project');
|
||||
const src = path.join(project_dir, 'src');
|
||||
const dest = path.join(project_dir, 'dest');
|
||||
const java_dir = path.join(src, 'one', 'two', 'three');
|
||||
const java_file = path.join(java_dir, 'test.java');
|
||||
const symlink_file = path.join(java_dir, 'symlink');
|
||||
const non_plugin_file = path.join(osenv.tmpdir(), 'non_plugin_file');
|
||||
const non_plugin_file = path.join(tempdir.name, 'non_plugin_file');
|
||||
|
||||
const copyFile = common.__get__('copyFile');
|
||||
const deleteJava = common.__get__('deleteJava');
|
||||
const copyNewFile = common.__get__('copyNewFile');
|
||||
|
||||
function outputFileSync (file, content) {
|
||||
const dir = path.dirname(file);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
fs.writeFileSync(file, content, 'utf-8');
|
||||
}
|
||||
|
||||
describe('common platform handler', function () {
|
||||
afterAll(() => {
|
||||
// Remove tempdir after all specs complete
|
||||
fs.rmSync(tempdir.name, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.removeSync(test_dir);
|
||||
fs.removeSync(non_plugin_file);
|
||||
fs.rmSync(test_dir, { recursive: true, force: true });
|
||||
fs.rmSync(non_plugin_file, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
describe('copyFile', function () {
|
||||
@@ -48,15 +62,15 @@ describe('common platform handler', function () {
|
||||
});
|
||||
|
||||
it('Test#002 : should throw if src not in plugin directory', function () {
|
||||
fs.ensureDirSync(project_dir);
|
||||
fs.outputFileSync(non_plugin_file, 'contents');
|
||||
fs.mkdirSync(project_dir, { recursive: true });
|
||||
outputFileSync(non_plugin_file, 'contents');
|
||||
const outside_file = '../non_plugin_file';
|
||||
expect(function () { copyFile(test_dir, outside_file, project_dir, dest); })
|
||||
.toThrow(new Error('File "' + path.resolve(test_dir, outside_file) + '" is located outside the plugin directory "' + test_dir + '"'));
|
||||
});
|
||||
|
||||
it('Test#003 : should allow symlink src, if inside plugin', function () {
|
||||
fs.outputFileSync(java_file, 'contents');
|
||||
outputFileSync(java_file, 'contents');
|
||||
|
||||
// This will fail on windows if not admin - ignore the error in that case.
|
||||
if (ignoreEPERMonWin32(java_file, symlink_file)) {
|
||||
@@ -67,8 +81,8 @@ describe('common platform handler', function () {
|
||||
});
|
||||
|
||||
it('Test#004 : should throw if symlink is linked to a file outside the plugin', function () {
|
||||
fs.ensureDirSync(java_dir);
|
||||
fs.outputFileSync(non_plugin_file, 'contents');
|
||||
fs.mkdirSync(java_dir, { recursive: true });
|
||||
outputFileSync(non_plugin_file, 'contents');
|
||||
|
||||
// This will fail on windows if not admin - ignore the error in that case.
|
||||
if (ignoreEPERMonWin32(non_plugin_file, symlink_file)) {
|
||||
@@ -80,37 +94,37 @@ describe('common platform handler', function () {
|
||||
});
|
||||
|
||||
it('Test#005 : should throw if dest is outside the project directory', function () {
|
||||
fs.outputFileSync(java_file, 'contents');
|
||||
outputFileSync(java_file, 'contents');
|
||||
expect(function () { copyFile(test_dir, java_file, project_dir, non_plugin_file); })
|
||||
.toThrow(new Error('Destination "' + path.resolve(project_dir, non_plugin_file) + '" for source file "' + path.resolve(test_dir, java_file) + '" is located outside the project'));
|
||||
});
|
||||
|
||||
it('Test#006 : should call mkdir -p on target path', function () {
|
||||
fs.outputFileSync(java_file, 'contents');
|
||||
it('Test#006 : should call mkdirSync target path', function () {
|
||||
outputFileSync(java_file, 'contents');
|
||||
|
||||
const s = spyOn(fs, 'ensureDirSync').and.callThrough();
|
||||
const s = spyOn(fs, 'mkdirSync').and.callThrough();
|
||||
const resolvedDest = path.resolve(project_dir, dest);
|
||||
|
||||
copyFile(test_dir, java_file, project_dir, dest);
|
||||
|
||||
expect(s).toHaveBeenCalled();
|
||||
expect(s).toHaveBeenCalledWith(path.dirname(resolvedDest));
|
||||
expect(s).toHaveBeenCalledWith(path.dirname(resolvedDest), { recursive: true });
|
||||
});
|
||||
|
||||
it('Test#007 : should call cp source/dest paths', function () {
|
||||
fs.outputFileSync(java_file, 'contents');
|
||||
outputFileSync(java_file, 'contents');
|
||||
|
||||
const s = spyOn(fs, 'copySync').and.callThrough();
|
||||
const s = spyOn(fs, 'cpSync').and.callThrough();
|
||||
const resolvedDest = path.resolve(project_dir, dest);
|
||||
|
||||
copyFile(test_dir, java_file, project_dir, dest);
|
||||
|
||||
expect(s).toHaveBeenCalled();
|
||||
expect(s).toHaveBeenCalledWith(java_file, resolvedDest);
|
||||
expect(s).toHaveBeenCalledWith(java_file, resolvedDest, { recursive: true });
|
||||
});
|
||||
|
||||
it('should handle relative paths when checking for sub paths', () => {
|
||||
fs.outputFileSync(java_file, 'contents');
|
||||
outputFileSync(java_file, 'contents');
|
||||
const relativeProjectPath = path.relative(process.cwd(), project_dir);
|
||||
|
||||
expect(() => {
|
||||
@@ -121,7 +135,7 @@ describe('common platform handler', function () {
|
||||
|
||||
describe('copyNewFile', function () {
|
||||
it('Test#008 : should throw if target path exists', function () {
|
||||
fs.ensureDirSync(dest);
|
||||
fs.mkdirSync(dest, { recursive: true });
|
||||
expect(function () { copyNewFile(test_dir, src, project_dir, dest); })
|
||||
.toThrow(new Error('"' + dest + '" already exists!'));
|
||||
});
|
||||
@@ -129,14 +143,14 @@ describe('common platform handler', function () {
|
||||
|
||||
describe('deleteJava', function () {
|
||||
beforeEach(function () {
|
||||
fs.outputFileSync(java_file, 'contents');
|
||||
outputFileSync(java_file, 'contents');
|
||||
});
|
||||
|
||||
it('Test#009 : should call fs.unlinkSync on the provided paths', function () {
|
||||
const s = spyOn(fs, 'removeSync').and.callThrough();
|
||||
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 () {
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
const rewire = require('rewire');
|
||||
const common = rewire('../../../lib/pluginHandlers');
|
||||
const android = common.__get__('handlers');
|
||||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
const os = require('os');
|
||||
const path = require('node:path');
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const temp = path.join(os.tmpdir(), 'plugman');
|
||||
const plugins_dir = path.join(temp, 'cordova/plugins');
|
||||
const dummyplugin = path.join(__dirname, '../../fixtures/org.test.plugins.dummyplugin');
|
||||
@@ -56,14 +56,14 @@ describe('android project handler', function () {
|
||||
let dummyProject;
|
||||
|
||||
beforeEach(function () {
|
||||
fs.ensureDirSync(temp);
|
||||
fs.mkdirSync(temp, { recursive: true });
|
||||
dummyProject = AndroidProject.getProjectFile(temp);
|
||||
copyFileSpy.calls.reset();
|
||||
common.__set__('copyFile', copyFileSpy);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
fs.removeSync(temp);
|
||||
fs.rmSync(temp, { recursive: true, force: true });
|
||||
common.__set__('copyFile', copyFileOrig);
|
||||
});
|
||||
|
||||
@@ -83,7 +83,7 @@ describe('android project handler', function () {
|
||||
|
||||
describe('of <source-file> elements', function () {
|
||||
beforeEach(function () {
|
||||
fs.copySync(android_studio_project, temp);
|
||||
fs.cpSync(android_studio_project, temp, { recursive: true });
|
||||
});
|
||||
|
||||
it('Test#003 : should copy stuff from one location to another by calling common.copyFile', function () {
|
||||
@@ -102,7 +102,7 @@ describe('android project handler', function () {
|
||||
it('Test#006 : should throw if target file already exists', function () {
|
||||
// write out a file
|
||||
let target = path.resolve(temp, 'app', 'src', 'main', 'java', 'com', 'phonegap', 'plugins', 'dummyplugin');
|
||||
fs.ensureDirSync(target);
|
||||
fs.mkdirSync(target, { recursive: true });
|
||||
target = path.join(target, 'DummyPlugin.java');
|
||||
fs.writeFileSync(target, 'some bs', 'utf-8');
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
@@ -192,7 +192,7 @@ describe('android project handler', function () {
|
||||
const copyNewFileSpy = jasmine.createSpy('copyNewFile');
|
||||
|
||||
beforeEach(function () {
|
||||
fs.copySync(android_studio_project, temp);
|
||||
fs.cpSync(android_studio_project, temp, { recursive: true });
|
||||
|
||||
spyOn(dummyProject, 'addSystemLibrary');
|
||||
spyOn(dummyProject, 'addSubProject');
|
||||
@@ -222,7 +222,7 @@ describe('android project handler', function () {
|
||||
|
||||
it('Test#010 : should not copy anything if "custom" attribute is not set', function () {
|
||||
const framework = { src: 'plugin-lib' };
|
||||
const cpSpy = spyOn(fs, 'copySync');
|
||||
const cpSpy = spyOn(fs, 'cpSync');
|
||||
android.framework.install(framework, dummyPluginInfo, dummyProject);
|
||||
expect(dummyProject.addSystemLibrary).toHaveBeenCalledWith(someString, framework.src);
|
||||
expect(cpSpy).not.toHaveBeenCalled();
|
||||
@@ -289,23 +289,23 @@ describe('android project handler', function () {
|
||||
|
||||
describe('uninstallation', function () {
|
||||
const deleteJavaOrig = common.__get__('deleteJava');
|
||||
const originalRemoveSync = fs.removeSync;
|
||||
const originalRmSync = fs.rmSync;
|
||||
const deleteJavaSpy = jasmine.createSpy('deleteJava');
|
||||
let dummyProject;
|
||||
let removeSyncSpy;
|
||||
let rmSyncSpy;
|
||||
|
||||
beforeEach(function () {
|
||||
fs.ensureDirSync(temp);
|
||||
fs.ensureDirSync(plugins_dir);
|
||||
fs.copySync(android_studio_project, temp);
|
||||
fs.mkdirSync(temp, { recursive: true });
|
||||
fs.mkdirSync(plugins_dir, { recursive: true });
|
||||
fs.cpSync(android_studio_project, temp, { recursive: true });
|
||||
AndroidProject.purgeCache();
|
||||
dummyProject = AndroidProject.getProjectFile(temp);
|
||||
removeSyncSpy = spyOn(fs, 'removeSync');
|
||||
rmSyncSpy = spyOn(fs, 'rmSync');
|
||||
common.__set__('deleteJava', deleteJavaSpy);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
originalRemoveSync.call(fs, temp);
|
||||
originalRmSync.call(fs, temp, { recursive: true });
|
||||
common.__set__('deleteJava', deleteJavaOrig);
|
||||
});
|
||||
|
||||
@@ -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(removeSyncSpy).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(removeSyncSpy).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(removeSyncSpy).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(removeSyncSpy).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(removeSyncSpy).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(removeSyncSpy).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(removeSyncSpy).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(removeSyncSpy).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(removeSyncSpy).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(removeSyncSpy).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'));
|
||||
});
|
||||
});
|
||||
@@ -397,7 +397,7 @@ describe('android project handler', function () {
|
||||
const someString = jasmine.any(String);
|
||||
|
||||
beforeEach(function () {
|
||||
fs.ensureDirSync(path.join(dummyProject.projectDir, dummyPluginInfo.id));
|
||||
fs.mkdirSync(path.join(dummyProject.projectDir, dummyPluginInfo.id), { recursive: true });
|
||||
|
||||
spyOn(dummyProject, 'removeSystemLibrary');
|
||||
spyOn(dummyProject, 'removeSubProject');
|
||||
@@ -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(removeSyncSpy).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(removeSyncSpy).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(removeSyncSpy).toHaveBeenCalledWith(wwwDest);
|
||||
expect(removeSyncSpy).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(removeSyncSpy).toHaveBeenCalledWith(wwwDest);
|
||||
expect(removeSyncSpy).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(removeSyncSpy).toHaveBeenCalledWith(wwwDest);
|
||||
expect(removeSyncSpy).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(removeSyncSpy).toHaveBeenCalledWith(wwwDest);
|
||||
expect(removeSyncSpy).not.toHaveBeenCalledWith(platformWwwDest);
|
||||
expect(rmSyncSpy).toHaveBeenCalledWith(wwwDest, { recursive: true, force: true });
|
||||
expect(rmSyncSpy).not.toHaveBeenCalledWith(platformWwwDest, { recursive: true, force: true });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
|
||||
const rewire = require('rewire');
|
||||
const path = require('path');
|
||||
const path = require('node:path');
|
||||
const CordovaError = require('cordova-common').CordovaError;
|
||||
const GradlePropertiesParser = require('../../lib/config/GradlePropertiesParser');
|
||||
const utils = require('../../lib/utils');
|
||||
@@ -630,43 +630,106 @@ describe('prepare', () => {
|
||||
let preparedIcons;
|
||||
let resourceMap;
|
||||
|
||||
beforeEach(function () {
|
||||
// Mocked Data
|
||||
platformResourcesDir = PATH_RESOURCE;
|
||||
preparedIcons = {
|
||||
android_icons: {
|
||||
mdpi: mockGetIconItem({
|
||||
density: 'mdpi',
|
||||
background: 'res/icon/android/mdpi-background.png',
|
||||
foreground: 'res/icon/android/mdpi-foreground.png',
|
||||
monochrome: 'res/icon/android/mdpi-monochrome.png'
|
||||
})
|
||||
},
|
||||
default_icon: undefined
|
||||
};
|
||||
describe('without monochrome', () => {
|
||||
beforeEach(function () {
|
||||
// Mocked Data
|
||||
platformResourcesDir = PATH_RESOURCE;
|
||||
preparedIcons = {
|
||||
android_icons: {
|
||||
mdpi: mockGetIconItem({
|
||||
density: 'mdpi',
|
||||
background: 'res/icon/android/mdpi-background.png',
|
||||
foreground: 'res/icon/android/mdpi-foreground.png'
|
||||
})
|
||||
},
|
||||
default_icon: undefined
|
||||
};
|
||||
|
||||
resourceMap = createResourceMap();
|
||||
resourceMap = createResourceMap();
|
||||
|
||||
fsWriteFileSyncSpy = jasmine.createSpy('writeFileSync');
|
||||
prepare.__set__('fs', {
|
||||
writeFileSync: fsWriteFileSyncSpy
|
||||
fsWriteFileSyncSpy = jasmine.createSpy('writeFileSync');
|
||||
prepare.__set__('fs', {
|
||||
writeFileSync: fsWriteFileSyncSpy
|
||||
});
|
||||
});
|
||||
|
||||
it('Test#001 : Should update resource map with prepared icons.', function () {
|
||||
// Get method for testing
|
||||
const updateIconResourceForAdaptive = prepare.__get__('updateIconResourceForAdaptive');
|
||||
|
||||
// Run Test
|
||||
const expectedModification = {};
|
||||
expectedModification[path.join(PATH_RESOURCE, 'mipmap-mdpi-v26', 'ic_launcher_background.png')] = 'res/icon/android/mdpi-background.png';
|
||||
expectedModification[path.join(PATH_RESOURCE, 'mipmap-mdpi-v26', 'ic_launcher_foreground.png')] = 'res/icon/android/mdpi-foreground.png';
|
||||
|
||||
const expected = Object.assign({}, resourceMap, expectedModification);
|
||||
const actual = updateIconResourceForAdaptive(preparedIcons, resourceMap, platformResourcesDir);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('Test#001 : Should update resource map with prepared icons.', function () {
|
||||
// Get method for testing
|
||||
const updateIconResourceForAdaptive = prepare.__get__('updateIconResourceForAdaptive');
|
||||
describe('with monochrome', () => {
|
||||
beforeEach(function () {
|
||||
// Mocked Data
|
||||
platformResourcesDir = PATH_RESOURCE;
|
||||
preparedIcons = {
|
||||
android_icons: {
|
||||
mdpi: mockGetIconItem({
|
||||
density: 'mdpi',
|
||||
background: 'res/icon/android/mdpi-background.png',
|
||||
foreground: 'res/icon/android/mdpi-foreground.png',
|
||||
monochrome: 'res/icon/android/mdpi-monochrome.png'
|
||||
})
|
||||
},
|
||||
default_icon: undefined
|
||||
};
|
||||
|
||||
// Run Test
|
||||
const expectedModification = {};
|
||||
expectedModification[path.join(PATH_RESOURCE, 'mipmap-mdpi-v26', 'ic_launcher_background.png')] = 'res/icon/android/mdpi-background.png';
|
||||
expectedModification[path.join(PATH_RESOURCE, 'mipmap-mdpi-v26', 'ic_launcher_foreground.png')] = 'res/icon/android/mdpi-foreground.png';
|
||||
expectedModification[path.join(PATH_RESOURCE, 'mipmap-mdpi-v26', 'ic_launcher_monochrome.png')] = 'res/icon/android/mdpi-monochrome.png';
|
||||
resourceMap = createResourceMap();
|
||||
|
||||
const expected = Object.assign({}, resourceMap, expectedModification);
|
||||
const actual = updateIconResourceForAdaptive(preparedIcons, resourceMap, platformResourcesDir);
|
||||
fsWriteFileSyncSpy = jasmine.createSpy('writeFileSync');
|
||||
prepare.__set__('fs', {
|
||||
writeFileSync: fsWriteFileSyncSpy
|
||||
});
|
||||
});
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
it('Test#002 : Should update resource map with prepared icons.', function () {
|
||||
// Get method for testing
|
||||
const updateIconResourceForAdaptive = prepare.__get__('updateIconResourceForAdaptive');
|
||||
|
||||
// Run Test
|
||||
const expectedModification = {};
|
||||
expectedModification[path.join(PATH_RESOURCE, 'mipmap-mdpi-v26', 'ic_launcher_background.png')] = 'res/icon/android/mdpi-background.png';
|
||||
expectedModification[path.join(PATH_RESOURCE, 'mipmap-mdpi-v26', 'ic_launcher_foreground.png')] = 'res/icon/android/mdpi-foreground.png';
|
||||
expectedModification[path.join(PATH_RESOURCE, 'mipmap-mdpi-v26', 'ic_launcher_monochrome.png')] = 'res/icon/android/mdpi-monochrome.png';
|
||||
|
||||
const expected = Object.assign({}, resourceMap, expectedModification);
|
||||
const actual = updateIconResourceForAdaptive(preparedIcons, resourceMap, platformResourcesDir);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('Test#003 : should emit if monochrome is supplied without adaptive background', () => {
|
||||
const updateIconResourceForAdaptive = prepare.__get__('updateIconResourceForAdaptive');
|
||||
|
||||
preparedIcons.android_icons.mdpi.background = undefined;
|
||||
updateIconResourceForAdaptive(preparedIcons, resourceMap, platformResourcesDir);
|
||||
|
||||
const actualEmitArgs = emitSpy.calls.mostRecent().args;
|
||||
expect(actualEmitArgs[0]).toBe('warn');
|
||||
expect(actualEmitArgs[1]).toMatch(/Monochrome icon found but without adaptive properties./g);
|
||||
});
|
||||
|
||||
it('Test#004 : should emit if monochrome is supplied without adaptive foreground', () => {
|
||||
const updateIconResourceForAdaptive = prepare.__get__('updateIconResourceForAdaptive');
|
||||
|
||||
preparedIcons.android_icons.mdpi.foreground = undefined;
|
||||
updateIconResourceForAdaptive(preparedIcons, resourceMap, platformResourcesDir);
|
||||
|
||||
const actualEmitArgs = emitSpy.calls.mostRecent().args;
|
||||
expect(actualEmitArgs[0]).toBe('warn');
|
||||
expect(actualEmitArgs[1]).toMatch(/Monochrome icon found but without adaptive properties./g);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -836,9 +899,9 @@ describe('prepare', () => {
|
||||
|
||||
// Spies
|
||||
let replaceFileContents;
|
||||
let ensureDirSyncSpy;
|
||||
let copySyncSpy;
|
||||
let removeSyncSpy;
|
||||
let mkdirSyncSpy;
|
||||
let cpSyncSpy;
|
||||
let rmSyncSpy;
|
||||
|
||||
// Mock Data
|
||||
let cordovaProject;
|
||||
@@ -867,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'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -887,7 +950,7 @@ describe('prepare', () => {
|
||||
|
||||
prepare.__set__('updateWww', jasmine.createSpy('updateWww'));
|
||||
prepare.__set__('updateIcons', jasmine.createSpy('updateIcons').and.returnValue(Promise.resolve()));
|
||||
prepare.__set__('updateProjectSplashScreen', jasmine.createSpy('updateProjectSplashScreen'));
|
||||
prepare.__set__('updateProjectTheme', jasmine.createSpy('updateProjectTheme'));
|
||||
prepare.__set__('warnForDeprecatedSplashScreen', jasmine.createSpy('warnForDeprecatedSplashScreen')
|
||||
.and.returnValue(Promise.resolve()));
|
||||
prepare.__set__('updateFileResources', jasmine.createSpy('updateFileResources').and.returnValue(Promise.resolve()));
|
||||
@@ -938,16 +1001,15 @@ describe('prepare', () => {
|
||||
`))
|
||||
});
|
||||
|
||||
ensureDirSyncSpy = jasmine.createSpy('ensureDirSync');
|
||||
copySyncSpy = jasmine.createSpy('copySync');
|
||||
removeSyncSpy = jasmine.createSpy('removeSync');
|
||||
mkdirSyncSpy = jasmine.createSpy('mkdirSync');
|
||||
cpSyncSpy = jasmine.createSpy('cpSync');
|
||||
rmSyncSpy = jasmine.createSpy('rmSync');
|
||||
|
||||
prepare.__set__('fs', {
|
||||
writeFileSync: jasmine.createSpy('writeFileSync'),
|
||||
writeJSONSync: jasmine.createSpy('writeJSONSync'),
|
||||
ensureDirSync: ensureDirSyncSpy,
|
||||
copySync: copySyncSpy,
|
||||
removeSync: removeSyncSpy,
|
||||
mkdirSync: mkdirSyncSpy,
|
||||
cpSync: cpSyncSpy,
|
||||
rmSync: rmSyncSpy,
|
||||
existsSync: jasmine.createSpy('existsSync')
|
||||
});
|
||||
});
|
||||
@@ -959,9 +1021,9 @@ describe('prepare', () => {
|
||||
|
||||
await api.prepare(cordovaProject, options).then(() => {
|
||||
expect(replaceFileContents).toHaveBeenCalledWith(renamedJavaActivityPath, /package [\w.]*;/, 'package ' + packageName + ';');
|
||||
expect(ensureDirSyncSpy).toHaveBeenCalledWith(renamedPath);
|
||||
expect(copySyncSpy).toHaveBeenCalledWith(initialJavaActivityPath, renamedJavaActivityPath);
|
||||
expect(removeSyncSpy).toHaveBeenCalledWith(initialJavaActivityPath);
|
||||
expect(mkdirSyncSpy).toHaveBeenCalledWith(renamedPath, { recursive: true });
|
||||
expect(cpSyncSpy).toHaveBeenCalledWith(initialJavaActivityPath, renamedJavaActivityPath);
|
||||
expect(rmSyncSpy).toHaveBeenCalledWith(initialJavaActivityPath);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -970,9 +1032,9 @@ describe('prepare', () => {
|
||||
|
||||
await api.prepare(cordovaProject, options).then(() => {
|
||||
expect(replaceFileContents).toHaveBeenCalledTimes(0);
|
||||
expect(ensureDirSyncSpy).toHaveBeenCalledTimes(0);
|
||||
expect(copySyncSpy).toHaveBeenCalledTimes(0);
|
||||
expect(removeSyncSpy).toHaveBeenCalledTimes(0);
|
||||
expect(mkdirSyncSpy).toHaveBeenCalledTimes(0);
|
||||
expect(cpSyncSpy).toHaveBeenCalledTimes(0);
|
||||
expect(rmSyncSpy).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -39,12 +39,21 @@
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/Theme.App.SplashScreen"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation"
|
||||
android:exported="true">
|
||||
<intent-filter android:label="@string/launcher_name">
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.cdv.core.file.provider" android:exported="false" android:grantUriPermissions="true">
|
||||
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/cdv_core_file_provider_paths" />
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.media.action.IMAGE_CAPTURE" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
|
||||
@@ -21,7 +21,11 @@ apply plugin: 'com.android.application'
|
||||
|
||||
if (cordovaConfig.IS_GRADLE_PLUGIN_KOTLIN_ENABLED) {
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
|
||||
if(!cdvHelpers.isVersionGreaterThanEqual(cordovaConfig.KOTLIN_VERSION, '1.8.0')) {
|
||||
println "Kotlin version < 1.8.0 detected. Applying kotlin-android-extensions plugin."
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
}
|
||||
}
|
||||
|
||||
buildscript {
|
||||
@@ -179,7 +183,11 @@ task cdvPrintProps {
|
||||
}
|
||||
|
||||
android {
|
||||
namespace cordovaConfig.PACKAGE_NAMESPACE
|
||||
namespace = cordovaConfig.PACKAGE_NAMESPACE
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
versionCode cdvVersionCode ?: new BigInteger("" + privateHelpers.extractIntFromManifest("versionCode"))
|
||||
@@ -194,7 +202,7 @@ android {
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
abortOnError = false
|
||||
}
|
||||
|
||||
buildToolsVersion cordovaConfig.BUILD_TOOLS_VERSION
|
||||
@@ -248,8 +256,19 @@ android {
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
sourceCompatibility JavaLanguageVersion.of(cordovaConfig.JAVA_SOURCE_COMPATIBILITY)
|
||||
targetCompatibility JavaLanguageVersion.of(cordovaConfig.JAVA_TARGET_COMPATIBILITY)
|
||||
}
|
||||
|
||||
if (cordovaConfig.IS_GRADLE_PLUGIN_KOTLIN_ENABLED) {
|
||||
// If KOTLIN_JVM_TARGET is null, fallback to JAVA_TARGET_COMPATIBILITY,
|
||||
// as they generally should be equal
|
||||
cordovaConfig.KOTLIN_JVM_TARGET = cordovaConfig.KOTLIN_JVM_TARGET ?:
|
||||
cordovaConfig.JAVA_TARGET_COMPATIBILITY
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaLanguageVersion.of(cordovaConfig.KOTLIN_JVM_TARGET)
|
||||
}
|
||||
}
|
||||
|
||||
if (cdvReleaseSigningPropertiesFile) {
|
||||
@@ -292,7 +311,16 @@ dependencies {
|
||||
implementation "androidx.core:core-splashscreen:${cordovaConfig.ANDROIDX_CORE_SPLASHSCREEN_VERSION}"
|
||||
|
||||
if (cordovaConfig.IS_GRADLE_PLUGIN_KOTLIN_ENABLED) {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${cordovaConfig.KOTLIN_VERSION}"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:${cordovaConfig.KOTLIN_VERSION}"
|
||||
}
|
||||
|
||||
constraints {
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:${cordovaConfig.KOTLIN_VERSION}") {
|
||||
because("kotlin-stdlib-jdk7 is now a part of kotlin-stdlib")
|
||||
}
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:${cordovaConfig.KOTLIN_VERSION}") {
|
||||
because("kotlin-stdlib-jdk8 is now a part of kotlin-stdlib")
|
||||
}
|
||||
}
|
||||
|
||||
// SUB-PROJECT DEPENDENCIES START
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
# 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.
|
||||
|
||||
|
||||
# This file was originally created by the Android Tools, but is now
|
||||
# used by cordova-android to manage the state of the various third party
|
||||
# libraries used in your application
|
||||
|
||||
@@ -1,4 +1,22 @@
|
||||
<?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.
|
||||
-->
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@mipmap/ic_launcher_background" />
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
|
||||
|
||||
@@ -1,4 +1,22 @@
|
||||
<?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.
|
||||
-->
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@mipmap/ic_launcher_background" />
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user