Compare commits

..

34 Commits

Author SHA1 Message Date
Erisu 688d2cf5ad release(android-v14.0.0): updated version and RELEASENOTES.md 2025-03-23 18:37:46 +09:00
エリス f6e384a9ea fix: replace fs-extra.ensureFileSync with fs.writeFileSync (#1790) 2025-03-22 00:45:00 +09:00
Erisu 839f9b878b Revert "release(android-v14.0.0): updated version and RELEASENOTES.md"
This reverts commit 2258d33a72.
2025-03-20 12:52:07 +09:00
Erisu d4eca414e3 Revert "chore: bump version 14.0.1-dev"
This reverts commit 5da9bd6d9d.
2025-03-20 12:51:45 +09:00
Erisu 5da9bd6d9d chore: bump version 14.0.1-dev 2025-03-19 11:26:04 +09:00
Erisu 2258d33a72 release(android-v14.0.0): updated version and RELEASENOTES.md 2025-03-19 10:56:30 +09:00
エリス ca4caf3fc1 dep!: bump npm packages (#1788)
* dep: bump nyc@17.1.0
* dep!: bump which@5.0.0
* dep: bump semver@7.7.1
* dep: bump jasmine@5.6.0
* dep: bump android-versions@2.1.0
* dep: bump cordova-common@5.0.1
* dep: bump fast-glob@3.3.3
* dep!: bump nopt@8.1.0
* chore: rebuilt package-lock
2025-03-19 10:16:55 +09:00
エリス 7ab18487cf feat!: bump node engine requirement >=20.5.0 (#1789) 2025-03-19 10:15:25 +09:00
エリス ff11f659f0 fix: copy gradle wrapper from tools to platform root dir (#1781) 2025-03-18 10:55:43 +09:00
エリス aad36fe565 feat: bump gradle to 8.13 (#1785) 2025-03-18 10:54:49 +09:00
エリス 7f9529408b chore: add androidx build test to gitignore (#1786) 2025-03-17 10:59:43 +09:00
エリス bb4f86e7b9 feat: add AndroidEdgeToEdge preference & theme flag (#1779) 2025-03-14 12:22:05 +09:00
エリス d0b59863ac feat!: bump java default targets to 11 (#1784)
* feat!: bump java source, target & kotlin jvm target to default 11
* chore!: remove java <= 8 logic
* refactor: setting of kotlin's jvmTarget
2025-03-14 11:55:43 +09:00
エリス 7544fdf1ed chore: bump CordovaWebView version to 14.0.0-dev (#1782) 2025-03-04 00:05:00 +09:00
Andrii Kurdiumov 8f458b042b feat: Account for Node security patch (#1778)
As of https://nodejs.org/en/blog/vulnerability/april-2024-security-releases-2#command-injection-via-args-parameter-of-child_processspawn-without-shell-option-enabled-on-windows-cve-2024-27980---high

Cordova produce unrecognized error on Windows.

Fixes: https://github.com/apache/cordova-cli/issues/456

---------

Co-authored-by: Norman Breau <norman@breautek.com>
2025-02-18 20:53:18 +09:00
エリス eb0f002112 style: update & resolve doc block warnings (#1774)
* style: resolve throw symbols (except InvalidArgumentException)
* style: resolve unknown symbol & reduce indention for PluginEntry
* fix: define IllegalArgumentException not InvalidArgumentException
2025-01-30 18:17:22 +09:00
エリス e012478537 refactor: replace fs-extra with node:fs (#1772)
* spec: add devDependencies "tmp"
2025-01-29 10:39:11 +09:00
エリス b623311efa feat!: deprecate CordovaPlugin's method initialize (#1771) 2025-01-28 12:28:32 +09:00
エリス 1f349f2984 fix: creation of cdv-gradle-config.json w/ --link flag (#1770) 2025-01-28 12:15:19 +09:00
エリス 9f5518000f refactor: prefix node:* (#1769)
* refactor: prefix node:* to path
* refactor: prefix node:* to os
* refactor: prefix node:* to fs
* refactor: prefix node:* to util
2025-01-28 12:13:36 +09:00
エリス 92116dee48 feat!: use kotlin-stdlib instead of kotlin-stdlib-jdk* (#1767) 2025-01-28 11:58:23 +09:00
エリス 34220ae0e3 feat: androidx.core:core-splashscreen@1.0.1 (#1768) 2025-01-28 11:57:42 +09:00
エリス 1fe44d71c5 feat: com.google.gms:google-services@4.4.2 (#1766) 2025-01-28 11:14:07 +09:00
エリス 58c2e3ae15 feat: androidx.webkit:webkit@1.12.1 (#1765) 2025-01-28 10:54:19 +09:00
エリス ea045dee63 feat: androidx.appcompat:appcompat@1.7.0 (#1764) 2025-01-28 10:33:25 +09:00
エリス cee7b0b8ac feat!: SDK 35 Support (#1763)
* feat(gradle)!: bump to 8.9 w/ AGP@8.7.3
* feat!: bump android sdk@35 & minimum-build-tool@35.0.0
2025-01-28 10:32:49 +09:00
エリス 6f0efd3a0d chore: bump 14.0.0-dev (#1762) 2025-01-28 10:32:04 +09:00
dependabot[bot] dff2fc6331 chore(deps): bump cross-spawn from 7.0.3 to 7.0.6 (#1748)
Bumps [cross-spawn](https://github.com/moxystudio/node-cross-spawn) from 7.0.3 to 7.0.6.
- [Changelog](https://github.com/moxystudio/node-cross-spawn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/moxystudio/node-cross-spawn/compare/v7.0.3...v7.0.6)

---
updated-dependencies:
- dependency-name: cross-spawn
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-19 21:25:05 -08:00
Darryl Pogue 1347e48d14 chore(ci): Fix dependabot PR failures (#1750) 2024-11-19 20:55:50 -08:00
dependabot[bot] 5a2c50d1ed chore(deps): bump micromatch from 4.0.5 to 4.0.8 (#1736)
Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.5 to 4.0.8.
- [Release notes](https://github.com/micromatch/micromatch/releases)
- [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/micromatch/compare/4.0.5...4.0.8)

---
updated-dependencies:
- dependency-name: micromatch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-22 23:16:21 -07:00
Norman Breau 5eddc460e4 fix(docs): Incorrect JDK requirement stated in README (#1739) 2024-09-17 09:33:52 -03:00
5r1m 3503bfa31b Update AndroidManifest.xml by extending android:configChanges with "navigation" as application is restarted when BT keyboard is connected in some devices (#1718)
In some devices (especially pixel) connecting with BT keyboard is resulting in application being restarted. The existing "keyboard" seems to be not sufficient as in this case it was triggering "navigation" change.

https://stackoverflow.com/questions/25735227/bluetooth-keyboard-will-cause-activity-destroy-and-recreate/27238892#27238892
2024-06-24 08:43:22 -03:00
dependabot[bot] d281727113 chore(deps): bump braces from 3.0.2 to 3.0.3 (#1716)
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-17 09:38:52 -07:00
Erisu 172e947d18 chore: bump version 13.0.1-dev 2024-05-15 19:45:51 +09:00
71 changed files with 2483 additions and 1513 deletions
+8 -2
View File
@@ -17,7 +17,13 @@
name: Node CI name: Node CI
on: [push, pull_request] on:
push:
branches-ignore:
- 'dependabot/**'
pull_request:
branches:
- '*'
jobs: jobs:
test: test:
@@ -27,7 +33,7 @@ jobs:
strategy: strategy:
matrix: matrix:
node-version: [16.x, 18.x, 20.x, 22.x] node-version: [20.x, 22.x]
os: [ubuntu-latest, windows-latest, macos-latest] os: [ubuntu-latest, windows-latest, macos-latest]
steps: steps:
+7 -1
View File
@@ -17,7 +17,13 @@
name: Release Auditing name: Release Auditing
on: [push, pull_request] on:
push:
branches-ignore:
- 'dependabot/**'
pull_request:
branches:
- '*'
jobs: jobs:
test: test:
+1
View File
@@ -31,6 +31,7 @@ example
/test/.externalNativeBuild /test/.externalNativeBuild
/test/androidx/cdv-gradle-config.json /test/androidx/cdv-gradle-config.json
/test/androidx/tools /test/androidx/tools
/test/androidx/build
/test/assets/www/.tmp* /test/assets/www/.tmp*
/test/assets/www/cordova.js /test/assets/www/cordova.js
/test/bin /test/bin
+1 -1
View File
@@ -32,7 +32,7 @@ Cordova Android is an Android application library that allows for Cordova-based
## Requirements ## Requirements
* Java Development Kit (JDK) 11 * Java Development Kit (JDK) 17
* [Android SDK](https://developer.android.com/) * [Android SDK](https://developer.android.com/)
* [Node.js](https://nodejs.org) * [Node.js](https://nodejs.org)
+46
View File
@@ -20,6 +20,52 @@
--> -->
## Release Notes for Cordova (Android) ## Release Notes for Cordova (Android)
### 14.0.0 (Mar 23, 2025)
* [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) ### 13.0.0 (May 15, 2024)
**Breaking Changes:** **Breaking Changes:**
+2 -2
View File
@@ -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 url The URL to load
* @param props Properties that can be passed in to the activity: * @param props Properties that can be passed in to the activity:
* wait: int => wait msec before loading URL * wait: int => wait msec before loading URL
* loadingDialog: "Title,Message" => display a native loading dialog * loadingDialog: "Title,Message" => display a native loading dialog
* loadUrlTimeoutValue: int => time in msec to wait before triggering a timeout error * 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) * openExternal: boolean => open in a new browser (default=false)
* *
* Example: * Example:
+10 -10
View File
@@ -1,19 +1,19 @@
{ {
"MIN_SDK_VERSION": 24, "MIN_SDK_VERSION": 24,
"SDK_VERSION": 34, "SDK_VERSION": 35,
"COMPILE_SDK_VERSION": null, "COMPILE_SDK_VERSION": null,
"GRADLE_VERSION": "8.7", "GRADLE_VERSION": "8.13",
"MIN_BUILD_TOOLS_VERSION": "34.0.0", "MIN_BUILD_TOOLS_VERSION": "35.0.0",
"AGP_VERSION": "8.3.0", "AGP_VERSION": "8.7.3",
"KOTLIN_VERSION": "1.9.24", "KOTLIN_VERSION": "1.9.24",
"ANDROIDX_APP_COMPAT_VERSION": "1.6.1", "ANDROIDX_APP_COMPAT_VERSION": "1.7.0",
"ANDROIDX_WEBKIT_VERSION": "1.6.0", "ANDROIDX_WEBKIT_VERSION": "1.12.1",
"ANDROIDX_CORE_SPLASHSCREEN_VERSION": "1.0.0", "ANDROIDX_CORE_SPLASHSCREEN_VERSION": "1.0.1",
"GRADLE_PLUGIN_GOOGLE_SERVICES_VERSION": "4.3.15", "GRADLE_PLUGIN_GOOGLE_SERVICES_VERSION": "4.4.2",
"IS_GRADLE_PLUGIN_GOOGLE_SERVICES_ENABLED": false, "IS_GRADLE_PLUGIN_GOOGLE_SERVICES_ENABLED": false,
"IS_GRADLE_PLUGIN_KOTLIN_ENABLED": false, "IS_GRADLE_PLUGIN_KOTLIN_ENABLED": false,
"PACKAGE_NAMESPACE": "io.cordova.helloCordova", "PACKAGE_NAMESPACE": "io.cordova.helloCordova",
"JAVA_SOURCE_COMPATIBILITY": 8, "JAVA_SOURCE_COMPATIBILITY": 11,
"JAVA_TARGET_COMPATIBILITY": 8, "JAVA_TARGET_COMPATIBILITY": 11,
"KOTLIN_JVM_TARGET": null "KOTLIN_JVM_TARGET": null
} }
+1 -1
View File
@@ -150,7 +150,7 @@ def doGetConfigPreference(name, defaultValue) {
} }
def doApplyCordovaConfigCustomization() { 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')) { if (project.hasProperty('cdvMinSdkVersion')) {
cordovaConfig.MIN_SDK_VERSION = Integer.parseInt('' + cdvMinSdkVersion) cordovaConfig.MIN_SDK_VERSION = Integer.parseInt('' + cdvMinSdkVersion)
} }
@@ -39,7 +39,7 @@ public class AllowListPlugin extends CordovaPlugin {
// Used when instantiated via reflection by PluginManager // Used when instantiated via reflection by PluginManager
public AllowListPlugin() { } 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) { public AllowListPlugin(Context context) {
this(new AllowList(), new AllowList(), null); this(new AllowList(), new AllowList(), null);
new CustomConfigXmlParser().parse(context); new CustomConfigXmlParser().parse(context);
@@ -37,8 +37,7 @@ public class AuthenticationToken {
/** /**
* Sets the user name. * Sets the user name.
* *
* @param userName * @param userName the new user name
* the new user name
*/ */
public void setUserName(String userName) { public void setUserName(String userName) {
this.userName = userName; this.userName = userName;
@@ -56,8 +55,7 @@ public class AuthenticationToken {
/** /**
* Sets the password. * Sets the password.
* *
* @param password * @param password the new password
* the new password
*/ */
public void setPassword(String password) { public void setPassword(String password) {
this.password = password; this.password = password;
@@ -54,7 +54,7 @@ public class CallbackMap {
* obtained from registerCallback() * obtained from registerCallback()
* *
* @param mappedId The request code 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 * given mappedCode
*/ */
public synchronized Pair<CordovaPlugin, Integer> getAndRemoveCallback(int mappedId) { public synchronized Pair<CordovaPlugin, Integer> getAndRemoveCallback(int mappedId) {
+1 -1
View File
@@ -23,7 +23,7 @@ import java.util.List;
import android.app.Activity; import android.app.Activity;
@Deprecated // Use AllowList, CordovaPrefences, etc. directly. @Deprecated // Use AllowList, CordovaPreferences, etc. directly.
public class Config { public class Config {
private static final String TAG = "Config"; private static final String TAG = "Config";
@@ -49,7 +49,7 @@ import androidx.core.splashscreen.SplashScreen;
* application. It should be extended by the user to load the specific * application. It should be extended by the user to load the specific
* html file that contains the application. * html file that contains the application.
* *
* As an example: * <p>As an example:</p>
* *
* <pre> * <pre>
* package org.apache.cordova.examples; * package org.apache.cordova.examples;
@@ -68,17 +68,16 @@ import androidx.core.splashscreen.SplashScreen;
* } * }
* </pre> * </pre>
* *
* Cordova xml configuration: Cordova uses a configuration file at * <p>Cordova xml configuration: Cordova uses a configuration file at
* res/xml/config.xml to specify its settings. See "The config.xml File" * res/xml/config.xml to specify its settings. See the "Config.xml API" documentation for
* guide in cordova-docs at http://cordova.apache.org/docs for the documentation * configuration details at <a href="https://cordova.apache.org/docs">Apache Cordova Docs</a>.</p>
* for the configuration. The use of the set*Property() methods is
* deprecated in favor of the config.xml file.
* *
* <p>The use of the set*Property() methods is deprecated in favor of the config.xml file.</p>
*/ */
public class CordovaActivity extends AppCompatActivity { public class CordovaActivity extends AppCompatActivity {
public static String TAG = "CordovaActivity"; public static String TAG = "CordovaActivity";
// The webview for our app // The WebView for our app
protected CordovaWebView appView; protected CordovaWebView appView;
private static int ACTIVITY_STARTING = 0; private static int ACTIVITY_STARTING = 0;
@@ -206,7 +205,7 @@ public class CordovaActivity extends AppCompatActivity {
/** /**
* Construct the default web view object. * Construct the default web view object.
* <p/> * <p/>
* Override this to customize the webview that is used. * Override this to customize the WebView that is used.
*/ */
protected CordovaWebView makeWebView() { protected CordovaWebView makeWebView() {
return new CordovaWebViewImpl(makeWebViewEngine()); return new CordovaWebViewImpl(makeWebViewEngine());
@@ -227,7 +226,7 @@ public class CordovaActivity extends AppCompatActivity {
} }
/** /**
* Load the url into the webview. * Load the url into the WebView.
*/ */
public void loadUrl(String url) { public void loadUrl(String url) {
if (appView == null) { if (appView == null) {
@@ -250,7 +249,7 @@ public class CordovaActivity extends AppCompatActivity {
if (this.appView != null) { if (this.appView != null) {
// CB-9382 If there is an activity that started for result and main activity is waiting for callback // 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; boolean keepRunning = this.keepRunning || this.cordovaInterface.activityResultCallback != null;
this.appView.handlePause(keepRunning); this.appView.handlePause(keepRunning);
} }
@@ -47,8 +47,8 @@ public class CordovaClientCertRequest implements ICordovaClientCertRequest {
request.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") @SuppressLint("NewApi")
@Override @Override
@@ -57,8 +57,8 @@ public class CordovaClientCertRequest implements ICordovaClientCertRequest {
return request.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") @SuppressLint("NewApi")
@Override @Override
@@ -67,8 +67,8 @@ public class CordovaClientCertRequest implements ICordovaClientCertRequest {
return request.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") @SuppressLint("NewApi")
@Override @Override
@@ -77,8 +77,8 @@ public class CordovaClientCertRequest implements ICordovaClientCertRequest {
return request.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") @SuppressLint("NewApi")
@Override @Override
@@ -87,7 +87,7 @@ public class CordovaClientCertRequest implements ICordovaClientCertRequest {
return request.getPrincipals(); return request.getPrincipals();
} }
/* /**
* Ignore the request for now. Do not remember user's choice. * Ignore the request for now. Do not remember user's choice.
*/ */
@SuppressLint("NewApi") @SuppressLint("NewApi")
@@ -97,7 +97,7 @@ public class CordovaClientCertRequest implements ICordovaClientCertRequest {
request.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. * 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 privateKey The privateKey
@@ -118,8 +118,8 @@ public class CordovaDialogsHelper {
* If the client returns true, WebView will assume that the client will * If the client returns true, WebView will assume that the client will
* handle the prompt dialog and call the appropriate JsPromptResult method. * 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 * <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! * this purpose, perhaps we should hack console.log to do this instead!</p>
*/ */
public void showPrompt(String message, String defaultValue, final Result result) { 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). // Returning false would also show a dialog, but the default one shows the origin (ugly).
@@ -51,7 +51,8 @@ public interface CordovaInterface {
/** /**
* Get the Android activity. * 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 * @return the Activity
*/ */
@@ -74,7 +75,7 @@ public interface CordovaInterface {
public Object onMessage(String id, Object data); 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(); public ExecutorService getThreadPool();
@@ -89,7 +90,9 @@ public interface CordovaInterface {
public void requestPermissions(CordovaPlugin plugin, int requestCode, String [] permissions); 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); 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) { public boolean onActivityResult(int requestCode, int resultCode, Intent intent) {
CordovaPlugin callback = activityResultCallback; CordovaPlugin callback = activityResultCallback;
@@ -64,7 +64,11 @@ public class CordovaPlugin {
* Called after plugin construction and fields have been initialized. * Called after plugin construction and fields have been initialized.
* Prefer to use pluginInitialize instead since there is no value in * Prefer to use pluginInitialize instead since there is no value in
* having parameters on the initialize() function. * 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) { 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() { public String getServiceName() {
return serviceName; return serviceName;
@@ -84,11 +88,14 @@ public class CordovaPlugin {
/** /**
* Executes the request. * 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
* cordova.getThreadPool().execute(runnable); * amount of work, use:</p>
* *
* To run on the UI thread, use: * <pre>cordova.getThreadPool().execute(runnable);</pre>
* cordova.getActivity().runOnUiThread(runnable); *
* <p>To run on the UI thread, use:</p>
*
* <pre>cordova.getActivity().runOnUiThread(runnable);</pre>
* *
* @param action The action to execute. * @param action The action to execute.
* @param rawArgs The exec() arguments in JSON form. * @param rawArgs The exec() arguments in JSON form.
@@ -103,11 +110,13 @@ public class CordovaPlugin {
/** /**
* Executes the request. * 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: * <pre>cordova.getThreadPool().execute(runnable);</pre>
* cordova.getActivity().runOnUiThread(runnable); *
* <p>To run on the UI thread, use:</p>
*
* <pre>cordova.getActivity().runOnUiThread(runnable);</pre>
* *
* @param action The action to execute. * @param action The action to execute.
* @param args The exec() arguments. * @param args The exec() arguments.
@@ -122,10 +131,10 @@ public class CordovaPlugin {
/** /**
* Executes the request. * 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); * cordova.getThreadPool().execute(runnable);
* *
* To run on the UI thread, use: * <p>To run on the UI thread, use:</p>
* cordova.getActivity().runOnUiThread(runnable); * cordova.getActivity().runOnUiThread(runnable);
* *
* @param action The action to execute. * @param action The action to execute.
@@ -227,18 +236,18 @@ public class CordovaPlugin {
/** /**
* Hook for blocking the loading of external resources. * 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 * know whether to open a connection to an external resource. Return false
* to block the request: if any plugin returns false, Cordova will block * to block the request: if any plugin returns false, Cordova will block
* the request. If all plugins return null, the default policy will be * the request. If all plugins return null, the default policy will be
* enforced. If at least one plugin returns true, and no plugins return * 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 * WebViewClient.shouldInterceptRequest, such as XMLHttpRequest requests and
* img tag loads. WebSockets and media requests (such as <video> and <audio> * 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 * 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) { public Boolean shouldAllowRequest(String url) {
return null; return null;
@@ -246,13 +255,13 @@ public class CordovaPlugin {
/** /**
* Hook for blocking navigation by the Cordova WebView. This applies both to top-level and * 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 * to a new page. Return false to block the navigation: if any plugin
* returns false, Cordova will block the navigation. If all plugins return * returns false, Cordova will block the navigation. If all plugins return
* null, the default policy will be enforced. It at least one plugin returns * 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) { public Boolean shouldAllowNavigation(String url) {
return null; return null;
@@ -270,12 +279,12 @@ public class CordovaPlugin {
/** /**
* Hook for blocking the launching of Intents by the Cordova application. * 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 * 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 * any plugin returns false, Cordova will block the navigation. If all
* plugins return null, the default policy will be enforced. If at least one * 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 * plugin returns true, and no plugins return false, then the URL will be
* opened. * opened.</p>
*/ */
public Boolean shouldOpenExternalUrl(String url) { public Boolean shouldOpenExternalUrl(String url) {
return null; return null;
@@ -284,8 +293,8 @@ public class CordovaPlugin {
/** /**
* Allows plugins to handle a link being clicked. Return true here to cancel the navigation. * 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. * @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. * @return true to prevent the URL from loading. Default is false.
*/ */
public boolean onOverrideUrlLoading(String url) { public boolean onOverrideUrlLoading(String url) {
return false; return false;
@@ -295,17 +304,20 @@ public class CordovaPlugin {
* Hook for redirecting requests. Applies to WebView requests as well as requests made by plugins. * 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: * To handle the request directly, return a URI in the form:
* *
* cdvplugin://pluginId/... * <pre>cdvplugin://pluginId/...</pre>
* *
* And implement handleOpenForRead(). * <p>And implement handleOpenForRead().</p>
* To make this easier, use the toPluginUri() and fromPluginUri() helpers:
* *
* <p>To make this easier, use the toPluginUri() and fromPluginUri() helpers:</p>
*
* <pre>
* public Uri remapUri(Uri uri) { return toPluginUri(uri); } * public Uri remapUri(Uri uri) { return toPluginUri(uri); }
* *
* public CordovaResourceApi.OpenForReadResult handleOpenForRead(Uri uri) throws IOException { * public CordovaResourceApi.OpenForReadResult handleOpenForRead(Uri uri) throws IOException {
* Uri origUri = fromPluginUri(uri); * Uri origUri = fromPluginUri(uri);
* ... * ...
* } * }
* </pre>
*/ */
public Uri remapUri(Uri uri) { public Uri remapUri(Uri uri) {
return null; return null;
@@ -343,9 +355,9 @@ public class CordovaPlugin {
/** /**
* Called when the WebView does a top-level navigation or refreshes. * 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() { public void onReset() {
} }
@@ -358,9 +370,7 @@ public class CordovaPlugin {
* @param handler The HttpAuthHandler used to set the WebView's response * @param handler The HttpAuthHandler used to set the WebView's response
* @param host The host requiring authentication * @param host The host requiring authentication
* @param realm The realm for which authentication is required * @param realm The realm for which authentication is required
* * @return true if the plugin will resolve this auth challenge, else false
* @return Returns True if plugin will resolve this auth challenge, otherwise False
*
*/ */
public boolean onReceivedHttpAuthRequest(CordovaWebView view, ICordovaHttpAuthHandler handler, String host, String realm) { public boolean onReceivedHttpAuthRequest(CordovaWebView view, ICordovaHttpAuthHandler handler, String host, String realm) {
return false; return false;
@@ -372,9 +382,7 @@ public class CordovaPlugin {
* *
* @param view The WebView that is initiating the callback * @param view The WebView that is initiating the callback
* @param request The client certificate request * @param request The client certificate request
* * @return True if plugin will resolve this auth challenge, otherwise False
* @return Returns True if plugin will resolve this auth challenge, otherwise False
*
*/ */
public boolean onReceivedClientCertRequest(CordovaWebView view, ICordovaClientCertRequest request) { public boolean onReceivedClientCertRequest(CordovaWebView view, ICordovaClientCertRequest request) {
return false; return false;
@@ -392,20 +400,17 @@ public class CordovaPlugin {
* Called by the Plugin Manager when we need to actually request permissions * Called by the Plugin Manager when we need to actually request permissions
* *
* @param requestCode Passed to the activity to track the request * @param requestCode Passed to the activity to track the request
* * @return The permission that was stored in the plugin
* @return Returns the permission that was stored in the plugin
*/ */
public void requestPermissions(int requestCode) { public void requestPermissions(int requestCode) {
} }
/* /**
* Called by the WebView implementation to check for geolocation permissions, can be used * 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. * 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() { public boolean hasPermisssion() {
return true; return true;
} }
@@ -416,7 +421,6 @@ public class CordovaPlugin {
* @param requestCode * @param requestCode
* @param permissions * @param permissions
* @param grantResults * @param grantResults
*
* @deprecated Use {@link #onRequestPermissionsResult} instead. * @deprecated Use {@link #onRequestPermissionsResult} instead.
*/ */
@Deprecated @Deprecated
@@ -439,6 +443,7 @@ public class CordovaPlugin {
/** /**
* Allow plugins to supply a PathHandler for the WebViewAssetHandler * Allow plugins to supply a PathHandler for the WebViewAssetHandler
*
* @return a CordovaPluginPathHandler which listen for paths and returns a response * @return a CordovaPluginPathHandler which listen for paths and returns a response
*/ */
public CordovaPluginPathHandler getPathHandler() { 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, * @return true if the host application handled the situation that process has exited,
* otherwise, application will crash if render process crashed, or be killed * otherwise, application will crash if render process crashed, or be killed
@@ -45,21 +45,36 @@ import java.util.zip.GZIPInputStream;
/** /**
* What this class provides: * 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). * <ol>
* - All plugins should call remapUrl() on URLs they receive from JS *before* * <li>
* passing the URL onto other utility functions in this class. * Helpers for reading & writing to URLs.
* - For an example usage of this, refer to the org.apache.cordova.file plugin. * <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: * <p>Future Work:</p>
* - Consider using a Cursor to query content URLs for their size (like the file plugin does). * <ul>
* - Allow plugins to remapUri to "cdv-plugin://plugin-name/foo", which CordovaResourceApi * <li>Consider using a Cursor to query content URLs for their size (like the file plugin does).</li>
* would then delegate to pluginManager.getPlugin(plugin-name).openForRead(url) * <li>
* - Currently, plugins *can* do this by remapping to a data: URL, but it's inefficient * Allow plugins to remapUri to "cdv-plugin://plugin-name/foo", which CordovaResourceApi would then delegate to pluginManager.getPlugin(plugin-name).openForRead(url)
* for large payloads. * <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 { public class CordovaResourceApi {
@SuppressWarnings("unused") @SuppressWarnings("unused")
@@ -143,8 +158,7 @@ public class CordovaResourceApi {
} }
/** /**
* Returns a File that points to the resource, or null if the resource * @return A file that points to the resource, or null if the resource is not on the local filesystem.
* is not on the local filesystem.
*/ */
public File mapUriToFile(Uri uri) { public File mapUriToFile(Uri uri) {
assertBackgroundThread(); assertBackgroundThread();
@@ -223,11 +237,12 @@ public class CordovaResourceApi {
/** /**
* Opens a stream to the given URI, also providing the MIME type & length. * Opens a stream to the given URI, also providing the MIME type & length.
*
* @return Never returns null. * @return Never returns null.
* @throws Throws an InvalidArgumentException for relative URIs. Relative URIs should be * @throws IllegalArgumentException For relative URIs. Relative URIs should be resolved before
* resolved before being passed into this function. * being passed into this function.
* @throws Throws an IOException if the URI cannot be opened. * @throws IOException If the URI cannot be opened.
* @throws Throws an IllegalStateException if called on a foreground thread. * @throws IllegalStateException If called on a foreground thread.
*/ */
public OpenForReadResult openForRead(Uri uri) throws IOException { public OpenForReadResult openForRead(Uri uri) throws IOException {
return openForRead(uri, false); return openForRead(uri, false);
@@ -235,11 +250,12 @@ public class CordovaResourceApi {
/** /**
* Opens a stream to the given URI, also providing the MIME type & length. * Opens a stream to the given URI, also providing the MIME type & length.
*
* @return Never returns null. * @return Never returns null.
* @throws Throws an InvalidArgumentException for relative URIs. Relative URIs should be * @throws IllegalArgumentException For relative URIs. Relative URIs should be resolved before
* resolved before being passed into this function. * being passed into this function.
* @throws Throws an IOException if the URI cannot be opened. * @throws IOException If the URI cannot be opened.
* @throws Throws an IllegalStateException if called on a foreground thread and skipThreadCheck is false. * @throws IllegalStateException If called on a foreground thread and skipThreadCheck is false.
*/ */
public OpenForReadResult openForRead(Uri uri, boolean skipThreadCheck) throws IOException { public OpenForReadResult openForRead(Uri uri, boolean skipThreadCheck) throws IOException {
if (!skipThreadCheck) { if (!skipThreadCheck) {
@@ -320,10 +336,11 @@ public class CordovaResourceApi {
/** /**
* Opens a stream to the given URI. * Opens a stream to the given URI.
*
* @return Never returns null. * @return Never returns null.
* @throws Throws an InvalidArgumentException for relative URIs. Relative URIs should be * @throws IllegalArgumentException For relative URIs. Relative URIs should be resolved before
* resolved before being passed into this function. * being passed into this function.
* @throws Throws an IOException if the URI cannot be opened. * @throws IOException If the URI cannot be opened.
*/ */
public OutputStream openOutputStream(Uri uri, boolean append) throws IOException { public OutputStream openOutputStream(Uri uri, boolean append) throws IOException {
assertBackgroundThread(); assertBackgroundThread();
@@ -25,13 +25,13 @@ import android.view.View;
import android.webkit.WebChromeClient.CustomViewCallback; 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. * 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. * are not expected to implement it.
*/ */
public interface CordovaWebView { public interface CordovaWebView {
public static final String CORDOVA_VERSION = "13.0.0"; public static final String CORDOVA_VERSION = "14.0.0";
void init(CordovaInterface cordova, List<PluginEntry> pluginEntries, CordovaPreferences preferences); void init(CordovaInterface cordova, List<PluginEntry> pluginEntries, CordovaPreferences preferences);
@@ -70,34 +70,47 @@ public interface CordovaWebView {
/** /**
* Send JavaScript statement back to JavaScript. * 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 * Instead of executing snippets of JS, you should use the exec bridge
* to create a Java->JS communication channel. * to create a Java->JS communication channel.</p>
* To do this: *
* 1. Within plugin.xml (to have your JS run before deviceready): * <p>To do this:</p>
* <js-module><runs/></js-module> *
* 2. Within your .js (call exec on start-up): * <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/channel').onCordovaReady.subscribe(function() {
* require('cordova/exec')(win, null, 'Plugin', 'method', []); * require('cordova/exec')(win, null, 'Plugin', 'method', []);
* function win(message) { * function win(message) {
* ... process message from java here ... * ... 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); * PluginResult dataResult = new PluginResult(PluginResult.Status.OK, CODE);
* dataResult.setKeepCallback(true); * dataResult.setKeepCallback(true);
* savedCallbackContext.sendPluginResult(dataResult); * savedCallbackContext.sendPluginResult(dataResult);
* </pre>
*/ */
@Deprecated @Deprecated
void sendJavascript(String statememt); 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 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 clearHistory Clear the history stack, so new page becomes top of history
* @param params Parameters for new app * @param params Parameters for new app
*/ */
@@ -59,7 +59,7 @@ public interface CordovaWebViewEngine {
/** Clean up all resources associated with the WebView. */ /** Clean up all resources associated with the WebView. */
void destroy(); void destroy();
/** Add the evaulate Javascript method **/ /** Add the evaluate Javascript method **/
void evaluateJavascript(String js, ValueCallback<String> callback); void evaluateJavascript(String js, ValueCallback<String> callback);
/** /**
@@ -43,7 +43,7 @@ import java.util.Map;
import java.util.Set; 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. * Class uses two-phase initialization. You must call init() before calling any other methods.
*/ */
public class CordovaWebViewImpl implements CordovaWebView { public class CordovaWebViewImpl implements CordovaWebView {
@@ -154,7 +154,7 @@ public class CordovaWebViewImpl implements CordovaWebView {
stopLoading(); stopLoading();
LOG.e(TAG, "CordovaWebView: TIMEOUT ERROR!"); 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(); JSONObject data = new JSONObject();
try { try {
data.put("errorCode", -6); data.put("errorCode", -6);
@@ -219,7 +219,7 @@ public class CordovaWebViewImpl implements CordovaWebView {
engine.clearHistory(); engine.clearHistory();
} }
// If loading into our webview // If loading into our WebView
if (!openExternal) { if (!openExternal) {
// Make sure url is in allow list // Make sure url is in allow list
if (pluginManager.shouldAllowNavigation(url)) { if (pluginManager.shouldAllowNavigation(url)) {
@@ -487,7 +487,7 @@ public class CordovaWebViewImpl implements CordovaWebView {
// If app doesn't want to run in background // If app doesn't want to run in background
if (!keepRunning) { 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); engine.setPaused(true);
} }
} }
@@ -497,7 +497,7 @@ public class CordovaWebViewImpl implements CordovaWebView {
return; return;
} }
// Resume JavaScript timers. This affects all webviews within the app! // Resume JavaScript timers. This affects all WebViews within the app!
engine.setPaused(false); engine.setPaused(false);
this.pluginManager.onResume(keepRunning); this.pluginManager.onResume(keepRunning);
@@ -537,7 +537,7 @@ public class CordovaWebViewImpl implements CordovaWebView {
// We should use a blank data: url instead so it's more obvious // We should use a blank data: url instead so it's more obvious
this.loadUrl("about:blank"); 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(); engine.destroy();
hideCustomView(); hideCustomView();
} }
@@ -69,8 +69,9 @@ public class CoreAndroid extends CordovaPlugin {
* Executes the request and returns PluginResult. * Executes the request and returns PluginResult.
* *
* @param action The action to execute. * @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. * @param callbackContext The callback context from which we were invoked.
*
* @return A PluginResult object with a status and message. * @return A PluginResult object with a status and message.
*/ */
@Override @Override
@@ -83,9 +84,9 @@ public class CoreAndroid extends CordovaPlugin {
this.clearCache(); this.clearCache();
} }
else if (action.equals("show")) { 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 // 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() { cordova.getActivity().runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -154,7 +155,7 @@ public class CoreAndroid extends CordovaPlugin {
} }
/** /**
* Load the url into the webview. * Load the url into the WebView.
* *
* @param url * @param url
* @param props Properties that can be passed in to the Cordova activity (i.e. loadingDialog, wait, ...) * @param props Properties that can be passed in to the Cordova activity (i.e. loadingDialog, wait, ...)
@@ -354,9 +355,8 @@ public class CoreAndroid extends CordovaPlugin {
} }
} }
/* /**
* Unregister the receiver * Unregister the receiver
*
*/ */
@Override @Override
public void onDestroy() public void onDestroy()
@@ -31,32 +31,32 @@ public interface ICordovaClientCertRequest {
*/ */
public void cancel(); 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(); 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(); 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(); 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(); public Principal[] getPrincipals();
/* /**
* Ignore the request for now. Do not remember user's choice. * Ignore the request for now. Do not remember user's choice.
*/ */
public void ignore(); 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. * 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 privateKey The privateKey
+2 -2
View File
@@ -23,8 +23,8 @@ import android.util.Log;
/** /**
* Log to Android logging system. * Log to Android logging system.
* *
* Log message can be a string or a printf formatted string with arguments. * <p>Log message can be a string or a printf formatted string with arguments.
* See http://developer.android.com/reference/java/util/Formatter.html * See <a href="http://developer.android.com/reference/java/util/Formatter.html">Formatter</a></p>
*/ */
public class LOG { 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 * Combines as many messages as possible, without exceeding
* COMBINED_RESPONSE_CUTOFF in case of multiple response messages. * 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) { public String popAndEncode(boolean fromOnlineEvent) {
synchronized (this) { synchronized (this) {
@@ -66,7 +66,6 @@ public class PermissionHelper {
* *
* @param plugin The plugin the permission is being checked against * @param plugin The plugin the permission is being checked against
* @param permission The permission to be checked * @param permission The permission to be checked
*
* @return True if the permission has already been granted and false otherwise * @return True if the permission has already been granted and false otherwise
*/ */
public static boolean hasPermission(CordovaPlugin plugin, String permission) { public static boolean hasPermission(CordovaPlugin plugin, String permission) {
@@ -48,36 +48,36 @@ public final class PluginEntry {
/** /**
* Constructs with a CordovaPlugin already instantiated. * Constructs with a CordovaPlugin already instantiated.
* *
* @param service The name of the service * @param service The name of the service
* @param pluginClass The plugin class name * @param plugin The plugin class name
*/ */
public PluginEntry(String service, CordovaPlugin plugin) { public PluginEntry(String service, CordovaPlugin plugin) {
this(service, plugin.getClass().getName(), true, plugin); this(service, plugin.getClass().getName(), true, plugin);
} }
/** /**
* @param service The name of the service * @param service The name of the service
* @param plugin The CordovaPlugin already instantiated * @param plugin The CordovaPlugin already instantiated
* @param onload Create plugin object when HTML page is loaded * @param onload Create plugin object when HTML page is loaded
*/ */
public PluginEntry(String service, CordovaPlugin plugin, boolean onload) { public PluginEntry(String service, CordovaPlugin plugin, boolean onload) {
this(service, plugin.getClass().getName(), onload, plugin); this(service, plugin.getClass().getName(), onload, plugin);
} }
/** /**
* @param service The name of the service * @param service The name of the service
* @param pluginClass The plugin class name * @param pluginClass The plugin class name
* @param onload Create plugin object when HTML page is loaded * @param onload Create plugin object when HTML page is loaded
*/ */
public PluginEntry(String service, String pluginClass, boolean onload) { public PluginEntry(String service, String pluginClass, boolean onload) {
this(service, pluginClass, onload, null); this(service, pluginClass, onload, null);
} }
/** /**
* @param service The name of the service * @param service The name of the service
* @param pluginClass The plugin class name * @param pluginClass The plugin class name
* @param onload Create plugin object when HTML page is loaded * @param onload Create plugin object when HTML page is loaded
* @param plugin The CordovaPlugin already instantiated * @param plugin The CordovaPlugin already instantiated
*/ */
private PluginEntry(String service, String pluginClass, boolean onload, CordovaPlugin plugin) { private PluginEntry(String service, String pluginClass, boolean onload, CordovaPlugin plugin) {
this.service = service; this.service = service;
@@ -38,8 +38,8 @@ import android.webkit.WebView;
/** /**
* PluginManager is exposed to JavaScript in the Cordova WebView. * PluginManager is exposed to JavaScript in the Cordova WebView.
* *
* Calling native plugin code can be done by calling PluginManager.exec(...) * <p>Calling native plugin code can be done by calling PluginManager.exec(...)
* from JavaScript. * from JavaScript.</p>
*/ */
public class PluginManager { public class PluginManager {
private static String TAG = "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() { public void init() {
LOG.d(TAG, "init()"); LOG.d(TAG, "init()");
@@ -121,9 +121,9 @@ public class PluginManager {
* Receives a request for execution and fulfills it by finding the appropriate * Receives a request for execution and fulfills it by finding the appropriate
* Java class and calling it's execute method. * 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 * 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 service String containing the service to run
* @param action String containing the action that the class is supposed to perform. This is * @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 handler The HttpAuthHandler used to set the WebView's response
* @param host The host requiring authentication * @param host The host requiring authentication
* @param realm The realm for which authentication is required * @param realm The realm for which authentication is required
* * @return True if there is a plugin which will resolve this auth challenge, otherwise False
* @return Returns 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) { public boolean onReceivedHttpAuthRequest(CordovaWebView view, ICordovaHttpAuthHandler handler, String host, String realm) {
synchronized (this.pluginMap) { synchronized (this.pluginMap) {
@@ -273,9 +271,7 @@ public class PluginManager {
* *
* @param view The WebView that is initiating the callback * @param view The WebView that is initiating the callback
* @param request The client certificate request * @param request The client certificate request
* * @return True if plugin will resolve this auth challenge, otherwise False
* @return Returns True if plugin will resolve this auth challenge, otherwise False
*
*/ */
public boolean onReceivedClientCertRequest(CordovaWebView view, ICordovaClientCertRequest request) { public boolean onReceivedClientCertRequest(CordovaWebView view, ICordovaClientCertRequest request) {
synchronized (this.pluginMap) { synchronized (this.pluginMap) {
@@ -375,12 +371,14 @@ public class PluginManager {
} }
/** /**
* @todo should we move this somewhere public and accessible by all plugins? * 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 be restrictive on the "file://" return? e.g. "file:///android_asset/www/" * <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.
* Would be considered as a breaking change if we apply a more granular check. * 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() { private String getLaunchUrlPrefix() {
if (!app.getPreferences().getBoolean("AndroidInsecureFileModeEnabled", false)) { 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 * 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. * @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. * false to block the resource.
*/ */
public boolean shouldAllowRequest(String url) { public boolean shouldAllowRequest(String url) {
@@ -425,7 +423,7 @@ public class PluginManager {
return true; return true;
} }
if (url.startsWith("file://")) { 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! //DON'T CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING!
return !url.contains("/app_webview/"); 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 * 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. * @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. * false to block the navigation.
*/ */
public boolean shouldAllowNavigation(String url) { 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) { public boolean shouldAllowBridgeAccess(String url) {
synchronized (this.entryMap) { 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. * 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 * 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. * @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. * false to block the intent.
*/ */
public Boolean shouldOpenExternalUrl(String url) { 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. * @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. * @return Return false to allow the URL to load, return true to prevent the URL from loading.
@@ -623,9 +621,9 @@ public class PluginManager {
/** /**
* Called when the WebView's render process has exited. * 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 * otherwise, application will crash if render process crashed, or be killed
* if render process was killed by the system. * 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. * @return message string when messageType is MESSAGE_TYPE_STRING otherwise null.
* Otherwise, returns null.
*/ */
public String getStrMessage() { public String getStrMessage() {
return strMessage; return strMessage;
@@ -45,25 +45,25 @@ public class SplashScreenPlugin extends CordovaPlugin {
// Config preference values // Config preference values
/** /**
* @param boolean autoHide to auto splash screen (default=true) * Boolean flag to auto hide splash screen (default=true)
*/ */
private boolean autoHide; private boolean autoHide;
/** /**
* @param int delayTime in milliseconds (default=-1) * Integer value of how long to delay in milliseconds (default=-1)
*/ */
private int delayTime; 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; 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; private int fadeDuration;
// Internal variables // 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; private boolean keepOnScreen = true;
@@ -119,8 +119,8 @@ public class SystemWebChromeClient extends WebChromeClient {
* If the client returns true, WebView will assume that the client will * If the client returns true, WebView will assume that the client will
* handle the prompt dialog and call the appropriate JsPromptResult method. * 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 * <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! * this purpose, perhaps we should hack console.log to do this instead!</p>
*/ */
@Override @Override
public boolean onJsPrompt(WebView view, String origin, String message, String defaultValue, final JsPromptResult result) { public boolean onJsPrompt(WebView view, String origin, String message, String defaultValue, final JsPromptResult result) {
@@ -155,15 +155,15 @@ public class SystemWebChromeClient extends WebChromeClient {
quotaUpdater.updateQuota(MAX_QUOTA); 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. * 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 origin
* @param callback * @param callback
*/ */
@Override
public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) { public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
super.onGeolocationPermissionsShowPrompt(origin, callback); super.onGeolocationPermissionsShowPrompt(origin, callback);
callback.invoke(origin, true, false); callback.invoke(origin, true, false);
@@ -188,12 +188,13 @@ public class SystemWebChromeClient extends WebChromeClient {
parentEngine.getCordovaWebView().hideCustomView(); parentEngine.getCordovaWebView().hideCustomView();
} }
@Override
/** /**
* Ask the host application for a custom progress view to show while * Ask the host application for a custom progress view to show while
* a <video> is loading. * a <video> is loading.
*
* @return View The progress view. * @return View The progress view.
*/ */
@Override
public View getVideoLoadingProgressView() { public View getVideoLoadingProgressView() {
if (mVideoProgressView == null) { if (mVideoProgressView == null) {
// Create a new Loading view programmatically. // Create a new Loading view programmatically.
@@ -204,7 +205,7 @@ public class SystemWebChromeClient extends WebChromeClient {
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT); layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
layout.setLayoutParams(layoutParams); layout.setLayoutParams(layoutParams);
// the proress bar // the progress bar
ProgressBar bar = new ProgressBar(parentEngine.getView().getContext()); ProgressBar bar = new ProgressBar(parentEngine.getView().getContext());
LinearLayout.LayoutParams barLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); LinearLayout.LayoutParams barLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
barLayoutParams.gravity = Gravity.CENTER; barLayoutParams.gravity = Gravity.CENTER;
@@ -199,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 * 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. * 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. * @param url The url of the page.
*/ */
@Override @Override
@@ -216,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. * 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. * @param url The url of the page.
*/ */
@Override @Override
@@ -228,7 +228,7 @@ public class SystemWebViewClient extends WebViewClient {
} }
isCurrentlyLoading = false; isCurrentlyLoading = false;
/** /*
* Because of a timing issue we need to clear this history in onPageFinished as well as * 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 * 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 * true. You see when you load a url with a # in it which is common in jQuery applications
@@ -332,7 +332,6 @@ public class SystemWebViewClient extends WebViewClient {
* *
* @param host * @param host
* @param realm * @param realm
*
* @return the authentication token or null if did not exist * @return the authentication token or null if did not exist
*/ */
public AuthenticationToken removeAuthenticationToken(String host, String realm) { public AuthenticationToken removeAuthenticationToken(String host, String realm) {
@@ -342,15 +341,16 @@ public class SystemWebViewClient extends WebViewClient {
/** /**
* Gets the authentication token. * Gets the authentication token.
* *
* In order it tries: * <p>In order it tries:</p>
* 1- host + realm * <ol>
* 2- host * <li>host + realm</li>
* 3- realm * <li>host</li>
* 4- no host, no realm * <li>realm</li>
* <li>no host, no realm</li>
* </ol>
* *
* @param host * @param host
* @param realm * @param realm
*
* @return the authentication token * @return the authentication token
*/ */
public AuthenticationToken getAuthenticationToken(String host, String realm) { 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() { nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode.OnlineEventsBridgeModeDelegate() {
@Override @Override
public void setNetworkAvailable(boolean value) { 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 //thus resulting in a NullPointerException
if(webView!=null) { if(webView!=null) {
webView.setNetworkAvailable(value); webView.setNetworkAvailable(value);
@@ -175,9 +175,9 @@ public class SystemWebViewEngine implements CordovaWebViewEngine {
String databasePath = webView.getContext().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath(); String databasePath = webView.getContext().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
settings.setDatabaseEnabled(true); settings.setDatabaseEnabled(true);
// The default is to use the module's debuggable state to decide if 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 // should be enabled. However, users can configure InspectableWebView preference to forcefully enable
// or disable the webview inspecter. // or disable the WebView inspector.
String inspectableWebview = preferences.getString("InspectableWebview", null); String inspectableWebview = preferences.getString("InspectableWebview", null);
boolean shouldEnableInspector = false; boolean shouldEnableInspector = false;
if (inspectableWebview == null) { 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 @Override
public void loadUrl(final String url, boolean clearNavigationStack) { public void loadUrl(final String url, boolean clearNavigationStack) {
@@ -288,7 +288,7 @@ public class SystemWebViewEngine implements CordovaWebViewEngine {
*/ */
@Override @Override
public boolean goBack() { 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) // 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()) { if (webView.canGoBack()) {
webView.goBack(); webView.goBack();
+1 -1
View File
@@ -17,7 +17,7 @@
under the License. under the License.
*/ */
const os = require('os'); const os = require('node:os');
const execa = require('execa'); const execa = require('execa');
const events = require('cordova-common').events; const events = require('cordova-common').events;
const CordovaError = require('cordova-common').CordovaError; const CordovaError = require('cordova-common').CordovaError;
+1 -1
View File
@@ -17,7 +17,7 @@
under the License. under the License.
*/ */
const fs = require('fs'); const fs = require('node:fs');
const xml = require('cordova-common').xmlHelpers; const xml = require('cordova-common').xmlHelpers;
const DEFAULT_ORIENTATION = 'default'; const DEFAULT_ORIENTATION = 'default';
+2 -2
View File
@@ -17,8 +17,8 @@
under the License. under the License.
*/ */
const fs = require('fs'); const fs = require('node:fs');
const path = require('path'); const path = require('node:path');
const properties_parser = require('properties-parser'); const properties_parser = require('properties-parser');
const pluginHandlers = require('./pluginHandlers'); const pluginHandlers = require('./pluginHandlers');
const CordovaGradleConfigParserFactory = require('./config/CordovaGradleConfigParserFactory'); const CordovaGradleConfigParserFactory = require('./config/CordovaGradleConfigParserFactory');
+1 -1
View File
@@ -17,7 +17,7 @@
under the License. under the License.
*/ */
const path = require('path'); const path = require('node:path');
const AndroidProject = require('./AndroidProject'); const AndroidProject = require('./AndroidProject');
const PluginManager = require('cordova-common').PluginManager; const PluginManager = require('cordova-common').PluginManager;
+2 -2
View File
@@ -17,8 +17,8 @@
under the License. under the License.
*/ */
const path = require('path'); const path = require('node:path');
const fs = require('fs'); const fs = require('node:fs');
const nopt = require('nopt'); const nopt = require('nopt');
const untildify = require('untildify'); const untildify = require('untildify');
const { parseArgsStringToArgv } = require('string-argv'); const { parseArgsStringToArgv } = require('string-argv');
+15 -10
View File
@@ -17,8 +17,9 @@
under the License. under the License.
*/ */
const fs = require('fs-extra'); const fs = require('node:fs');
const path = require('path'); const fsp = require('node:fs/promises');
const path = require('node:path');
const execa = require('execa'); const execa = require('execa');
const glob = require('fast-glob'); const glob = require('fast-glob');
const events = require('cordova-common').events; const events = require('cordova-common').events;
@@ -174,7 +175,7 @@ class ProjectBuilder {
try { try {
fs.accessSync(subProjectGradle, fs.F_OK); fs.accessSync(subProjectGradle, fs.F_OK);
} catch (e) { } catch (e) {
fs.copySync(pluginBuildGradle, subProjectGradle); fs.cpSync(pluginBuildGradle, subProjectGradle);
} }
}; };
@@ -207,7 +208,7 @@ class ProjectBuilder {
settingsGradlePaths.join('')); settingsGradlePaths.join(''));
// Touch empty cdv-gradle-name.gradle file if missing. // 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'), ''); fs.writeFileSync(path.join(this.root, 'cdv-gradle-name.gradle'), '');
} }
@@ -289,17 +290,21 @@ class ProjectBuilder {
.then(function () { .then(function () {
events.emit('verbose', `Using Gradle: ${config.GRADLE_VERSION}`); events.emit('verbose', `Using Gradle: ${config.GRADLE_VERSION}`);
return self.installGradleWrapper(config.GRADLE_VERSION); return self.installGradleWrapper(config.GRADLE_VERSION);
}).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 });
}).then(function () { }).then(function () {
return self.prepBuildFiles(); return self.prepBuildFiles();
}).then(() => { }).then(() => {
const signingPropertiesPath = path.join(self.root, `${opts.buildType}${SIGNING_PROPERTIES}`); const signingPropertiesPath = path.join(self.root, `${opts.buildType}${SIGNING_PROPERTIES}`);
if (fs.existsSync(signingPropertiesPath)) fs.removeSync(signingPropertiesPath);
if (opts.packageInfo) { if (opts.packageInfo) {
fs.ensureFileSync(signingPropertiesPath); fs.writeFileSync(signingPropertiesPath, '', 'utf8');
const signingProperties = createEditor(signingPropertiesPath); const signingProperties = createEditor(signingPropertiesPath);
signingProperties.addHeadComment(TEMPLATE); signingProperties.addHeadComment(TEMPLATE);
opts.packageInfo.appendToProperties(signingProperties); opts.packageInfo.appendToProperties(signingProperties);
} else {
fs.rmSync(signingPropertiesPath, { force: true });
} }
}); });
} }
@@ -309,7 +314,7 @@ class ProjectBuilder {
* @returns The user defined configs * @returns The user defined configs
*/ */
_getCordovaConfig () { _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') || '{}');
} }
/* /*
@@ -342,7 +347,7 @@ class ProjectBuilder {
const args = this.getArgs('clean', opts); const args = this.getArgs('clean', opts);
return execa(wrapper, args, { stdio: 'inherit', cwd: path.resolve(this.root) }) return execa(wrapper, args, { stdio: 'inherit', cwd: path.resolve(this.root) })
.then(() => { .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}`)) ['debug', 'release'].map(config => path.join(this.root, `${config}${SIGNING_PROPERTIES}`))
.forEach(file => { .forEach(file => {
@@ -350,7 +355,7 @@ class ProjectBuilder {
const hasMarker = hasFile && fs.readFileSync(file, 'utf8') const hasMarker = hasFile && fs.readFileSync(file, 'utf8')
.includes(MARKER); .includes(MARKER);
if (hasFile && hasMarker) fs.removeSync(file); if (hasFile && hasMarker) fs.rmSync(file);
}); });
}); });
} }
+5 -3
View File
@@ -18,8 +18,8 @@
*/ */
const execa = require('execa'); const execa = require('execa');
const path = require('path'); const path = require('node:path');
const fs = require('fs-extra'); const fs = require('node:fs');
const { forgivingWhichSync, isWindows, isDarwin } = require('./utils'); const { forgivingWhichSync, isWindows, isDarwin } = require('./utils');
const java = require('./env/java'); const java = require('./env/java');
const { CordovaError, ConfigParser, events } = require('cordova-common'); const { CordovaError, ConfigParser, events } = require('cordova-common');
@@ -110,7 +110,9 @@ module.exports.get_gradle_wrapper = function () {
let program_dir; let program_dir;
// OK, This hack only works on Windows, not on Mac OS or Linux. We will be deleting this eventually! // OK, This hack only works on Windows, not on Mac OS or Linux. We will be deleting this eventually!
if (module.exports.isWindows()) { if (module.exports.isWindows()) {
const result = execa.sync(path.join(__dirname, 'getASPath.bat')); // "shell" option enabled for CVE-2024-27980 (Windows) Mitigation
// See https://nodejs.org/en/blog/vulnerability/april-2024-security-releases-2 for more details
const result = execa.sync(path.join(__dirname, 'getASPath.bat'), { shell: true });
// console.log('result.stdout =' + result.stdout.toString()); // console.log('result.stdout =' + result.stdout.toString());
// console.log('result.stderr =' + result.stderr.toString()); // console.log('result.stderr =' + result.stderr.toString());
+4 -4
View File
@@ -17,8 +17,8 @@
under the License. under the License.
*/ */
const fs = require('fs-extra'); const fs = require('node:fs');
const path = require('path'); const path = require('node:path');
const events = require('cordova-common').events; const events = require('cordova-common').events;
class CordovaGradleConfigParser { class CordovaGradleConfigParser {
@@ -41,7 +41,7 @@ class CordovaGradleConfigParser {
* @returns {Record<any, any>} The parsed JSON object representing the gradle config. * @returns {Record<any, any>} The parsed JSON object representing the gradle config.
*/ */
_readConfig (configPath) { _readConfig (configPath) {
return fs.readJSONSync(configPath, 'utf-8'); return JSON.parse(fs.readFileSync(configPath, 'utf-8') || '{}');
} }
setPackageName (packageName) { setPackageName (packageName) {
@@ -64,7 +64,7 @@ class CordovaGradleConfigParser {
*/ */
write () { write () {
events.emit('verbose', '[Cordova Gradle Config] Saving File'); 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');
} }
} }
+2 -2
View File
@@ -17,8 +17,8 @@
under the License. under the License.
*/ */
const fs = require('fs'); const fs = require('node:fs');
const path = require('path'); const path = require('node:path');
const propertiesParser = require('properties-parser'); const propertiesParser = require('properties-parser');
const events = require('cordova-common').events; const events = require('cordova-common').events;
+29 -29
View File
@@ -17,8 +17,8 @@
under the License. under the License.
*/ */
const path = require('path'); const path = require('node:path');
const fs = require('fs-extra'); const fs = require('node:fs');
const utils = require('./utils'); const utils = require('./utils');
const check_reqs = require('./check_reqs'); const check_reqs = require('./check_reqs');
const ROOT = path.join(__dirname, '..'); 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 app_path = path.join(projectPath, 'app', 'src', 'main');
const platform_www = path.join(projectPath, 'platform_www'); 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/ // 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 // The www dir is nuked on each prepare so we keep cordova.js in platform_www
fs.ensureDirSync(platform_www); fs.mkdirSync(platform_www, { recursive: true });
fs.copySync(srcCordovaJsPath, path.join(platform_www, 'cordova.js')); 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) { if (shared) {
const relativeFrameworkPath = path.relative(projectPath, getFrameworkDir(projectPath, true)); const relativeFrameworkPath = path.relative(projectPath, getFrameworkDir(projectPath, true));
fs.symlinkSync(relativeFrameworkPath, nestedCordovaLibPath, 'dir'); fs.symlinkSync(relativeFrameworkPath, nestedCordovaLibPath, 'dir');
} else { } else {
fs.ensureDirSync(nestedCordovaLibPath); fs.mkdirSync(nestedCordovaLibPath, { recursive: true });
fs.copySync(path.join(ROOT, 'framework', 'AndroidManifest.xml'), path.join(nestedCordovaLibPath, 'AndroidManifest.xml')); fs.cpSync(path.join(ROOT, 'framework', 'AndroidManifest.xml'), path.join(nestedCordovaLibPath, 'AndroidManifest.xml'));
const propertiesEditor = createEditor(path.join(ROOT, 'framework', 'project.properties')); const propertiesEditor = createEditor(path.join(ROOT, 'framework', 'project.properties'));
propertiesEditor.set('target', targetAPI); propertiesEditor.set('target', targetAPI);
propertiesEditor.save(path.join(nestedCordovaLibPath, 'project.properties')); propertiesEditor.save(path.join(nestedCordovaLibPath, 'project.properties'));
fs.copySync(path.join(ROOT, 'framework', 'build.gradle'), path.join(nestedCordovaLibPath, 'build.gradle')); fs.cpSync(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.cpSync(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.cpSync(path.join(ROOT, 'framework', 'repositories.gradle'), path.join(nestedCordovaLibPath, 'repositories.gradle'));
fs.copySync(path.join(ROOT, 'framework', 'src'), path.join(nestedCordovaLibPath, 'src')); fs.cpSync(path.join(ROOT, 'framework', 'src'), path.join(nestedCordovaLibPath, 'src'), { recursive: true });
fs.copySync(path.join(ROOT, 'framework', 'cdv-gradle-config-defaults.json'), path.join(projectPath, 'cdv-gradle-config.json'));
} }
} }
@@ -116,10 +116,10 @@ function prepBuildFiles (projectPath) {
function copyBuildRules (projectPath) { function copyBuildRules (projectPath) {
const srcDir = path.join(ROOT, 'templates', 'project'); const srcDir = path.join(ROOT, 'templates', 'project');
fs.copySync(path.join(srcDir, 'build.gradle'), path.join(projectPath, 'build.gradle')); fs.cpSync(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.cpSync(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.cpSync(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.cpSync(path.join(srcDir, 'repositories.gradle'), path.join(projectPath, 'repositories.gradle'));
copyGradleTools(projectPath); copyGradleTools(projectPath);
} }
@@ -128,9 +128,9 @@ function copyScripts (projectPath) {
const srcScriptsDir = path.join(ROOT, 'templates', 'cordova'); const srcScriptsDir = path.join(ROOT, 'templates', 'cordova');
const destScriptsDir = path.join(projectPath, 'cordova'); const destScriptsDir = path.join(projectPath, 'cordova');
// Delete old scripts directory if this is an update. // Delete old scripts directory if this is an update.
fs.removeSync(destScriptsDir); fs.rmSync(destScriptsDir, { recursive: true, force: true });
// Copy in the new ones. // Copy in the new ones.
fs.copySync(srcScriptsDir, destScriptsDir); fs.cpSync(srcScriptsDir, destScriptsDir, { recursive: true });
} }
/** /**
@@ -174,7 +174,7 @@ function validateProjectName (project_name) {
function copyGradleTools (projectPath) { function copyGradleTools (projectPath) {
const srcDir = path.join(ROOT, 'templates', 'project'); const srcDir = path.join(ROOT, 'templates', 'project');
fs.copySync(path.resolve(srcDir, 'tools'), path.resolve(projectPath, 'tools')); fs.cpSync(path.resolve(srcDir, 'tools'), path.resolve(projectPath, 'tools'), { recursive: true });
} }
/** /**
@@ -232,13 +232,13 @@ exports.create = function (project_path, config, options, events) {
const app_path = path.join(project_path, 'app', 'src', 'main'); const app_path = path.join(project_path, 'app', 'src', 'main');
// copy project template // copy project template
fs.ensureDirSync(app_path); fs.mkdirSync(app_path, { recursive: true });
fs.copySync(path.join(project_template_dir, 'assets'), path.join(app_path, 'assets')); fs.cpSync(path.join(project_template_dir, 'assets'), path.join(app_path, 'assets'), { recursive: true });
fs.copySync(path.join(project_template_dir, 'res'), path.join(app_path, 'res')); fs.cpSync(path.join(project_template_dir, 'res'), path.join(app_path, 'res'), { recursive: true });
fs.copySync(path.join(project_template_dir, 'gitignore'), path.join(project_path, '.gitignore')); 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). // 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 // copy cordova.js, cordova.jar
exports.copyJsAndLibrary(project_path, options.link, safe_activity_name, target_api); exports.copyJsAndLibrary(project_path, options.link, safe_activity_name, target_api);
@@ -247,9 +247,9 @@ exports.create = function (project_path, config, options, events) {
const java_path = path.join(app_path, 'java'); const java_path = path.join(app_path, 'java');
const assets_path = path.join(app_path, 'assets'); const assets_path = path.join(app_path, 'assets');
const resource_path = path.join(app_path, 'res'); const resource_path = path.join(app_path, 'res');
fs.ensureDirSync(java_path); fs.mkdirSync(java_path, { recursive: true });
fs.ensureDirSync(assets_path); fs.mkdirSync(assets_path, { recursive: true });
fs.ensureDirSync(resource_path); fs.mkdirSync(resource_path, { recursive: true });
// store package name in cdv-gradle-config // store package name in cdv-gradle-config
const cdvGradleConfig = CordovaGradleConfigParserFactory.create(project_path); const cdvGradleConfig = CordovaGradleConfigParserFactory.create(project_path);
@@ -261,8 +261,8 @@ exports.create = function (project_path, config, options, events) {
const activity_dir = path.join(java_path, packagePath); const activity_dir = path.join(java_path, packagePath);
const activity_path = path.join(activity_dir, safe_activity_name + '.java'); const activity_path = path.join(activity_dir, safe_activity_name + '.java');
fs.ensureDirSync(activity_dir); fs.mkdirSync(activity_dir, { recursive: true });
fs.copySync(path.join(project_template_dir, 'Activity.java'), activity_path); fs.cpSync(path.join(project_template_dir, 'Activity.java'), activity_path);
utils.replaceFileContents(activity_path, /__ACTIVITY__/, safe_activity_name); 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', 'strings.xml'), /__NAME__/, utils.escape(project_name));
utils.replaceFileContents(activity_path, /__ID__/, package_name); utils.replaceFileContents(activity_path, /__ID__/, package_name);
+2 -2
View File
@@ -18,9 +18,9 @@
*/ */
const execa = require('execa'); const execa = require('execa');
const fs = require('fs-extra'); const fs = require('node:fs');
const android_versions = require('android-versions'); const android_versions = require('android-versions');
const path = require('path'); const path = require('node:path');
const Adb = require('./Adb'); const Adb = require('./Adb');
const events = require('cordova-common').events; const events = require('cordova-common').events;
const CordovaError = require('cordova-common').CordovaError; const CordovaError = require('cordova-common').CordovaError;
+2 -2
View File
@@ -18,8 +18,8 @@
*/ */
const execa = require('execa'); const execa = require('execa');
const fs = require('fs-extra'); const fs = require('node:fs');
const path = require('path'); const path = require('node:path');
const glob = require('fast-glob'); const glob = require('fast-glob');
const { CordovaError, events } = require('cordova-common'); const { CordovaError, events } = require('cordova-common');
const utils = require('../utils'); const utils = require('../utils');
+9 -9
View File
@@ -14,8 +14,8 @@
* *
*/ */
const fs = require('fs-extra'); const fs = require('node:fs');
const path = require('path'); const path = require('node:path');
const isPathInside = require('is-path-inside'); const isPathInside = require('is-path-inside');
const events = require('cordova-common').events; const events = require('cordova-common').events;
const CordovaError = require('cordova-common').CordovaError; const CordovaError = require('cordova-common').CordovaError;
@@ -166,13 +166,13 @@ const handlers = {
scriptContent = 'cordova.define("' + moduleName + '", function(require, exports, module) {\n' + scriptContent + '\n});\n'; scriptContent = 'cordova.define("' + moduleName + '", function(require, exports, module) {\n' + scriptContent + '\n});\n';
const wwwDest = path.resolve(project.www, 'plugins', plugin.id, obj.src); 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'); fs.writeFileSync(wwwDest, scriptContent, 'utf-8');
if (options && options.usePlatformWww) { if (options && options.usePlatformWww) {
// CB-11022 copy file to both directories if usePlatformWww is specified // CB-11022 copy file to both directories if usePlatformWww is specified
const platformWwwDest = path.resolve(project.platformWww, 'plugins', plugin.id, obj.src); 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'); fs.writeFileSync(platformWwwDest, scriptContent, 'utf-8');
} }
}, },
@@ -217,11 +217,11 @@ function copyFile (plugin_dir, src, project_dir, dest, link) {
// check that dest path is located in project directory // check that dest path is located in project directory
if (!isPathInside(dest, project_dir)) { throw new CordovaError('Destination "' + dest + '" for source file "' + src + '" is located outside the project'); } if (!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) { if (link) {
symlinkFileOrDirTree(src, dest); symlinkFileOrDirTree(src, dest);
} else { } else {
fs.copySync(src, dest); fs.cpSync(src, dest, { recursive: true });
} }
} }
@@ -235,11 +235,11 @@ function copyNewFile (plugin_dir, src, project_dir, dest, link) {
function symlinkFileOrDirTree (src, dest) { function symlinkFileOrDirTree (src, dest) {
if (fs.existsSync(dest)) { if (fs.existsSync(dest)) {
fs.removeSync(dest); fs.rmSync(dest, { recursive: true, force: true });
} }
if (fs.statSync(src).isDirectory()) { if (fs.statSync(src).isDirectory()) {
fs.ensureDirSync(path.dirname(dest)); fs.mkdirSync(path.dirname(dest), { recursive: true });
fs.readdirSync(src).forEach(function (entry) { fs.readdirSync(src).forEach(function (entry) {
symlinkFileOrDirTree(path.join(src, entry), path.join(dest, entry)); symlinkFileOrDirTree(path.join(src, entry), path.join(dest, entry));
}); });
@@ -249,7 +249,7 @@ function symlinkFileOrDirTree (src, dest) {
} }
function removeFile (file) { function removeFile (file) {
fs.removeSync(file); fs.rmSync(file);
} }
// Sometimes we want to remove some java, and prune any unnecessary empty directories // Sometimes we want to remove some java, and prune any unnecessary empty directories
+33 -12
View File
@@ -17,8 +17,8 @@
under the License. under the License.
*/ */
const fs = require('fs-extra'); const fs = require('node:fs');
const path = require('path'); const path = require('node:path');
const nopt = require('nopt'); const nopt = require('nopt');
const glob = require('fast-glob'); const glob = require('fast-glob');
const dedent = require('dedent'); const dedent = require('dedent');
@@ -93,7 +93,7 @@ function updateUserProjectGradleConfig (project) {
// Write out changes // Write out changes
const projectGradleConfigPath = path.join(project.root, 'cdv-gradle-config.json'); 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) { function getUserGradleConfig (configXml) {
@@ -198,7 +198,7 @@ function updateConfigFilesFrom (sourceConfig, configMunger, locations) {
// First cleanup current config and merge project's one into own // First cleanup current config and merge project's one into own
// Overwrite platform config.xml with defaults.xml. // 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 // Then apply config changes from global munge to all config files
// in project (including project's config) // in project (including project's config)
@@ -271,7 +271,7 @@ function cleanWww (projectRoot, locations) {
*/ */
function updateProjectAccordingTo (platformConfig, locations) { function updateProjectAccordingTo (platformConfig, locations) {
updateProjectStrings(platformConfig, locations); updateProjectStrings(platformConfig, locations);
updateProjectSplashScreen(platformConfig, locations); updateProjectTheme(platformConfig, locations);
const name = platformConfig.name(); const name = platformConfig.name();
@@ -316,14 +316,14 @@ function updateProjectAccordingTo (platformConfig, locations) {
const newDestFile = path.join(locations.root, 'app', 'src', 'main', 'java', androidPkgName.replace(/\./g, '/'), path.basename(destFile)); const newDestFile = path.join(locations.root, 'app', 'src', 'main', 'java', androidPkgName.replace(/\./g, '/'), path.basename(destFile));
if (newDestFile.toLowerCase() !== destFile.toLowerCase()) { 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 // 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}`); events.emit('verbose', `copy ${destFile} to ${newDestFile}`);
fs.copySync(destFile, newDestFile); fs.cpSync(destFile, newDestFile);
utils.replaceFileContents(newDestFile, /package [\w.]*;/, 'package ' + androidPkgName + ';'); utils.replaceFileContents(newDestFile, /package [\w.]*;/, 'package ' + androidPkgName + ';');
events.emit('verbose', 'Wrote out Android package name "' + androidPkgName + '" to ' + newDestFile); 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 // If package was name changed we need to remove old java with main activity
events.emit('verbose', `remove ${destFile}`); events.emit('verbose', `remove ${destFile}`);
fs.removeSync(destFile); fs.rmSync(destFile);
// remove any empty directories // remove any empty directories
let currentDir = path.dirname(destFile); let currentDir = path.dirname(destFile);
const sourcesRoot = path.resolve(locations.root, 'src'); const sourcesRoot = path.resolve(locations.root, 'src');
@@ -376,11 +376,31 @@ function warnForDeprecatedSplashScreen (cordovaProject) {
* be used to update project * be used to update project
* @param {Object} locations A map of locations for this platform * @param {Object} locations A map of locations for this platform
*/ */
function updateProjectSplashScreen (platformConfig, locations) { function updateProjectTheme (platformConfig, locations) {
// res/values/themes.xml // res/values/themes.xml
const themes = xmlHelpers.parseElementtreeSync(locations.themes); const themes = xmlHelpers.parseElementtreeSync(locations.themes);
const splashScreenTheme = themes.find('style[@name="Theme.App.SplashScreen"]'); const splashScreenTheme = themes.find('style[@name="Theme.App.SplashScreen"]');
// Update edge-to-edge settings in app theme.
let hasE2E = false; // default case
const preferenceE2E = platformConfig.getPreference('AndroidEdgeToEdge', this.platform);
if (!preferenceE2E) {
events.emit('verbose', 'The preference name "AndroidEdgeToEdge" was not set. Defaulting to "false".');
} else {
const hasInvalidPreferenceE2E = preferenceE2E !== 'true' && preferenceE2E !== 'false';
if (hasInvalidPreferenceE2E) {
events.emit('verbose', 'Preference name "AndroidEdgeToEdge" has an invalid value. Valid values are "true" or "false". Defaulting to "false"');
}
hasE2E = hasInvalidPreferenceE2E ? false : preferenceE2E === 'true';
}
const optOutE2EKey = 'android:windowOptOutEdgeToEdgeEnforcement';
const optOutE2EItem = splashScreenTheme.find(`item[@name="${optOutE2EKey}"]`);
const optOutE2EValue = !hasE2E ? 'true' : 'false';
optOutE2EItem.text = optOutE2EValue;
events.emit('verbose', `Updating theme item "${optOutE2EKey}" with value "${optOutE2EValue}"`);
let splashBg = platformConfig.getPreference('AndroidWindowSplashScreenBackground', this.platform); let splashBg = platformConfig.getPreference('AndroidWindowSplashScreenBackground', this.platform);
if (!splashBg) { if (!splashBg) {
splashBg = platformConfig.getPreference('SplashScreenBackgroundColor', this.platform); splashBg = platformConfig.getPreference('SplashScreenBackgroundColor', this.platform);
@@ -397,6 +417,7 @@ function updateProjectSplashScreen (platformConfig, locations) {
splashBgNode.text = '@color/cdv_splashscreen_background'; splashBgNode.text = '@color/cdv_splashscreen_background';
[ [
// Splash Screen
'windowSplashScreenAnimatedIcon', 'windowSplashScreenAnimatedIcon',
'windowSplashScreenAnimationDuration', 'windowSplashScreenAnimationDuration',
'android:windowSplashScreenBrandingImage', 'android:windowSplashScreenBrandingImage',
@@ -536,16 +557,16 @@ function updateProjectSplashScreenIconBackgroundColor (splashIconBackgroundColor
function cleanupAndSetProjectSplashScreenImage (srcFile, destFilePath, possiblePreviousDestFilePath, cleanupOnly = false) { function cleanupAndSetProjectSplashScreenImage (srcFile, destFilePath, possiblePreviousDestFilePath, cleanupOnly = false) {
if (fs.existsSync(possiblePreviousDestFilePath)) { if (fs.existsSync(possiblePreviousDestFilePath)) {
fs.removeSync(possiblePreviousDestFilePath); fs.rmSync(possiblePreviousDestFilePath);
} }
if (cleanupOnly && fs.existsSync(destFilePath)) { if (cleanupOnly && fs.existsSync(destFilePath)) {
// Also remove dest file path for cleanup even if previous was not use. // 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)) { if (!cleanupOnly && srcFile && fs.existsSync(srcFile)) {
fs.copySync(srcFile, destFilePath); fs.cpSync(srcFile, destFilePath);
} }
} }
+1 -1
View File
@@ -17,7 +17,7 @@
under the License. under the License.
*/ */
const { inspect } = require('util'); const { inspect } = require('node:util');
const execa = require('execa'); const execa = require('execa');
const Adb = require('./Adb'); const Adb = require('./Adb');
const build = require('./build'); const build = require('./build');
+2 -2
View File
@@ -23,9 +23,9 @@
// TODO: Perhaps this should live in cordova-common? // TODO: Perhaps this should live in cordova-common?
const fs = require('fs-extra'); const fs = require('node:fs');
const which = require('which'); 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. * Reads, searches, and replaces the found occurences with replacementString and then writes the file back out.
+1870 -1027
View File
File diff suppressed because it is too large Load Diff
+12 -12
View File
@@ -1,6 +1,6 @@
{ {
"name": "cordova-android", "name": "cordova-android",
"version": "13.0.0", "version": "14.0.0",
"description": "cordova-android release", "description": "cordova-android release",
"types": "./types/index.d.ts", "types": "./types/index.d.ts",
"main": "lib/Api.js", "main": "lib/Api.js",
@@ -24,31 +24,31 @@
"author": "Apache Software Foundation", "author": "Apache Software Foundation",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"android-versions": "^2.0.0", "android-versions": "^2.1.0",
"cordova-common": "^5.0.0", "cordova-common": "^5.0.1",
"dedent": "^1.5.3", "dedent": "^1.5.3",
"execa": "^5.1.1", "execa": "^5.1.1",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.3",
"fs-extra": "^11.2.0",
"is-path-inside": "^3.0.3", "is-path-inside": "^3.0.3",
"nopt": "^7.2.1", "nopt": "^8.1.0",
"properties-parser": "^0.6.0", "properties-parser": "^0.6.0",
"semver": "^7.6.2", "semver": "^7.7.1",
"string-argv": "^0.3.1", "string-argv": "^0.3.1",
"untildify": "^4.0.0", "untildify": "^4.0.0",
"which": "^4.0.0" "which": "^5.0.0"
}, },
"devDependencies": { "devDependencies": {
"@cordova/eslint-config": "^5.1.0", "@cordova/eslint-config": "^5.1.0",
"cordova-js": "^6.1.0", "cordova-js": "^6.1.0",
"elementtree": "^0.1.7", "elementtree": "^0.1.7",
"jasmine": "^5.1.0", "jasmine": "^5.6.0",
"jasmine-spec-reporter": "^7.0.0", "jasmine-spec-reporter": "^7.0.0",
"nyc": "^15.1.0", "nyc": "^17.1.0",
"rewire": "^7.0.0" "rewire": "^7.0.0",
"tmp": "^0.2.3"
}, },
"engines": { "engines": {
"node": ">=16.13.0" "node": ">=20.5.0"
}, },
"nyc": { "nyc": {
"include": [ "include": [
+8 -7
View File
@@ -17,9 +17,9 @@
under the License. under the License.
*/ */
const os = require('os'); const os = require('node:os');
const fs = require('fs-extra'); const fs = require('node:fs');
const path = require('path'); const path = require('node:path');
const { EventEmitter } = require('events'); const { EventEmitter } = require('events');
const { ConfigParser, PluginInfoProvider } = require('cordova-common'); const { ConfigParser, PluginInfoProvider } = require('cordova-common');
const Api = require('../../lib/Api'); const Api = require('../../lib/Api');
@@ -49,14 +49,15 @@ describe('E2E', function () {
api = await makeProject(projectPath); api = await makeProject(projectPath);
}); });
afterEach(() => { afterEach(() => {
fs.removeSync(tmpDir); fs.rmSync(tmpDir, { recursive: true, force: true });
}); });
it('loads the API from a project directory', async () => { it('loads the API from a project directory', async () => {
// Allow test project to find the `cordova-android` module // Allow test project to find the `cordova-android` module
fs.ensureSymlinkSync( fs.mkdirSync(path.join(tmpDir, 'node_modules'), { recursive: true });
path.join(__dirname, '../..'), fs.symlinkSync(
path.join(tmpDir, 'node_modules/cordova-android'), path.join(__dirname, '..', '..'),
path.join(tmpDir, 'node_modules', 'cordova-android'),
'junction' 'junction'
); );
+3 -3
View File
@@ -17,9 +17,9 @@
under the License. under the License.
*/ */
const fs = require('fs'); const fs = require('node:fs');
const os = require('os'); const os = require('node:os');
const path = require('path'); const path = require('node:path');
const rewire = require('rewire'); const rewire = require('rewire');
describe('AndroidManifest', () => { describe('AndroidManifest', () => {
+1 -1
View File
@@ -17,7 +17,7 @@
under the License. under the License.
*/ */
const path = require('path'); const path = require('node:path');
const rewire = require('rewire'); const rewire = require('rewire');
const MockCordovaGradleConfigParser = require('./mocks/config/MockCordovaGradleConfigParser'); const MockCordovaGradleConfigParser = require('./mocks/config/MockCordovaGradleConfigParser');
const CordovaGradleConfigParserFactory = require('../../lib/config/CordovaGradleConfigParserFactory'); const CordovaGradleConfigParserFactory = require('../../lib/config/CordovaGradleConfigParserFactory');
+2 -2
View File
@@ -17,8 +17,8 @@
under the License. under the License.
*/ */
const os = require('os'); const os = require('node:os');
const path = require('path'); const path = require('node:path');
const common = require('cordova-common'); const common = require('cordova-common');
const EventEmitter = require('events'); const EventEmitter = require('events');
+2 -2
View File
@@ -17,8 +17,8 @@
under the License. under the License.
*/ */
const fs = require('fs'); const fs = require('node:fs');
const path = require('path'); const path = require('node:path');
const rewire = require('rewire'); const rewire = require('rewire');
describe('android_sdk', () => { describe('android_sdk', () => {
+12 -12
View File
@@ -17,8 +17,8 @@
under the License. under the License.
*/ */
const fs = require('fs-extra'); const fs = require('node:fs');
const path = require('path'); const path = require('node:path');
const rewire = require('rewire'); const rewire = require('rewire');
const { isWindows } = require('../../../lib/utils'); const { isWindows } = require('../../../lib/utils');
@@ -134,14 +134,14 @@ describe('ProjectBuilder', () => {
execaSpy.and.resolveTo(); execaSpy.and.resolveTo();
}); });
it('should run gradle wrapper 8.7', async () => { it('should run gradle wrapper 8.13', async () => {
await builder.installGradleWrapper('8.7'); await builder.installGradleWrapper('8.13');
expect(execaSpy).toHaveBeenCalledWith('gradle', ['-p', path.normalize('/root/tools'), 'wrapper', '--gradle-version', '8.7'], jasmine.any(Object)); 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 () => { it('CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL should override gradle version', async () => {
process.env.CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL = 'https://dist.local'; process.env.CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL = 'https://dist.local';
await builder.installGradleWrapper('8.7'); await builder.installGradleWrapper('8.13');
delete process.env.CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL; 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)); expect(execaSpy).toHaveBeenCalledWith('gradle', ['-p', path.normalize('/root/tools'), 'wrapper', '--gradle-distribution-url', 'https://dist.local'], jasmine.any(Object));
}); });
@@ -220,7 +220,7 @@ describe('ProjectBuilder', () => {
beforeEach(() => { beforeEach(() => {
const marker = ProjectBuilder.__get__('MARKER'); const marker = ProjectBuilder.__get__('MARKER');
spyOn(fs, 'readFileSync').and.returnValue(`Some Header Here: ${marker}`); spyOn(fs, 'readFileSync').and.returnValue(`Some Header Here: ${marker}`);
spyOn(fs, 'removeSync'); spyOn(fs, 'rmSync');
spyOn(builder, 'getArgs'); spyOn(builder, 'getArgs');
execaSpy.and.returnValue(Promise.resolve()); execaSpy.and.returnValue(Promise.resolve());
}); });
@@ -250,7 +250,7 @@ describe('ProjectBuilder', () => {
it('should remove "out" folder', () => { it('should remove "out" folder', () => {
return builder.clean({}).then(() => { return builder.clean({}).then(() => {
expect(fs.removeSync).toHaveBeenCalledWith(path.join(rootDir, 'out')); expect(fs.rmSync).toHaveBeenCalledWith(path.join(rootDir, 'out'), { recursive: true, force: true });
}); });
}); });
@@ -261,8 +261,8 @@ describe('ProjectBuilder', () => {
spyOn(fs, 'existsSync').and.returnValue(true); spyOn(fs, 'existsSync').and.returnValue(true);
return builder.clean({}).then(() => { return builder.clean({}).then(() => {
expect(fs.removeSync).toHaveBeenCalledWith(debugSigningFile); expect(fs.rmSync).toHaveBeenCalledWith(debugSigningFile);
expect(fs.removeSync).toHaveBeenCalledWith(releaseSigningFile); expect(fs.rmSync).toHaveBeenCalledWith(releaseSigningFile);
}); });
}); });
@@ -273,8 +273,8 @@ describe('ProjectBuilder', () => {
spyOn(fs, 'existsSync').and.returnValue(false); spyOn(fs, 'existsSync').and.returnValue(false);
return builder.clean({}).then(() => { return builder.clean({}).then(() => {
expect(fs.removeSync).not.toHaveBeenCalledWith(debugSigningFile); expect(fs.rmSync).not.toHaveBeenCalledWith(debugSigningFile);
expect(fs.removeSync).not.toHaveBeenCalledWith(releaseSigningFile); expect(fs.rmSync).not.toHaveBeenCalledWith(releaseSigningFile);
}); });
}); });
}); });
+2 -2
View File
@@ -19,8 +19,8 @@
const rewire = require('rewire'); const rewire = require('rewire');
const android_sdk = require('../../lib/android_sdk'); const android_sdk = require('../../lib/android_sdk');
const fs = require('fs-extra'); const fs = require('node:fs');
const path = require('path'); const path = require('node:path');
const events = require('cordova-common').events; const events = require('cordova-common').events;
const which = require('which'); const which = require('which');
+12 -12
View File
@@ -21,8 +21,8 @@ const rewire = require('rewire');
const utils = require('../../lib/utils'); const utils = require('../../lib/utils');
const create = rewire('../../lib/create'); const create = rewire('../../lib/create');
const check_reqs = require('../../lib/check_reqs'); const check_reqs = require('../../lib/check_reqs');
const fs = require('fs-extra'); const fs = require('node:fs');
const path = require('path'); const path = require('node:path');
const MockCordovaGradleConfigParser = require('./mocks/config/MockCordovaGradleConfigParser'); const MockCordovaGradleConfigParser = require('./mocks/config/MockCordovaGradleConfigParser');
const CordovaGradleConfigParserFactory = require('../../lib/config/CordovaGradleConfigParserFactory'); const CordovaGradleConfigParserFactory = require('../../lib/config/CordovaGradleConfigParserFactory');
@@ -142,8 +142,8 @@ describe('create', function () {
spyOn(create, 'prepBuildFiles'); spyOn(create, 'prepBuildFiles');
revert_manifest_mock = create.__set__('AndroidManifest', Manifest_mock); revert_manifest_mock = create.__set__('AndroidManifest', Manifest_mock);
spyOn(fs, 'existsSync').and.returnValue(false); spyOn(fs, 'existsSync').and.returnValue(false);
spyOn(fs, 'copySync'); spyOn(fs, 'cpSync');
spyOn(fs, 'ensureDirSync'); spyOn(fs, 'mkdirSync');
spyOn(utils, 'replaceFileContents'); spyOn(utils, 'replaceFileContents');
config_mock = jasmine.createSpyObj('ConfigParser mock instance', ['packageName', 'android_packageName', 'name', 'android_activityName']); config_mock = jasmine.createSpyObj('ConfigParser mock instance', ['packageName', 'android_packageName', 'name', 'android_activityName']);
events_mock = jasmine.createSpyObj('EventEmitter mock instance', ['emit']); events_mock = jasmine.createSpyObj('EventEmitter mock instance', ['emit']);
@@ -238,17 +238,17 @@ describe('create', function () {
describe('happy path', function () { describe('happy path', function () {
it('should copy project templates from a specified custom template', () => { it('should copy project templates from a specified custom template', () => {
return create.create(project_path, config_mock, { customTemplate: '/template/path' }, events_mock).then(() => { 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.cpSync).toHaveBeenCalledWith(path.join('/template/path', 'assets'), path.join(app_path, 'assets'), { recursive: true });
expect(fs.copySync).toHaveBeenCalledWith(path.join('/template/path', 'res'), path.join(app_path, 'res')); expect(fs.cpSync).toHaveBeenCalledWith(path.join('/template/path', 'res'), path.join(app_path, 'res'), { recursive: true });
expect(fs.copySync).toHaveBeenCalledWith(path.join('/template/path', 'gitignore'), path.join(project_path, '.gitignore')); 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', () => { 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(() => { 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.cpSync).toHaveBeenCalledWith(path.join(default_templates, 'assets'), path.join(app_path, 'assets'), { recursive: true });
expect(fs.copySync).toHaveBeenCalledWith(path.join(default_templates, 'res'), path.join(app_path, 'res')); expect(fs.cpSync).toHaveBeenCalledWith(path.join(default_templates, 'res'), path.join(app_path, 'res'), { recursive: true });
expect(fs.copySync).toHaveBeenCalledWith(path.join(default_templates, 'gitignore'), path.join(project_path, '.gitignore')); 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', () => { it('should create a java src directory based on the provided project package name', () => {
config_mock.packageName.and.returnValue('org.apache.cordova'); config_mock.packageName.and.returnValue('org.apache.cordova');
return create.create(project_path, config_mock, {}, events_mock).then(() => { 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,7 +270,7 @@ describe('create', function () {
config_mock.android_activityName.and.returnValue('CEEDEEVEE'); config_mock.android_activityName.and.returnValue('CEEDEEVEE');
const activity_path = path.join(app_path, 'java', 'org', 'apache', 'cordova', 'CEEDEEVEE.java'); const activity_path = path.join(app_path, 'java', 'org', 'apache', 'cordova', 'CEEDEEVEE.java');
return create.create(project_path, config_mock, {}, events_mock).then(() => { 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, /__ACTIVITY__/, 'CEEDEEVEE');
expect(utils.replaceFileContents).toHaveBeenCalledWith(activity_path, /__ID__/, 'org.apache.cordova'); expect(utils.replaceFileContents).toHaveBeenCalledWith(activity_path, /__ID__/, 'org.apache.cordova');
}); });
+2 -2
View File
@@ -17,8 +17,8 @@
under the License. under the License.
*/ */
const fs = require('fs-extra'); const fs = require('node:fs');
const path = require('path'); const path = require('node:path');
const rewire = require('rewire'); const rewire = require('rewire');
const which = require('which'); const which = require('which');
+1 -1
View File
@@ -17,7 +17,7 @@
under the License. under the License.
*/ */
const path = require('path'); const path = require('node:path');
const rewire = require('rewire'); const rewire = require('rewire');
const { CordovaError } = require('cordova-common'); const { CordovaError } = require('cordova-common');
const utils = require('../../lib/utils'); const utils = require('../../lib/utils');
+38 -24
View File
@@ -18,27 +18,41 @@
const rewire = require('rewire'); const rewire = require('rewire');
const common = rewire('../../../lib/pluginHandlers'); const common = rewire('../../../lib/pluginHandlers');
const path = require('path'); const path = require('node:path');
const fs = require('fs-extra'); const fs = require('node:fs');
const osenv = require('os'); 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 project_dir = path.join(test_dir, 'project');
const src = path.join(project_dir, 'src'); const src = path.join(project_dir, 'src');
const dest = path.join(project_dir, 'dest'); const dest = path.join(project_dir, 'dest');
const java_dir = path.join(src, 'one', 'two', 'three'); const java_dir = path.join(src, 'one', 'two', 'three');
const java_file = path.join(java_dir, 'test.java'); const java_file = path.join(java_dir, 'test.java');
const symlink_file = path.join(java_dir, 'symlink'); 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 copyFile = common.__get__('copyFile');
const deleteJava = common.__get__('deleteJava'); const deleteJava = common.__get__('deleteJava');
const copyNewFile = common.__get__('copyNewFile'); 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 () { describe('common platform handler', function () {
afterAll(() => {
// Remove tempdir after all specs complete
fs.rmSync(tempdir.name, { recursive: true, force: true });
});
afterEach(() => { afterEach(() => {
fs.removeSync(test_dir); fs.rmSync(test_dir, { recursive: true, force: true });
fs.removeSync(non_plugin_file); fs.rmSync(non_plugin_file, { recursive: true, force: true });
}); });
describe('copyFile', function () { describe('copyFile', function () {
@@ -48,15 +62,15 @@ describe('common platform handler', function () {
}); });
it('Test#002 : should throw if src not in plugin directory', function () { it('Test#002 : should throw if src not in plugin directory', function () {
fs.ensureDirSync(project_dir); fs.mkdirSync(project_dir, { recursive: true });
fs.outputFileSync(non_plugin_file, 'contents'); outputFileSync(non_plugin_file, 'contents');
const outside_file = '../non_plugin_file'; const outside_file = '../non_plugin_file';
expect(function () { copyFile(test_dir, outside_file, project_dir, dest); }) 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 + '"')); .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 () { 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. // This will fail on windows if not admin - ignore the error in that case.
if (ignoreEPERMonWin32(java_file, symlink_file)) { 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 () { it('Test#004 : should throw if symlink is linked to a file outside the plugin', function () {
fs.ensureDirSync(java_dir); fs.mkdirSync(java_dir, { recursive: true });
fs.outputFileSync(non_plugin_file, 'contents'); outputFileSync(non_plugin_file, 'contents');
// This will fail on windows if not admin - ignore the error in that case. // This will fail on windows if not admin - ignore the error in that case.
if (ignoreEPERMonWin32(non_plugin_file, symlink_file)) { 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 () { 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); }) 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')); .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 () { it('Test#006 : should call mkdirSync target path', function () {
fs.outputFileSync(java_file, 'contents'); 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); const resolvedDest = path.resolve(project_dir, dest);
copyFile(test_dir, java_file, project_dir, dest); copyFile(test_dir, java_file, project_dir, dest);
expect(s).toHaveBeenCalled(); 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 () { 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); const resolvedDest = path.resolve(project_dir, dest);
copyFile(test_dir, java_file, project_dir, dest); copyFile(test_dir, java_file, project_dir, dest);
expect(s).toHaveBeenCalled(); 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', () => { 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); const relativeProjectPath = path.relative(process.cwd(), project_dir);
expect(() => { expect(() => {
@@ -121,7 +135,7 @@ describe('common platform handler', function () {
describe('copyNewFile', function () { describe('copyNewFile', function () {
it('Test#008 : should throw if target path exists', 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); }) expect(function () { copyNewFile(test_dir, src, project_dir, dest); })
.toThrow(new Error('"' + dest + '" already exists!')); .toThrow(new Error('"' + dest + '" already exists!'));
}); });
@@ -129,11 +143,11 @@ describe('common platform handler', function () {
describe('deleteJava', function () { describe('deleteJava', function () {
beforeEach(function () { beforeEach(function () {
fs.outputFileSync(java_file, 'contents'); outputFileSync(java_file, 'contents');
}); });
it('Test#009 : should call fs.unlinkSync on the provided paths', function () { 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); deleteJava(project_dir, java_file);
expect(s).toHaveBeenCalled(); expect(s).toHaveBeenCalled();
expect(s).toHaveBeenCalledWith(path.resolve(project_dir, java_file)); expect(s).toHaveBeenCalledWith(path.resolve(project_dir, java_file));
+37 -37
View File
@@ -20,9 +20,9 @@
const rewire = require('rewire'); const rewire = require('rewire');
const common = rewire('../../../lib/pluginHandlers'); const common = rewire('../../../lib/pluginHandlers');
const android = common.__get__('handlers'); const android = common.__get__('handlers');
const path = require('path'); const path = require('node:path');
const fs = require('fs-extra'); const fs = require('node:fs');
const os = require('os'); const os = require('node:os');
const temp = path.join(os.tmpdir(), 'plugman'); const temp = path.join(os.tmpdir(), 'plugman');
const plugins_dir = path.join(temp, 'cordova/plugins'); const plugins_dir = path.join(temp, 'cordova/plugins');
const dummyplugin = path.join(__dirname, '../../fixtures/org.test.plugins.dummyplugin'); const dummyplugin = path.join(__dirname, '../../fixtures/org.test.plugins.dummyplugin');
@@ -56,14 +56,14 @@ describe('android project handler', function () {
let dummyProject; let dummyProject;
beforeEach(function () { beforeEach(function () {
fs.ensureDirSync(temp); fs.mkdirSync(temp, { recursive: true });
dummyProject = AndroidProject.getProjectFile(temp); dummyProject = AndroidProject.getProjectFile(temp);
copyFileSpy.calls.reset(); copyFileSpy.calls.reset();
common.__set__('copyFile', copyFileSpy); common.__set__('copyFile', copyFileSpy);
}); });
afterEach(function () { afterEach(function () {
fs.removeSync(temp); fs.rmSync(temp, { recursive: true, force: true });
common.__set__('copyFile', copyFileOrig); common.__set__('copyFile', copyFileOrig);
}); });
@@ -83,7 +83,7 @@ describe('android project handler', function () {
describe('of <source-file> elements', function () { describe('of <source-file> elements', function () {
beforeEach(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 () { 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 () { it('Test#006 : should throw if target file already exists', function () {
// write out a file // write out a file
let target = path.resolve(temp, 'app', 'src', 'main', 'java', 'com', 'phonegap', 'plugins', 'dummyplugin'); 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'); target = path.join(target, 'DummyPlugin.java');
fs.writeFileSync(target, 'some bs', 'utf-8'); fs.writeFileSync(target, 'some bs', 'utf-8');
@@ -192,7 +192,7 @@ describe('android project handler', function () {
const copyNewFileSpy = jasmine.createSpy('copyNewFile'); const copyNewFileSpy = jasmine.createSpy('copyNewFile');
beforeEach(function () { beforeEach(function () {
fs.copySync(android_studio_project, temp); fs.cpSync(android_studio_project, temp, { recursive: true });
spyOn(dummyProject, 'addSystemLibrary'); spyOn(dummyProject, 'addSystemLibrary');
spyOn(dummyProject, 'addSubProject'); 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 () { it('Test#010 : should not copy anything if "custom" attribute is not set', function () {
const framework = { src: 'plugin-lib' }; const framework = { src: 'plugin-lib' };
const cpSpy = spyOn(fs, 'copySync'); const cpSpy = spyOn(fs, 'cpSync');
android.framework.install(framework, dummyPluginInfo, dummyProject); android.framework.install(framework, dummyPluginInfo, dummyProject);
expect(dummyProject.addSystemLibrary).toHaveBeenCalledWith(someString, framework.src); expect(dummyProject.addSystemLibrary).toHaveBeenCalledWith(someString, framework.src);
expect(cpSpy).not.toHaveBeenCalled(); expect(cpSpy).not.toHaveBeenCalled();
@@ -289,23 +289,23 @@ describe('android project handler', function () {
describe('uninstallation', function () { describe('uninstallation', function () {
const deleteJavaOrig = common.__get__('deleteJava'); const deleteJavaOrig = common.__get__('deleteJava');
const originalRemoveSync = fs.removeSync; const originalRmSync = fs.rmSync;
const deleteJavaSpy = jasmine.createSpy('deleteJava'); const deleteJavaSpy = jasmine.createSpy('deleteJava');
let dummyProject; let dummyProject;
let removeSyncSpy; let rmSyncSpy;
beforeEach(function () { beforeEach(function () {
fs.ensureDirSync(temp); fs.mkdirSync(temp, { recursive: true });
fs.ensureDirSync(plugins_dir); fs.mkdirSync(plugins_dir, { recursive: true });
fs.copySync(android_studio_project, temp); fs.cpSync(android_studio_project, temp, { recursive: true });
AndroidProject.purgeCache(); AndroidProject.purgeCache();
dummyProject = AndroidProject.getProjectFile(temp); dummyProject = AndroidProject.getProjectFile(temp);
removeSyncSpy = spyOn(fs, 'removeSync'); rmSyncSpy = spyOn(fs, 'rmSync');
common.__set__('deleteJava', deleteJavaSpy); common.__set__('deleteJava', deleteJavaSpy);
}); });
afterEach(function () { afterEach(function () {
originalRemoveSync.call(fs, temp); originalRmSync.call(fs, temp, { recursive: true });
common.__set__('deleteJava', deleteJavaOrig); 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 () { it('Test#017 : should remove jar files for Android Studio projects', function () {
android['lib-file'].install(valid_libs[0], dummyPluginInfo, dummyProject); android['lib-file'].install(valid_libs[0], dummyPluginInfo, dummyProject);
android['lib-file'].uninstall(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'));
}); });
}); });
@@ -321,7 +321,7 @@ describe('android project handler', function () {
it('Test#018 : should remove files for Android Studio projects', function () { it('Test#018 : should remove files for Android Studio projects', function () {
android['resource-file'].install(valid_resources[0], dummyPluginInfo, dummyProject); android['resource-file'].install(valid_resources[0], dummyPluginInfo, dummyProject);
android['resource-file'].uninstall(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'));
}); });
}); });
@@ -341,49 +341,49 @@ describe('android project handler', function () {
it('Test#019b : should remove stuff by calling common.removeFile for Android Studio projects, of jar with new app target-dir scheme', function () { 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'].install(valid_source[2], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(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')); expect(rmSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/libs/TestLib.jar'));
}); });
it('Test#019c : should remove stuff by calling common.removeFile for Android Studio projects, of aar with new app target-dir scheme', function () { 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'].install(valid_source[3], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(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')); expect(rmSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/libs/TestAar.aar'));
}); });
it('Test#019d : should remove stuff by calling common.removeFile for Android Studio projects, of xml with old target-dir scheme', function () { 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'].install(valid_source[4], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(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')); expect(rmSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/src/main/res/xml/mysettings.xml'));
}); });
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 () { 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'].install(valid_source[5], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(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')); expect(rmSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/src/main/res/values/other.extension'));
}); });
it('Test#019f : should remove stuff by calling common.removeFile for Android Studio projects, of aidl with old target-dir scheme (GH-547)', function () { 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'].install(valid_source[6], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(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')); expect(rmSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/src/main/aidl/com/mytest/myapi.aidl'));
}); });
it('Test#019g : should remove stuff by calling common.removeFile for Android Studio projects, of aar with old target-dir scheme (GH-547)', function () { 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'].install(valid_source[7], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(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')); expect(rmSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/libs/testaar2.aar'));
}); });
it('Test#019h : should remove stuff by calling common.removeFile for Android Studio projects, of jar with old target-dir scheme (GH-547)', function () { 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'].install(valid_source[8], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(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')); expect(rmSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/libs/testjar2.jar'));
}); });
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 () { 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'].install(valid_source[9], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(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')); expect(rmSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/src/main/jniLibs/x86/libnative.so'));
}); });
it('Test#019j : should remove stuff by calling common.deleteJava for Android Studio projects, with target-dir that includes "app"', function () { it('Test#019j : should remove stuff by calling common.deleteJava for Android Studio projects, with target-dir that includes "app"', function () {
@@ -397,7 +397,7 @@ describe('android project handler', function () {
const someString = jasmine.any(String); const someString = jasmine.any(String);
beforeEach(function () { 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, 'removeSystemLibrary');
spyOn(dummyProject, 'removeSubProject'); spyOn(dummyProject, 'removeSubProject');
@@ -424,13 +424,13 @@ describe('android project handler', function () {
const framework = { src: 'plugin-lib', custom: true }; const framework = { src: 'plugin-lib', custom: true };
android.framework.uninstall(framework, dummyPluginInfo, dummyProject); android.framework.uninstall(framework, dummyPluginInfo, dummyProject);
expect(dummyProject.removeSubProject).toHaveBeenCalledWith(dummyProject.projectDir, someString); expect(dummyProject.removeSubProject).toHaveBeenCalledWith(dummyProject.projectDir, someString);
expect(removeSyncSpy).toHaveBeenCalledWith(someString); expect(rmSyncSpy).toHaveBeenCalledWith(someString);
}); });
it('Test#24 : should install gradleReference using project.removeGradleReference', function () { it('Test#24 : should install gradleReference using project.removeGradleReference', function () {
const framework = { src: 'plugin-lib', custom: true, type: 'gradleReference' }; const framework = { src: 'plugin-lib', custom: true, type: 'gradleReference' };
android.framework.uninstall(framework, dummyPluginInfo, dummyProject); android.framework.uninstall(framework, dummyPluginInfo, dummyProject);
expect(removeSyncSpy).toHaveBeenCalledWith(someString); expect(rmSyncSpy).toHaveBeenCalledWith(someString);
expect(dummyProject.removeGradleReference).toHaveBeenCalledWith(dummyProject.projectDir, someString); 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 () { 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 }); android['js-module'].uninstall(jsModule, dummyPluginInfo, dummyProject, { usePlatformWww: true });
expect(removeSyncSpy).toHaveBeenCalledWith(wwwDest); expect(rmSyncSpy).toHaveBeenCalledWith(wwwDest);
expect(removeSyncSpy).toHaveBeenCalledWith(platformWwwDest); expect(rmSyncSpy).toHaveBeenCalledWith(platformWwwDest);
}); });
it('Test#026 : should put module to www only when options.usePlatformWww flag is not specified', function () { it('Test#026 : should put module to www only when options.usePlatformWww flag is not specified', function () {
android['js-module'].uninstall(jsModule, dummyPluginInfo, dummyProject); android['js-module'].uninstall(jsModule, dummyPluginInfo, dummyProject);
expect(removeSyncSpy).toHaveBeenCalledWith(wwwDest); expect(rmSyncSpy).toHaveBeenCalledWith(wwwDest);
expect(removeSyncSpy).not.toHaveBeenCalledWith(platformWwwDest); expect(rmSyncSpy).not.toHaveBeenCalledWith(platformWwwDest);
}); });
}); });
@@ -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 () { 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 }); android.asset.uninstall(asset, dummyPluginInfo, dummyProject, { usePlatformWww: true });
expect(removeSyncSpy).toHaveBeenCalledWith(wwwDest); expect(rmSyncSpy).toHaveBeenCalledWith(wwwDest);
expect(removeSyncSpy).toHaveBeenCalledWith(platformWwwDest); expect(rmSyncSpy).toHaveBeenCalledWith(platformWwwDest);
}); });
it('Test#028 : should put module to www only when options.usePlatformWww flag is not specified', function () { it('Test#028 : should put module to www only when options.usePlatformWww flag is not specified', function () {
android.asset.uninstall(asset, dummyPluginInfo, dummyProject); android.asset.uninstall(asset, dummyPluginInfo, dummyProject);
expect(removeSyncSpy).toHaveBeenCalledWith(wwwDest); expect(rmSyncSpy).toHaveBeenCalledWith(wwwDest);
expect(removeSyncSpy).not.toHaveBeenCalledWith(platformWwwDest); expect(rmSyncSpy).not.toHaveBeenCalledWith(platformWwwDest);
}); });
}); });
}); });
+17 -18
View File
@@ -18,7 +18,7 @@
*/ */
const rewire = require('rewire'); const rewire = require('rewire');
const path = require('path'); const path = require('node:path');
const CordovaError = require('cordova-common').CordovaError; const CordovaError = require('cordova-common').CordovaError;
const GradlePropertiesParser = require('../../lib/config/GradlePropertiesParser'); const GradlePropertiesParser = require('../../lib/config/GradlePropertiesParser');
const utils = require('../../lib/utils'); const utils = require('../../lib/utils');
@@ -899,9 +899,9 @@ describe('prepare', () => {
// Spies // Spies
let replaceFileContents; let replaceFileContents;
let ensureDirSyncSpy; let mkdirSyncSpy;
let copySyncSpy; let cpSyncSpy;
let removeSyncSpy; let rmSyncSpy;
// Mock Data // Mock Data
let cordovaProject; let cordovaProject;
@@ -950,7 +950,7 @@ describe('prepare', () => {
prepare.__set__('updateWww', jasmine.createSpy('updateWww')); prepare.__set__('updateWww', jasmine.createSpy('updateWww'));
prepare.__set__('updateIcons', jasmine.createSpy('updateIcons').and.returnValue(Promise.resolve())); 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') prepare.__set__('warnForDeprecatedSplashScreen', jasmine.createSpy('warnForDeprecatedSplashScreen')
.and.returnValue(Promise.resolve())); .and.returnValue(Promise.resolve()));
prepare.__set__('updateFileResources', jasmine.createSpy('updateFileResources').and.returnValue(Promise.resolve())); prepare.__set__('updateFileResources', jasmine.createSpy('updateFileResources').and.returnValue(Promise.resolve()));
@@ -1001,16 +1001,15 @@ describe('prepare', () => {
`)) `))
}); });
ensureDirSyncSpy = jasmine.createSpy('ensureDirSync'); mkdirSyncSpy = jasmine.createSpy('mkdirSync');
copySyncSpy = jasmine.createSpy('copySync'); cpSyncSpy = jasmine.createSpy('cpSync');
removeSyncSpy = jasmine.createSpy('removeSync'); rmSyncSpy = jasmine.createSpy('rmSync');
prepare.__set__('fs', { prepare.__set__('fs', {
writeFileSync: jasmine.createSpy('writeFileSync'), writeFileSync: jasmine.createSpy('writeFileSync'),
writeJSONSync: jasmine.createSpy('writeJSONSync'), mkdirSync: mkdirSyncSpy,
ensureDirSync: ensureDirSyncSpy, cpSync: cpSyncSpy,
copySync: copySyncSpy, rmSync: rmSyncSpy,
removeSync: removeSyncSpy,
existsSync: jasmine.createSpy('existsSync') existsSync: jasmine.createSpy('existsSync')
}); });
}); });
@@ -1022,9 +1021,9 @@ describe('prepare', () => {
await api.prepare(cordovaProject, options).then(() => { await api.prepare(cordovaProject, options).then(() => {
expect(replaceFileContents).toHaveBeenCalledWith(renamedJavaActivityPath, /package [\w.]*;/, 'package ' + packageName + ';'); expect(replaceFileContents).toHaveBeenCalledWith(renamedJavaActivityPath, /package [\w.]*;/, 'package ' + packageName + ';');
expect(ensureDirSyncSpy).toHaveBeenCalledWith(renamedPath); expect(mkdirSyncSpy).toHaveBeenCalledWith(renamedPath, { recursive: true });
expect(copySyncSpy).toHaveBeenCalledWith(initialJavaActivityPath, renamedJavaActivityPath); expect(cpSyncSpy).toHaveBeenCalledWith(initialJavaActivityPath, renamedJavaActivityPath);
expect(removeSyncSpy).toHaveBeenCalledWith(initialJavaActivityPath); expect(rmSyncSpy).toHaveBeenCalledWith(initialJavaActivityPath);
}); });
}); });
@@ -1033,9 +1032,9 @@ describe('prepare', () => {
await api.prepare(cordovaProject, options).then(() => { await api.prepare(cordovaProject, options).then(() => {
expect(replaceFileContents).toHaveBeenCalledTimes(0); expect(replaceFileContents).toHaveBeenCalledTimes(0);
expect(ensureDirSyncSpy).toHaveBeenCalledTimes(0); expect(mkdirSyncSpy).toHaveBeenCalledTimes(0);
expect(copySyncSpy).toHaveBeenCalledTimes(0); expect(cpSyncSpy).toHaveBeenCalledTimes(0);
expect(removeSyncSpy).toHaveBeenCalledTimes(0); expect(rmSyncSpy).toHaveBeenCalledTimes(0);
}); });
}); });
}); });
+1 -1
View File
@@ -39,7 +39,7 @@
android:launchMode="singleTop" android:launchMode="singleTop"
android:theme="@style/Theme.App.SplashScreen" android:theme="@style/Theme.App.SplashScreen"
android:windowSoftInputMode="adjustResize" 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"> android:exported="true">
<intent-filter android:label="@string/launcher_name"> <intent-filter android:label="@string/launcher_name">
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
+15 -26
View File
@@ -261,33 +261,13 @@ android {
} }
if (cordovaConfig.IS_GRADLE_PLUGIN_KOTLIN_ENABLED) { if (cordovaConfig.IS_GRADLE_PLUGIN_KOTLIN_ENABLED) {
if (cordovaConfig.KOTLIN_JVM_TARGET == null) { // If KOTLIN_JVM_TARGET is null, fallback to JAVA_TARGET_COMPATIBILITY,
// If the value is null, fallback to JAVA_TARGET_COMPATIBILITY, // as they generally should be equal
// as they generally should be equal cordovaConfig.KOTLIN_JVM_TARGET = cordovaConfig.KOTLIN_JVM_TARGET ?:
def javaTarget = JavaLanguageVersion.of(cordovaConfig.JAVA_TARGET_COMPATIBILITY) cordovaConfig.JAVA_TARGET_COMPATIBILITY
// check if javaTarget is <= 8; if so, we need to prefix it with "1."
// Starting with 9 and later, the value can be used as is.
if (javaTarget.compareTo(JavaLanguageVersion.of(8)) <= 0) {
javaTarget = "1." + javaTarget
}
cordovaConfig.KOTLIN_JVM_TARGET = javaTarget
}
// Similar to above, check if kotlin target is <= 8, if so prefix it.
// This allows the user to use consistent set of values in config.xml
// Rather than having to be aware whether the "1."" prefix is needed.
// This check is only done if the value isn't already prefixed with 1.
if (
!cordovaConfig.KOTLIN_JVM_TARGET.startsWith("1.") &&
JavaLanguageVersion.of(cordovaConfig.KOTLIN_JVM_TARGET).compareTo(JavaLanguageVersion.of(8)) <= 0
) {
cordovaConfig.KOTLIN_JVM_TARGET = "1." + cordovaConfig.KOTLIN_JVM_TARGET
}
kotlinOptions { kotlinOptions {
jvmTarget = cordovaConfig.KOTLIN_JVM_TARGET jvmTarget = JavaLanguageVersion.of(cordovaConfig.KOTLIN_JVM_TARGET)
} }
} }
@@ -331,7 +311,16 @@ dependencies {
implementation "androidx.core:core-splashscreen:${cordovaConfig.ANDROIDX_CORE_SPLASHSCREEN_VERSION}" implementation "androidx.core:core-splashscreen:${cordovaConfig.ANDROIDX_CORE_SPLASHSCREEN_VERSION}"
if (cordovaConfig.IS_GRADLE_PLUGIN_KOTLIN_ENABLED) { 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 // SUB-PROJECT DEPENDENCIES START
+4 -1
View File
@@ -17,7 +17,7 @@
specific language governing permissions and limitations specific language governing permissions and limitations
under the License. under the License.
--> -->
<resources> <resources xmlns:tools="http://schemas.android.com/tools">
<style name="Theme.App.SplashScreen" parent="Theme.SplashScreen.IconBackground"> <style name="Theme.App.SplashScreen" parent="Theme.SplashScreen.IconBackground">
<!-- Optional: Set the splash screen background. (Default: #FFFFFF) --> <!-- Optional: Set the splash screen background. (Default: #FFFFFF) -->
<item name="windowSplashScreenBackground">@color/cdv_splashscreen_background</item> <item name="windowSplashScreenBackground">@color/cdv_splashscreen_background</item>
@@ -30,5 +30,8 @@
<!-- Required: Set the theme of the Activity that directly follows your splash screen. --> <!-- Required: Set the theme of the Activity that directly follows your splash screen. -->
<item name="postSplashScreenTheme">@style/Theme.AppCompat.NoActionBar</item> <item name="postSplashScreenTheme">@style/Theme.AppCompat.NoActionBar</item>
<!-- Disable Edge-to-Edge for SDK 35 -->
<item name="android:windowOptOutEdgeToEdgeEnforcement" tools:targetApi="35">true</item>
</style> </style>
</resources> </resources>
@@ -66,7 +66,7 @@ public class StandardActivityTest {
@Test @Test
public void webViewCheck() { public void webViewCheck() {
StandardActivity activity = (StandardActivity) mActivityRule.getActivity(); StandardActivity activity = (StandardActivity) mActivityRule.getActivity();
//Fish the webview out of the mostly locked down Activity using the Android SDK // Fish the WebView out of the mostly locked down Activity using the Android SDK
View view = activity.getWindow().getCurrentFocus(); View view = activity.getWindow().getCurrentFocus();
assertEquals(SystemWebView.class, view.getClass()); assertEquals(SystemWebView.class, view.getClass());
} }
@@ -44,7 +44,7 @@ public class EmbeddedWebViewActivity extends AppCompatActivity {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); setContentView(R.layout.activity_main);
//Set up the webview // Set up the WebView
ConfigXmlParser parser = new ConfigXmlParser(); ConfigXmlParser parser = new ConfigXmlParser();
parser.parse(this); parser.parse(this);
+3 -3
View File
@@ -19,9 +19,9 @@
under the License. under the License.
*/ */
const path = require('path'); const path = require('node:path');
const execa = require('execa'); const execa = require('execa');
const fs = require('fs-extra'); const fs = require('node:fs');
const ProjectBuilder = require('../lib/builders/ProjectBuilder'); const ProjectBuilder = require('../lib/builders/ProjectBuilder');
class AndroidTestRunner { class AndroidTestRunner {
@@ -62,7 +62,7 @@ class AndroidTestRunner {
.then(_ => { .then(_ => {
// TODO we should probably not only copy these files, but instead create a new project from scratch // TODO we should probably not only copy these files, but instead create a new project from scratch
fs.copyFileSync(path.resolve(this.projectDir, '../../framework/cdv-gradle-config-defaults.json'), path.resolve(this.projectDir, 'cdv-gradle-config.json')); fs.copyFileSync(path.resolve(this.projectDir, '../../framework/cdv-gradle-config-defaults.json'), path.resolve(this.projectDir, 'cdv-gradle-config.json'));
fs.copySync(path.resolve(this.projectDir, '../../templates/project/tools'), path.resolve(this.projectDir, 'tools')); fs.cpSync(path.resolve(this.projectDir, '../../templates/project/tools'), path.resolve(this.projectDir, 'tools'), { recursive: true });
fs.copyFileSync( fs.copyFileSync(
path.join(__dirname, '../templates/project/assets/www/cordova.js'), path.join(__dirname, '../templates/project/assets/www/cordova.js'),
path.join(this.projectDir, 'app/src/main/assets/www/cordova.js') path.join(this.projectDir, 'app/src/main/assets/www/cordova.js')