Compare commits

...

124 Commits

Author SHA1 Message Date
Erisu
7572fc4912 android-v10.0.0 Updated version and RELEASENOTES.md for release 10.0.0 2021-07-17 13:56:20 +09:00
エリス
9286496378 fix: add missing apache-license header to getASPath.bat (#1283) 2021-07-16 17:59:30 +09:00
エリス
9d3d8d0521 chore!: bump all dependencies (#1279)
* chore: bump jasmine@^3.8.0
* chore!: bump release dependencies

- android-versions@^1.7.0
- execa@^5.1.1
- fast-glob@^3.2.7
- fs-extra@^10.0.0

* chore: rebuilt package-lock.json
2021-07-15 21:02:32 +09:00
エリス
636136c88e chore!: drop node 10 support (#1278) 2021-07-15 12:32:56 +09:00
Niklas Merz
bedb4427f4 fix: add WebViewAssetloader to default allow list (#1275) 2021-07-14 22:29:15 +09:00
Raphael von der Grün
35f0d07521 build: build cordova.js during npm prepare (#1271) 2021-07-14 10:25:34 +02:00
エリス
b94409ffe0 Remove gradle.daemon from default forced settings (#1276)
Co-authored-by: Fernando Serboncini <fserb@fserb.com.br>
2021-07-14 17:22:11 +09:00
エリス
adc380cf9f cleanup!: remove deprecated settings & add todo comments (#1274)
* chore: add todo task for setSaveFormData
* cleanup!: remove deprecated setSavePassword
* cleanup!: remove deprecated setDatabasePath
* chore: add todo task for setGeolocationDatabasePath
2021-07-14 15:37:12 +09:00
エリス
7da531ced5 chore: remove old VERSION file (#1273) 2021-07-14 15:34:27 +09:00
Dominik Hendrix
3e870e3a04 fix: request focus after custom view hided (#1216)
Co-authored-by: Dominik Hendrix <hendrix@schaffrath-digital.de>
2021-07-14 11:42:19 +09:00
エリス
f15fec81cf cleanup: delete old ANT & Eclipse files (#1272) 2021-07-14 11:38:40 +09:00
Raphael von der Grün
53d60dd707 refactor(Api)!: use version from package.json (#1270) 2021-07-13 12:27:25 +02:00
hiepxanh
68a302e9d2 fix(build): support tilde expansion on windows (#563)
Co-authored-by: Raphael von der Grün <raphinesse@gmail.com>
2021-07-13 19:02:09 +09:00
Raphael von der Grün
16ff6e1b2f refactor!: do not copy JS lib to platform project (#1269) 2021-07-13 11:01:50 +02:00
エリス
f6d1deeff6 feat!: release build defaults to aab package type (#1268) 2021-07-13 16:34:38 +09:00
Raphael von der Grün
1f0ea173b0 refactor: do not infer project root from script location (#1265)
* fix(Api): do not infer project root from script location

* fix(builders): do not infer project root from script location

* fix(target): do not infer project root from script location

* test(e2e): cleanup and extend E2E tests

- Renames the file with the only existing E2E test
- Makes existing test use the API instance returned by
  `Api.createPlatform`
- Adds another test that ensures we can still require the API from
  `platformProjectPath/cordova/Api.js`

* fix(check_reqs): do not infer project root from script location
2021-07-13 08:51:20 +02:00
Raphael von der Grün
70a1eff705 refactor: use target SDK of built APK to determine best emulator (#1267)
* refactor(emulator): require emulatorId in emulator.run

* refactor: use effective targetSdk to find best emulator
2021-07-12 09:48:36 +02:00
Raphael von der Grün
fb36e03aeb refactor(run)!: cleanup run method (#1266)
* refactor(run)!: get rid of emit-and-throw & throw-literal antipatterns

* refactor(run)!: convert run method to async/await

* refactor(run): require build module in advance

* refactor(run): minor cleanup

* refactor(run): drop always-undefined option arch

`parseBuildOptions` only sets `arch` if something truthy is passed for
parameter `resolvedTarget` to which we pass `null`.

* refactor(run): destructure buildOptions
2021-07-11 15:47:07 +02:00
jcesarmobile
8a9cb8f6b7 feat: remove java 1.8 version check (#1241) 2021-07-07 13:19:09 +02:00
エリス
b31e024dbb fix: missing super.onRequestPermissionsResult error (MissingSuperCall) (#1264) 2021-07-07 19:57:33 +09:00
エリス
422ce4f6bf feat: bump appcompat@1.3.0 (#1262) 2021-07-07 11:17:05 +09:00
エリス
b8d32d7c60 feat: bump gradle@7.1.1 (#1257) 2021-07-07 11:11:24 +09:00
エリス
83b3998801 feat: bump android-gradle-plugin@4.2.2 (#1255) 2021-07-07 11:08:03 +09:00
エリス
180a1a39f0 feat: bump kotlin@1.5.20 (#1256)
* feat: bump kotlin@1.5.10
* fix: kotlin@1.5.20 (applied suggestion)

Co-authored-by: jcesarmobile <jcesarmobile@gmail.com>
2021-07-07 10:55:47 +09:00
エリス
a1ed525a0b feat: bump android.webkit@1.4.0 (#1258) 2021-07-07 10:55:17 +09:00
Raphael von der Grün
2037b62f99 test(check_reqs): do not hardcode DEFAULT_TARGET_API (#1263)
Should have been part of #1212
2021-07-06 18:12:00 +02:00
エリス
21f64806c0 feat: support webkit version override (#1254) 2021-07-06 23:39:12 +09:00
エリス
e8cbeaaa1b refactor: gradle cleanup (#1253)
* refactor: move androidx.webkit version to cdv-gradle-config-defaults
* chore: remove unnecessary cordova.gradle apply
* refactor: maven publish to use version info from package.json
* refactor: separate framwork publishing outside of app dev scope
2021-07-06 22:38:16 +09:00
Raphael von der Grün
facffb0809 refactor!: remove most platform binaries (#1100)
* Remove binaries cordova/lib/*

* Remove binary bin/android_sdk_version

* Remove binary bin/update script

* Remove binary bin/check_reqs

* Remove binary bin/create script

* Remove binary cordova/build

* Remove binary cordova/run

* Remove binary cordova/clean

* Remove binary cordova/log

* Remove unused module cordova/loggingHelper

* Update README

* Restore target-listing binaries used by CLI

Usage: cordova-lib/src/cordova/targets.js

* Restore binary bin/android_sdk_version for CLI compatibility

This is used in CLI to implement an Android SDK version check for plugins.
See https://cordova.apache.org/docs/en/latest/plugin_ref/spec.html#engines-and-engine

* Remove version.bat
2021-07-06 15:37:46 +02:00
Raphael von der Grün
6f35d0b2b7 refactor(check_reqs): drop originalError param from check_android_target (#1260) 2021-07-06 13:33:26 +02:00
Raphael von der Grün
334d02d26e tests(prepare): factor out common vars (#1259) 2021-07-06 13:01:37 +02:00
エリス
69b24dbf6a feat!: drop abandoned com.github.dcendents:android-maven-gradle-plugin (#1252)
* feat: use maven-publish plugin (mavenCentral)
* fix: Apache License url
* fix: framework build failure without useAndroidX
* fix: add aar build artifact
2021-07-06 15:57:40 +09:00
Norman Breau
510596f515 feat!: unify & fix gradle library/tooling overrides (#1212)
* enhancement: Control SDK versions and other default projects in one place
* fix: target/compile sdk usage
* refactor: cleanup gradle process
* chore: cleanup and remove unused changes
* chore: remove more unneeded FILE_PATH
* chore: fix lint error
* revert change intended to be part of a different PR
* chore: apply changes to revert to fit new changes
* fix: Ensure proper types
* breaking: Removed TempateFile class
  * Replaced the one and only usage of it with the properties-parser editor.
  * Breaking change because we are converting a method into an asynchronous method.
* refactor: Use the sync version of properties editor
* Gh 1178 fix sdk use gradlearg fix (#2)
* fix: readd gradleArg support
* fix: variable name
* refactor: remove unused mock variables
* Update bin/templates/cordova/lib/builders/ProjectBuilder.js
* Update bin/lib/create.js
* fix: const naming (review suggestion)
* fix: use defaults for framework building
* chore: apply review suggestion
* chore: rename config.json & defaults.json (review suggestions)
* refactor: updateUserProjectGradleConfig method
* refactor: minor changes in updateUserProjectGradleConfig
* refactor: major changes in updateUserProjectGradleConfig
* fix: wrong handling of missing preferences
* fix: usage of undefined this
* fix(create.spec): mocking of getPreference
* test(check_reqs): reduce diff size
* refactor: add wrapper to load gradle config defaults
* fix(check_reqs): get_target
  * Reads default SDK from default gradle config now
* fix(check_reqs.spec): return correct types from mocks
* revert to using get_target in create
* fix: e2e test

Co-authored-by: Erisu <ellis.bryan@gmail.com>
Co-authored-by: Raphael von der Grün <raphinesse@gmail.com>
2021-07-06 15:38:28 +09:00
エリス
47aa116b1d breaking: drop jcenter & update dependencies (#1251) 2021-07-03 13:04:13 +09:00
エリス
015db819ae feat(allow-list)!: integrate and refactor core plugin (#1138)
* refactor!: naming to allow list
* feat(allow-list): integrate core plugin
* refactor(allow-list): cleanup
* refactor: drop deprecated launch-external case for CustomConfigParser::handleStartTag
* fix: apply review comments
2021-07-02 11:52:05 +09:00
jcesarmobile
01569ce71a feat!: upgrade android gradle plugin to 4.2.1 (#1232) 2021-07-01 16:50:39 +09:00
Raphael von der Grün
0f13f4a5ac refactor(env/java): improve tests and implementation (#1246)
This basically fixes up the changes from #1220.

* test(env/java): replace test that duplicates implementation
* test(env/java): stub _ensure to focus on unit under test
* test(env/java): add test for invalid output
* refactor(env/java): keep try block small
* refactor(env/java): shorten excessive comment
2021-06-23 18:22:04 +02:00
Norman Breau
6d803e2f72 Bugfix/java checks (#1228)
* fix: Java version parsing if java executable prints out additional information with --version

* fix: Ensure JAVA_HOME path comes first in the PATH environment

* refactor: Removed redundent code in favour of keeping a change introduced from another PR
2021-05-09 17:52:35 -03:00
Norman Breau
ae4dba2bb8 feat: CORDOVA_JAVA_HOME env variable (#1229)
* feat: CORDOVA_JAVA_HOME env variable

* refactor: Improve CORDOVA_JAVA_HOME env test

* fix(test) path separator issue
2021-05-09 17:02:25 -03:00
David
a45804329b fix(requirements check): use regex to get java version from javac output (#1220)
* fix(requirements check): use regex to get java version from javac output

* fix(lint): format code

* fix(node 10): remove optional chaining from version check
2021-05-09 09:33:08 -03:00
Raphael von der Grün
1b7874607e fix(prepare): delete splash screens if none are used (#1227)
Currently, we copy default splash screens to the platform folder during platform creation, but then we do not delete them when the user uses no splash screens at all. This causes #1226 and what is described in #689.

The new implementation does not return early when updating splashes and none are defined. Instead, we let the cleanup map we create initially take care of deleting all unused splash screens. This also deletes the default splashes during the first prepare after platform creation.

Fixes #1226
2021-05-06 09:30:19 +02:00
Niklas Merz
ff1ae2125a feat: add backwards compatibility mode for WebViewAssetLoader (#1222) 2021-05-06 08:17:58 +02:00
Norman Breau
f9e8ce8fab ci: Add Node16 to CI matrix (#1218) 2021-04-26 12:47:22 -03:00
Niklas Merz
5e7be8e1d6 breaking: implement WebViewAssetLoader (#1137)
Implement AndroidX WebViewAssetLoader with hook for plugins


Co-authored-by: エリス <erisu@users.noreply.github.com>
2021-04-22 14:32:14 +02:00
Jakub Blejder
b2d9d639b4 feat: overload PluginEntry constructor to set onload property (#1166)
Co-authored-by: Erisu <ellis.bryan@gmail.com>
2021-04-19 07:51:18 +09:00
エリス
8d407708d4 feat: allow appcompat version to be configurable (#1208) 2021-04-19 07:30:06 +09:00
エリス
19bbf1e8c1 test(AndroidManifest): update theme to Theme.AppCompat.NoActionBar (#1207) 2021-04-16 18:32:46 +09:00
Hans Krywalsky
2a84d7c44d breaking: only support androidx (#1052)
* GH-841 only support androix
2021-04-16 06:52:14 +09:00
エリス
eeb645c886 chore! (npm): update all dependencies (#1205) 2021-04-15 08:23:41 +09:00
Mosab A
676f98d82d feat!: upgrade kotlin@1.4.32 (#1204) 2021-04-14 11:29:09 +09:00
Raphael von der Grün
9c3195c1ee refactor!: drop support for android SDK tool (#1083)
* refactor(emulator)!: remove support for legacy `android` binary
`emulator.list_images` now always uses the `avdmanager` binary.
* refactor(android_sdk)!: remove support for legacy `android` binary
`android_sdk.list_targets` now always uses the `avdmanager` binary.
* refactor(check_reqs)!: do not look for legacy `android` binary
* refactor: replace installation instructions involving `android` binary
2021-04-13 19:16:43 +09:00
Niklas Merz
2a92c77772 remove app cache settings (#1141) 2021-04-13 19:14:37 +09:00
Norman Breau
eefa91b3ea breaking: remove keystore password prompt (#1048) 2021-04-13 19:13:01 +09:00
Norman Breau
6b33772613 feat: Deprecated onRequestPermissionResult in favour for onRequestPermissionsResult for consistency (#1047) 2021-04-13 18:56:04 +09:00
エリス
cab9840a03 feat!: upgrade jfrog gradle-bintray-plugin@1.8.5 (#1201)
* breaking: upgrade jfrog gradle-bintray-plugin@1.8.5
* original contribution PR: #1079

Co-authored-by: Mosab A <47486787+mosabab@users.noreply.github.com>
2021-04-13 18:10:07 +09:00
エリス
63b2356575 feat!: upgrade kotlin@1.4.31 (#1200)
* feat: upgrade kotlin@1.4.31 (#1176)

Co-authored-by: Mosab A <47486787+mosabab@users.noreply.github.com>
2021-04-13 17:50:05 +09:00
エリス
a9ee9196bd feat!: upgrade Google Services Gradle Plugin@4.3.5 (#1199)
* feat: upgrade Google Services Gradle Plugin google-services@4.3.5 (#1177)

Co-authored-by: Mosab A <47486787+mosabab@users.noreply.github.com>
2021-04-13 17:20:54 +09:00
エリス
6588994586 feat!: upgrade android gradle plugin@4.1.3 (#1198)
* feat: upgrade gradle-plugin@4.1.3 (#1175)

Co-authored-by: Mosab A <47486787+mosabab@users.noreply.github.com>
2021-04-13 16:43:30 +09:00
エリス
46d4d924cc feat!: upgrade gradle@6.8.3 (#1197)
* [Frequent Updates] Gradle 6.6.1 & Android Gradle Plugin 4.0.1 & com.jfrog.bintray.gradle 1.8.5 (#1079)
* Update ProjectBuilder.js
* Update build.gradle
* Update wrapper.gradle
* chore: remove non-gradle wrapper changes
* feat: upgrade gradle@6.8.3 (#1174)

Co-authored-by: Mosab A <47486787+mosabab@users.noreply.github.com>
2021-04-13 15:35:34 +09:00
エリス
dc8854d16c feat: target sdk 30 w/ build-tool 30.0.3 (#1182) 2021-04-13 15:34:21 +09:00
エリス
a33044921d feat: bump version to 10.0.0-dev (#1181) 2021-04-13 15:34:00 +09:00
Erisu
9c165cb94f chore: set package-lock.json to 9.2.0-dev 2021-04-10 01:55:22 +09:00
Erisu
e0d5d14895 Update JS snapshot to version 9.2.0-dev (via coho) 2021-04-10 00:26:30 +09:00
Erisu
35bc354531 Set VERSION to 9.2.0-dev (via coho) 2021-04-10 00:26:26 +09:00
Erisu
ac4d3d1d45 android-v9.1.0 Updated version and RELEASENOTES.md for release 9.1.0 2021-04-10 00:25:19 +09:00
エリス
be9abf5f91 chore: add missing header license (#1196) 2021-04-09 23:44:50 +09:00
Raphael von der Grün
c04ea9b1c0 refactor: unify target resolution for devices & emulators (#1101)
* refactor: unify target resolution for devices & emulators
* fix: use unified target methods in platform-centric bins
2021-04-09 15:37:56 +09:00
anasofiagribeiro
c774bf3311 feat: support gzip encoding requests & use GZIPInputStream (#1104) 2021-04-09 11:26:23 +09:00
エリス
9071d5131a Revert "[Frequent Updates] Gradle 6.6.1 & Android Gradle Plugin 4.0.1 & com.jfrog.bintray.gradle 1.8.5 (#1079)" (#1193)
This reverts commit 33476b4754.
2021-04-09 11:23:23 +09:00
エリス
c676ca98ff revert: feat: upgrade kotlin@1.4.31 #1176 (#1194) 2021-04-09 11:23:09 +09:00
エリス
690ff3f364 Revert "feat: upgrade Google Services Gradle Plugin google-services@4.3.5 (#1177)" (#1191)
This reverts commit e8ec3b1e37.
2021-04-08 14:04:52 +09:00
エリス
04b0106bca Revert "feat: upgrade gradle-plugin@4.1.3 (#1175)" (#1189)
This reverts commit 1430304d36.
2021-04-08 14:04:26 +09:00
エリス
31233089f1 Revert "feat: upgrade gradle@6.8.3 (#1174)" (#1190)
This reverts commit fe4d4aeff0.
2021-04-08 14:03:34 +09:00
エリス
1f5426f939 fix: copy repositories.gradle to project on create (#1186) 2021-03-30 22:57:14 +09:00
Norman Breau
1ec87634d4 fix(regression): Cannot read version of undefined caused by Java refactor (https://github.com/apache/cordova-android/pull/1130#discussion_r563597125) (#1185) 2021-03-30 22:40:34 +09:00
Alexander
d22af021ee feat: handle intent:// scheme links with browser_fallback_url param (#1167)
* fix showWebPage url intent:// now works
* android handle external url `intent://` scheme
* code refactoring

Co-authored-by: Андреянов Александр Николаевич <a.andreyanov@sevstar.net>
Co-authored-by: Tim Brust <github@timbrust.de>
2021-03-30 21:43:27 +09:00
エリス
c9ab33eded chore: rebuilt package-lock (#1183) 2021-03-30 19:41:21 +09:00
エリス
6dcd67a902 fix: unit-test failure (#1184) 2021-03-30 18:46:43 +09:00
Daniel Stone
cb1cf4dc8e fix(splashscreen): nav & title bar showing in fullscreen mode (#733)
Co-authored-by: Daniel Stone <daniel.stone@powerdms.com>
Co-authored-by: distinctdan <distinctdan@users.noreply.github.com>
2021-03-30 14:55:16 +09:00
goffioul
6cbf69d109 fix: restore key event handlers when DOM element is fullscreen (#1157)
* GH-1156: Restore key event handlers when a DOM element is fullscreen

Make sure to call dispatchKeyEvent from base class in WrapperView, if
the event hasn't been handled by the engine.

* Remove unwanted whitespace in condition

Co-authored-by: エリス <erisu@users.noreply.github.com>
Co-authored-by: Michael Goffioul <michael.goffioul@lincor.com>
2021-03-30 14:54:43 +09:00
Mosab A
e8ec3b1e37 feat: upgrade Google Services Gradle Plugin google-services@4.3.5 (#1177) 2021-03-30 14:44:08 +09:00
Guillem Perez
f927014d06 fix(android): Avoid Crash Report: ConcurrentModificationException (#1073)
Authored-by: lempere <lempere@lempere.com>
2021-03-28 09:49:39 -03:00
Rick Habets
19a5feb875 fix: add not null checks to prevent running on destroyed activity (#1148)
* (android) #1002: Add Null Pointer Checks to prevent Cordova from running on a destroyed activity

* (android) Add logging statements if Cordova Activity does not exist anymore (i.e. is destroyed)

Co-authored-by: Habets Rick <rick.habets@kbc.be>
2021-03-27 12:19:46 -03:00
ebhsgit
9dcf3eb68b Fix for #924 - Concurrent Modification Exception (#1091)
Co-authored-by: 8bhsolutions <48874658+8bhsolutions@users.noreply.github.com>
2021-03-27 12:17:39 -03:00
Norman Breau
11364918b2 add repositories support (#1179)
Authored-by: Engin Diri <engin.diri@lidl.com>
2021-03-27 12:11:56 -03:00
Mosab A
1430304d36 feat: upgrade gradle-plugin@4.1.3 (#1175)
* Update build.gradle

* Update build.gradle

* Update build.gradle

* Update build.gradle

* Update build.gradle

* Update build.gradle
2021-03-27 10:36:39 -03:00
Mosab A
2a92c2e595 feat: upgrade kotlin@1.4.31 (#1176)
* Update build.gradle

* Update build.gradle
2021-03-27 10:36:09 -03:00
Mosab A
fe4d4aeff0 feat: upgrade gradle@6.8.3 (#1174)
* Gradle Update to 6.8.3

* Update build.gradle

* Update wrapper.gradle

* Update wrapper.gradle
2021-03-27 10:35:27 -03:00
Shashank Agrawal
23a1710557 feat(android-studio): display app name as project name (#1173)
* (android) Feature: Write name of the Android app to .idea/.name for Android Studio #1172

* Missing space before function parentheses.

* Add test for writeNameForAndroidStudio #1172

* Use ES6 for new code. Code DRYness in test spec. #1172
2021-03-27 10:06:26 -03:00
Norman Breau
774de78691 refactor: java checks (#1130)
Co-authored-by: エリス <erisu@users.noreply.github.com>
Co-authored-by: Raphael von der Grün <raphinesse@gmail.com>

Update spec/unit/java.spec.js

Co-authored-by: Raphael von der Grün <raphinesse@gmail.com>

Update spec/unit/java.spec.js

Co-authored-by: Raphael von der Grün <raphinesse@gmail.com>

Update bin/templates/cordova/lib/utils.js

Co-authored-by: Raphael von der Grün <raphinesse@gmail.com>

Update bin/templates/cordova/lib/check_reqs.js

Co-authored-by: Raphael von der Grün <raphinesse@gmail.com>

Update spec/unit/check_reqs.spec.js

Co-authored-by: Raphael von der Grün <raphinesse@gmail.com>

Update spec/unit/check_reqs.spec.js

Co-authored-by: Raphael von der Grün <raphinesse@gmail.com>

Co-authored-by: Raphael von der Grün <raphinesse@gmail.com>
2021-03-27 10:05:50 -03:00
Norman Breau
3081e5e6e9 fix: optional arch parameter (#1153) 2021-01-19 21:33:06 -04:00
Pieter Van Poyer
7428bd3a7f Features/webp support for splashscreen (#1113)
* - linting
- platform independent paths in testing
- addes some unittest
- remove duplication + add comments
- delete webp's if png's added, delete png's if webp' added.
- Update bin/templates/cordova/lib/prepare.js Co-authored-by: エリス <erisu@users.noreply.github.com>
- fix https://github.com/apache/cordova-plugin-splashscreen/issues/257 webp support for android

* revert changes

* refactor: use source extension for target in getImageResourcePath

* fix(prepare): include more extensions in initial splash-screen resource map

* tests(prepare): quick-fix for tests

* backward slashes must be changed to forward slashes for fast-glob package.

Co-authored-by: Raphael von der Grün <raphinesse@gmail.com>
2020-12-16 17:21:35 -04:00
Raphael von der Grün
55feadff05 fix(prepare): mapImageResources always returning [] (#1136) 2020-12-04 09:14:19 +01:00
Norman Breau
97e2d15634 test(java): remove duplicate code in BackButtonMultipageTest (#1129)
* Removed duplicate code in test

* test: Remove duplicate code in BackButtonMultipageTest

Authored-by: leofernandesmo <leonardo.fernandes@ifal.edu.br>
Co-authored-by: breautek <norman@nbsolutions.ca>
2020-11-21 09:10:07 -04:00
Raphael von der Grün
b245337501 refactor(ProjectBuilder): clean up output file collection code (#1099)
* refactor(ProjectBuilder): less repetitive fileSorter

This reverts the fileSorter to the state from before #937, but using our
own simple re-implementation of `compare-func`.

* fix(ProjectBuilder): apply sort RegExp to basename only

* refactor(ProjectBuilder): use fast-glob instead of hand-rolled equivalent

* refactor(ProjectBuilder): factor out common isPathArchSpecific

* refactor(ProjectBuilder): use includes instead of indexOf

* refactor(ProjectBuilder): move sorting into findOutputFilesHelper

* refactor(ProjectBuilder): simplify findOutputFiles signature
2020-11-21 10:44:56 +01:00
Raphael von der Grün
bb7d733cde refactor: unify installation on devices & emulators (#1123)
* refactor: unify installation on devices & emulators

This change replaces the almost identical methods `device.install` and
`emulator.install` with the generic `target.install`.

* fix: use unified installation in platform-centric bins
2020-11-20 22:12:18 +01:00
Raphael von der Grün
aa679ea1d6 feat(Adb): list devices _and_ emulators in one go (#1125) 2020-11-19 21:30:56 +01:00
Raphael von der Grün
0e8234abfd refactor(check_reqs): cleanup default Java location detection on Windows (#1102)
* test(check_reqs): test default Java location detection on Windows

* refactor(check_reqs): use glob for default Java location detection on Windows

This changes the implementation to be closer to what it was before #842
with everything being in one place.

* fix: remove always-taken if statement

* feat: take both Program Files variants from env

* refactor(check_reqs): cosmetic changes
2020-11-19 21:08:43 +01:00
Pieter Van Poyer
d5b9029a23 Android - allow changing min sdk version (#1117)
* try to allow changing min sdk version

* undo testing with cdvMinSdkVersion=21

* Update framework/build.gradle

No printing of default behaviour

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

* Update framework/build.gradle

typo + match case of cdvMinSdkVersion

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

* resolve PR feedback timbru

Co-authored-by: Norman Breau <norman@nbsolutions.ca>
2020-11-18 13:48:46 -04:00
Raphael von der Grün
671e1fd1c6 refactor: remove copied Adb.install from emulator.install (#1108)
`emulator.install` contains a copy of the code of `Adb.install` just to
be able to pass custom options to `execa`.

This change removes that duplicated code in favor of a new option in
`Adb.install` that allows to pass options through to `execa`.
2020-11-17 09:06:44 +01:00
エリス
c144c08112 fix(android): allow file access for existing behavior (#1111) 2020-11-04 14:15:26 +09:00
Raphael von der Grün
aada3e813d refactor: do not kill adb on UNIX-like systems (#1103) 2020-10-22 18:03:28 +02:00
Raphael von der Grün
335b0f2575 test: fix unit test failures for certain random orders (#1094)
* test(Api): do not clobber global events.emit w/ spy

* test(Api): remove unnecessary rewiring

* test(check_reqs): add missing spyOn call

* test(check_reqs): fix process.env restoration

* test(check_reqs): restore module under test before each test
2020-10-19 10:38:37 +02:00
Raphael von der Grün
b1f01d7a65 test: ensure single top-level describe block in test file (#1094)
This makes it easier to map test output to files and allows for common
setup & teardown of all tests in a file.

* test(prepare): wrap in top-level describe block
* test(Api): wrap in top-level describe block
2020-10-19 10:01:09 +02:00
Darryl Pogue
3b56160d38 chore(asf): Update GitHub repo metadata 2020-10-06 20:24:23 -07:00
Norman Breau
39e315628e fix: Reflect minimum required NodeJS (#1045)
Project uses APIs only added in 10.10, namely ProjectBuilder.js readdirSync(), with `withFileTypes` option.
https://nodejs.org/api/fs.html#fs_fs_readdirsync_path_options
2020-10-06 10:12:04 -03:00
Raphael von der Grün
e125ab1b9a refactor(retry): simplify retryPromise using modern JS (#1086) 2020-10-06 10:56:21 +02:00
Raphael von der Grün
5d3591b853 refactor(utils): reduce number of utils (#1085)
* refactor(utils): remove utils.grep

* refactor(utils): replace utils.scanDirectory w/ fast-glob

Note that fast-glob is already in our dependency graph anyway.
2020-10-06 09:04:48 +02:00
Raphael von der Grün
206238893b fix(prepare): fix pattern used to collect image resources (#1084)
The pattern contained an additional plus that slipped in during the
refactoring done in #842. See [the diff][1] for details.

[1]: 09e8248d1f (diff-26c51bfaa44eff1e46fd61ec3225ec13L640-R650)
2020-10-06 08:38:09 +02:00
Mosab A
33476b4754 [Frequent Updates] Gradle 6.6.1 & Android Gradle Plugin 4.0.1 & com.jfrog.bintray.gradle 1.8.5 (#1079)
* Update ProjectBuilder.js

* Update build.gradle

* Update build.gradle

* Update build.gradle

* Update build.gradle

* Update build.gradle

* Update wrapper.gradle

* Update build.gradle

* Update wrapper.gradle

* Update wrapper.gradle

* Update wrapper.gradle

* Update build.gradle

* Update build.gradle

* Update ProjectBuilder.js

* Update ProjectBuilder.js
2020-10-05 21:26:59 -03:00
Raphael von der Grün
2c888f418b chore(pkg): remove deprecated no-op field "engineStrict" (#1081)
See https://docs.npmjs.com/files/package.json#enginestrict

Related to https://github.com/apache/cordova/issues/55
2020-10-04 13:32:28 +02:00
Norman Breau
565106fc1f refactor: Stop suppressing un-needed TruelyRandom lints (#1046) 2020-08-11 10:13:33 -03:00
Norman Breau
a45217e6b9 ci: Added Node 14.x (#975) 2020-07-24 14:02:13 -03:00
Raphael von der Grün
d7790ca8bc chore: remove unused emulator.create_image and its dependencies (#1019) 2020-07-09 00:07:33 +02:00
Raphael von der Grün
8ef8d994df fix(pluginHandlers): properly check if path is inside another (#1014) 2020-07-08 23:31:16 +02:00
Raphael von der Grün
80ad635348 test(pluginHandlers/common): better setup & teardown (#1013) 2020-07-08 15:15:53 +02:00
Norman Breau
ec944cf068 fix: gradle ignore properties (#1018) 2020-07-05 11:19:56 -03:00
Raphael von der Grün
ba5781c3bf refactor: save ProjectBuilder instance in Api instance (#1016)
This reduces dependence on the `builders` module and reduces repitition.

This also facilitates another WIP refactoring I am working on.
2020-07-03 18:54:24 +02:00
Raphael von der Grün
d86cb99dd5 Remove unnecessary stuff (#1015)
* Remove licenses for previously bundled packages

* Remove utils module with duplicate function

* Remove unused function check_reqs.check_ant

* Remove unused test helper
2020-07-03 18:53:10 +02:00
Raphael von der Grün
3204b9804b test(java): fix, improve and move clean script (#1017)
This includes the following changes:

- move this developer-only script to test/ where it conceptually belongs
  - this also prevents it from being distributed with this package
- fix paths for `android` and `androidx` variants
- make paths relative to the script, not to CWD
- use `removeSync` instead of `existsSync` and `existsSync`
- rename npm script to `clean:java-unit-tests` to clarify scope
2020-07-03 18:50:53 +02:00
Raphael von der Grün
ce735256d3 test: fix missing stack traces in jasmine output (#1012)
`true` is not a valid value for the `displayStacktrace` option of
jasmine-spec-reporter.
2020-07-02 22:39:03 +02:00
Erisu
cccf812454 Set package-lock.json to 9.1.0-dev 2020-06-24 12:28:24 +09:00
Erisu
f7e1979665 Update JS snapshot to version 9.1.0-dev (via coho) 2020-06-23 18:33:24 +09:00
Erisu
49fc5da207 Set VERSION to 9.1.0-dev (via coho) 2020-06-23 18:33:21 +09:00
240 changed files with 4641 additions and 14386 deletions

View File

@@ -15,6 +15,30 @@
# specific language governing permissions and limitations
# under the License.
github:
description: Apache Cordova Android
homepage: https://cordova.apache.org/
labels:
- cordova
- cordova-platform
- android
- java
- mobile
- javascript
- nodejs
- hacktoberfest
features:
wiki: false
issues: true
projects: true
enabled_merge_buttons:
squash: true
merge: false
rebase: false
notifications:
commits: commits@cordova.apache.org
issues: issues@cordova.apache.org

View File

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

View File

@@ -27,7 +27,7 @@ jobs:
strategy:
matrix:
node-version: [10.x, 12.x]
node-version: [12.x, 14.x, 16.x]
os: [ubuntu-latest, windows-latest, macos-latest]
steps:

7
.gitignore vendored
View File

@@ -25,14 +25,15 @@ example
/framework/libs
/framework/javadoc-public
/framework/javadoc-private
**/assets/www/cordova.js
/test/.externalNativeBuild
/test/android/gradle
/test/android/gradlew
/test/android/gradlew.bat
/test/androidx/gradle
/test/androidx/gradlew
/test/androidx/gradlew.bat
/test/androidx/cdv-gradle-config.json
/test/assets/www/.tmp*
/test/assets/www/cordova.js

View File

@@ -3,3 +3,4 @@ coverage
test
spec
framework/build
cordova-js-src

View File

@@ -1,5 +1,5 @@
*.properties
bin
templates
gen
proguard-project.txt
spec

65
LICENSE
View File

@@ -200,68 +200,3 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
ADDITIONAL LICENSES:
================================================================================
bin/node_modules/q
================================================================================
Copyright 20092017 Kristopher Michael Kowal. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
================================================================================
bin/node_modules/nopt
================================================================================
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
================================================================================
bin/node_modules/which
================================================================================
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@@ -37,21 +37,7 @@ Cordova Android is an Android application library that allows for Cordova-based
## Cordova Android Developer Tools
We recommend using the [Cordova command-line tool](https://www.npmjs.com/package/cordova) to create projects and be able to easily install plugins.
However, the following scripts can be used instead:
./bin/create [path package activity] ... creates the ./example app or a cordova android project
./bin/check_reqs ....................... checks that your environment is set up for cordova-android development
./bin/update [path] .................... updates an existing cordova-android project to the version of the framework
These commands live in a generated Cordova Android project. Any interactions with the emulator require you to have an AVD defined.
./cordova/clean ........................ cleans the project
./cordova/build ........................ calls `clean` then compiles the project
./cordova/log ........................ streams device or emulator logs to STDOUT
./cordova/run ........................ calls `build` then deploys to a connected Android device. If no Android device is detected, will launch an emulator and deploy to it.
./cordova/version ...................... returns the cordova-android version of the current project
Use the [Cordova command-line tool](https://www.npmjs.com/package/cordova) to create projects and install plugins.
## Using Android Studio

View File

@@ -20,6 +20,148 @@
-->
## Release Notes for Cordova (Android)
### 10.0.0 (Jul 17, 2021)
**Breaking:**
* [GH-1052](https://github.com/apache/cordova-android/pull/1052) feat!: only support `AndroidX`
* [GH-1137](https://github.com/apache/cordova-android/pull/1137) feat!: implement `WebViewAssetLoader`
* [GH-1268](https://github.com/apache/cordova-android/pull/1268) feat!: release build defaults to `aab` package type
* [GH-1182](https://github.com/apache/cordova-android/pull/1182) feat!: bump `target sdk@30` w/ `build-tool@30.0.3`
* [GH-1257](https://github.com/apache/cordova-android/pull/1257) feat!: upgrade `gradle@7.1.1`
* [GH-1197](https://github.com/apache/cordova-android/pull/1197) feat!: upgrade `gradle@6.8.3`
* [GH-1256](https://github.com/apache/cordova-android/pull/1256) feat!: upgrade `kotlin@1.5.20`
* [GH-1204](https://github.com/apache/cordova-android/pull/1204) feat!: upgrade `kotlin@1.4.32`
* [GH-1200](https://github.com/apache/cordova-android/pull/1200) feat!: upgrade `kotlin@1.4.31`
* [GH-1255](https://github.com/apache/cordova-android/pull/1255) feat!: upgrade `android-gradle-plugin@4.2.2`
* [GH-1232](https://github.com/apache/cordova-android/pull/1232) feat!: upgrade `android-gradle-plugin@4.2.1`
* [GH-1198](https://github.com/apache/cordova-android/pull/1198) feat!: upgrade `android-gradle-plugin@4.1.3`
* [GH-1199](https://github.com/apache/cordova-android/pull/1199) feat!: upgrade `Google Services Gradle Plugin@4.3.5`
* [GH-1262](https://github.com/apache/cordova-android/pull/1262) feat!: bump `appcompat@1.3.0`
* [GH-1258](https://github.com/apache/cordova-android/pull/1258) feat!: bump `android.webkit@1.4.0`
* [GH-1252](https://github.com/apache/cordova-android/pull/1252) feat!: drop abandoned `com.github.dcendents:android-maven-gradle-plugin`
* [GH-1212](https://github.com/apache/cordova-android/pull/1212) feat!: unify & fix gradle library/tooling overrides
* [GH-1138](https://github.com/apache/cordova-android/pull/1138) feat(allow-list)!: integrate and refactor core plugin
* [GH-1201](https://github.com/apache/cordova-android/pull/1201) feat!: upgrade jfrog `gradle-bintray-plugin@1.8.5`
* [GH-1279](https://github.com/apache/cordova-android/pull/1279) chore!: bump all dependencies
* [GH-1278](https://github.com/apache/cordova-android/pull/1278) chore!: drop `node` 10 support
* [GH-1205](https://github.com/apache/cordova-android/pull/1205) chore! (`npm`): update all dependencies
* [GH-1274](https://github.com/apache/cordova-android/pull/1274) cleanup!: remove deprecated settings & add todo comments
* [GH-1048](https://github.com/apache/cordova-android/pull/1048) cleanup!: remove `keystore` password prompt
* [GH-1251](https://github.com/apache/cordova-android/pull/1251) cleanup!: drop `jcenter` & update dependencies
* [GH-1269](https://github.com/apache/cordova-android/pull/1269) refactor!: do not copy JS lib to platform project
* [GH-1270](https://github.com/apache/cordova-android/pull/1270) refactor(Api)!: use version from `package.json`
* [GH-1266](https://github.com/apache/cordova-android/pull/1266) refactor(run)!: `run` method
* [GH-1083](https://github.com/apache/cordova-android/pull/1083) refactor!: drop support for `android` SDK tool
* [GH-1100](https://github.com/apache/cordova-android/pull/1100) refactor!: remove most platform binaries
**Features:**
* [GH-1241](https://github.com/apache/cordova-android/pull/1241) feat: remove `java` 1.8 version check
* [GH-1254](https://github.com/apache/cordova-android/pull/1254) feat: support `webkit` version override
* [GH-1229](https://github.com/apache/cordova-android/pull/1229) feat: `CORDOVA_JAVA_HOME` env variable
* [GH-1222](https://github.com/apache/cordova-android/pull/1222) feat: add backwards compatibility mode for `WebViewAssetLoader`
* [GH-1166](https://github.com/apache/cordova-android/pull/1166) feat: overload `PluginEntry` constructor to set onload property
* [GH-1208](https://github.com/apache/cordova-android/pull/1208) feat: allow `appcompat` version to be configurable
* [GH-1047](https://github.com/apache/cordova-android/pull/1047) feat: Deprecated `onRequestPermissionResult` in favour for `onRequestPermissionsResult` for consistency
**Fixes:**
* [GH-1283](https://github.com/apache/cordova-android/pull/1283) fix: add missing apache-license header to `getASPath.bat`
* [GH-1275](https://github.com/apache/cordova-android/pull/1275) fix: add `WebViewAssetloader` to default allow list
* [GH-1216](https://github.com/apache/cordova-android/pull/1216) fix: request focus after custom view hided
* [GH-1264](https://github.com/apache/cordova-android/pull/1264) fix: missing `super.onRequestPermissionsResult` error (`MissingSuperCall`)
* [GH-563](https://github.com/apache/cordova-android/pull/563) fix(build): support tilde expansion on Windows
* [GH-1220](https://github.com/apache/cordova-android/pull/1220) fix(`requirements` check): use regex to get java version from javac output
* [GH-1227](https://github.com/apache/cordova-android/pull/1227) fix(prepare): delete splash screens if none are used
* [GH-1228](https://github.com/apache/cordova-android/pull/1228) fix: java checks
* [GH-1276](https://github.com/apache/cordova-android/pull/1276) fix: remove forced default `gradle.daemon` setting
**Refactors:**
* [GH-1265](https://github.com/apache/cordova-android/pull/1265) refactor: do not infer project root from script location
* [GH-1267](https://github.com/apache/cordova-android/pull/1267) refactor: use target SDK of built APK to determine best emulator
* [GH-1253](https://github.com/apache/cordova-android/pull/1253) refactor: `gradle` cleanup
* [GH-1260](https://github.com/apache/cordova-android/pull/1260) refactor(`check_reqs`): drop `originalError` param from `check_android_target`
* [GH-1246](https://github.com/apache/cordova-android/pull/1246) refactor(`env/java`): improve tests and implementation
**Chores & Cleanup:**
* [GH-1273](https://github.com/apache/cordova-android/pull/1273) chore: remove old `VERSION` file
* [GH-1272](https://github.com/apache/cordova-android/pull/1272) cleanup: delete old ANT & Eclipse files
* [GH-1141](https://github.com/apache/cordova-android/pull/1141) cleanup: remove app cache settings
**CI, Build & Testing:**
* [GH-1218](https://github.com/apache/cordova-android/pull/1218) ci: Add `Node16` to CI matrix
* [GH-1271](https://github.com/apache/cordova-android/pull/1271) build: build `cordova.js` during npm prepare
* [GH-1207](https://github.com/apache/cordova-android/pull/1207) test(`AndroidManifest`): update theme to `Theme.AppCompat.NoActionBar`
* [GH-1263](https://github.com/apache/cordova-android/pull/1263) test(`check_reqs`): do not hardcode `DEFAULT_TARGET_API`
* [GH-1259](https://github.com/apache/cordova-android/pull/1259) test(`prepare`): factor out common vars
### 9.1.0 (Apr 09, 2021)
**Features:**
* [GH-1104](https://github.com/apache/cordova-android/pull/1104) feat: support `gzip` encoding requests & use `GZIPInputStream`
* [GH-1167](https://github.com/apache/cordova-android/pull/1167) feat: handle `intent://` scheme links with `browser_fallback_url` param
* [GH-1179](https://github.com/apache/cordova-android/pull/1179) feat: add `repositories` support
* [GH-1173](https://github.com/apache/cordova-android/pull/1173) feat(android-studio): display app name as project name
* [GH-1113](https://github.com/apache/cordova-android/pull/1113) feat: `webp` support for splashscreen
* [GH-1125](https://github.com/apache/cordova-android/pull/1125) feat(Adb): list `devices` _and_ `emulators` in one go
**Fixes:**
* [GH-1186](https://github.com/apache/cordova-android/pull/1186) fix: copy `repositories.gradle` to project on create
* [GH-1184](https://github.com/apache/cordova-android/pull/1184) fix: unit-test failure
* [GH-733](https://github.com/apache/cordova-android/pull/733) fix(splashscreen): nav & title bar showing in fullscreen mode
* [GH-1157](https://github.com/apache/cordova-android/pull/1157) fix: restore key event handlers when DOM element is fullscreen
* [GH-1073](https://github.com/apache/cordova-android/pull/1073) fix(android): Avoid Crash Report: ConcurrentModificationException
* [GH-1148](https://github.com/apache/cordova-android/pull/1148) fix: add not null checks to prevent running on destroyed activity
* [GH-1091](https://github.com/apache/cordova-android/pull/1091) fix: concurrent modification exception (#924)
* [GH-1153](https://github.com/apache/cordova-android/pull/1153) fix: optional arch parameter
* [GH-1136](https://github.com/apache/cordova-android/pull/1136) fix(prepare): `mapImageResources` always returning `[]`
* [GH-1111](https://github.com/apache/cordova-android/pull/1111) fix(android): allow file access for existing behavior
* [GH-1045](https://github.com/apache/cordova-android/pull/1045) fix: Reflect minimum required NodeJS
* [GH-1084](https://github.com/apache/cordova-android/pull/1084) fix(prepare): fix pattern used to collect image resources
* [GH-1014](https://github.com/apache/cordova-android/pull/1014) fix(`pluginHandlers`): properly check if path is inside another
* [GH-1018](https://github.com/apache/cordova-android/pull/1018) fix: gradle ignore properties
* [GH-1185](https://github.com/apache/cordova-android/pull/1185) fix(regression): Cannot read version of undefined caused by Java refactor
* [GH-1117](https://github.com/apache/cordova-android/pull/1117) fix: allow changing min sdk version
**Refactors:**
* [GH-1101](https://github.com/apache/cordova-android/pull/1101) refactor: unify target resolution for devices & emulators
* [GH-1130](https://github.com/apache/cordova-android/pull/1130) refactor: java checks
* [GH-1099](https://github.com/apache/cordova-android/pull/1099) refactor(`ProjectBuilder`): clean up output file collection code
* [GH-1123](https://github.com/apache/cordova-android/pull/1123) refactor: unify installation on devices & emulators
* [GH-1102](https://github.com/apache/cordova-android/pull/1102) refactor(`check_reqs`): cleanup default Java location detection on **Windows**
* [GH-1103](https://github.com/apache/cordova-android/pull/1103) refactor: do not kill adb on UNIX-like systems
* [GH-1086](https://github.com/apache/cordova-android/pull/1086) refactor(retry): simplify retryPromise using modern JS
* [GH-1085](https://github.com/apache/cordova-android/pull/1085) refactor(utils): reduce number of utils
* [GH-1046](https://github.com/apache/cordova-android/pull/1046) refactor: Stop suppressing un-needed TruelyRandom lints
* [GH-1016](https://github.com/apache/cordova-android/pull/1016) refactor: save `ProjectBuilder` instance in Api instance
* [GH-1108](https://github.com/apache/cordova-android/pull/1108) refactor: remove copied Adb.install from `emulator.install`
**Chores:**
* [GH-1196](https://github.com/apache/cordova-android/pull/1196) chore: add missing header license
* chore(asf): Update GitHub repo metadata
* [GH-1183](https://github.com/apache/cordova-android/pull/1183) chore: rebuilt package-lock
* [GH-1015](https://github.com/apache/cordova-android/pull/1015) chore: remove unnecessary stuff
* [GH-1081](https://github.com/apache/cordova-android/pull/1081) chore(pkg): remove deprecated `no-op` field `"engineStrict"`
* [GH-1019](https://github.com/apache/cordova-android/pull/1019) chore: remove unused `emulator.create_image` and its dependencies
**Tests & CI:**
* [GH-1017](https://github.com/apache/cordova-android/pull/1017) test(java): fix, improve and move clean script
* [GH-1012](https://github.com/apache/cordova-android/pull/1012) test: fix missing stack traces in jasmine output
* [GH-1013](https://github.com/apache/cordova-android/pull/1013) test(`pluginHandlers/common`): better setup & teardown
* [GH-1094](https://github.com/apache/cordova-android/pull/1094) test: fix unit test failures for certain random orders
* [GH-1094](https://github.com/apache/cordova-android/pull/1094) test: ensure single top-level describe block in test file
* [GH-1129](https://github.com/apache/cordova-android/pull/1129) test(java): remove duplicate code in `BackButtonMultipageTest`
* [GH-975](https://github.com/apache/cordova-android/pull/975) ci: Added Node 14.x
### 9.0.0 (Jun 23, 2020)
* [GH-1005](https://github.com/apache/cordova-android/pull/1005) chore: set AndroidX off by default

View File

@@ -1 +0,0 @@
9.0.0-dev

View File

@@ -1,26 +0,0 @@
:: Licensed to the Apache Software Foundation (ASF) under one
:: or more contributor license agreements. See the NOTICE file
:: distributed with this work for additional information
:: regarding copyright ownership. The ASF licenses this file
:: to you under the Apache License, Version 2.0 (the
:: "License"); you may not use this file except in compliance
:: with the License. You may obtain a copy of the License at
::
:: http://www.apache.org/licenses/LICENSE-2.0
::
:: Unless required by applicable law or agreed to in writing,
:: software distributed under the License is distributed on an
:: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
:: KIND, either express or implied. See the License for the
:: specific language governing permissions and limitations
:: under the License.
@ECHO OFF
SET script_path="%~dp0android_sdk_version"
IF EXIST %script_path% (
node %script_path% %*
) ELSE (
ECHO.
ECHO ERROR: Could not find 'android_sdk_version' script in 'bin' folder, aborting...>&2
EXIT /B 1
)

View File

@@ -1,26 +0,0 @@
:: Licensed to the Apache Software Foundation (ASF) under one
:: or more contributor license agreements. See the NOTICE file
:: distributed with this work for additional information
:: regarding copyright ownership. The ASF licenses this file
:: to you under the Apache License, Version 2.0 (the
:: "License"); you may not use this file except in compliance
:: with the License. You may obtain a copy of the License at
::
:: http://www.apache.org/licenses/LICENSE-2.0
::
:: Unless required by applicable law or agreed to in writing,
:: software distributed under the License is distributed on an
:: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
:: KIND, either express or implied. See the License for the
:: specific language governing permissions and limitations
:: under the License.
@ECHO OFF
SET script_path="%~dp0check_reqs"
IF EXIST %script_path% (
node %script_path% %*
) ELSE (
ECHO.
ECHO ERROR: Could not find 'check_reqs' script in 'bin' folder, aborting...>&2
EXIT /B 1
)

View File

@@ -1,61 +0,0 @@
#!/usr/bin/env node
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
var path = require('path');
var ConfigParser = require('cordova-common').ConfigParser;
var Api = require('./templates/cordova/Api');
var argv = require('nopt')({
help: Boolean,
cli: Boolean,
shared: Boolean,
link: Boolean,
'activity-name': [String, undefined]
}, { d: '--verbose' });
if (argv.help || argv.argv.remain.length === 0) {
console.log('Usage: ' + path.relative(process.cwd(), path.join(__dirname, 'create')) + ' <path_to_new_project> <package_name> <project_name> [<template_path>] [--activity-name <activity_name>] [--link]');
console.log(' <path_to_new_project>: Path to your new Cordova Android project');
console.log(' <package_name>: Package name, following reverse-domain style convention');
console.log(' <project_name>: Project name');
console.log(' <template_path>: Path to a custom application template to use');
console.log(' --activity-name <activity_name>: Activity name');
console.log(' --link will use the CordovaLib project directly instead of making a copy.');
process.exit(1);
}
var config = new ConfigParser(path.resolve(__dirname, 'templates/project/res/xml/config.xml'));
if (argv.argv.remain[1]) config.setPackageName(argv.argv.remain[1]);
if (argv.argv.remain[2]) config.setName(argv.argv.remain[2]);
if (argv['activity-name']) config.setName(argv['activity-name']);
var options = {
link: argv.link || argv.shared,
customTemplate: argv.argv.remain[3],
activityName: argv['activity-name']
};
require('./templates/cordova/loggingHelper').adjustLoggerLevel(argv);
Api.createPlatform(argv.argv.remain[0], config, options).catch(err => {
console.error(err);
process.exitCode = 1;
});

View File

@@ -1,26 +0,0 @@
:: Licensed to the Apache Software Foundation (ASF) under one
:: or more contributor license agreements. See the NOTICE file
:: distributed with this work for additional information
:: regarding copyright ownership. The ASF licenses this file
:: to you under the Apache License, Version 2.0 (the
:: "License"); you may not use this file except in compliance
:: with the License. You may obtain a copy of the License at
::
:: http://www.apache.org/licenses/LICENSE-2.0
::
:: Unless required by applicable law or agreed to in writing,
:: software distributed under the License is distributed on an
:: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
:: KIND, either express or implied. See the License for the
:: specific language governing permissions and limitations
:: under the License.
@ECHO OFF
SET script_path="%~dp0create"
IF EXIST %script_path% (
node %script_path% %*
) ELSE (
ECHO.
ECHO ERROR: Could not find 'create' script in 'bin' folder, aborting...>&2
EXIT /B 1
)

View File

@@ -1,51 +0,0 @@
#!/usr/bin/env node
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
var args = process.argv;
var Api = require('./Api');
var nopt = require('nopt');
var path = require('path');
// Support basic help commands
if (['--help', '/?', '-h', 'help', '-help', '/help'].indexOf(args[2]) >= 0) {
require('./lib/build').help();
}
// Do some basic argument parsing
var buildOpts = nopt({
verbose: Boolean,
silent: Boolean,
debug: Boolean,
release: Boolean,
nobuild: Boolean,
buildConfig: path
}, { d: '--verbose' });
// Make buildOptions compatible with PlatformApi build method spec
buildOpts.argv = buildOpts.argv.original;
require('./loggingHelper').adjustLoggerLevel(buildOpts);
new Api().build(buildOpts)
.catch(function (err) {
console.error(err.stack);
process.exit(2);
});

View File

@@ -1,26 +0,0 @@
:: Licensed to the Apache Software Foundation (ASF) under one
:: or more contributor license agreements. See the NOTICE file
:: distributed with this work for additional information
:: regarding copyright ownership. The ASF licenses this file
:: to you under the Apache License, Version 2.0 (the
:: "License"); you may not use this file except in compliance
:: with the License. You may obtain a copy of the License at
::
:: http://www.apache.org/licenses/LICENSE-2.0
::
:: Unless required by applicable law or agreed to in writing,
:: software distributed under the License is distributed on an
:: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
:: KIND, either express or implied. See the License for the
:: specific language governing permissions and limitations
:: under the License.
@ECHO OFF
SET script_path="%~dp0build"
IF EXIST %script_path% (
node %script_path% %*
) ELSE (
ECHO.
ECHO ERROR: Could not find 'build' script in 'cordova' folder, aborting...>&2
EXIT /B 1
)

View File

@@ -1,51 +0,0 @@
#!/usr/bin/env node
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
var Api = require('./Api');
var path = require('path');
var nopt = require('nopt');
// Support basic help commands
if (['--help', '/?', '-h', 'help', '-help', '/help'].indexOf(process.argv[2]) >= 0) {
console.log('Usage: ' + path.relative(process.cwd(), process.argv[1]));
console.log('Cleans the project directory.');
process.exit(0);
}
// Do some basic argument parsing
var opts = nopt({
verbose: Boolean,
silent: Boolean
}, { d: '--verbose' });
// Make buildOptions compatible with PlatformApi clean method spec
opts.argv = opts.argv.original;
// Skip cleaning prepared files when not invoking via cordova CLI.
opts.noPrepare = true;
require('./loggingHelper').adjustLoggerLevel(opts);
new Api().clean(opts)
.catch(function (err) {
console.error(err.stack);
process.exit(2);
});

View File

@@ -1,26 +0,0 @@
:: Licensed to the Apache Software Foundation (ASF) under one
:: or more contributor license agreements. See the NOTICE file
:: distributed with this work for additional information
:: regarding copyright ownership. The ASF licenses this file
:: to you under the Apache License, Version 2.0 (the
:: "License"); you may not use this file except in compliance
:: with the License. You may obtain a copy of the License at
::
:: http://www.apache.org/licenses/LICENSE-2.0
::
:: Unless required by applicable law or agreed to in writing,
:: software distributed under the License is distributed on an
:: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
:: KIND, either express or implied. See the License for the
:: specific language governing permissions and limitations
:: under the License.
@ECHO OFF
SET script_path="%~dp0clean"
IF EXIST %script_path% (
node %script_path% %*
) ELSE (
ECHO.
ECHO ERROR: Could not find 'clean' script in 'cordova' folder, aborting...>&2
EXIT /B 1
)

View File

@@ -1,109 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
const execa = require('execa');
var build = require('./build');
var path = require('path');
var Adb = require('./Adb');
var AndroidManifest = require('./AndroidManifest');
var CordovaError = require('cordova-common').CordovaError;
var events = require('cordova-common').events;
/**
* Returns a promise for the list of the device ID's found
* @param lookHarder When true, try restarting adb if no devices are found.
*/
module.exports.list = function (lookHarder) {
return Adb.devices().then(function (list) {
if (list.length === 0 && lookHarder) {
// adb kill-server doesn't seem to do the trick.
// Could probably find a x-platform version of killall, but I'm not actually
// sure that this scenario even happens on non-OSX machines.
return execa('killall', ['adb']).then(function () {
events.emit('verbose', 'Restarting adb to see if more devices are detected.');
return Adb.devices();
}, function () {
// For non-killall OS's.
return list;
});
}
return list;
});
};
module.exports.resolveTarget = function (target) {
return this.list(true).then(function (device_list) {
if (!device_list || !device_list.length) {
return Promise.reject(new CordovaError('Failed to deploy to device, no devices found.'));
}
// default device
target = target || device_list[0];
if (device_list.indexOf(target) < 0) {
return Promise.reject(new CordovaError('ERROR: Unable to find target \'' + target + '\'.'));
}
return build.detectArchitecture(target).then(function (arch) {
return { target: target, arch: arch, isEmulator: false };
});
});
};
/*
* Installs a previously built application on the device
* and launches it.
* Returns a promise.
*/
module.exports.install = function (target, buildResults) {
return Promise.resolve().then(function () {
if (target && typeof target === 'object') {
return target;
}
return module.exports.resolveTarget(target);
}).then(function (resolvedTarget) {
var apk_path = build.findBestApkForArchitecture(buildResults, resolvedTarget.arch);
var manifest = new AndroidManifest(path.join(__dirname, '../../app/src/main/AndroidManifest.xml'));
var pkgName = manifest.getPackageId();
var launchName = pkgName + '/.' + manifest.getActivity().getName();
events.emit('log', 'Using apk: ' + apk_path);
events.emit('log', 'Package name: ' + pkgName);
return Adb.install(resolvedTarget.target, apk_path, { replace: true }).catch(function (error) {
// CB-9557 CB-10157 only uninstall and reinstall app if the one that
// is already installed on device was signed w/different certificate
if (!/INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES/.test(error.toString())) { throw error; }
events.emit('warn', 'Uninstalling app from device and reinstalling it again because the ' +
'installed app already signed with different key');
// This promise is always resolved, even if 'adb uninstall' fails to uninstall app
// or the app doesn't installed at all, so no error catching needed.
return Adb.uninstall(resolvedTarget.target, pkgName).then(function () {
return Adb.install(resolvedTarget.target, apk_path, { replace: true });
});
}).then(function () {
// unlock screen
return Adb.shell(resolvedTarget.target, 'input keyevent 82');
}).then(function () {
return Adb.start(resolvedTarget.target, launchName);
}).then(function () {
events.emit('log', 'LAUNCH SUCCESS');
});
});
};

View File

@@ -1,517 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
const execa = require('execa');
const fs = require('fs-extra');
var android_versions = require('android-versions');
var retry = require('./retry');
var build = require('./build');
var path = require('path');
var Adb = require('./Adb');
var AndroidManifest = require('./AndroidManifest');
var events = require('cordova-common').events;
var CordovaError = require('cordova-common').CordovaError;
var android_sdk = require('./android_sdk');
var check_reqs = require('./check_reqs');
var which = require('which');
var os = require('os');
// constants
const ONE_SECOND = 1000; // in milliseconds
const ONE_MINUTE = 60 * ONE_SECOND; // in milliseconds
const INSTALL_COMMAND_TIMEOUT = 5 * ONE_MINUTE; // in milliseconds
const NUM_INSTALL_RETRIES = 3;
const CHECK_BOOTED_INTERVAL = 3 * ONE_SECOND; // in milliseconds
const EXEC_KILL_SIGNAL = 'SIGKILL';
function forgivingWhichSync (cmd) {
const whichResult = which.sync(cmd, { nothrow: true });
// On null, returns empty string to maintain backwards compatibility
// realpathSync follows symlinks
return whichResult === null ? '' : fs.realpathSync(whichResult);
}
module.exports.list_images_using_avdmanager = function () {
return execa('avdmanager', ['list', 'avd']).then(({ stdout: output }) => {
var response = output.split('\n');
var emulator_list = [];
for (var i = 1; i < response.length; i++) {
// To return more detailed information use img_obj
var img_obj = {};
if (response[i].match(/Name:\s/)) {
img_obj.name = response[i].split('Name: ')[1].replace('\r', '');
if (response[i + 1].match(/Device:\s/)) {
i++;
img_obj.device = response[i].split('Device: ')[1].replace('\r', '');
}
if (response[i + 1].match(/Path:\s/)) {
i++;
img_obj.path = response[i].split('Path: ')[1].replace('\r', '');
}
if (response[i + 1].match(/Target:\s/)) {
i++;
if (response[i + 1].match(/ABI:\s/)) {
img_obj.abi = response[i + 1].split('ABI: ')[1].replace('\r', '');
}
// This next conditional just aims to match the old output of `android list avd`
// We do so so that we don't have to change the logic when parsing for the
// best emulator target to spawn (see below in `best_image`)
// This allows us to transitionally support both `android` and `avdmanager` binaries,
// depending on what SDK version the user has
if (response[i + 1].match(/Based\son:\s/)) {
img_obj.target = response[i + 1].split('Based on:')[1];
if (img_obj.target.match(/Tag\/ABI:\s/)) {
img_obj.target = img_obj.target.split('Tag/ABI:')[0].replace('\r', '').trim();
if (img_obj.target.indexOf('(') > -1) {
img_obj.target = img_obj.target.substr(0, img_obj.target.indexOf('(') - 1).trim();
}
}
var version_string = img_obj.target.replace(/Android\s+/, '');
var api_level = android_sdk.version_string_to_api_level[version_string];
if (api_level) {
img_obj.target += ' (API level ' + api_level + ')';
}
}
}
if (response[i + 1].match(/Skin:\s/)) {
i++;
img_obj.skin = response[i].split('Skin: ')[1].replace('\r', '');
}
emulator_list.push(img_obj);
}
/* To just return a list of names use this
if (response[i].match(/Name:\s/)) {
emulator_list.push(response[i].split('Name: ')[1].replace('\r', '');
} */
}
return emulator_list;
});
};
module.exports.list_images_using_android = function () {
return execa('android', ['list', 'avd']).then(({ stdout: output }) => {
var response = output.split('\n');
var emulator_list = [];
for (var i = 1; i < response.length; i++) {
// To return more detailed information use img_obj
var img_obj = {};
if (response[i].match(/Name:\s/)) {
img_obj.name = response[i].split('Name: ')[1].replace('\r', '');
if (response[i + 1].match(/Device:\s/)) {
i++;
img_obj.device = response[i].split('Device: ')[1].replace('\r', '');
}
if (response[i + 1].match(/Path:\s/)) {
i++;
img_obj.path = response[i].split('Path: ')[1].replace('\r', '');
}
if (response[i + 1].match(/\(API\slevel\s/) || (response[i + 2] && response[i + 2].match(/\(API\slevel\s/))) {
i++;
var secondLine = response[i + 1].match(/\(API\slevel\s/) ? response[i + 1] : '';
img_obj.target = (response[i] + secondLine).split('Target: ')[1].replace('\r', '');
}
if (response[i + 1].match(/ABI:\s/)) {
i++;
img_obj.abi = response[i].split('ABI: ')[1].replace('\r', '');
}
if (response[i + 1].match(/Skin:\s/)) {
i++;
img_obj.skin = response[i].split('Skin: ')[1].replace('\r', '');
}
emulator_list.push(img_obj);
}
/* To just return a list of names use this
if (response[i].match(/Name:\s/)) {
emulator_list.push(response[i].split('Name: ')[1].replace('\r', '');
} */
}
return emulator_list;
});
};
/**
* Returns a Promise for a list of emulator images in the form of objects
* {
name : <emulator_name>,
device : <device>,
path : <path_to_emulator_image>,
target : <api_target>,
abi : <cpu>,
skin : <skin>
}
*/
module.exports.list_images = function () {
return Promise.resolve().then(function () {
if (forgivingWhichSync('avdmanager')) {
return module.exports.list_images_using_avdmanager();
} else if (forgivingWhichSync('android')) {
return module.exports.list_images_using_android();
} else {
return Promise.reject(new CordovaError('Could not find either `android` or `avdmanager` on your $PATH! Are you sure the Android SDK is installed and available?'));
}
}).then(function (avds) {
// In case we're missing the Android OS version string from the target description, add it.
return avds.map(function (avd) {
if (avd.target && avd.target.indexOf('Android API') > -1 && avd.target.indexOf('API level') < 0) {
var api_level = avd.target.match(/\d+/);
if (api_level) {
var level = android_versions.get(api_level);
if (level) {
avd.target = 'Android ' + level.semver + ' (API level ' + api_level + ')';
}
}
}
return avd;
});
});
};
/**
* Will return the closest avd to the projects target
* or undefined if no avds exist.
* Returns a promise.
*/
module.exports.best_image = function () {
return this.list_images().then(function (images) {
// Just return undefined if there is no images
if (images.length === 0) return;
var closest = 9999;
var best = images[0];
var project_target = parseInt(check_reqs.get_target().replace('android-', ''));
for (var i in images) {
var target = images[i].target;
if (target && target.indexOf('API level') > -1) {
var num = parseInt(target.split('(API level ')[1].replace(')', ''));
if (num === project_target) {
return images[i];
} else if (project_target - num < closest && project_target > num) {
closest = project_target - num;
best = images[i];
}
}
}
return best;
});
};
// Returns a promise.
module.exports.list_started = function () {
return Adb.devices({ emulators: true });
};
// Returns a promise.
// TODO: we should remove this, there's a more robust method under android_sdk.js
module.exports.list_targets = function () {
return execa('android', ['list', 'targets'], { cwd: os.tmpdir() }).then(({ stdout: output }) => {
var target_out = output.split('\n');
var targets = [];
for (var i = target_out.length; i >= 0; i--) {
if (target_out[i].match(/id:/)) {
targets.push(targets[i].split(' ')[1]);
}
}
return targets;
});
};
/*
* Gets unused port for android emulator, between 5554 and 5584
* Returns a promise.
*/
module.exports.get_available_port = function () {
var self = this;
return self.list_started().then(function (emulators) {
for (var p = 5584; p >= 5554; p -= 2) {
if (emulators.indexOf('emulator-' + p) === -1) {
events.emit('verbose', 'Found available port: ' + p);
return p;
}
}
throw new CordovaError('Could not find an available avd port');
});
};
/*
* Starts an emulator with the given ID,
* and returns the started ID of that emulator.
* If no ID is given it will use the first image available,
* if no image is available it will error out (maybe create one?).
* If no boot timeout is given or the value is negative it will wait forever for
* the emulator to boot
*
* Returns a promise.
*/
module.exports.start = function (emulator_ID, boot_timeout) {
var self = this;
return Promise.resolve().then(function () {
if (emulator_ID) return Promise.resolve(emulator_ID);
return self.best_image().then(function (best) {
if (best && best.name) {
events.emit('warn', 'No emulator specified, defaulting to ' + best.name);
return best.name;
}
var androidCmd = check_reqs.getAbsoluteAndroidCmd();
return Promise.reject(new CordovaError('No emulator images (avds) found.\n' +
'1. Download desired System Image by running: ' + androidCmd + ' sdk\n' +
'2. Create an AVD by running: ' + androidCmd + ' avd\n' +
'HINT: For a faster emulator, use an Intel System Image and install the HAXM device driver\n'));
});
}).then(function (emulatorId) {
return self.get_available_port().then(function (port) {
// Figure out the directory the emulator binary runs in, and set the cwd to that directory.
// Workaround for https://code.google.com/p/android/issues/detail?id=235461
var emulator_dir = path.dirname(which.sync('emulator'));
var args = ['-avd', emulatorId, '-port', port];
// Don't wait for it to finish, since the emulator will probably keep running for a long time.
execa('emulator', args, { stdio: 'inherit', detached: true, cwd: emulator_dir })
.unref();
// wait for emulator to start
events.emit('log', 'Waiting for emulator to start...');
return self.wait_for_emulator(port);
});
}).then(function (emulatorId) {
if (!emulatorId) { return Promise.reject(new CordovaError('Failed to start emulator')); }
// wait for emulator to boot up
process.stdout.write('Waiting for emulator to boot (this may take a while)...');
return self.wait_for_boot(emulatorId, boot_timeout).then(function (success) {
if (success) {
events.emit('log', 'BOOT COMPLETE');
// unlock screen
return Adb.shell(emulatorId, 'input keyevent 82').then(function () {
// return the new emulator id for the started emulators
return emulatorId;
});
} else {
// We timed out waiting for the boot to happen
return null;
}
});
});
};
/*
* Waits for an emulator to boot on a given port.
* Returns this emulator's ID in a promise.
*/
module.exports.wait_for_emulator = function (port) {
var self = this;
return Promise.resolve().then(function () {
var emulator_id = 'emulator-' + port;
return Adb.shell(emulator_id, 'getprop dev.bootcomplete').then(function (output) {
if (output.indexOf('1') >= 0) {
return emulator_id;
}
return self.wait_for_emulator(port);
}, function (error) {
if ((error && error.message &&
(error.message.indexOf('not found') > -1)) ||
(error.message.indexOf('device offline') > -1) ||
(error.message.indexOf('device still connecting') > -1) ||
(error.message.indexOf('device still authorizing') > -1)) {
// emulator not yet started, continue waiting
return self.wait_for_emulator(port);
} else {
// something unexpected has happened
throw error;
}
});
});
};
/*
* Waits for the core android process of the emulator to start. Returns a
* promise that resolves to a boolean indicating success. Not specifying a
* time_remaining or passing a negative value will cause it to wait forever
*/
module.exports.wait_for_boot = function (emulator_id, time_remaining) {
var self = this;
return Adb.shell(emulator_id, 'getprop sys.boot_completed').then(function (output) {
if (output.match(/1/)) {
return true;
} else if (time_remaining === 0) {
return false;
} else {
process.stdout.write('.');
return new Promise(resolve => {
const delay = time_remaining < CHECK_BOOTED_INTERVAL ? time_remaining : CHECK_BOOTED_INTERVAL;
setTimeout(() => {
const updated_time = time_remaining >= 0 ? Math.max(time_remaining - CHECK_BOOTED_INTERVAL, 0) : time_remaining;
resolve(self.wait_for_boot(emulator_id, updated_time));
}, delay);
});
}
});
};
/*
* Create avd
* TODO : Enter the stdin input required to complete the creation of an avd.
* Returns a promise.
*/
module.exports.create_image = function (name, target) {
console.log('Creating new avd named ' + name);
if (target) {
return execa('android', ['create', 'avd', '--name', name, '--target', target]).then(null, function (error) {
console.error('ERROR : Failed to create emulator image : ');
console.error(' Do you have the latest android targets including ' + target + '?');
console.error(error.message);
});
} else {
console.log('WARNING : Project target not found, creating avd with a different target but the project may fail to install.');
// TODO: there's a more robust method for finding targets in android_sdk.js
return execa('android', ['create', 'avd', '--name', name, '--target', this.list_targets()[0]]).then(function () {
// TODO: This seems like another error case, even though it always happens.
console.error('ERROR : Unable to create an avd emulator, no targets found.');
console.error('Ensure you have targets available by running the "android" command');
return Promise.reject(new CordovaError());
}, function (error) {
console.error('ERROR : Failed to create emulator image : ');
console.error(error.message);
});
}
};
module.exports.resolveTarget = function (target) {
return this.list_started().then(function (emulator_list) {
if (emulator_list.length < 1) {
return Promise.reject(new CordovaError('No running Android emulators found, please start an emulator before deploying your project.'));
}
// default emulator
target = target || emulator_list[0];
if (emulator_list.indexOf(target) < 0) {
return Promise.reject(new CordovaError('Unable to find target \'' + target + '\'. Failed to deploy to emulator.'));
}
return build.detectArchitecture(target).then(function (arch) {
return { target: target, arch: arch, isEmulator: true };
});
});
};
/*
* Installs a previously built application on the emulator and launches it.
* If no target is specified, then it picks one.
* If no started emulators are found, error out.
* Returns a promise.
*/
module.exports.install = function (givenTarget, buildResults) {
var target;
// We need to find the proper path to the Android Manifest
const manifestPath = path.join(__dirname, '..', '..', 'app', 'src', 'main', 'AndroidManifest.xml');
const manifest = new AndroidManifest(manifestPath);
const pkgName = manifest.getPackageId();
// resolve the target emulator
return Promise.resolve().then(function () {
if (givenTarget && typeof givenTarget === 'object') {
return givenTarget;
} else {
return module.exports.resolveTarget(givenTarget);
}
// set the resolved target
}).then(function (resolvedTarget) {
target = resolvedTarget;
// install the app
}).then(function () {
// This promise is always resolved, even if 'adb uninstall' fails to uninstall app
// or the app doesn't installed at all, so no error catching needed.
return Promise.resolve().then(function () {
var apk_path = build.findBestApkForArchitecture(buildResults, target.arch);
var execOptions = {
cwd: os.tmpdir(),
timeout: INSTALL_COMMAND_TIMEOUT, // in milliseconds
killSignal: EXEC_KILL_SIGNAL
};
events.emit('log', 'Using apk: ' + apk_path);
events.emit('log', 'Package name: ' + pkgName);
events.emit('verbose', 'Installing app on emulator...');
// A special function to call adb install in specific environment w/ specific options.
// Introduced as a part of fix for http://issues.apache.org/jira/browse/CB-9119
// to workaround sporadic emulator hangs
function adbInstallWithOptions (target, apk, opts) {
events.emit('verbose', 'Installing apk ' + apk + ' on ' + target + '...');
const args = ['-s', target, 'install', '-r', apk];
return execa('adb', args, opts).then(({ stdout }) => {
// adb does not return an error code even if installation fails. Instead it puts a specific
// message to stdout, so we have to use RegExp matching to detect installation failure.
if (/Failure/.test(stdout)) {
if (stdout.match(/INSTALL_PARSE_FAILED_NO_CERTIFICATES/)) {
stdout += 'Sign the build using \'-- --keystore\' or \'--buildConfig\'' +
' or sign and deploy the unsigned apk manually using Android tools.';
} else if (stdout.match(/INSTALL_FAILED_VERSION_DOWNGRADE/)) {
stdout += 'You\'re trying to install apk with a lower versionCode that is already installed.' +
'\nEither uninstall an app or increment the versionCode.';
}
throw new CordovaError('Failed to install apk to emulator: ' + stdout);
}
});
}
function installPromise () {
return adbInstallWithOptions(target.target, apk_path, execOptions).catch(function (error) {
// CB-9557 CB-10157 only uninstall and reinstall app if the one that
// is already installed on device was signed w/different certificate
if (!/INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES/.test(error.toString())) { throw error; }
events.emit('warn', 'Uninstalling app from device and reinstalling it because the ' +
'currently installed app was signed with different key');
// This promise is always resolved, even if 'adb uninstall' fails to uninstall app
// or the app doesn't installed at all, so no error catching needed.
return Adb.uninstall(target.target, pkgName).then(function () {
return adbInstallWithOptions(target.target, apk_path, execOptions);
});
});
}
return retry.retryPromise(NUM_INSTALL_RETRIES, installPromise).then(function (output) {
events.emit('log', 'INSTALL SUCCESS');
});
});
// unlock screen
}).then(function () {
events.emit('verbose', 'Unlocking screen...');
return Adb.shell(target.target, 'input keyevent 82');
}).then(function () {
Adb.start(target.target, pkgName + '/.' + manifest.getActivity().getName());
// report success or failure
}).then(function (output) {
events.emit('log', 'LAUNCH SUCCESS');
});
};

View File

@@ -1,3 +0,0 @@
@ECHO OFF
for /f "tokens=2*" %%a in ('REG QUERY "HKEY_LOCAL_MACHINE\SOFTWARE\Android Studio" /v Path') do set "ASPath=%%~b"
ECHO %ASPath%

View File

@@ -1,42 +0,0 @@
#!/usr/bin/env node
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
var device = require('./device');
var args = process.argv;
if (args.length > 2) {
var install_target;
if (args[2].substring(0, 9) === '--target=') {
install_target = args[2].substring(9, args[2].length);
device.install(install_target).catch(function (err) {
console.error('ERROR: ' + err);
process.exit(2);
});
} else {
console.error('ERROR : argument \'' + args[2] + '\' not recognized.');
process.exit(2);
}
} else {
device.install().catch(function (err) {
console.error('ERROR: ' + err);
process.exit(2);
});
}

View File

@@ -1,26 +0,0 @@
:: Licensed to the Apache Software Foundation (ASF) under one
:: or more contributor license agreements. See the NOTICE file
:: distributed with this work for additional information
:: regarding copyright ownership. The ASF licenses this file
:: to you under the Apache License, Version 2.0 (the
:: "License"); you may not use this file except in compliance
:: with the License. You may obtain a copy of the License at
::
:: http://www.apache.org/licenses/LICENSE-2.0
::
:: Unless required by applicable law or agreed to in writing,
:: software distributed under the License is distributed on an
:: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
:: KIND, either express or implied. See the License for the
:: specific language governing permissions and limitations
:: under the License.
@ECHO OFF
SET script_path="%~dp0install-device"
IF EXIST %script_path% (
node %script_path% %*
) ELSE (
ECHO.
ECHO ERROR: Could not find 'install-device' script in 'cordova\lib' folder, aborting...>&2
EXIT /B 1
)

View File

@@ -1,38 +0,0 @@
#!/usr/bin/env node
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
var emulator = require('./emulator');
var args = process.argv;
var install_target;
if (args.length > 2) {
if (args[2].substring(0, 9) === '--target=') {
install_target = args[2].substring(9, args[2].length);
} else {
console.error('ERROR : argument \'' + args[2] + '\' not recognized.');
process.exit(2);
}
}
emulator.install(install_target).catch(function (err) {
console.error('ERROR: ' + err);
process.exit(2);
});

View File

@@ -1,26 +0,0 @@
:: Licensed to the Apache Software Foundation (ASF) under one
:: or more contributor license agreements. See the NOTICE file
:: distributed with this work for additional information
:: regarding copyright ownership. The ASF licenses this file
:: to you under the Apache License, Version 2.0 (the
:: "License"); you may not use this file except in compliance
:: with the License. You may obtain a copy of the License at
::
:: http://www.apache.org/licenses/LICENSE-2.0
::
:: Unless required by applicable law or agreed to in writing,
:: software distributed under the License is distributed on an
:: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
:: KIND, either express or implied. See the License for the
:: specific language governing permissions and limitations
:: under the License.
@ECHO OFF
SET script_path="%~dp0install-emulator"
IF EXIST %script_path% (
node %script_path% %*
) ELSE (
ECHO.
ECHO ERROR: Could not find 'install-emulator' script in 'cordova\lib' folder, aborting...>&2
EXIT /B 1
)

View File

@@ -1,26 +0,0 @@
:: Licensed to the Apache Software Foundation (ASF) under one
:: or more contributor license agreements. See the NOTICE file
:: distributed with this work for additional information
:: regarding copyright ownership. The ASF licenses this file
:: to you under the Apache License, Version 2.0 (the
:: "License"); you may not use this file except in compliance
:: with the License. You may obtain a copy of the License at
::
:: http://www.apache.org/licenses/LICENSE-2.0
::
:: Unless required by applicable law or agreed to in writing,
:: software distributed under the License is distributed on an
:: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
:: KIND, either express or implied. See the License for the
:: specific language governing permissions and limitations
:: under the License.
@ECHO OFF
SET script_path="%~dp0list-devices"
IF EXIST %script_path% (
node %script_path% %*
) ELSE (
ECHO.
ECHO ERROR: Could not find 'list-devices' script in 'cordova\lib' folder, aborting...>&2
EXIT /B 1
)

View File

@@ -1,26 +0,0 @@
:: Licensed to the Apache Software Foundation (ASF) under one
:: or more contributor license agreements. See the NOTICE file
:: distributed with this work for additional information
:: regarding copyright ownership. The ASF licenses this file
:: to you under the Apache License, Version 2.0 (the
:: "License"); you may not use this file except in compliance
:: with the License. You may obtain a copy of the License at
::
:: http://www.apache.org/licenses/LICENSE-2.0
::
:: Unless required by applicable law or agreed to in writing,
:: software distributed under the License is distributed on an
:: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
:: KIND, either express or implied. See the License for the
:: specific language governing permissions and limitations
:: under the License.
@ECHO OFF
SET script_path="%~dp0list-emulator-images"
IF EXIST %script_path% (
node %script_path% %*
) ELSE (
ECHO.
ECHO ERROR: Could not find 'list-emulator-images' script in 'cordova\lib' folder, aborting...>&2
EXIT /B 1
)

View File

@@ -1,34 +0,0 @@
#!/usr/bin/env node
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
var emulators = require('./emulator');
// Usage support for when args are given
require('./check_reqs').check_android().then(function () {
emulators.list_started().then(function (emulator_list) {
emulator_list && emulator_list.forEach(function (emu) {
console.log(emu);
});
}, function (err) {
console.error('ERROR: ' + err);
process.exit(2);
});
});

View File

@@ -1,26 +0,0 @@
:: Licensed to the Apache Software Foundation (ASF) under one
:: or more contributor license agreements. See the NOTICE file
:: distributed with this work for additional information
:: regarding copyright ownership. The ASF licenses this file
:: to you under the Apache License, Version 2.0 (the
:: "License"); you may not use this file except in compliance
:: with the License. You may obtain a copy of the License at
::
:: http://www.apache.org/licenses/LICENSE-2.0
::
:: Unless required by applicable law or agreed to in writing,
:: software distributed under the License is distributed on an
:: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
:: KIND, either express or implied. See the License for the
:: specific language governing permissions and limitations
:: under the License.
@ECHO OFF
SET script_path="%~dp0list-started-emulators"
IF EXIST %script_path% (
node %script_path% %*
) ELSE (
ECHO.
ECHO ERROR: Could not find 'list-started-emulators' script in 'cordova\lib' folder, aborting...>&2
EXIT /B 1
)

View File

@@ -1,45 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
var path = require('path');
var os = require('os');
var execa = require('execa');
var ROOT = path.join(__dirname, '..', '..');
/*
* Starts running logcat in the shell.
* Returns a promise.
*/
module.exports.run = function () {
var adb = execa('adb', ['logcat'], { cwd: os.tmpdir(), stderr: 'inherit' });
adb.stdout.on('data', function (data) {
var lines = data ? data.toString().split('\n') : [];
var out = lines.filter(function (x) { return x.indexOf('nativeGetEnabledTags') < 0; });
console.log(out.join('\n'));
});
return adb;
};
module.exports.help = function () {
console.log('Usage: ' + path.relative(process.cwd(), path.join(ROOT, 'cordova', 'log')));
console.log('Gives the logcat output on the command line.');
process.exit(0);
};

View File

@@ -1,137 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
var path = require('path');
var emulator = require('./emulator');
var device = require('./device');
var PackageType = require('./PackageType');
const { CordovaError, events } = require('cordova-common');
function getInstallTarget (runOptions) {
var install_target;
if (runOptions.target) {
install_target = runOptions.target;
} else if (runOptions.device) {
install_target = '--device';
} else if (runOptions.emulator) {
install_target = '--emulator';
}
return install_target;
}
/**
* Runs the application on a device if available. If no device is found, it will
* use a started emulator. If no started emulators are found it will attempt
* to start an avd. If no avds are found it will error out.
*
* @param {Object} runOptions various run/build options. See Api.js build/run
* methods for reference.
*
* @return {Promise}
*/
module.exports.run = function (runOptions) {
runOptions = runOptions || {};
var self = this;
var install_target = getInstallTarget(runOptions);
return Promise.resolve().then(function () {
if (!install_target) {
// no target given, deploy to device if available, otherwise use the emulator.
return device.list().then(function (device_list) {
if (device_list.length > 0) {
events.emit('warn', 'No target specified, deploying to device \'' + device_list[0] + '\'.');
install_target = device_list[0];
} else {
events.emit('warn', 'No target specified and no devices found, deploying to emulator');
install_target = '--emulator';
}
});
}
}).then(function () {
if (install_target === '--device') {
return device.resolveTarget(null);
} else if (install_target === '--emulator') {
// Give preference to any already started emulators. Else, start one.
return emulator.list_started().then(function (started) {
return started && started.length > 0 ? started[0] : emulator.start();
}).then(function (emulatorId) {
return emulator.resolveTarget(emulatorId);
});
}
// They specified a specific device/emulator ID.
return device.list().then(function (devices) {
if (devices.indexOf(install_target) > -1) {
return device.resolveTarget(install_target);
}
return emulator.list_started().then(function (started_emulators) {
if (started_emulators.indexOf(install_target) > -1) {
return emulator.resolveTarget(install_target);
}
return emulator.list_images().then(function (avds) {
// if target emulator isn't started, then start it.
for (var avd in avds) {
if (avds[avd].name === install_target) {
return emulator.start(install_target).then(function (emulatorId) {
return emulator.resolveTarget(emulatorId);
});
}
}
return Promise.reject(new CordovaError(`Target '${install_target}' not found, unable to run project`));
});
});
});
}).then(function (resolvedTarget) {
return new Promise((resolve) => {
const builder = require('./builders/builders').getBuilder();
const buildOptions = require('./build').parseBuildOptions(runOptions, null, self.root);
// Android app bundles cannot be deployed directly to the device
if (buildOptions.packageType === PackageType.BUNDLE) {
const packageTypeErrorMessage = 'Package type "bundle" is not supported during cordova run.';
events.emit('error', packageTypeErrorMessage);
throw packageTypeErrorMessage;
}
resolve(builder.fetchBuildResults(buildOptions.buildType, buildOptions.arch));
}).then(function (buildResults) {
if (resolvedTarget && resolvedTarget.isEmulator) {
return emulator.wait_for_boot(resolvedTarget.target).then(function () {
return emulator.install(resolvedTarget, buildResults);
});
}
return device.install(resolvedTarget, buildResults);
});
});
};
module.exports.help = function () {
console.log('Usage: ' + path.relative(process.cwd(), process.argv[1]) + ' [options]');
console.log('Build options :');
console.log(' --debug : Builds project in debug mode');
console.log(' --release : Builds project in release mode');
console.log(' --nobuild : Runs the currently built project without recompiling');
console.log('Deploy options :');
console.log(' --device : Will deploy the built project to a device');
console.log(' --emulator : Will deploy the built project to an emulator if one exists');
console.log(' --target=<target_id> : Installs to the target with the specified id.');
process.exit(0);
};

View File

@@ -1,38 +0,0 @@
#!/usr/bin/env node
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
var emulator = require('./emulator');
var args = process.argv;
var install_target;
if (args.length > 2) {
if (args[2].substring(0, 9) === '--target=') {
install_target = args[2].substring(9, args[2].length);
} else {
console.error('ERROR : argument \'' + args[2] + '\' not recognized.');
process.exit(2);
}
}
emulator.start(install_target).catch(function (err) {
console.error('ERROR: ' + err);
process.exit(2);
});

View File

@@ -1,26 +0,0 @@
:: Licensed to the Apache Software Foundation (ASF) under one
:: or more contributor license agreements. See the NOTICE file
:: distributed with this work for additional information
:: regarding copyright ownership. The ASF licenses this file
:: to you under the Apache License, Version 2.0 (the
:: "License"); you may not use this file except in compliance
:: with the License. You may obtain a copy of the License at
::
:: http://www.apache.org/licenses/LICENSE-2.0
::
:: Unless required by applicable law or agreed to in writing,
:: software distributed under the License is distributed on an
:: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
:: KIND, either express or implied. See the License for the
:: specific language governing permissions and limitations
:: under the License.
@ECHO OFF
SET script_path="%~dp0start-emulator"
IF EXIST %script_path% (
node %script_path% %*
) ELSE (
ECHO.
ECHO ERROR: Could not find 'start-emulator' script in 'cordova\lib' folder, aborting...>&2
EXIT /B 1
)

View File

@@ -1,97 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
/*
Provides a set of utility methods, which can also be spied on during unit tests.
*/
// TODO: Perhaps this should live in cordova-common?
const fs = require('fs-extra');
const path = require('path');
/**
* Reads, searches, and replaces the found occurences with replacementString and then writes the file back out.
* A backup is not made.
*
* @param {string} file A file path to a readable & writable file
* @param {RegExp} searchRegex The search regex
* @param {string} replacementString The string to replace the found occurences
* @returns void
*/
exports.replaceFileContents = function (file, searchRegex, replacementString) {
let contents = fs.readFileSync(file).toString();
contents = contents.replace(searchRegex, replacementString);
fs.writeFileSync(file, contents);
};
/**
* Reads a file and scans for regex. Returns the line of the first occurence or null if no occurences are found.
*
* @param {string} file A file path
* @param {RegExp} regex A search regex
* @returns string|null
*/
exports.grep = function (file, regex) {
const contents = fs.readFileSync(file).toString().replace(/\\r/g, '').split('\n');
for (let i = 0; i < contents.length; i++) {
const line = contents[i];
if (regex.test(line)) {
return line;
}
}
return null;
};
/**
* Scans directories and outputs a list of found paths that matches the regex
*
* @param {string} directory The starting directory
* @param {RegExp} regex The search regex
* @param {boolean} recursive Enables recursion
* @returns Array<string>
*/
exports.scanDirectory = function (directory, regex, recursive) {
let output = [];
if (fs.existsSync(directory)) {
const items = fs.readdirSync(directory);
for (let i = 0; i < items.length; i++) {
const item = items[i];
const itemPath = path.join(directory, item);
const stats = fs.statSync(itemPath);
if (regex.test(itemPath)) {
output.push(itemPath);
}
if (stats.isDirectory()) {
if (recursive) {
output = output.concat(exports.scanDirectory(itemPath, regex, recursive));
} else {
// Move onto the next item
continue;
}
}
}
}
return output;
};

View File

@@ -1,36 +0,0 @@
#!/usr/bin/env node
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
var log = require('./lib/log');
var reqs = require('./lib/check_reqs');
var args = process.argv;
// Usage support for when args are given
if (args.length > 2) {
log.help();
} else {
reqs.run().then(function () {
return log.run();
}, function (err) {
console.error('ERROR: ' + err);
process.exit(2);
});
}

View File

@@ -1,26 +0,0 @@
:: Licensed to the Apache Software Foundation (ASF) under one
:: or more contributor license agreements. See the NOTICE file
:: distributed with this work for additional information
:: regarding copyright ownership. The ASF licenses this file
:: to you under the Apache License, Version 2.0 (the
:: "License"); you may not use this file except in compliance
:: with the License. You may obtain a copy of the License at
::
:: http://www.apache.org/licenses/LICENSE-2.0
::
:: Unless required by applicable law or agreed to in writing,
:: software distributed under the License is distributed on an
:: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
:: KIND, either express or implied. See the License for the
:: specific language governing permissions and limitations
:: under the License.
@ECHO OFF
SET script_path="%~dp0log"
IF EXIST %script_path% (
node %script_path% %*
) ELSE (
ECHO.
ECHO ERROR: Could not find 'log' script in 'cordova' folder, aborting...>&2
EXIT /B 1
)

View File

@@ -1,18 +0,0 @@
var CordovaLogger = require('cordova-common').CordovaLogger;
module.exports = {
adjustLoggerLevel: function (opts) {
if (opts instanceof Array) {
opts.silent = opts.indexOf('--silent') !== -1;
opts.verbose = opts.indexOf('--verbose') !== -1;
}
if (opts.silent) {
CordovaLogger.get().setLevel('error');
}
if (opts.verbose) {
CordovaLogger.get().setLevel('verbose');
}
}
};

View File

@@ -1,54 +0,0 @@
#!/usr/bin/env node
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
var Api = require('./Api');
var nopt = require('nopt');
var path = require('path');
// Support basic help commands
if (['--help', '/?', '-h', 'help', '-help', '/help'].indexOf(process.argv[2]) >= 0) {
require('./lib/run').help();
}
// Do some basic argument parsing
var runOpts = nopt({
verbose: Boolean,
silent: Boolean,
debug: Boolean,
release: Boolean,
nobuild: Boolean,
buildConfig: path,
archs: String,
device: Boolean,
emulator: Boolean,
target: String
}, { d: '--verbose' });
// Make runOptions compatible with PlatformApi run method spec
runOpts.argv = runOpts.argv.remain;
require('./loggingHelper').adjustLoggerLevel(runOpts);
new Api().run(runOpts)
.catch(function (err) {
console.error(err, err.stack);
process.exit(2);
});

View File

@@ -1,26 +0,0 @@
:: Licensed to the Apache Software Foundation (ASF) under one
:: or more contributor license agreements. See the NOTICE file
:: distributed with this work for additional information
:: regarding copyright ownership. The ASF licenses this file
:: to you under the Apache License, Version 2.0 (the
:: "License"); you may not use this file except in compliance
:: with the License. You may obtain a copy of the License at
::
:: http://www.apache.org/licenses/LICENSE-2.0
::
:: Unless required by applicable law or agreed to in writing,
:: software distributed under the License is distributed on an
:: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
:: KIND, either express or implied. See the License for the
:: specific language governing permissions and limitations
:: under the License.
@ECHO OFF
SET script_path="%~dp0run"
IF EXIST %script_path% (
node %script_path% %*
) ELSE (
ECHO.
ECHO ERROR: Could not find 'run' script in 'cordova' folder, aborting...>&2
EXIT /B 1
)

View File

@@ -1,26 +0,0 @@
:: Licensed to the Apache Software Foundation (ASF) under one
:: or more contributor license agreements. See the NOTICE file
:: distributed with this work for additional information
:: regarding copyright ownership. The ASF licenses this file
:: to you under the Apache License, Version 2.0 (the
:: "License"); you may not use this file except in compliance
:: with the License. You may obtain a copy of the License at
::
:: http://www.apache.org/licenses/LICENSE-2.0
::
:: Unless required by applicable law or agreed to in writing,
:: software distributed under the License is distributed on an
:: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
:: KIND, either express or implied. See the License for the
:: specific language governing permissions and limitations
:: under the License.
@ECHO OFF
SET script_path="%~dp0version"
IF EXIST %script_path% (
node %script_path% %*
) ELSE (
ECHO.
ECHO ERROR: Could not find 'version' script in 'cordova' folder, aborting...>&2
EXIT /B 1
)

File diff suppressed because it is too large Load Diff

View File

@@ -1,52 +0,0 @@
/* Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.3.50'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
//This replaces project.properties w.r.t. build settings
project.ext {
defaultBuildToolsVersion="29.0.2" //String
defaultMinSdkVersion=22 //Integer - Minimum requirement is Android 5.1
defaultTargetSdkVersion=29 //Integer - We ALWAYS target the latest by default
defaultCompileSdkVersion=29 //Integer - We ALWAYS compile with the latest by default
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View File

@@ -1,40 +0,0 @@
#!/usr/bin/env node
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
var path = require('path');
var Api = require('./templates/cordova/Api');
var args = require('nopt')({
link: Boolean,
shared: Boolean,
help: Boolean
}, { d: '--verbose' });
if (args.help || args.argv.remain.length === 0) {
console.log('Usage: ' + path.relative(process.cwd(), path.join(__dirname, 'update')) + ' <path_to_project> [--link]');
console.log(' --link will use the CordovaLib project directly instead of making a copy.');
process.exit(1);
}
require('./templates/cordova/loggingHelper').adjustLoggerLevel(args);
Api.updatePlatform(args.argv.remain[0], { link: (args.link || args.shared) }).catch(err => {
console.error(err);
process.exitCode = 1;
});

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
<classpathentry kind="output" path="bin/default"/>
</classpath>

View File

@@ -1,4 +0,0 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.source=1.6

View File

@@ -1,34 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
# This file is used to override default values used by the Ant build system.
#
# This file must be checked in Version Control Systems, as it is
# integral to the build system of your project.
# This file is only used by the Ant script.
# You can use this to override default values such as
# 'source.dir' for the location of your java source folder and
# 'out.dir' for the location of your output folder.
# You can also use it define how the release builds are signed by declaring
# the following properties:
# 'key.store' for the location of your keystore and
# 'key.alias' for the name of the key to use.
# The password will be asked during the build when you use the 'release' target.

View File

@@ -16,53 +16,37 @@
under the License.
*/
ext {
apply from: 'cordova.gradle'
cdvCompileSdkVersion = privateHelpers.getProjectTarget()
cdvBuildToolsVersion = privateHelpers.findLatestInstalledBuildTools()
}
buildscript {
repositories {
google()
jcenter()
}
apply from: 'cordova.gradle'
apply from: 'repositories.gradle'
repositories repos
dependencies {
// The gradle plugin and the maven plugin have to be updated after each version of Android
// studio comes out
classpath 'com.android.tools.build:gradle:4.0.0'
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3'
// Android Gradle Plugin (AGP) Build Tools
classpath "com.android.tools.build:gradle:${cordovaConfig.AGP_VERSION}"
}
}
allprojects {
repositories {
google()
jcenter()
}
apply from: 'repositories.gradle'
repositories repos
}
apply plugin: 'com.android.library'
apply plugin: 'com.github.dcendents.android-maven'
apply plugin: 'com.jfrog.bintray'
group = 'org.apache.cordova'
version = '9.0.0-dev'
android {
compileSdkVersion cdvCompileSdkVersion
buildToolsVersion cdvBuildToolsVersion
compileSdkVersion cordovaConfig.SDK_VERSION
buildToolsVersion cordovaConfig.BUILD_TOOLS_VERSION
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
// For the Android Cordova Lib, we will hardcode the minSdkVersion and not allow changes.
// For the Android Cordova Lib, we allow changing the minSdkVersion, but it is at the users own risk
defaultConfig {
minSdkVersion 22
minSdkVersion cordovaConfig.MIN_SDK_VERSION
}
sourceSets {
@@ -85,64 +69,17 @@ android {
}
}
install {
repositories.mavenInstaller {
pom {
project {
packaging 'aar'
name 'Cordova'
url 'https://cordova.apache.org'
licenses {
license {
name 'The Apache Software License, Version 2.0'
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
developers {
developer {
id 'stevengill'
name 'Steve Gill'
}
}
scm {
connection 'scm:git:https://github.com/apache/cordova-android.git'
developerConnection 'scm:git:git@github.com:apache/cordova-android.git'
url 'https://github.com/apache/cordova-android'
}
}
}
}
dependencies {
implementation "androidx.appcompat:appcompat:${cordovaConfig.ANDROIDX_APP_COMPAT_VERSION}"
implementation "androidx.webkit:webkit:${cordovaConfig.ANDROIDX_WEBKIT_VERSION}"
}
task sourcesJar(type: Jar) {
from android.sourceSets.main.java.srcDirs
classifier = 'sources'
}
artifacts {
archives sourcesJar
}
bintray {
user = System.getenv('BINTRAY_USER')
key = System.getenv('BINTRAY_KEY')
configurations = ['archives']
pkg {
repo = 'maven'
name = 'cordova-android'
userOrg = 'cordova'
licenses = ['Apache-2.0']
vcsUrl = 'https://github.com/apache/cordova-android'
websiteUrl = 'https://cordova.apache.org'
issueTrackerUrl = 'https://github.com/apache/cordova-android/issues'
publicDownloadNumbers = true
licenses = ['Apache-2.0']
labels = ['android', 'cordova', 'phonegap']
version {
name = '9.0.0-dev'
released = new Date()
vcsTag = '9.0.0-dev'
}
}
/**
* In a project created though CLI, the `cordova-publish.gradle` file is not copied to the `framework` dir.
* App development (CLI) projects can not and should not publish our framework.
* In this case, there is no need for the gradle build process to know about the publish process.
*/
def cordovaPublishGradle = './cordova-publish.gradle'
if(file(cordovaPublishGradle).exists()) {
apply from: cordovaPublishGradle
}

View File

@@ -1,192 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<project name="Cordova" default="jar">
<!-- LOAD VERSION -->
<loadfile property="version" srcFile="../VERSION">
<filterchain>
<striplinebreaks/>
</filterchain>
</loadfile>
<!-- check that the version of ant is at least 1.8.0 -->
<antversion property="thisantversion" atleast="1.8.0" />
<fail message="The required minimum version of ant is 1.8.0, you have ${ant.version}"
unless="thisantversion" />
<!-- The local.properties file is created and updated by the 'android'
tool. (For example "sdkdir/tools/android update project -p ." inside
of this directory where the AndroidManifest.xml file exists. This
properties file that gets built contains the path to the SDK. It
should *NOT* be checked into Version Control Systems since it holds
data about the local machine. -->
<available file="local.properties" property="exists.local.properties" />
<fail message="You need to create the file 'local.properties' by running 'android update project -p .' here."
unless="exists.local.properties" />
<loadproperties srcFile="local.properties" />
<!-- The ant.properties file can be created by you. It is only edited by the
'android' tool to add properties to it.
This is the place to change some Ant specific build properties.
Here are some properties you may want to change/update:
source.dir
The name of the source directory. Default is 'src'.
out.dir
The name of the output directory. Default is 'bin'.
For other overridable properties, look at the beginning of the rules
files in the SDK, at tools/ant/build.xml
Properties related to the SDK location or the project target should
be updated using the 'android' tool with the 'update' action.
This file is an integral part of the build system for your
application and should be checked into Version Control Systems.
-->
<property file="ant.properties" />
<!-- The project.properties file is created and updated by the 'android'
tool, as well as ADT.
This contains project specific properties such as project target, and library
dependencies. Lower level build properties are stored in ant.properties
(or in .classpath for Eclipse projects).
This file is an integral part of the build system for your
application and should be checked into Version Control Systems. -->
<loadproperties srcFile="project.properties" />
<!-- quick check on sdk.dir -->
<fail
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project'"
unless="sdk.dir"
/>
<!-- version-tag: custom -->
<!-- extension targets. Uncomment the ones where you want to do custom work
in between standard targets -->
<!--
<target name="-pre-build">
</target>
<target name="-pre-compile">
</target>
/* This is typically used for code obfuscation.
Compiled code location: ${out.classes.absolute.dir}
If this is not done in place, override ${out.dex.input.absolute.dir} */
<target name="-post-compile">
</target>
-->
<target name="-pre-clean">
<!-- delete generated javadoc -->
<delete dir="javadoc-public" failonerror="false" />
<delete dir="javadoc-private" failonerror="false" />
<!-- delete generated jar -->
<delete file="cordova-${version}.jar" failonerror="false" />
</target>
<!-- Import the actual build file.
To customize existing targets, there are two options:
- Customize only one target:
- copy/paste the target into this file, *before* the
<import> task.
- customize it to your needs.
- Customize the whole content of build.xml
- copy/paste the content of the rules files (minus the top node)
into this file, replacing the <import> task.
- customize to your needs.
***********************
****** IMPORTANT ******
***********************
In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
in order to avoid having your file be overridden by tools such as "android update project"
-->
<import file="${sdk.dir}/tools/ant/build.xml" />
<!-- Build Cordova jar file that includes all native code, and Cordova JS file
that includes all JavaScript code.
-->
<target name="jar" depends="-compile">
<jar
basedir="bin/classes"
excludes="org/apache/cordova/R.class,org/apache/cordova/R$*.class"
jarfile="cordova-${version}.jar" />
</target>
<target name="javadoc">
<delete dir="javadoc-public" failonerror="false" />
<javadoc
access="public"
destdir="javadoc-public"
classpath="${sdk.dir}/platforms/${target}/android.jar">
<packageset dir="src">
<include name="org/apache/cordova/**" />
</packageset>
</javadoc>
<delete dir="javadoc-private" failonerror="false" />
<javadoc
access="private"
destdir="javadoc-private"
classpath="${sdk.dir}/platforms/${target}/android.jar">
<packageset dir="src">
<include name="org/apache/cordova/**" />
</packageset>
</javadoc>
</target>
<!-- tests for Java files -->
<property name="test.dir" location="test/org/apache/cordova" />
<path id="test.classpath">
<!-- requires both junit and cordova -->
<pathelement location="libs/junit-4.10.jar" />
<pathelement location="cordova-${version}.jar" />
<pathelement location="${test.dir}" />
</path>
<target name="compile-test">
<javac srcdir="${test.dir}" >
<classpath refid="test.classpath" />
</javac>
</target>
<target name="test" depends="jar, compile-test">
<junit showoutput="true">
<classpath refid="test.classpath" />
<formatter type="brief" usefile="false" />
<batchtest fork="yes">
<fileset dir="${test.dir}">
<include name="*Test.java" />
<include name="**/*Test.java" />
</fileset>
</batchtest>
</junit>
</target>
<target name="cordova_debug" depends="debug">
</target>
<target name="cordova_release" depends="release">
</target>
</project>

View File

@@ -0,0 +1,13 @@
{
"MIN_SDK_VERSION": 22,
"SDK_VERSION": 30,
"GRADLE_VERSION": "7.1.1",
"BUILD_TOOLS_VERSION": "30.0.3",
"AGP_VERSION": "4.2.2",
"KOTLIN_VERSION": "1.5.20",
"ANDROIDX_APP_COMPAT_VERSION": "1.3.0",
"ANDROIDX_WEBKIT_VERSION": "1.4.0",
"GRADLE_PLUGIN_GOOGLE_SERVICES_VERSION": "4.3.5",
"IS_GRADLE_PLUGIN_GOOGLE_SERVICES_ENABLED": false,
"IS_GRADLE_PLUGIN_KOTLIN_ENABLED": false
}

View File

@@ -0,0 +1,79 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
apply plugin: 'maven-publish'
String getCordovaAndroidVersion() {
// Fetch Data from Cordova-Android package.json (Used only by framework build/publishing)
def cordovaAndroidRepoPackageJson = "$projectDir/../package.json"
if(file(cordovaAndroidRepoPackageJson).exists()) {
def packageJsonFile = new File(cordovaAndroidRepoPackageJson)
def packageJson = new groovy.json.JsonSlurper().parseText(packageJsonFile.text)
return packageJson.version
}
}
task sourcesJar(type: Jar) {
from android.sourceSets.main.java.srcDirs
classifier = 'sources'
}
publishing {
publications {
Cordova(MavenPublication) {
groupId = 'org.apache.cordova'
artifactId = 'framework'
version = getCordovaAndroidVersion()
artifact(sourcesJar)
artifact("$buildDir/outputs/aar/framework-release.aar")
pom {
name = 'Cordova'
description = 'A library to build Cordova-based projects for the Android platform.'
url = 'https://cordova.apache.org'
licenses {
license {
name = 'Apache License, Version 2.0'
url = 'https://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
developers {
developer {
id = 'stevengill'
name = 'Steve Gill'
}
developer {
id = 'erisu'
name = 'Bryan Ellis'
email = 'erisu@apache.org'
}
}
scm {
connection = 'scm:git:https://github.com/apache/cordova-android.git'
developerConnection = 'scm:git:git@github.com:apache/cordova-android.git'
url = 'https://github.com/apache/cordova-android'
}
}
}
}
}

View File

@@ -18,8 +18,7 @@
*/
import java.util.regex.Pattern
import groovy.swing.SwingBuilder
import com.g00fy2.versioncompare.Version
import io.github.g00fy2.versioncompare.Version
String doEnsureValueExists(filePath, props, key) {
if (props.get(key) == null) {
@@ -131,30 +130,6 @@ def doExtractStringFromManifest(name) {
return matcher.group(1)
}
def doPromptForPassword(msg) {
if (System.console() == null) {
def ret = null
new SwingBuilder().edt {
dialog(modal: true, title: 'Enter password', alwaysOnTop: true, resizable: false, locationRelativeTo: null, pack: true, show: true) {
vbox {
label(text: msg)
def input = passwordField()
button(defaultButton: true, text: 'OK', actionPerformed: {
ret = input.password;
dispose();
})
}
}
}
if (!ret) {
throw new GradleException('User canceled build')
}
return new String(ret)
} else {
return System.console().readPassword('\n' + msg);
}
}
def doGetConfigXml() {
def xml = file("src/main/res/xml/config.xml").getText()
// Disable namespace awareness since Cordova doesn't use them properly
@@ -175,15 +150,62 @@ def doGetConfigPreference(name, defaultValue) {
return ret
}
def doApplyCordovaConfigCustomization() {
// Apply user overide properties that comes from the "--gradleArg=-P" parameters
if (project.hasProperty('cdvMinSdkVersion')) {
cordovaConfig.MIN_SDK_VERSION = Integer.parseInt('' + cdvMinSdkVersion)
}
if (project.hasProperty('cdvSdkVersion')) {
cordovaConfig.SDK_VERSION = Integer.parseInt('' + cdvSdkVersion)
}
if (project.hasProperty('cdvMaxSdkVersion')) {
cordovaConfig.MAX_SDK_VERSION = Integer.parseInt('' + cdvMaxSdkVersion)
}
if (project.hasProperty('cdvBuildToolsVersion')) {
cordovaConfig.BUILD_TOOLS_VERSION = cdvBuildToolsVersion
}
if (project.hasProperty('cdvAndroidXAppCompatVersion')) {
cordovaConfig.ANDROIDX_APP_COMPAT_VERSION = cdvAndroidXAppCompatVersion
}
if (project.hasProperty('cdvAndroidXWebKitVersion')) {
cordovaConfig.ANDROIDX_WEBKIT_VERSION = cdvAndroidXWebKitVersion
}
// Ensure the latest installed build tools is selected, with or without defined override
cordovaConfig.LATEST_INSTALLED_BUILD_TOOLS = doFindLatestInstalledBuildTools(
cordovaConfig.BUILD_TOOLS_VERSION
)
}
// Properties exported here are visible to all plugins.
ext {
def defaultsFilePath = './cdv-gradle-config-defaults.json'
def projectConfigFilePath = "$rootDir/cdv-gradle-config.json"
def targetConfigFilePath = null
/**
* Check if the project config file path exists. This file will exist if coming from CLI project.
* If this file does not exist, falls back onto the default file.
* This scenario can occur if building the framework's AAR package for publishing.
*/
if(file(projectConfigFilePath).exists()) {
targetConfigFilePath = projectConfigFilePath
} else {
targetConfigFilePath = defaultsFilePath
}
def jsonFile = new File(targetConfigFilePath)
cordovaConfig = new groovy.json.JsonSlurper().parseText(jsonFile.text)
// Apply Gradle Properties
doApplyCordovaConfigCustomization()
// These helpers are shared, but are not guaranteed to be stable / unchanged.
privateHelpers = {}
privateHelpers.getProjectTarget = { doGetProjectTarget() }
privateHelpers.findLatestInstalledBuildTools = { doFindLatestInstalledBuildTools('19.1.0') }
privateHelpers.applyCordovaConfigCustomization = { doApplyCordovaConfigCustomization() }
privateHelpers.extractIntFromManifest = { name -> doExtractIntFromManifest(name) }
privateHelpers.extractStringFromManifest = { name -> doExtractStringFromManifest(name) }
privateHelpers.promptForPassword = { msg -> doPromptForPassword(msg) }
privateHelpers.ensureValueExists = { filePath, props, key -> doEnsureValueExists(filePath, props, key) }
// These helpers can be used by plugins / projects and will not change.
@@ -196,10 +218,11 @@ ext {
buildscript {
repositories {
jcenter()
google()
mavenCentral()
}
dependencies {
classpath 'com.g00fy2:versioncompare:1.3.4@jar'
classpath 'io.github.g00fy2:versioncompare:1.4.1@jar'
}
}

View File

@@ -1,14 +0,0 @@
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system use,
# "build.properties", and override values to adapt the script to your
# project structure.
# Indicates whether an apk should be generated for each density.
split.density=false
# Project target.
target=android-14
apk-configurations=

View File

@@ -10,6 +10,8 @@
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
android.useAndroidX=true
android.enableJetifier=true
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit

View File

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

View File

@@ -1,3 +1,5 @@
# GENERATED FILE! DO NOT EDIT!
# This file was originally created by the Android Tools, but is now
# used by cordova-android to manage the project configuration.
@@ -5,7 +7,6 @@
split.density=false
# Project target.
target=android-29
apk-configurations=
renderscript.opt.level=O0
android.library=true

View File

@@ -16,6 +16,7 @@
under the License.
*/
wrapper {
gradleVersion = '6.5'
ext.repos = {
google()
mavenCentral()
}

View File

@@ -28,7 +28,7 @@ import org.apache.cordova.LOG;
import android.net.Uri;
public class Whitelist {
public class AllowList {
private static class URLPattern {
public Pattern scheme;
public Pattern host;
@@ -92,12 +92,12 @@ public class Whitelist {
}
}
private ArrayList<URLPattern> whiteList;
private ArrayList<URLPattern> allowList;
public static final String TAG = "Whitelist";
public static final String TAG = "CordovaAllowList";
public Whitelist() {
this.whiteList = new ArrayList<URLPattern>();
public AllowList() {
this.allowList = new ArrayList<URLPattern>();
}
/* Match patterns (from http://developer.chrome.com/extensions/match_patterns.html)
@@ -111,13 +111,13 @@ public class Whitelist {
* the scheme to be omitted for backwards compatibility. (Also host is not required
* to begin with a "*" or "*.".)
*/
public void addWhiteListEntry(String origin, boolean subdomains) {
if (whiteList != null) {
public void addAllowListEntry(String origin, boolean subdomains) {
if (allowList != null) {
try {
// Unlimited access to network resources
if (origin.compareTo("*") == 0) {
LOG.d(TAG, "Unlimited access to network resources");
whiteList = null;
allowList = null;
}
else { // specific access
Pattern parts = Pattern.compile("^((\\*|[A-Za-z-]+):(//)?)?(\\*|((\\*\\.)?[^*/:]+))?(:(\\d+))?(/.*)?");
@@ -131,10 +131,10 @@ public class Whitelist {
String path = m.group(9);
if (scheme == null) {
// XXX making it stupid friendly for people who forget to include protocol/SSL
whiteList.add(new URLPattern("http", host, port, path));
whiteList.add(new URLPattern("https", host, port, path));
allowList.add(new URLPattern("http", host, port, path));
allowList.add(new URLPattern("https", host, port, path));
} else {
whiteList.add(new URLPattern(scheme, host, port, path));
allowList.add(new URLPattern(scheme, host, port, path));
}
}
}
@@ -149,15 +149,15 @@ public class Whitelist {
* Determine if URL is in approved list of URLs to load.
*
* @param uri
* @return true if wide open or whitelisted
* @return true if wide open or allow listed
*/
public boolean isUrlWhiteListed(String uri) {
// If there is no whitelist, then it's wide open
if (whiteList == null) return true;
public boolean isUrlAllowListed(String uri) {
// If there is no allowList, then it's wide open
if (allowList == null) return true;
Uri parsedUri = Uri.parse(uri);
// Look for match in white list
Iterator<URLPattern> pit = whiteList.iterator();
// Look for match in allow list
Iterator<URLPattern> pit = allowList.iterator();
while (pit.hasNext()) {
URLPattern p = pit.next();
if (p.matches(parsedUri)) {

View File

@@ -0,0 +1,165 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.ConfigXmlParser;
import org.apache.cordova.LOG;
import org.apache.cordova.AllowList;
import org.apache.cordova.CordovaPreferences;
import org.xmlpull.v1.XmlPullParser;
import android.content.Context;
public class AllowListPlugin extends CordovaPlugin {
public static final String PLUGIN_NAME = "CordovaAllowListPlugin";
protected static final String LOG_TAG = "CordovaAllowListPlugin";
private AllowList allowedNavigations;
private AllowList allowedIntents;
private AllowList allowedRequests;
// Used when instantiated via reflection by PluginManager
public AllowListPlugin() { }
// These can be used by embedders to allow Java-configuration of an allow list.
public AllowListPlugin(Context context) {
this(new AllowList(), new AllowList(), null);
new CustomConfigXmlParser().parse(context);
}
public AllowListPlugin(XmlPullParser xmlParser) {
this(new AllowList(), new AllowList(), null);
new CustomConfigXmlParser().parse(xmlParser);
}
public AllowListPlugin(AllowList allowedNavigations, AllowList allowedIntents, AllowList allowedRequests) {
if (allowedRequests == null) {
allowedRequests = new AllowList();
allowedRequests.addAllowListEntry("file:///*", false);
allowedRequests.addAllowListEntry("data:*", false);
}
this.allowedNavigations = allowedNavigations;
this.allowedIntents = allowedIntents;
this.allowedRequests = allowedRequests;
}
@Override
public void pluginInitialize() {
if (this.allowedNavigations == null) {
this.allowedNavigations = new AllowList();
this.allowedIntents = new AllowList();
this.allowedRequests = new AllowList();
new CustomConfigXmlParser().parse(webView.getContext());
}
}
private class CustomConfigXmlParser extends ConfigXmlParser {
private CordovaPreferences prefs = new CordovaPreferences();
@Override
public void handleStartTag(XmlPullParser xml) {
String strNode = xml.getName();
if (strNode.equals("content")) {
String startPage = xml.getAttributeValue(null, "src");
allowedNavigations.addAllowListEntry(startPage, false);
// Allow origin for WebViewAssetLoader
if (!this.prefs.getBoolean("AndroidInsecureFileModeEnabled", false)) {
allowedNavigations.addAllowListEntry("https://" + this.prefs.getString("hostname", "localhost"), false);
}
} else if (strNode.equals("allow-navigation")) {
String origin = xml.getAttributeValue(null, "href");
if ("*".equals(origin)) {
allowedNavigations.addAllowListEntry("http://*/*", false);
allowedNavigations.addAllowListEntry("https://*/*", false);
allowedNavigations.addAllowListEntry("data:*", false);
} else {
allowedNavigations.addAllowListEntry(origin, false);
}
} else if (strNode.equals("allow-intent")) {
String origin = xml.getAttributeValue(null, "href");
allowedIntents.addAllowListEntry(origin, false);
} else if (strNode.equals("access")) {
String origin = xml.getAttributeValue(null, "origin");
if (origin != null) {
if ("*".equals(origin)) {
allowedRequests.addAllowListEntry("http://*/*", false);
allowedRequests.addAllowListEntry("https://*/*", false);
} else {
String subdomains = xml.getAttributeValue(null, "subdomains");
allowedRequests.addAllowListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0));
}
}
}
}
@Override
public void handleEndTag(XmlPullParser xml) { }
}
@Override
public Boolean shouldAllowNavigation(String url) {
return this.allowedNavigations.isUrlAllowListed(url)
? true
: null; // default policy
}
@Override
public Boolean shouldAllowRequest(String url) {
return (this.shouldAllowNavigation(url) || this.allowedRequests.isUrlAllowListed(url))
? true
: null; // default policy
}
@Override
public Boolean shouldOpenExternalUrl(String url) {
return (this.allowedIntents.isUrlAllowListed(url))
? true
: null; // default policy
}
public AllowList getAllowedNavigations() {
return this.allowedNavigations;
}
public void setAllowedNavigations(AllowList allowedNavigations) {
this.allowedNavigations = allowedNavigations;
}
public AllowList getAllowedIntents() {
return this.allowedIntents;
}
public void setAllowedIntents(AllowList allowedIntents) {
this.allowedIntents = allowedIntents;
}
public AllowList getAllowedRequests() {
return this.allowedRequests;
}
public void setAllowedRequests(AllowList allowedRequests) {
this.allowedRequests = allowedRequests;
}
}

View File

@@ -23,7 +23,7 @@ import java.util.List;
import android.app.Activity;
@Deprecated // Use Whitelist, CordovaPrefences, etc. directly.
@Deprecated // Use AllowList, CordovaPrefences, etc. directly.
public class Config {
private static final String TAG = "Config";

View File

@@ -33,7 +33,7 @@ import android.content.Context;
public class ConfigXmlParser {
private static String TAG = "ConfigXmlParser";
private String launchUrl = "file:///android_asset/www/index.html";
private String launchUrl = null;
private CordovaPreferences prefs = new CordovaPreferences();
private ArrayList<PluginEntry> pluginEntries = new ArrayList<PluginEntry>(20);
@@ -46,6 +46,14 @@ public class ConfigXmlParser {
}
public String getLaunchUrl() {
if (launchUrl == null) {
launchUrl = "https://" + this.prefs.getString("hostname", "localhost");
}
if (this.prefs.getBoolean("AndroidInsecureFileModeEnabled", false)) {
launchUrl = "file:///android_asset/www/index.html";
}
return launchUrl;
}
@@ -60,6 +68,15 @@ public class ConfigXmlParser {
return;
}
}
pluginEntries.add(
new PluginEntry(
AllowListPlugin.PLUGIN_NAME,
"org.apache.cordova.AllowListPlugin",
true
)
);
parse(action.getResources().getXml(id));
}
@@ -139,7 +156,11 @@ public class ConfigXmlParser {
if (src.charAt(0) == '/') {
src = src.substring(1);
}
launchUrl = "file:///android_asset/www/" + src;
if (this.prefs.getBoolean("AndroidInsecureFileModeEnabled", false)) {
launchUrl = "file:///android_asset/www/" + src;
} else {
launchUrl = "https://" + this.prefs.getString("hostname", "localhost") + "/" + src;
}
}
}
}

View File

@@ -24,7 +24,6 @@ import java.util.Locale;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Activity;
import android.app.AlertDialog;
import android.annotation.SuppressLint;
import android.content.DialogInterface;
@@ -32,7 +31,6 @@ import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Color;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
@@ -43,6 +41,8 @@ import android.view.WindowManager;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;
import androidx.appcompat.app.AppCompatActivity;
/**
* This class is the main Android activity that represents the Cordova
* application. It should be extended by the user to load the specific
@@ -74,7 +74,7 @@ import android.widget.FrameLayout;
* deprecated in favor of the config.xml file.
*
*/
public class CordovaActivity extends Activity {
public class CordovaActivity extends AppCompatActivity {
public static String TAG = "CordovaActivity";
// The webview for our app
@@ -125,6 +125,9 @@ public class CordovaActivity extends Activity {
// (as was the case in previous cordova versions)
if (!preferences.getBoolean("FullscreenNotImmersive", false)) {
immersiveMode = true;
// The splashscreen plugin needs the flags set before we're focused to prevent
// the nav and title bars from flashing in.
setImmersiveUiVisibility();
} else {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
@@ -321,22 +324,26 @@ public class CordovaActivity extends Activity {
/**
* Called when view focus is changed
*/
@SuppressLint("InlinedApi")
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus && immersiveMode) {
final int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
getWindow().getDecorView().setSystemUiVisibility(uiOptions);
setImmersiveUiVisibility();
}
}
@SuppressLint("InlinedApi")
protected void setImmersiveUiVisibility() {
final int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
getWindow().getDecorView().setSystemUiVisibility(uiOptions);
}
@SuppressLint("NewApi")
@Override
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
@@ -506,6 +513,8 @@ public class CordovaActivity extends Activity {
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[],
int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
try
{
cordovaInterface.onRequestPermissionResult(requestCode, permissions, grantResults);

View File

@@ -114,7 +114,6 @@ public class CordovaBridge {
/** Called by cordova.js to initialize the bridge. */
//On old Androids SecureRandom isn't really secure, this is the least of your problems if
//you're running Android 4.3 and below in 2017
@SuppressLint("TrulyRandom")
int generateBridgeSecret() {
SecureRandom randGen = new SecureRandom();
expectedBridgeSecret = randGen.nextInt(Integer.MAX_VALUE);

View File

@@ -18,11 +18,10 @@
*/
package org.apache.cordova;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import org.apache.cordova.CordovaPlugin;
import androidx.appcompat.app.AppCompatActivity;
import java.util.concurrent.ExecutorService;
@@ -56,7 +55,7 @@ public interface CordovaInterface {
*
* @return the Activity
*/
public abstract Activity getActivity();
public abstract AppCompatActivity getActivity();
/**
* Get the Android context.

View File

@@ -20,7 +20,6 @@
package org.apache.cordova;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -28,6 +27,8 @@ import android.os.Build;
import android.os.Bundle;
import android.util.Pair;
import androidx.appcompat.app.AppCompatActivity;
import org.json.JSONException;
import org.json.JSONObject;
@@ -39,7 +40,7 @@ import java.util.concurrent.Executors;
*/
public class CordovaInterfaceImpl implements CordovaInterface {
private static final String TAG = "CordovaInterfaceImpl";
protected Activity activity;
protected AppCompatActivity activity;
protected ExecutorService threadPool;
protected PluginManager pluginManager;
@@ -51,11 +52,11 @@ public class CordovaInterfaceImpl implements CordovaInterface {
protected boolean activityWasDestroyed = false;
protected Bundle savedPluginState;
public CordovaInterfaceImpl(Activity activity) {
public CordovaInterfaceImpl(AppCompatActivity activity) {
this(activity, Executors.newCachedThreadPool());
}
public CordovaInterfaceImpl(Activity activity, ExecutorService threadPool) {
public CordovaInterfaceImpl(AppCompatActivity activity, ExecutorService threadPool) {
this.activity = activity;
this.threadPool = threadPool;
this.permissionResultCallbacks = new CallbackMap();
@@ -76,13 +77,13 @@ public class CordovaInterfaceImpl implements CordovaInterface {
public void setActivityResultCallback(CordovaPlugin plugin) {
// Cancel any previously pending activity.
if (activityResultCallback != null) {
activityResultCallback.onActivityResult(activityResultRequestCode, Activity.RESULT_CANCELED, null);
activityResultCallback.onActivityResult(activityResultRequestCode, AppCompatActivity.RESULT_CANCELED, null);
}
activityResultCallback = plugin;
}
@Override
public Activity getActivity() {
public AppCompatActivity getActivity() {
return activity;
}

View File

@@ -414,9 +414,32 @@ public class CordovaPlugin {
* @param requestCode
* @param permissions
* @param grantResults
*
* @deprecated Use {@link #onRequestPermissionsResult} instead.
*/
@Deprecated
public void onRequestPermissionResult(int requestCode, String[] permissions,
int[] grantResults) throws JSONException {
}
/**
* Called by the system when the user grants permissions
*
* @param requestCode
* @param permissions
* @param grantResults
*/
public void onRequestPermissionsResult(int requestCode, String[] permissions,
int[] grantResults) throws JSONException {
}
/**
* Allow plugins to supply a PathHandler for the WebViewAssetHandler
* @return a CordovaPluginPathHandler which listen for paths and returns a response
*/
public CordovaPluginPathHandler getPathHandler() {
return null;
}
}

View File

@@ -17,12 +17,22 @@
under the License.
*/
const fs = require('fs');
package org.apache.cordova;
if (fs.existsSync('./test/gradlew')) {
fs.unlinkSync('./test/gradlew');
}
import androidx.webkit.WebViewAssetLoader;
if (fs.existsSync('./test/gradlew.bat')) {
fs.unlinkSync('./test/gradlew.bat');
/**
* Wrapper class for path and handler
*/
public class CordovaPluginPathHandler {
private final WebViewAssetLoader.PathHandler handler;
public CordovaPluginPathHandler(WebViewAssetLoader.PathHandler handler) {
this.handler = handler;
}
public WebViewAssetLoader.PathHandler getPathHandler() {
return handler;
}
}

View File

@@ -41,6 +41,7 @@ import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.channels.FileChannel;
import java.util.Locale;
import java.util.zip.GZIPInputStream;
/**
* What this class provides:
@@ -286,13 +287,19 @@ public class CordovaResourceApi {
case URI_TYPE_HTTP:
case URI_TYPE_HTTPS: {
HttpURLConnection conn = (HttpURLConnection)new URL(uri.toString()).openConnection();
conn.setRequestProperty("Accept-Encoding", "gzip");
conn.setDoInput(true);
String mimeType = conn.getHeaderField("Content-Type");
if (mimeType != null) {
mimeType = mimeType.split(";")[0];
}
int length = conn.getContentLength();
InputStream inputStream = conn.getInputStream();
InputStream inputStream;
if ("gzip".equals(conn.getContentEncoding())) {
inputStream = new GZIPInputStream(conn.getInputStream());
} else {
inputStream = conn.getInputStream();
}
return new OpenForReadResult(uri, inputStream, mimeType, length, null);
}
case URI_TYPE_PLUGIN: {

View File

@@ -31,7 +31,7 @@ import android.webkit.WebChromeClient.CustomViewCallback;
* are not expected to implement it.
*/
public interface CordovaWebView {
public static final String CORDOVA_VERSION = "9.0.0-dev";
public static final String CORDOVA_VERSION = "10.0.0-dev";
void init(CordovaInterface cordova, List<PluginEntry> pluginEntries, CordovaPreferences preferences);
@@ -94,7 +94,7 @@ public interface CordovaWebView {
/**
* Load the specified URL in the Cordova webview or a new browser instance.
*
* NOTE: If openExternal is false, only whitelisted URLs can be loaded.
* NOTE: If openExternal is false, only allow listed URLs can be loaded.
*
* @param url The url to load.
* @param openExternal Load url in browser instead of Cordova webview.

View File

@@ -19,6 +19,7 @@
package org.apache.cordova;
import android.annotation.SuppressLint;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
@@ -34,6 +35,7 @@ import org.json.JSONException;
import org.json.JSONObject;
import java.lang.reflect.Constructor;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -115,7 +117,6 @@ public class CordovaWebViewImpl implements CordovaWebView {
pluginManager.addService(CoreAndroid.PLUGIN_NAME, "org.apache.cordova.CoreAndroid");
pluginManager.init();
}
@Override
@@ -176,22 +177,28 @@ public class CordovaWebViewImpl implements CordovaWebView {
e.printStackTrace();
}
// If timeout, then stop loading and handle error
if (loadUrlTimeout == currentLoadUrlTimeout) {
// If timeout, then stop loading and handle error (if activity still exists)
if (loadUrlTimeout == currentLoadUrlTimeout && cordova.getActivity() != null) {
cordova.getActivity().runOnUiThread(loadError);
} else if (cordova.getActivity() == null) {
LOG.d(TAG, "Cordova activity does not exist.");
}
}
};
final boolean _recreatePlugins = recreatePlugins;
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
if (loadUrlTimeoutValue > 0) {
cordova.getThreadPool().execute(timeoutCheck);
if (cordova.getActivity() != null) {
final boolean _recreatePlugins = recreatePlugins;
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
if (loadUrlTimeoutValue > 0) {
cordova.getThreadPool().execute(timeoutCheck);
}
engine.loadUrl(url, _recreatePlugins);
}
engine.loadUrl(url, _recreatePlugins);
}
});
});
} else {
LOG.d(TAG, "Cordova activity does not exist.");
}
}
@@ -211,36 +218,52 @@ public class CordovaWebViewImpl implements CordovaWebView {
// If loading into our webview
if (!openExternal) {
// Make sure url is in whitelist
// Make sure url is in allow list
if (pluginManager.shouldAllowNavigation(url)) {
// TODO: What about params?
// Load new URL
loadUrlIntoView(url, true);
return;
} else {
LOG.w(TAG, "showWebPage: Refusing to load URL into webview since it is not in the <allow-navigation> whitelist. URL=" + url);
LOG.w(TAG, "showWebPage: Refusing to load URL into webview since it is not in the <allow-navigation> allow list. URL=" + url);
return;
}
}
if (!pluginManager.shouldOpenExternalUrl(url)) {
LOG.w(TAG, "showWebPage: Refusing to send intent for URL since it is not in the <allow-intent> whitelist. URL=" + url);
LOG.w(TAG, "showWebPage: Refusing to send intent for URL since it is not in the <allow-intent> allow list. URL=" + url);
return;
}
Intent intent = null;
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
// To send an intent without CATEGORY_BROWSER, a custom plugin should be used.
intent.addCategory(Intent.CATEGORY_BROWSABLE);
Uri uri = Uri.parse(url);
// Omitting the MIME type for file: URLs causes "No Activity found to handle Intent".
// Adding the MIME type to http: URLs causes them to not be handled by the downloader.
if ("file".equals(uri.getScheme())) {
intent.setDataAndType(uri, resourceApi.getMimeType(uri));
if (url.startsWith("intent://")) {
intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
} else {
intent.setData(uri);
intent = new Intent(Intent.ACTION_VIEW);
// To send an intent without CATEGORY_BROWSER, a custom plugin should be used.
intent.addCategory(Intent.CATEGORY_BROWSABLE);
Uri uri = Uri.parse(url);
// Omitting the MIME type for file: URLs causes "No Activity found to handle Intent".
// Adding the MIME type to http: URLs causes them to not be handled by the downloader.
if ("file".equals(uri.getScheme())) {
intent.setDataAndType(uri, resourceApi.getMimeType(uri));
} else {
intent.setData(uri);
}
}
if (cordova.getActivity() != null) {
cordova.getActivity().startActivity(intent);
} else {
LOG.d(TAG, "Cordova activity does not exist.");
}
} catch (URISyntaxException e) {
LOG.e(TAG, "Error parsing url " + url, e);
} catch (ActivityNotFoundException e) {
if (url.startsWith("intent://") && intent != null && intent.getStringExtra("browser_fallback_url") != null) {
showWebPage(intent.getStringExtra("browser_fallback_url"), openExternal, clearHistory, params);
} else {
LOG.e(TAG, "Error loading url " + url, e);
}
cordova.getActivity().startActivity(intent);
} catch (android.content.ActivityNotFoundException e) {
LOG.e(TAG, "Error loading url " + url, e);
}
}
@@ -255,7 +278,12 @@ public class CordovaWebViewImpl implements CordovaWebView {
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
return engine.getView().dispatchKeyEvent(event);
boolean ret = engine.getView().dispatchKeyEvent(event);
if (!ret) {
// If the engine didn't handle the event, handle it normally.
ret = super.dispatchKeyEvent(event);
}
return ret;
}
}
@@ -310,6 +338,7 @@ public class CordovaWebViewImpl implements CordovaWebView {
// Show the content view.
engine.getView().setVisibility(View.VISIBLE);
engine.getView().requestFocus();
}
@Override
@@ -553,11 +582,15 @@ public class CordovaWebViewImpl implements CordovaWebView {
public void run() {
try {
Thread.sleep(2000);
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
pluginManager.postMessage("spinner", "stop");
}
});
if (cordova.getActivity() != null) {
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
pluginManager.postMessage("spinner", "stop");
}
});
} else {
LOG.d(TAG, "Cordova activity does not exist.");
}
} catch (InterruptedException e) {
}
}

View File

@@ -79,7 +79,9 @@ public class PermissionHelper {
Arrays.fill(requestResults, PackageManager.PERMISSION_GRANTED);
try {
// This one is deprecated - see https://github.com/apache/cordova-android/issues/592
plugin.onRequestPermissionResult(requestCode, permissions, requestResults);
plugin.onRequestPermissionsResult(requestCode, permissions, requestResults);
} catch (JSONException e) {
LOG.e(LOG_TAG, "JSONException when delivering permissions results", e);
}

View File

@@ -47,11 +47,23 @@ public final class PluginEntry {
/**
* Constructs with a CordovaPlugin already instantiated.
*
* @param service The name of the service
* @param pluginClass The plugin class name
*/
public PluginEntry(String service, CordovaPlugin plugin) {
this(service, plugin.getClass().getName(), true, plugin);
}
/**
* @param service The name of the service
* @param plugin The CordovaPlugin already instantiated
* @param onload Create plugin object when HTML page is loaded
*/
public PluginEntry(String service, CordovaPlugin plugin, boolean onload) {
this(service, plugin.getClass().getName(), onload, plugin);
}
/**
* @param service The name of the service
* @param pluginClass The plugin class name
@@ -61,6 +73,12 @@ public final class PluginEntry {
this(service, pluginClass, onload, null);
}
/**
* @param service The name of the service
* @param pluginClass The plugin class name
* @param onload Create plugin object when HTML page is loaded
* @param plugin The CordovaPlugin already instantiated
*/
private PluginEntry(String service, String pluginClass, boolean onload, CordovaPlugin plugin) {
this.service = service;
this.pluginClass = pluginClass;

View File

@@ -18,8 +18,11 @@
*/
package org.apache.cordova;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.json.JSONException;
@@ -28,6 +31,7 @@ import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.os.Debug;
import android.os.Build;
/**
* PluginManager is exposed to JavaScript in the Cordova WebView.
@@ -40,8 +44,8 @@ public class PluginManager {
private static final int SLOW_EXEC_WARNING_THRESHOLD = Debug.isDebuggerConnected() ? 60 : 16;
// List of service entries
private final LinkedHashMap<String, CordovaPlugin> pluginMap = new LinkedHashMap<String, CordovaPlugin>();
private final LinkedHashMap<String, PluginEntry> entryMap = new LinkedHashMap<String, PluginEntry>();
private final Map<String, CordovaPlugin> pluginMap = Collections.synchronizedMap(new LinkedHashMap<String, CordovaPlugin>());
private final Map<String, PluginEntry> entryMap = Collections.synchronizedMap(new LinkedHashMap<String, PluginEntry>());
private final CordovaInterface ctx;
private final CordovaWebView app;
@@ -90,13 +94,17 @@ public class PluginManager {
* Create plugins objects that have onload set.
*/
private void startupPlugins() {
for (PluginEntry entry : entryMap.values()) {
// Add a null entry to for each non-startup plugin to avoid ConcurrentModificationException
// When iterating plugins.
if (entry.onload) {
getPlugin(entry.service);
} else {
pluginMap.put(entry.service, null);
synchronized (entryMap) {
for (PluginEntry entry : entryMap.values()) {
// Add a null entry to for each non-startup plugin to avoid ConcurrentModificationException
// When iterating plugins.
if (entry.onload) {
getPlugin(entry.service);
}
else {
LOG.d(TAG, "startupPlugins: put - " + entry.service);
pluginMap.put(entry.service, null);
}
}
}
}
@@ -169,6 +177,7 @@ public class PluginManager {
ret = instantiatePlugin(pe.pluginClass);
}
ret.privateInitialize(service, ctx, app, app.getPreferences());
LOG.d(TAG, "getPlugin - put: " + service);
pluginMap.put(service, ret);
}
return ret;
@@ -196,6 +205,7 @@ public class PluginManager {
this.entryMap.put(entry.service, entry);
if (entry.plugin != null) {
entry.plugin.privateInitialize(entry.service, ctx, app, app.getPreferences());
LOG.d(TAG, "addService: put - " + entry.service);
pluginMap.put(entry.service, entry.plugin);
}
}
@@ -206,9 +216,11 @@ public class PluginManager {
* @param multitasking Flag indicating if multitasking is turned on for app
*/
public void onPause(boolean multitasking) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
plugin.onPause(multitasking);
synchronized (this.pluginMap) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
plugin.onPause(multitasking);
}
}
}
}
@@ -226,9 +238,11 @@ public class PluginManager {
*
*/
public boolean onReceivedHttpAuthRequest(CordovaWebView view, ICordovaHttpAuthHandler handler, String host, String realm) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null && plugin.onReceivedHttpAuthRequest(app, handler, host, realm)) {
return true;
synchronized (this.pluginMap) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null && plugin.onReceivedHttpAuthRequest(app, handler, host, realm)) {
return true;
}
}
}
return false;
@@ -245,9 +259,11 @@ public class PluginManager {
*
*/
public boolean onReceivedClientCertRequest(CordovaWebView view, ICordovaClientCertRequest request) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null && plugin.onReceivedClientCertRequest(app, request)) {
return true;
synchronized (this.pluginMap) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null && plugin.onReceivedClientCertRequest(app, request)) {
return true;
}
}
}
return false;
@@ -259,9 +275,11 @@ public class PluginManager {
* @param multitasking Flag indicating if multitasking is turned on for app
*/
public void onResume(boolean multitasking) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
plugin.onResume(multitasking);
synchronized (this.pluginMap) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
plugin.onResume(multitasking);
}
}
}
}
@@ -270,9 +288,11 @@ public class PluginManager {
* Called when the activity is becoming visible to the user.
*/
public void onStart() {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
plugin.onStart();
synchronized (this.pluginMap) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
plugin.onStart();
}
}
}
}
@@ -281,9 +301,11 @@ public class PluginManager {
* Called when the activity is no longer visible to the user.
*/
public void onStop() {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
plugin.onStop();
synchronized (this.pluginMap) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
plugin.onStop();
}
}
}
}
@@ -292,9 +314,11 @@ public class PluginManager {
* The final call you receive before your activity is destroyed.
*/
public void onDestroy() {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
plugin.onDestroy();
synchronized (this.pluginMap) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
plugin.onDestroy();
}
}
}
}
@@ -307,11 +331,22 @@ public class PluginManager {
* @return Object to stop propagation or null
*/
public Object postMessage(String id, Object data) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
Object obj = plugin.onMessage(id, data);
if (obj != null) {
return obj;
LOG.d(TAG, "postMessage: " + id);
synchronized (this.pluginMap) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
this.pluginMap.forEach((s, plugin) -> {
if (plugin != null) {
plugin.onMessage(id, data);
}
});
} else {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
Object obj = plugin.onMessage(id, data);
if (obj != null) {
return obj;
}
}
}
}
}
@@ -322,9 +357,11 @@ public class PluginManager {
* Called when the activity receives a new intent.
*/
public void onNewIntent(Intent intent) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
plugin.onNewIntent(intent);
synchronized (this.pluginMap) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
plugin.onNewIntent(intent);
}
}
}
}
@@ -341,12 +378,14 @@ public class PluginManager {
* false to block the resource.
*/
public boolean shouldAllowRequest(String url) {
for (PluginEntry entry : this.entryMap.values()) {
CordovaPlugin plugin = pluginMap.get(entry.service);
if (plugin != null) {
Boolean result = plugin.shouldAllowRequest(url);
if (result != null) {
return result;
synchronized (this.entryMap) {
for (PluginEntry entry : this.entryMap.values()) {
CordovaPlugin plugin = pluginMap.get(entry.service);
if (plugin != null) {
Boolean result = plugin.shouldAllowRequest(url);
if (result != null) {
return result;
}
}
}
}
@@ -379,12 +418,14 @@ public class PluginManager {
* false to block the navigation.
*/
public boolean shouldAllowNavigation(String url) {
for (PluginEntry entry : this.entryMap.values()) {
CordovaPlugin plugin = pluginMap.get(entry.service);
if (plugin != null) {
Boolean result = plugin.shouldAllowNavigation(url);
if (result != null) {
return result;
synchronized (this.entryMap) {
for (PluginEntry entry : this.entryMap.values()) {
CordovaPlugin plugin = pluginMap.get(entry.service);
if (plugin != null) {
Boolean result = plugin.shouldAllowNavigation(url);
if (result != null) {
return result;
}
}
}
}
@@ -398,12 +439,14 @@ public class PluginManager {
* Called when the webview is requesting the exec() bridge be enabled.
*/
public boolean shouldAllowBridgeAccess(String url) {
for (PluginEntry entry : this.entryMap.values()) {
CordovaPlugin plugin = pluginMap.get(entry.service);
if (plugin != null) {
Boolean result = plugin.shouldAllowBridgeAccess(url);
if (result != null) {
return result;
synchronized (this.entryMap) {
for (PluginEntry entry : this.entryMap.values()) {
CordovaPlugin plugin = pluginMap.get(entry.service);
if (plugin != null) {
Boolean result = plugin.shouldAllowBridgeAccess(url);
if (result != null) {
return result;
}
}
}
}
@@ -425,12 +468,14 @@ public class PluginManager {
* false to block the intent.
*/
public Boolean shouldOpenExternalUrl(String url) {
for (PluginEntry entry : this.entryMap.values()) {
CordovaPlugin plugin = pluginMap.get(entry.service);
if (plugin != null) {
Boolean result = plugin.shouldOpenExternalUrl(url);
if (result != null) {
return result;
synchronized (this.entryMap) {
for (PluginEntry entry : this.entryMap.values()) {
CordovaPlugin plugin = pluginMap.get(entry.service);
if (plugin != null) {
Boolean result = plugin.shouldOpenExternalUrl(url);
if (result != null) {
return result;
}
}
}
}
@@ -446,32 +491,38 @@ public class PluginManager {
* @return Return false to allow the URL to load, return true to prevent the URL from loading.
*/
public boolean onOverrideUrlLoading(String url) {
for (PluginEntry entry : this.entryMap.values()) {
CordovaPlugin plugin = pluginMap.get(entry.service);
if (plugin != null && plugin.onOverrideUrlLoading(url)) {
return true;
synchronized (this.entryMap) {
for (PluginEntry entry : this.entryMap.values()) {
CordovaPlugin plugin = pluginMap.get(entry.service);
if (plugin != null && plugin.onOverrideUrlLoading(url)) {
return true;
}
}
return false;
}
return false;
}
/**
* Called when the app navigates or refreshes.
*/
public void onReset() {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
plugin.onReset();
synchronized (this.pluginMap) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
plugin.onReset();
}
}
}
}
Uri remapUri(Uri uri) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
Uri ret = plugin.remapUri(uri);
if (ret != null) {
return ret;
synchronized (this.pluginMap) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
Uri ret = plugin.remapUri(uri);
if (ret != null) {
return ret;
}
}
}
}
@@ -504,23 +555,42 @@ public class PluginManager {
* @param newConfig The new device configuration
*/
public void onConfigurationChanged(Configuration newConfig) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
plugin.onConfigurationChanged(newConfig);
synchronized (this.pluginMap) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
plugin.onConfigurationChanged(newConfig);
}
}
}
}
public Bundle onSaveInstanceState() {
Bundle state = new Bundle();
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
Bundle pluginState = plugin.onSaveInstanceState();
if(pluginState != null) {
state.putBundle(plugin.getServiceName(), pluginState);
synchronized (this.pluginMap) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
Bundle pluginState = plugin.onSaveInstanceState();
if (pluginState != null) {
state.putBundle(plugin.getServiceName(), pluginState);
}
}
}
}
return state;
}
/**
* Collect all plugins PathHandlers
*
* @return list of PathHandlers in no particular order
*/
public ArrayList<CordovaPluginPathHandler> getPluginPathHandlers() {
ArrayList<CordovaPluginPathHandler> handlers = new ArrayList<CordovaPluginPathHandler>();
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null && plugin.getPathHandler() != null) {
handlers.add(plugin.getPathHandler());
}
}
return handlers;
}
}

View File

@@ -27,7 +27,7 @@
<script type="text/javascript" charset="utf-8" src="../main.js"></script>
</head>
<body onload="init();" id="stage" class="theme">
<h1>Whitelist Page 1</h1>
<h1>Allow List Page 1</h1>
<div id="info">
<h4>Cordova: <span id="cordova"> &nbsp;</span></h4>
<h4>Deviceready: <span id="deviceready"> &nbsp;</span></h4>

View File

@@ -27,7 +27,7 @@
<script type="text/javascript" charset="utf-8" src="../main.js"></script>
</head>
<body onload="init();" id="stage" class="theme">
<h1>Whitelist Page 2</h1>
<h1>Allow List Page 2</h1>
<div id="info">
<h4>Cordova: <span id="cordova"> &nbsp;</span></h4>
<h4>Deviceready: <span id="deviceready"> &nbsp;</span></h4>

View File

@@ -18,17 +18,18 @@
*/
package org.apache.cordova.engine;
import android.annotation.TargetApi;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.net.Uri;
import android.net.http.SslError;
import android.os.Build;
import android.webkit.ClientCertRequest;
import android.webkit.HttpAuthHandler;
import android.webkit.MimeTypeMap;
import android.webkit.SslErrorHandler;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
@@ -36,14 +37,17 @@ import android.webkit.WebViewClient;
import org.apache.cordova.AuthenticationToken;
import org.apache.cordova.CordovaClientCertRequest;
import org.apache.cordova.CordovaHttpAuthHandler;
import org.apache.cordova.CordovaPluginPathHandler;
import org.apache.cordova.CordovaResourceApi;
import org.apache.cordova.LOG;
import org.apache.cordova.PluginManager;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Hashtable;
import androidx.webkit.WebViewAssetLoader;
/**
* This class is the WebViewClient that implements callbacks for our web view.
@@ -56,6 +60,7 @@ public class SystemWebViewClient extends WebViewClient {
private static final String TAG = "SystemWebViewClient";
protected final SystemWebViewEngine parentEngine;
private final WebViewAssetLoader assetLoader;
private boolean doClearHistory = false;
boolean isCurrentlyLoading;
@@ -64,6 +69,52 @@ public class SystemWebViewClient extends WebViewClient {
public SystemWebViewClient(SystemWebViewEngine parentEngine) {
this.parentEngine = parentEngine;
WebViewAssetLoader.Builder assetLoaderBuilder = new WebViewAssetLoader.Builder()
.setDomain(parentEngine.preferences.getString("hostname", "localhost"))
.setHttpAllowed(true);
assetLoaderBuilder.addPathHandler("/", path -> {
try {
// Check if there a plugins with pathHandlers
PluginManager pluginManager = this.parentEngine.pluginManager;
if (pluginManager != null) {
for (CordovaPluginPathHandler handler : pluginManager.getPluginPathHandlers()) {
if (handler.getPathHandler() != null) {
WebResourceResponse response = handler.getPathHandler().handle(path);
if (response != null) {
return response;
}
};
}
}
if (path.isEmpty()) {
path = "index.html";
}
InputStream is = parentEngine.webView.getContext().getAssets().open("www/" + path, AssetManager.ACCESS_STREAMING);
String mimeType = "text/html";
String extension = MimeTypeMap.getFileExtensionFromUrl(path);
if (extension != null) {
if (path.endsWith(".js") || path.endsWith(".mjs")) {
// Make sure JS files get the proper mimetype to support ES modules
mimeType = "application/javascript";
} else if (path.endsWith(".wasm")) {
mimeType = "application/wasm";
} else {
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
}
}
return new WebResourceResponse(mimeType, null, is);
} catch (Exception e) {
e.printStackTrace();
LOG.e(TAG, e.getMessage());
}
return null;
});
this.assetLoader = assetLoaderBuilder.build();
}
/**
@@ -320,10 +371,10 @@ public class SystemWebViewClient extends WebViewClient {
@SuppressWarnings("deprecation")
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
try {
// Check the against the whitelist and lock out access to the WebView directory
// Check the against the allow list and lock out access to the WebView directory
// Changing this will cause problems for your application
if (!parentEngine.pluginManager.shouldAllowRequest(url)) {
LOG.w(TAG, "URL blocked by whitelist: " + url);
LOG.w(TAG, "URL blocked by allow list: " + url);
// Results in a 404.
return new WebResourceResponse("text/plain", "UTF-8", null);
}
@@ -366,4 +417,9 @@ public class SystemWebViewClient extends WebViewClient {
return false;
}
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
return this.assetLoader.shouldInterceptRequest(request.getUrl());
}
}

View File

@@ -155,21 +155,24 @@ public class SystemWebViewEngine implements CordovaWebViewEngine {
String manufacturer = android.os.Build.MANUFACTURER;
LOG.d(TAG, "CordovaWebView is running on device made by: " + manufacturer);
//We don't save any form data in the application
// We don't save any form data in the application
// @todo remove when Cordova drop API level 26 support
settings.setSaveFormData(false);
settings.setSavePassword(false);
// Jellybean rightfully tried to lock this down. Too bad they didn't give us a whitelist
// while we do this
settings.setAllowUniversalAccessFromFileURLs(true);
if (preferences.getBoolean("AndroidInsecureFileModeEnabled", false)) {
//These settings are deprecated and loading content via file:// URLs is generally discouraged,
//but we allow this for compatibility reasons
LOG.d(TAG, "Enabled insecure file access");
settings.setAllowFileAccess(true);
settings.setAllowUniversalAccessFromFileURLs(true);
}
settings.setMediaPlaybackRequiresUserGesture(false);
// Enable database
// We keep this disabled because we use or shim to get around DOM_EXCEPTION_ERROR_16
String databasePath = webView.getContext().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
settings.setDatabaseEnabled(true);
settings.setDatabasePath(databasePath);
//Determine whether we're in debug or release mode, and turn on Debugging!
ApplicationInfo appInfo = webView.getContext().getApplicationContext().getApplicationInfo();
@@ -177,6 +180,7 @@ public class SystemWebViewEngine implements CordovaWebViewEngine {
enableRemoteDebugging();
}
// @todo remove when Cordova drop API level 24 support
settings.setGeolocationDatabasePath(databasePath);
// Enable DOM storage
@@ -185,12 +189,6 @@ public class SystemWebViewEngine implements CordovaWebViewEngine {
// Enable built-in geolocation
settings.setGeolocationEnabled(true);
// Enable AppCache
// Fix for CB-2282
settings.setAppCacheMaxSize(5 * 1048576);
settings.setAppCachePath(databasePath);
settings.setAppCacheEnabled(true);
// Fix for CB-1405
// Google issue 4641
String defaultUserAgent = settings.getUserAgentString();

View File

@@ -24,42 +24,40 @@ var CordovaError = require('cordova-common').CordovaError;
var Adb = {};
function isDevice (line) {
return line.match(/\w+\tdevice/) && !line.match(/emulator/);
}
function isEmulator (line) {
return line.match(/device/) && line.match(/emulator/);
}
/**
* Lists available/connected devices and emulators
*
* @param {Object} opts Various options
* @param {Boolean} opts.emulators Specifies whether this method returns
* emulators only
*
* @return {Promise<String[]>} list of available/connected
* devices/emulators
*/
Adb.devices = function (opts) {
return execa('adb', ['devices'], { cwd: os.tmpdir() }).then(({ stdout: output }) => {
return output.split('\n').filter(function (line) {
// Filter out either real devices or emulators, depending on options
return (line && opts && opts.emulators) ? isEmulator(line) : isDevice(line);
}).map(function (line) {
return line.replace(/\tdevice/, '').replace('\r', '');
});
});
Adb.devices = async function () {
const { stdout } = await execa('adb', ['devices'], { cwd: os.tmpdir() });
// Split into lines & drop first one (header)
const rawDeviceLines = stdout.trim().split(/\r?\n/).slice(1);
return rawDeviceLines
.map(line => line.split('\t'))
// We are only interested in fully booted devices & emulators. These
// have a state of `device`. For a list of all the other possible states
// see https://github.com/aosp-mirror/platform_system_core/blob/2abdb1eb5b83c8f39874644af576c869815f5c5b/adb/transport.cpp#L1129
.filter(([, state]) => state === 'device')
.map(([id]) => id);
};
Adb.install = function (target, packagePath, opts) {
Adb.install = function (target, packagePath, { replace = false, execOptions = {} } = {}) {
events.emit('verbose', 'Installing apk ' + packagePath + ' on target ' + target + '...');
var args = ['-s', target, 'install'];
if (opts && opts.replace) args.push('-r');
return execa('adb', args.concat(packagePath), { cwd: os.tmpdir() }).then(({ stdout: output }) => {
// 'adb install' seems to always returns no error, even if installation fails
// so we catching output to detect installation failure
if (replace) args.push('-r');
const opts = { cwd: os.tmpdir(), ...execOptions };
return execa('adb', args.concat(packagePath), opts).then(({ stdout: output }) => {
// adb does not return an error code even if installation fails. Instead it puts a specific
// message to stdout, so we have to use RegExp matching to detect installation failure.
if (output.match(/Failure/)) {
if (output.match(/INSTALL_PARSE_FAILED_NO_CERTIFICATES/)) {
output += '\n\n' + 'Sign the build using \'-- --keystore\' or \'--buildConfig\'' +
@@ -69,7 +67,7 @@ Adb.install = function (target, packagePath, opts) {
'\nEither uninstall an app or increment the versionCode.';
}
return Promise.reject(new CordovaError('Failed to install apk to device: ' + output));
throw new CordovaError('Failed to install apk to target: ' + output);
}
});
};

View File

@@ -17,26 +17,18 @@
under the License.
*/
/**
* @todo update coho to update this line.
* @todo use `package.json` instead but first
* figure out how this fit in with the platform-centered workflow structure.
* This workflow would not have the `package.json` file.
*/
// Coho updates this line
const VERSION = '9.0.0-dev';
var path = require('path');
var AndroidProject = require('./lib/AndroidProject');
var AndroidProject = require('./AndroidProject');
var PluginManager = require('cordova-common').PluginManager;
var CordovaLogger = require('cordova-common').CordovaLogger;
var selfEvents = require('cordova-common').events;
var ConfigParser = require('cordova-common').ConfigParser;
const prepare = require('./lib/prepare').prepare;
const prepare = require('./prepare').prepare;
var PLATFORM = 'android';
const VERSION = require('../package').version;
function setupEvents (externalEventEmitter) {
if (externalEventEmitter) {
@@ -66,7 +58,7 @@ function setupEvents (externalEventEmitter) {
class Api {
constructor (platform, platformRootDir, events) {
this.platform = PLATFORM;
this.root = path.resolve(__dirname, '..');
this.root = platformRootDir;
setupEvents(events);
@@ -85,6 +77,8 @@ class Api {
build: path.join(this.root, 'build'),
javaSrc: path.join(appMain, 'java')
};
this._builder = require('./builders/builders').getBuilder(this.root);
}
/**
@@ -161,7 +155,7 @@ class Api {
if (plugin.getFrameworks(this.platform).length === 0) return;
selfEvents.emit('verbose', 'Updating build files since android plugin contained <framework>');
// This should pick the correct builder, not just get gradle
require('./lib/builders/builders').getBuilder().prepBuildFiles();
this._builder.prepBuildFiles();
}.bind(this))
// CB-11022 Return truthy value to prevent running prepare after
.then(() => true);
@@ -193,7 +187,7 @@ class Api {
if (plugin.getFrameworks(this.platform).length === 0) return;
selfEvents.emit('verbose', 'Updating build files since android plugin contained <framework>');
require('./lib/builders/builders').getBuilder().prepBuildFiles();
this._builder.prepBuildFiles();
}.bind(this))
// CB-11022 Return truthy value to prevent running prepare after
.then(() => true);
@@ -247,8 +241,8 @@ class Api {
build (buildOptions) {
var self = this;
return require('./lib/check_reqs').run().then(function () {
return require('./lib/build').run.call(self, buildOptions);
return require('./check_reqs').run().then(function () {
return require('./build').run.call(self, buildOptions);
}).then(function (buildResults) {
// Cast build result to array of build artifacts
return buildResults.paths.map(function (apkPath) {
@@ -276,8 +270,8 @@ class Api {
*/
run (runOptions) {
var self = this;
return require('./lib/check_reqs').run().then(function () {
return require('./lib/run').run.call(self, runOptions);
return require('./check_reqs').run().then(function () {
return require('./run').run.call(self, runOptions);
});
}
@@ -295,10 +289,10 @@ class Api {
cleanOptions = {};
}
return require('./lib/check_reqs').run().then(function () {
return require('./lib/build').runClean.call(self, cleanOptions);
return require('./check_reqs').run().then(function () {
return require('./build').runClean.call(self, cleanOptions);
}).then(function () {
return require('./lib/prepare').clean.call(self, cleanOptions);
return require('./prepare').clean.call(self, cleanOptions);
});
}
@@ -311,7 +305,7 @@ class Api {
* objects for current platform.
*/
requirements () {
return require('./lib/check_reqs').check_all();
return require('./check_reqs').check_all(this.root);
}
/**
@@ -336,7 +330,7 @@ class Api {
events = setupEvents(events);
var result;
try {
result = require('../../lib/create').create(destination, config, options, events).then(function (destination) {
result = require('./create').create(destination, config, options, events).then(function (destination) {
return new Api(PLATFORM, destination, events);
});
} catch (e) {

View File

@@ -76,23 +76,12 @@ function parse_targets (output) {
return targets;
}
module.exports.list_targets_with_android = function () {
return execa('android', ['list', 'target']).then(result => parse_targets(result.stdout));
};
module.exports.list_targets_with_avdmanager = function () {
return execa('avdmanager', ['list', 'target']).then(result => parse_targets(result.stdout));
};
module.exports.list_targets = function () {
return module.exports.list_targets_with_avdmanager().catch(function (err) {
// If there's an error, like avdmanager could not be found, we can try
// as a last resort, to run `android`, in case this is a super old
// SDK installation.
if (err && (err.code === 'ENOENT' || (err.stderr && err.stderr.match(/not recognized/)))) {
return module.exports.list_targets_with_android();
} else throw err;
}).then(function (targets) {
return module.exports.list_targets_with_avdmanager().then(function (targets) {
if (targets.length === 0) {
return Promise.reject(new Error('No android targets (SDKs) installed!'));
}

View File

@@ -20,13 +20,11 @@
var path = require('path');
var fs = require('fs');
var nopt = require('nopt');
const untildify = require('untildify');
var Adb = require('./Adb');
var builders = require('./builders/builders');
var events = require('cordova-common').events;
const execa = require('execa');
var CordovaError = require('cordova-common').CordovaError;
var PackageType = require('./PackageType');
module.exports.parseBuildOptions = parseOpts;
@@ -85,9 +83,7 @@ function parseOpts (options, resolvedTarget, projectRoot) {
if (config.android && config.android[ret.buildType]) {
var androidInfo = config.android[ret.buildType];
if (androidInfo.keystore && !packageArgs.keystore) {
if (androidInfo.keystore.substr(0, 1) === '~') {
androidInfo.keystore = process.env.HOME + androidInfo.keystore.substr(1);
}
androidInfo.keystore = untildify(androidInfo.keystore);
packageArgs.keystore = path.resolve(path.dirname(buildConfig), androidInfo.keystore);
events.emit('log', 'Reading the keystore from: ' + packageArgs.keystore);
}
@@ -133,7 +129,11 @@ function parseOpts (options, resolvedTarget, projectRoot) {
ret.packageType = packageArgs.packageType;
}
} else {
ret.packageType = PackageType.APK;
if (ret.buildType === 'release') {
ret.packageType = PackageType.BUNDLE;
} else {
ret.packageType = PackageType.APK;
}
}
return ret;
@@ -145,7 +145,7 @@ function parseOpts (options, resolvedTarget, projectRoot) {
*/
module.exports.runClean = function (options) {
var opts = parseOpts(options, null, this.root);
var builder = builders.getBuilder();
var builder = this._builder;
return builder.prepEnv(opts).then(function () {
return builder.clean(opts);
@@ -166,7 +166,7 @@ module.exports.runClean = function (options) {
*/
module.exports.run = function (options, optResolvedTarget) {
var opts = parseOpts(options, optResolvedTarget, this.root);
var builder = builders.getBuilder();
var builder = this._builder;
return builder.prepEnv(opts).then(function () {
if (opts.prepEnv) {
@@ -196,45 +196,8 @@ module.exports.run = function (options, optResolvedTarget) {
* Returns "arm" or "x86".
*/
module.exports.detectArchitecture = function (target) {
function helper () {
return Adb.shell(target, 'cat /proc/cpuinfo').then(function (output) {
return /intel/i.exec(output) ? 'x86' : 'arm';
});
}
function timeout (ms, err) {
return new Promise((resolve, reject) => {
setTimeout(() => reject(err), ms);
});
}
// It sometimes happens (at least on OS X), that this command will hang forever.
// To fix it, either unplug & replug device, or restart adb server.
return Promise.race([
helper(),
timeout(5000, new CordovaError(
'Device communication timed out. Try unplugging & replugging the device.'
))
]).catch(err => {
if (/timed out/.exec('' + err)) {
// adb kill-server doesn't seem to do the trick.
// Could probably find a x-platform version of killall, but I'm not actually
// sure that this scenario even happens on non-OSX machines.
events.emit('verbose', 'adb timed out while detecting device/emulator architecture. Killing adb and trying again.');
return execa('killall', ['adb']).then(function () {
return helper().then(null, function () {
// The double kill is sadly often necessary, at least on mac.
events.emit('warn', 'adb timed out a second time while detecting device/emulator architecture. Killing adb and trying again.');
return execa('killall', ['adb']).then(function () {
return helper().then(null, function () {
return Promise.reject(new CordovaError('adb timed out a third time while detecting device/emulator architecture. Try unplugging & replugging the device.'));
});
});
});
}, function () {
// For non-killall OS's.
return Promise.reject(err);
});
}
throw err;
return Adb.shell(target, 'cat /proc/cpuinfo').then(function (output) {
return /intel/i.exec(output) ? 'x86' : 'arm';
});
};
@@ -281,26 +244,3 @@ PackageInfo.prototype = {
propertiesParser.save();
}
};
module.exports.help = function () {
console.log('Usage: ' + path.relative(process.cwd(), path.join('../build')) + ' [flags] [Signed APK flags]');
console.log('Flags:');
console.log(' \'--debug\': will build project in debug mode (default)');
console.log(' \'--release\': will build project for release');
console.log(' \'--nobuild\': will skip build process (useful when using run command)');
console.log(' \'--prepenv\': don\'t build, but copy in build scripts where necessary');
console.log(' \'--versionCode=#\': Override versionCode for this build. Useful for uploading multiple APKs.');
console.log(' \'--minSdkVersion=#\': Override minSdkVersion for this build.');
console.log(' \'--maxSdkVersion=#\': Override maxSdkVersion for this build. (Not Recommended)');
console.log(' \'--targetSdkVersion=#\': Override targetSdkVersion for this build.');
console.log(' \'--gradleArg=<gradle command line arg>\': Extra args to pass to the gradle command. Use one flag per arg. Ex. --gradleArg=-PcdvBuildMultipleApks=true');
console.log(' \'--packageType=<apk|bundle>\': Builds an APK or a bundle');
console.log('');
console.log('Signed APK flags (overwrites debug/release-signing.proprties) :');
console.log(' \'--keystore=<path to keystore>\': Key store used to build a signed archive. (Required)');
console.log(' \'--alias=\': Alias for the key store. (Required)');
console.log(' \'--storePassword=\': Password for the key store. (Optional - prompted)');
console.log(' \'--password=\': Password for the key. (Optional - prompted)');
console.log(' \'--keystoreType\': Type of the keystore. (Optional)');
process.exit(0);
};

View File

@@ -20,10 +20,12 @@
var fs = require('fs-extra');
var path = require('path');
const execa = require('execa');
const glob = require('fast-glob');
var events = require('cordova-common').events;
var CordovaError = require('cordova-common').CordovaError;
var check_reqs = require('../check_reqs');
var PackageType = require('../PackageType');
const { compareByAll } = require('../utils');
const { createEditor } = require('properties-parser');
const MARKER = 'YOUR CHANGES WILL BE ERASED!';
@@ -32,88 +34,51 @@ const TEMPLATE =
'# This file is automatically generated.\n' +
'# Do not modify this file -- ' + MARKER + '\n';
const archSpecificRegex = /-x86|-arm/;
const unsignedBuildRegex = /-unsigned/;
const isPathArchSpecific = p => /-x86|-arm/.test(path.basename(p));
const fileSorter = (filePathA, filePathB) => {
const archSpecificA = archSpecificRegex.test(filePathA);
const archSpecificB = archSpecificRegex.test(filePathB);
const outputFileComparator = compareByAll([
// Sort arch specific builds after generic ones
isPathArchSpecific,
// If they are not equal, then sort by specific archs after generic ones
if (archSpecificA !== archSpecificB) {
return archSpecificA < archSpecificB ? -1 : 1;
}
// Sort unsigned builds after signed ones
filePath => /-unsigned/.test(path.basename(filePath)),
// Otherwise, move onto the next sort item, which is by sorting unsigned bulds after signed ones
const unsignedA = unsignedBuildRegex.test(filePathA);
const unsignedB = unsignedBuildRegex.test(filePathB);
// Sort by file modification time, latest first
filePath => -fs.statSync(filePath).mtime.getTime(),
if (unsignedA !== unsignedB) {
return unsignedA < unsignedB ? -1 : 1;
}
// Then, sort by modification time, latest first
const modTimeA = fs.statSync(filePathA).mtime.getTime();
const modTimeB = fs.statSync(filePathB).mtime.getTime();
if (modTimeA !== modTimeB) {
return modTimeA < modTimeB ? 1 : -1;
}
// Finally, if all above is the same, sort by file name length, ascending
return filePathB.length < filePathA.length ? -1 : 1;
};
// Sort by file name length, ascending
filePath => filePath.length
]);
/**
* If the provided directory does not exist or extension is missing, return an empty array.
* If the director exists, loop the directories and collect list of files matching the extension.
*
* @param {String} dir Directory to scan
* @param {String} extension
* @param {'apk' | 'aab'} bundleType
* @param {'debug' | 'release'} buildType
* @param {{arch?: string}} options
*/
function recursivelyFindFiles (dir, extension) {
if (!fs.existsSync(dir) || !extension) return [];
const files = fs.readdirSync(dir, { withFileTypes: true })
.map(entry => {
const item = path.resolve(dir, entry.name);
if (entry.isDirectory()) return recursivelyFindFiles(item, extension);
if (path.extname(entry.name) === `.${extension}`) return item;
return false;
});
return Array.prototype.concat(...files)
.filter(file => file !== false);
}
/**
* @param {String} dir
* @param {String} build_type
* @param {String} arch
* @param {String} extension
*/
function findOutputFilesHelper (dir, build_type, arch, extension) {
let files = recursivelyFindFiles(path.resolve(dir, build_type), extension);
function findOutputFiles (bundleType, buildType, { arch } = {}) {
let files = glob.sync(`**/*.${bundleType}`, {
absolute: true,
cwd: path.resolve(this[`${bundleType}Dir`], buildType)
}).map(path.normalize);
if (files.length === 0) return files;
// Assume arch-specific build if newest apk has -x86 or -arm.
const archSpecific = !!/-x86|-arm/.exec(path.basename(files[0]));
const archSpecific = isPathArchSpecific(files[0]);
// And show only arch-specific ones (or non-arch-specific)
files = files.filter(p => !!/-x86|-arm/.exec(path.basename(p)) === archSpecific);
files = files.filter(p => isPathArchSpecific(p) === archSpecific);
if (archSpecific && files.length > 1 && arch) {
files = files.filter(p => path.basename(p).indexOf('-' + arch) !== -1);
files = files.filter(p => path.basename(p).includes('-' + arch));
}
return files;
return files.sort(outputFileComparator);
}
class ProjectBuilder {
constructor (rootDirectory) {
this.root = rootDirectory || path.resolve(__dirname, '../../..');
this.root = rootDirectory;
this.apkDir = path.join(this.root, 'app', 'build', 'outputs', 'apk');
this.aabDir = path.join(this.root, 'app', 'build', 'outputs', 'bundle');
}
@@ -195,7 +160,7 @@ class ProjectBuilder {
// Makes the project buildable, minus the gradle wrapper.
prepBuildFiles () {
// Update the version of build.gradle in each dependent library.
var pluginBuildGradle = path.join(this.root, 'cordova', 'lib', 'plugin-build.gradle');
var pluginBuildGradle = path.join(__dirname, 'plugin-build.gradle');
var propertiesObj = this.readProjectProperties();
var subProjects = propertiesObj.libs;
@@ -300,10 +265,11 @@ class ProjectBuilder {
}).then(function () {
return self.prepBuildFiles();
}).then(() => {
const config = this._getCordovaConfig();
// update/set the distributionUrl in the gradle-wrapper.properties
const gradleWrapperPropertiesPath = path.join(self.root, 'gradle/wrapper/gradle-wrapper.properties');
const gradleWrapperProperties = createEditor(gradleWrapperPropertiesPath);
const distributionUrl = process.env.CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL || 'https://services.gradle.org/distributions/gradle-6.5-all.zip';
const distributionUrl = process.env.CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL || `https://services.gradle.org/distributions/gradle-${config.GRADLE_VERSION}-all.zip`;
gradleWrapperProperties.set('distributionUrl', distributionUrl);
gradleWrapperProperties.save();
@@ -322,31 +288,41 @@ class ProjectBuilder {
});
}
/**
* @private
* @returns The user defined configs
*/
_getCordovaConfig () {
return fs.readJSONSync(path.join(this.root, 'cdv-gradle-config.json'));
}
/*
* Builds the project with gradle.
* Returns a promise.
*/
build (opts) {
async build (opts) {
var wrapper = path.join(this.root, 'gradlew');
var args = this.getArgs(opts.buildType === 'debug' ? 'debug' : 'release', opts);
return execa(wrapper, args, { stdio: 'inherit' })
.catch(function (error) {
if (error.toString().indexOf('failed to find target with hash string') >= 0) {
return check_reqs.check_android_target(error).then(function () {
// If due to some odd reason - check_android_target succeeds
// we should still fail here.
throw error;
});
try {
return await execa(wrapper, args, { stdio: 'inherit', cwd: path.resolve(this.root) });
} catch (error) {
if (error.toString().includes('failed to find target with hash string')) {
// Add hint from check_android_target to error message
try {
await check_reqs.check_android_target(this.root);
} catch (checkAndroidTargetError) {
error.message += '\n' + checkAndroidTargetError.message;
}
throw error;
});
}
throw error;
}
}
clean (opts) {
const wrapper = path.join(this.root, 'gradlew');
const args = this.getArgs('clean', opts);
return execa(wrapper, args, { stdio: 'inherit' })
return execa(wrapper, args, { stdio: 'inherit', cwd: path.resolve(this.root) })
.then(() => {
fs.removeSync(path.join(this.root, 'out'));
@@ -362,11 +338,11 @@ class ProjectBuilder {
}
findOutputApks (build_type, arch) {
return findOutputFilesHelper(this.apkDir, build_type, arch, 'apk').sort(fileSorter);
return findOutputFiles.call(this, 'apk', build_type, { arch });
}
findOutputBundles (build_type) {
return findOutputFilesHelper(this.aabDir, build_type, false, 'aab').sort(fileSorter);
return findOutputFiles.call(this, 'aab', build_type);
}
fetchBuildResults (build_type, arch) {

View File

@@ -21,7 +21,7 @@
buildscript {
repositories {
google()
jcenter()
mavenCentral()
}
// Switch the Android Gradle plugin version requirement depending on the
@@ -42,8 +42,8 @@ dependencies {
}
android {
compileSdkVersion cdvCompileSdkVersion
buildToolsVersion cdvBuildToolsVersion
compileSdkVersion cordovaConfig.SDK_VERSION
buildToolsVersion cordovaConfig.BUILD_TOOLS_VERSION
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_6

View File

@@ -20,95 +20,46 @@
const execa = require('execa');
var path = require('path');
var fs = require('fs-extra');
var os = require('os');
var which = require('which');
var REPO_ROOT = path.join(__dirname, '..', '..', '..', '..');
var PROJECT_ROOT = path.join(__dirname, '..', '..');
const { forgivingWhichSync, isWindows, isDarwin } = require('./utils');
const java = require('./env/java');
const { CordovaError, ConfigParser, events } = require('cordova-common');
var android_sdk = require('./android_sdk');
const { createEditor } = require('properties-parser');
const { SDK_VERSION } = require('./gradle-config-defaults');
function forgivingWhichSync (cmd) {
const whichResult = which.sync(cmd, { nothrow: true });
// Re-exporting these for backwards compatibility and for unit testing.
// TODO: Remove uses and use the ./utils module directly.
Object.assign(module.exports, { isWindows, isDarwin });
// On null, returns empty string to maintain backwards compatibility
// realpathSync follows symlinks
return whichResult === null ? '' : fs.realpathSync(whichResult);
}
/**
* @param {string} projectRoot
* @returns {string} The android target in format "android-${target}"
*/
module.exports.get_target = function (projectRoot) {
const userTargetSdkVersion = getUserTargetSdkVersion(projectRoot);
function getJDKDirectory (directory) {
const p = path.resolve(directory, 'java');
if (fs.existsSync(p)) {
const directories = fs.readdirSync(p);
for (let i = 0; i < directories.length; i++) {
const dir = directories[i];
if (/^(jdk)+./.test(dir)) {
return path.resolve(directory, 'java', dir);
}
}
if (userTargetSdkVersion && userTargetSdkVersion < SDK_VERSION) {
events.emit('warn', `android-targetSdkVersion should be greater than or equal to ${SDK_VERSION}.`);
}
return null;
}
module.exports.isWindows = function () {
return (os.platform() === 'win32');
};
module.exports.isDarwin = function () {
return (os.platform() === 'darwin');
return `android-${Math.max(userTargetSdkVersion, SDK_VERSION)}`;
};
/**
* @description Get valid target from framework/project.properties if run from this repo
* Otherwise get target from project.properties file within a generated cordova-android project
* @returns {string} The android target in format "android-${target}"
* @param {string} projectRoot
* @returns {number} target sdk or 0 if undefined
*/
module.exports.get_target = function () {
const projectPropertiesPaths = [
path.join(REPO_ROOT, 'framework', 'project.properties'),
path.join(PROJECT_ROOT, 'project.properties')
];
// Get the minimum required target API from the framework.
let target = projectPropertiesPaths.filter(filePath => fs.existsSync(filePath))
.map(filePath => createEditor(filePath).get('target'))
.pop();
if (!target) {
throw new Error(`We could not locate the target from the "project.properties" at either "${projectPropertiesPaths.join('", "')}".`);
}
function getUserTargetSdkVersion (projectRoot) {
// If the repo config.xml file exists, find the desired targetSdkVersion.
const configFile = path.join(REPO_ROOT, 'config.xml');
if (!fs.existsSync(configFile)) return target;
// We need to use the cordova project's config.xml here, since the platform
// project's config.xml does not yet have the user's preferences when this
// function is called during `Api.createPlatform`.
const configFile = path.join(projectRoot, '../../config.xml');
if (!fs.existsSync(configFile)) return 0;
const configParser = new ConfigParser(configFile);
const desiredAPI = parseInt(configParser.getPreference('android-targetSdkVersion', 'android'), 10);
if (!isNaN(desiredAPI)) {
const minimumAPI = parseInt(target.split('-').pop(), 10);
if (desiredAPI >= minimumAPI) {
target = `android-${desiredAPI}`;
} else {
events.emit('warn', `android-targetSdkVersion should be greater than or equal to ${minimumAPI}.`);
}
}
return target;
};
// Returns a promise. Called only by build and clean commands.
module.exports.check_ant = function () {
return execa('ant', ['-version']).then(({ stdout: output }) => {
// Parse Ant version from command output
return /version ((?:\d+\.)+(?:\d+))/i.exec(output)[1];
}).catch(function (err) {
if (err) {
throw new CordovaError('Failed to run `ant -version`. Make sure you have `ant` on your $PATH.');
}
});
};
const targetSdkVersion = parseInt(configParser.getPreference('android-targetSdkVersion', 'android'), 10);
return isNaN(targetSdkVersion) ? 0 : targetSdkVersion;
}
module.exports.get_gradle_wrapper = function () {
var androidStudioPath;
@@ -168,75 +119,14 @@ module.exports.check_gradle = function () {
'in your path, or install Android Studio'));
};
// Returns a promise.
module.exports.check_java = function () {
var javacPath = forgivingWhichSync('javac');
var hasJavaHome = !!process.env.JAVA_HOME;
return Promise.resolve().then(function () {
if (hasJavaHome) {
// Windows java installer doesn't add javac to PATH, nor set JAVA_HOME (ugh).
if (!javacPath) {
process.env.PATH += path.delimiter + path.join(process.env.JAVA_HOME, 'bin');
}
} else {
if (javacPath) {
// OS X has a command for finding JAVA_HOME.
var find_java = '/usr/libexec/java_home';
var default_java_error_msg = 'Failed to find \'JAVA_HOME\' environment variable. Try setting it manually.';
if (fs.existsSync(find_java)) {
return execa(find_java).then(({ stdout }) => {
process.env.JAVA_HOME = stdout;
}).catch(function (err) {
if (err) {
throw new CordovaError(default_java_error_msg);
}
});
} else {
// See if we can derive it from javac's location.
var maybeJavaHome = path.dirname(path.dirname(javacPath));
if (fs.existsSync(path.join(maybeJavaHome, 'lib', 'tools.jar'))) {
process.env.JAVA_HOME = maybeJavaHome;
} else {
throw new CordovaError(default_java_error_msg);
}
}
} else if (module.exports.isWindows()) {
const programFilesEnv = path.resolve(process.env.ProgramFiles);
const programFiles = 'C:\\Program Files\\';
const programFilesx86 = 'C:\\Program Files (x86)\\';
let firstJdkDir =
getJDKDirectory(programFilesEnv) ||
getJDKDirectory(programFiles) ||
getJDKDirectory(programFilesx86);
if (firstJdkDir) {
firstJdkDir = firstJdkDir.replace(/\//g, path.sep);
if (!javacPath) {
process.env.PATH += path.delimiter + path.join(firstJdkDir, 'bin');
}
process.env.JAVA_HOME = firstJdkDir;
}
}
}
}).then(function () {
return execa('javac', ['-version'], { all: true })
.then(({ all: output }) => {
// Java <= 8 writes version info to stderr, Java >= 9 to stdout
const match = /javac\s+([\d.]+)/i.exec(output);
return match && match[1];
}, () => {
var msg =
'Failed to run "javac -version", make sure that you have a JDK version 8 installed.\n' +
'You can get it from the following location:\n' +
'https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html';
if (process.env.JAVA_HOME) {
msg += '\n\n';
msg += 'Your JAVA_HOME is invalid: ' + process.env.JAVA_HOME;
}
throw new CordovaError(msg);
});
});
/**
* Checks for the java installation and correct version
*
* Despite the name, it should return the Java version value, it's used by the Cordova CLI.
*/
module.exports.check_java = async function () {
const javaVersion = await java.getVersion();
return javaVersion;
};
// Returns a promise.
@@ -249,7 +139,6 @@ module.exports.check_android = function () {
}
}
var androidCmdPath = forgivingWhichSync('android');
var adbInPath = forgivingWhichSync('adb');
var avdmanagerInPath = forgivingWhichSync('avdmanager');
var hasAndroidHome = false;
@@ -260,7 +149,7 @@ module.exports.check_android = function () {
// First ensure ANDROID_HOME is set
// If we have no hints (nothing in PATH), try a few default locations
if (!hasAndroidHome && !androidCmdPath && !adbInPath && !avdmanagerInPath) {
if (!hasAndroidHome && !adbInPath && !avdmanagerInPath) {
if (process.env.ANDROID_HOME) {
// Fallback to deprecated `ANDROID_HOME` variable
maybeSetAndroidHome(path.join(process.env.ANDROID_HOME));
@@ -310,17 +199,6 @@ module.exports.check_android = function () {
if (!hasAndroidHome) {
// If we dont have ANDROID_SDK_ROOT, but we do have some tools on the PATH, try to infer from the tooling PATH.
var parentDir, grandParentDir;
if (androidCmdPath) {
parentDir = path.dirname(androidCmdPath);
grandParentDir = path.dirname(parentDir);
if (path.basename(parentDir) === 'tools' || fs.existsSync(path.join(grandParentDir, 'tools', 'android'))) {
maybeSetAndroidHome(grandParentDir);
} else {
throw new CordovaError('Failed to find \'ANDROID_SDK_ROOT\' environment variable. Try setting it manually.\n' +
'Detected \'android\' command at ' + parentDir + ' but no \'tools\' directory found near.\n' +
'Try reinstall Android SDK or update your PATH to include valid path to SDK' + path.sep + 'tools directory.');
}
}
if (adbInPath) {
parentDir = path.dirname(adbInPath);
grandParentDir = path.dirname(parentDir);
@@ -353,9 +231,6 @@ module.exports.check_android = function () {
'\nTry update it manually to point to valid SDK directory.');
}
// Next let's make sure relevant parts of the SDK tooling is in our PATH
if (hasAndroidHome && !androidCmdPath) {
process.env.PATH += path.delimiter + path.join(process.env.ANDROID_SDK_ROOT, 'tools');
}
if (hasAndroidHome && !adbInPath) {
process.env.PATH += path.delimiter + path.join(process.env.ANDROID_SDK_ROOT, 'platform-tools');
}
@@ -366,40 +241,18 @@ module.exports.check_android = function () {
});
};
// TODO: is this actually needed?
module.exports.getAbsoluteAndroidCmd = function () {
var cmd = forgivingWhichSync('android');
if (cmd.length === 0) {
cmd = forgivingWhichSync('sdkmanager');
}
if (module.exports.isWindows()) {
return '"' + cmd + '"';
}
return cmd.replace(/(\s)/g, '\\$1');
};
module.exports.check_android_target = function (originalError) {
module.exports.check_android_target = function (projectRoot) {
// valid_target can look like:
// android-19
// android-L
// Google Inc.:Google APIs:20
// Google Inc.:Glass Development Kit Preview:20
var desired_api_level = module.exports.get_target();
var desired_api_level = module.exports.get_target(projectRoot);
return android_sdk.list_targets().then(function (targets) {
if (targets.indexOf(desired_api_level) >= 0) {
return targets;
}
var androidCmd = module.exports.getAbsoluteAndroidCmd();
var msg = 'Please install Android target / API level: "' + desired_api_level + '".\n\n' +
'Hint: Open the SDK manager by running: ' + androidCmd + '\n' +
'You will require:\n' +
'1. "SDK Platform" for API level ' + desired_api_level + '\n' +
'2. "Android SDK Platform-tools (latest)\n' +
'3. "Android SDK Build-tools" (latest)';
if (originalError) {
msg = originalError + '\n' + msg;
}
throw new CordovaError(msg);
throw new CordovaError(`Please install the Android SDK Platform "platforms;${desired_api_level}"`);
});
};
@@ -412,13 +265,6 @@ module.exports.run = function () {
return Promise.all([this.check_java(), this.check_android()]).then(function (values) {
console.log('Using Android SDK: ' + process.env.ANDROID_SDK_ROOT);
if (!String(values[0]).startsWith('1.8.')) {
throw new CordovaError(
'Requirements check failed for JDK 8 (\'1.8.*\')! Detected version: ' + values[0] + '\n' +
'Check your ANDROID_SDK_ROOT / JAVA_HOME / PATH environment variables.'
);
}
if (!values[1]) {
throw new CordovaError('Requirements check failed for Android SDK! Android SDK was not detected.');
}
@@ -446,9 +292,10 @@ var Requirement = function (id, name, version, installed) {
* Methods that runs all checks one by one and returns a result of checks
* as an array of Requirement objects. This method intended to be used by cordova-lib check_reqs method
*
* @param {string} projectRoot
* @return Promise<Requirement[]> Array of requirements. Due to implementation, promise is always fulfilled.
*/
module.exports.check_all = function () {
module.exports.check_all = function (projectRoot) {
var requirements = [
new Requirement('java', 'Java JDK'),
new Requirement('androidSdk', 'Android SDK'),
@@ -459,7 +306,7 @@ module.exports.check_all = function () {
var checkFns = [
this.check_java,
this.check_android,
this.check_android_target,
this.check_android_target.bind(this, projectRoot),
this.check_gradle
];

View File

@@ -30,15 +30,12 @@ class GradlePropertiesParser {
*/
constructor (platformDir) {
this._defaults = {
// 10 seconds -> 6 seconds
'org.gradle.daemon': 'true',
// to allow dex in process
'org.gradle.jvmargs': '-Xmx2048m',
// Android X
'android.useAndroidX': 'false',
'android.enableJetifier': 'false'
'android.useAndroidX': 'true',
'android.enableJetifier': 'true'
// Shaves another 100ms, but produces a "try at own risk" warning. Not worth it (yet):
// 'org.gradle.parallel': 'true'

View File

@@ -19,12 +19,13 @@
var path = require('path');
var fs = require('fs-extra');
var utils = require('../../bin/lib/utils');
var check_reqs = require('./../templates/cordova/lib/check_reqs');
var ROOT = path.join(__dirname, '..', '..');
var utils = require('./utils');
var check_reqs = require('./check_reqs');
var ROOT = path.join(__dirname, '..');
const { createEditor } = require('properties-parser');
var CordovaError = require('cordova-common').CordovaError;
var AndroidManifest = require('../templates/cordova/lib/AndroidManifest');
var AndroidManifest = require('./AndroidManifest');
// Export all helper functions, and make sure internally within this module, we
// reference these methods via the `exports` object - this helps with testing
@@ -36,21 +37,18 @@ exports.copyScripts = copyScripts;
exports.copyBuildRules = copyBuildRules;
exports.writeProjectProperties = writeProjectProperties;
exports.prepBuildFiles = prepBuildFiles;
exports.writeNameForAndroidStudio = writeNameForAndroidStudio;
function getFrameworkDir (projectPath, shared) {
return shared ? path.join(ROOT, 'framework') : path.join(projectPath, 'CordovaLib');
}
function copyJsAndLibrary (projectPath, shared, projectName, isLegacy) {
function copyJsAndLibrary (projectPath, shared, projectName, targetAPI) {
var nestedCordovaLibPath = getFrameworkDir(projectPath, false);
var srcCordovaJsPath = path.join(ROOT, 'bin', 'templates', 'project', 'assets', 'www', 'cordova.js');
var srcCordovaJsPath = path.join(ROOT, 'templates', 'project', 'assets', 'www', 'cordova.js');
var app_path = path.join(projectPath, 'app', 'src', 'main');
const platform_www = path.join(projectPath, 'platform_www');
if (isLegacy) {
app_path = projectPath;
}
fs.copySync(srcCordovaJsPath, path.join(app_path, 'assets', 'www', 'cordova.js'));
// Copy the cordova.js file to platforms/<platform>/platform_www/
@@ -58,20 +56,20 @@ function copyJsAndLibrary (projectPath, shared, projectName, isLegacy) {
fs.ensureDirSync(platform_www);
fs.copySync(srcCordovaJsPath, path.join(platform_www, 'cordova.js'));
// Copy cordova-js-src directory into platform_www directory.
// We need these files to build cordova.js if using browserify method.
fs.copySync(path.join(ROOT, 'cordova-js-src'), path.join(platform_www, 'cordova-js-src'));
if (shared) {
var relativeFrameworkPath = path.relative(projectPath, getFrameworkDir(projectPath, true));
fs.symlinkSync(relativeFrameworkPath, nestedCordovaLibPath, 'dir');
} else {
fs.ensureDirSync(nestedCordovaLibPath);
fs.copySync(path.join(ROOT, 'framework', 'AndroidManifest.xml'), path.join(nestedCordovaLibPath, 'AndroidManifest.xml'));
fs.copySync(path.join(ROOT, 'framework', 'project.properties'), path.join(nestedCordovaLibPath, 'project.properties'));
const propertiesEditor = createEditor(path.join(ROOT, 'framework', 'project.properties'));
propertiesEditor.set('target', targetAPI);
propertiesEditor.save(path.join(nestedCordovaLibPath, 'project.properties'));
fs.copySync(path.join(ROOT, 'framework', 'build.gradle'), path.join(nestedCordovaLibPath, 'build.gradle'));
fs.copySync(path.join(ROOT, 'framework', 'cordova.gradle'), path.join(nestedCordovaLibPath, 'cordova.gradle'));
fs.copySync(path.join(ROOT, 'framework', 'repositories.gradle'), path.join(nestedCordovaLibPath, 'repositories.gradle'));
fs.copySync(path.join(ROOT, 'framework', 'src'), path.join(nestedCordovaLibPath, 'src'));
fs.copySync(path.join(ROOT, 'framework', 'cdv-gradle-config-defaults.json'), path.join(projectPath, 'cdv-gradle-config.json'));
}
}
@@ -87,7 +85,7 @@ function extractSubProjectPaths (data) {
function writeProjectProperties (projectPath, target_api) {
var dstPath = path.join(projectPath, 'project.properties');
var templatePath = path.join(ROOT, 'bin', 'templates', 'project', 'project.properties');
var templatePath = path.join(ROOT, 'templates', 'project', 'project.properties');
var srcPath = fs.existsSync(dstPath) ? dstPath : templatePath;
var data = fs.readFileSync(srcPath, 'utf8');
@@ -111,12 +109,12 @@ function writeProjectProperties (projectPath, target_api) {
// This makes no sense, what if you're building with a different build system?
function prepBuildFiles (projectPath) {
var buildModule = require('../templates/cordova/lib/builders/builders');
var buildModule = require('./builders/builders');
buildModule.getBuilder(projectPath).prepBuildFiles();
}
function copyBuildRules (projectPath, isLegacy) {
var srcDir = path.join(ROOT, 'bin', 'templates', 'project');
var srcDir = path.join(ROOT, 'templates', 'project');
if (isLegacy) {
// The project's build.gradle is identical to the earlier build.gradle, so it should still work
@@ -125,38 +123,19 @@ function copyBuildRules (projectPath, isLegacy) {
} else {
fs.copySync(path.join(srcDir, 'build.gradle'), path.join(projectPath, 'build.gradle'));
fs.copySync(path.join(srcDir, 'app', 'build.gradle'), path.join(projectPath, 'app', 'build.gradle'));
fs.copySync(path.join(srcDir, 'app', 'repositories.gradle'), path.join(projectPath, 'app', 'repositories.gradle'));
fs.copySync(path.join(srcDir, 'repositories.gradle'), path.join(projectPath, 'repositories.gradle'));
fs.copySync(path.join(srcDir, 'wrapper.gradle'), path.join(projectPath, 'wrapper.gradle'));
}
}
function copyScripts (projectPath) {
var bin = path.join(ROOT, 'bin');
var srcScriptsDir = path.join(bin, 'templates', 'cordova');
var srcScriptsDir = path.join(ROOT, 'templates', 'cordova');
var destScriptsDir = path.join(projectPath, 'cordova');
// Delete old scripts directory if this is an update.
fs.removeSync(destScriptsDir);
// Copy in the new ones.
fs.copySync(srcScriptsDir, destScriptsDir);
const nodeModulesDir = path.join(ROOT, 'node_modules');
if (fs.existsSync(nodeModulesDir)) fs.copySync(nodeModulesDir, path.join(destScriptsDir, 'node_modules'));
fs.copySync(path.join(bin, 'check_reqs'), path.join(destScriptsDir, 'check_reqs'));
fs.copySync(path.join(bin, 'check_reqs.bat'), path.join(destScriptsDir, 'check_reqs.bat'));
fs.copySync(path.join(bin, 'android_sdk_version'), path.join(destScriptsDir, 'android_sdk_version'));
fs.copySync(path.join(bin, 'android_sdk_version.bat'), path.join(destScriptsDir, 'android_sdk_version.bat'));
var check_reqs = path.join(destScriptsDir, 'check_reqs');
var android_sdk_version = path.join(destScriptsDir, 'android_sdk_version');
// TODO: the two files being edited on-the-fly here are shared between
// platform and project-level commands. the below is updating the
// `require` path for the two libraries. if there's a better way to share
// modules across both the repo and generated projects, we should make sure
// to remove/update this.
const templatesCordovaRegex = /templates\/cordova\//;
utils.replaceFileContents(android_sdk_version, templatesCordovaRegex, '');
utils.replaceFileContents(check_reqs, templatesCordovaRegex, '');
}
/**
@@ -197,6 +176,19 @@ function validateProjectName (project_name) {
return Promise.resolve();
}
/**
* Write the name of the app in "platforms/android/.idea/.name" so that Android Studio can show that name in the
* project listing. This is helpful to quickly look in the Android Studio listing if there are so many projects in
* Android Studio.
*
* https://github.com/apache/cordova-android/issues/1172
*/
function writeNameForAndroidStudio (project_path, project_name) {
const ideaPath = path.join(project_path, '.idea');
fs.ensureDirSync(ideaPath);
fs.writeFileSync(path.join(ideaPath, '.name'), project_name);
}
/**
* Creates an android application with the given options.
*
@@ -230,7 +222,7 @@ exports.create = function (project_path, config, options, events) {
? config.name().replace(/[^\w.]/g, '_') : 'CordovaExample';
var safe_activity_name = config.android_activityName() || options.activityName || 'MainActivity';
var target_api = check_reqs.get_target();
var target_api = check_reqs.get_target(project_path);
// Make the package conform to Java package types
return exports.validatePackageName(package_name)
@@ -247,7 +239,7 @@ exports.create = function (project_path, config, options, events) {
events.emit('verbose', 'Copying android template project to ' + project_path);
var project_template_dir = options.customTemplate || path.join(ROOT, 'bin', 'templates', 'project');
var project_template_dir = options.customTemplate || path.join(ROOT, 'templates', 'project');
var app_path = path.join(project_path, 'app', 'src', 'main');
// copy project template
@@ -260,7 +252,7 @@ exports.create = function (project_path, config, options, events) {
fs.ensureDirSync(path.join(app_path, 'libs'));
// copy cordova.js, cordova.jar
exports.copyJsAndLibrary(project_path, options.link, safe_activity_name);
exports.copyJsAndLibrary(project_path, options.link, safe_activity_name, target_api);
// Set up ther Android Studio paths
var java_path = path.join(app_path, 'java');
@@ -294,12 +286,13 @@ exports.create = function (project_path, config, options, events) {
// Link it to local android install.
exports.writeProjectProperties(project_path, target_api);
exports.prepBuildFiles(project_path);
exports.writeNameForAndroidStudio(project_path, project_name);
events.emit('log', generateDoneMessage('create', options.link));
}).then(() => project_path);
};
function generateDoneMessage (type, link) {
var pkg = require('../../package');
var pkg = require('../package');
var msg = 'Android project ' + (type === 'update' ? 'updated ' : 'created ') + 'with ' + pkg.name + '@' + pkg.version;
if (link) {
msg += ' and has a linked CordovaLib';

293
lib/emulator.js Normal file
View File

@@ -0,0 +1,293 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
const execa = require('execa');
const fs = require('fs-extra');
var android_versions = require('android-versions');
var path = require('path');
var Adb = require('./Adb');
var events = require('cordova-common').events;
var CordovaError = require('cordova-common').CordovaError;
var android_sdk = require('./android_sdk');
var which = require('which');
// constants
const ONE_SECOND = 1000; // in milliseconds
const CHECK_BOOTED_INTERVAL = 3 * ONE_SECOND; // in milliseconds
function forgivingWhichSync (cmd) {
const whichResult = which.sync(cmd, { nothrow: true });
// On null, returns empty string to maintain backwards compatibility
// realpathSync follows symlinks
return whichResult === null ? '' : fs.realpathSync(whichResult);
}
module.exports.list_images_using_avdmanager = function () {
return execa('avdmanager', ['list', 'avd']).then(({ stdout: output }) => {
var response = output.split('\n');
var emulator_list = [];
for (var i = 1; i < response.length; i++) {
// To return more detailed information use img_obj
var img_obj = {};
if (response[i].match(/Name:\s/)) {
img_obj.name = response[i].split('Name: ')[1].replace('\r', '');
if (response[i + 1].match(/Device:\s/)) {
i++;
img_obj.device = response[i].split('Device: ')[1].replace('\r', '');
}
if (response[i + 1].match(/Path:\s/)) {
i++;
img_obj.path = response[i].split('Path: ')[1].replace('\r', '');
}
if (response[i + 1].match(/Target:\s/)) {
i++;
if (response[i + 1].match(/ABI:\s/)) {
img_obj.abi = response[i + 1].split('ABI: ')[1].replace('\r', '');
}
// This next conditional just aims to match the old output of `android list avd`
// We do so so that we don't have to change the logic when parsing for the
// best emulator target to spawn (see below in `best_image`)
// This allows us to transitionally support both `android` and `avdmanager` binaries,
// depending on what SDK version the user has
if (response[i + 1].match(/Based\son:\s/)) {
img_obj.target = response[i + 1].split('Based on:')[1];
if (img_obj.target.match(/Tag\/ABI:\s/)) {
img_obj.target = img_obj.target.split('Tag/ABI:')[0].replace('\r', '').trim();
if (img_obj.target.indexOf('(') > -1) {
img_obj.target = img_obj.target.substr(0, img_obj.target.indexOf('(') - 1).trim();
}
}
var version_string = img_obj.target.replace(/Android\s+/, '');
var api_level = android_sdk.version_string_to_api_level[version_string];
if (api_level) {
img_obj.target += ' (API level ' + api_level + ')';
}
}
}
if (response[i + 1].match(/Skin:\s/)) {
i++;
img_obj.skin = response[i].split('Skin: ')[1].replace('\r', '');
}
emulator_list.push(img_obj);
}
/* To just return a list of names use this
if (response[i].match(/Name:\s/)) {
emulator_list.push(response[i].split('Name: ')[1].replace('\r', '');
} */
}
return emulator_list;
});
};
/**
* Returns a Promise for a list of emulator images in the form of objects
* {
name : <emulator_name>,
device : <device>,
path : <path_to_emulator_image>,
target : <api_target>,
abi : <cpu>,
skin : <skin>
}
*/
module.exports.list_images = function () {
return Promise.resolve().then(function () {
if (forgivingWhichSync('avdmanager')) {
return module.exports.list_images_using_avdmanager();
} else {
return Promise.reject(new CordovaError('Could not find `avdmanager` on your $PATH! Are you sure the Android SDK is installed and available?'));
}
}).then(function (avds) {
// In case we're missing the Android OS version string from the target description, add it.
return avds.map(function (avd) {
if (avd.target && avd.target.indexOf('Android API') > -1 && avd.target.indexOf('API level') < 0) {
var api_level = avd.target.match(/\d+/);
if (api_level) {
var level = android_versions.get(api_level);
if (level) {
avd.target = 'Android ' + level.semver + ' (API level ' + api_level + ')';
}
}
}
return avd;
});
});
};
/**
* Returns the best image (if any) for given target.
*
* @param {Number} project_target Android targetSDK API level
* @return {{name: string} | undefined} the closest avd to the given target
* or undefined if no avds exist.
*/
module.exports.best_image = function (project_target) {
return this.list_images().then(function (images) {
// Just return undefined if there is no images
if (images.length === 0) return;
var closest = 9999;
var best = images[0];
for (var i in images) {
var target = images[i].target;
if (target && target.indexOf('API level') > -1) {
var num = parseInt(target.split('(API level ')[1].replace(')', ''));
if (num === project_target) {
return images[i];
} else if (project_target - num < closest && project_target > num) {
closest = project_target - num;
best = images[i];
}
}
}
return best;
});
};
exports.list_started = async () => {
return (await Adb.devices())
.filter(id => id.startsWith('emulator-'));
};
/*
* Gets unused port for android emulator, between 5554 and 5584
* Returns a promise.
*/
module.exports.get_available_port = function () {
var self = this;
return self.list_started().then(function (emulators) {
for (var p = 5584; p >= 5554; p -= 2) {
if (emulators.indexOf('emulator-' + p) === -1) {
events.emit('verbose', 'Found available port: ' + p);
return p;
}
}
throw new CordovaError('Could not find an available avd port');
});
};
/*
* Starts an emulator with the given ID,
* and returns the started ID of that emulator.
* If no boot timeout is given or the value is negative it will wait forever for
* the emulator to boot
*
* Returns a promise.
*/
module.exports.start = function (emulatorId, boot_timeout) {
var self = this;
return Promise.resolve().then(function () {
if (!emulatorId) {
throw new CordovaError('No emulator ID given');
}
return self.get_available_port().then(function (port) {
// Figure out the directory the emulator binary runs in, and set the cwd to that directory.
// Workaround for https://code.google.com/p/android/issues/detail?id=235461
var emulator_dir = path.dirname(which.sync('emulator'));
var args = ['-avd', emulatorId, '-port', port];
// Don't wait for it to finish, since the emulator will probably keep running for a long time.
execa('emulator', args, { stdio: 'inherit', detached: true, cwd: emulator_dir })
.unref();
// wait for emulator to start
events.emit('log', 'Waiting for emulator to start...');
return self.wait_for_emulator(port);
});
}).then(function (emulatorId) {
if (!emulatorId) { return Promise.reject(new CordovaError('Failed to start emulator')); }
// wait for emulator to boot up
process.stdout.write('Waiting for emulator to boot (this may take a while)...');
return self.wait_for_boot(emulatorId, boot_timeout).then(function (success) {
if (success) {
events.emit('log', 'BOOT COMPLETE');
// unlock screen
return Adb.shell(emulatorId, 'input keyevent 82').then(function () {
// return the new emulator id for the started emulators
return emulatorId;
});
} else {
// We timed out waiting for the boot to happen
return null;
}
});
});
};
/*
* Waits for an emulator to boot on a given port.
* Returns this emulator's ID in a promise.
*/
module.exports.wait_for_emulator = function (port) {
var self = this;
return Promise.resolve().then(function () {
var emulator_id = 'emulator-' + port;
return Adb.shell(emulator_id, 'getprop dev.bootcomplete').then(function (output) {
if (output.indexOf('1') >= 0) {
return emulator_id;
}
return self.wait_for_emulator(port);
}, function (error) {
if ((error && error.message &&
(error.message.indexOf('not found') > -1)) ||
(error.message.indexOf('device offline') > -1) ||
(error.message.indexOf('device still connecting') > -1) ||
(error.message.indexOf('device still authorizing') > -1)) {
// emulator not yet started, continue waiting
return self.wait_for_emulator(port);
} else {
// something unexpected has happened
throw error;
}
});
});
};
/*
* Waits for the core android process of the emulator to start. Returns a
* promise that resolves to a boolean indicating success. Not specifying a
* time_remaining or passing a negative value will cause it to wait forever
*/
module.exports.wait_for_boot = function (emulator_id, time_remaining) {
var self = this;
return Adb.shell(emulator_id, 'getprop sys.boot_completed').then(function (output) {
if (output.match(/1/)) {
return true;
} else if (time_remaining === 0) {
return false;
} else {
process.stdout.write('.');
return new Promise(resolve => {
const delay = time_remaining < CHECK_BOOTED_INTERVAL ? time_remaining : CHECK_BOOTED_INTERVAL;
setTimeout(() => {
const updated_time = time_remaining >= 0 ? Math.max(time_remaining - CHECK_BOOTED_INTERVAL, 0) : time_remaining;
resolve(self.wait_for_boot(emulator_id, updated_time));
}, delay);
});
}
});
};

128
lib/env/java.js vendored Normal file
View File

@@ -0,0 +1,128 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
const execa = require('execa');
const fs = require('fs-extra');
const path = require('path');
const glob = require('fast-glob');
const { CordovaError, events } = require('cordova-common');
const utils = require('../utils');
const semver = require('semver');
/**
* Will be set to true on successful ensureness.
* If true, skips the expensive java checks.
*/
let javaIsEnsured = false;
const java = {
/**
* Gets the version from the javac executable.
*
* @returns {semver.SemVer}
*/
getVersion: async () => {
await java._ensure(process.env);
// Java <= 8 writes version info to stderr, Java >= 9 to stdout
let javacOutput;
try {
javacOutput = (await execa('javac', ['-version'], { all: true })).all;
} catch (ex) {
events.emit('verbose', ex.shortMessage);
let msg =
'Failed to run "javac -version", make sure that you have a JDK version 8 installed.\n' +
'You can get it from the following location:\n' +
'https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html';
if (process.env.JAVA_HOME) {
msg += '\n\n';
msg += 'Your JAVA_HOME is invalid: ' + process.env.JAVA_HOME;
}
throw new CordovaError(msg);
}
// We have to filter the output, because javac prints _JAVA_OPTIONS
// before printing the version which causes semver.coerce to fail to get
// the correct version if those options contain any numbers.
const match = /javac\s+([\d.]+)/i.exec(javacOutput);
return semver.coerce(match && match[1]);
},
/**
* Ensures that Java is installed. Will throw exception if not.
* Will set JAVA_HOME and PATH environment variables.
*
* This function becomes a no-op if already ran previously.
*/
_ensure: async (environment) => {
if (javaIsEnsured) {
return;
}
const javaHome = environment.CORDOVA_JAVA_HOME || environment.JAVA_HOME;
if (javaHome) {
// Ensure that CORDOVA_JAVA_HOME overrides
environment.JAVA_HOME = javaHome;
// Ensure that the JAVA_HOME bin path is before anything else
// to cover cases where different Java versions is in the PATH
environment.PATH = path.join(environment.JAVA_HOME, 'bin') + path.delimiter + environment.PATH;
} else {
const javacPath = utils.forgivingWhichSync('javac');
if (javacPath) {
// OS X has a command for finding JAVA_HOME.
const find_java = '/usr/libexec/java_home';
const default_java_error_msg = 'Failed to find \'JAVA_HOME\' environment variable. Try setting it manually.';
if (fs.existsSync(find_java)) {
try {
environment.JAVA_HOME = (await execa(find_java)).stdout;
} catch (ex) {
events.emit('verbose', ex.shortMessage);
throw new CordovaError(default_java_error_msg);
}
} else {
// See if we can derive it from javac's location.
var maybeJavaHome = path.dirname(path.dirname(javacPath));
if (fs.existsSync(path.join(maybeJavaHome, 'lib', 'tools.jar'))) {
environment.JAVA_HOME = maybeJavaHome;
} else {
throw new CordovaError(default_java_error_msg);
}
}
} else if (utils.isWindows()) {
const baseDirs = [environment.ProgramFiles, environment['ProgramFiles(x86)']];
const globOpts = { absolute: true, onlyDirectories: true };
const flatMap = (arr, f) => [].concat(...arr.map(f));
const jdkDir = flatMap(baseDirs, cwd => {
return glob.sync('java/jdk*', { cwd, ...globOpts });
}
)[0];
if (jdkDir) {
environment.PATH += path.delimiter + path.join(jdkDir, 'bin');
environment.JAVA_HOME = path.normalize(jdkDir);
}
}
}
javaIsEnsured = true;
}
};
module.exports = java;

View File

@@ -16,11 +16,5 @@
:: under the License.
@ECHO OFF
SET script_path="%~dp0update"
IF EXIST %script_path% (
node %script_path% %*
) ELSE (
ECHO.
ECHO ERROR: Could not find 'update' script in 'bin' folder, aborting...>&2
EXIT /B 1
)
for /f "tokens=2*" %%a in ('REG QUERY "HKEY_LOCAL_MACHINE\SOFTWARE\Android Studio" /v Path') do set "ASPath=%%~b"
ECHO %ASPath%

View File

@@ -0,0 +1,29 @@
/*!
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
const ABS_MODULE_PATH = '/framework/cdv-gradle-config-defaults.json';
try {
// Try relative require first, …
module.exports = require('..' + ABS_MODULE_PATH);
} catch (error) {
// … then fall back to installed-package require
if (error.code !== 'MODULE_NOT_FOUND') throw error;
module.exports = require('cordova-android' + ABS_MODULE_PATH);
}

View File

@@ -16,6 +16,7 @@
var fs = require('fs-extra');
var path = require('path');
var isPathInside = require('is-path-inside');
var events = require('cordova-common').events;
var CordovaError = require('cordova-common').CordovaError;
@@ -209,12 +210,12 @@ function copyFile (plugin_dir, src, project_dir, dest, link) {
// check that src path is inside plugin directory
var real_path = fs.realpathSync(src);
var real_plugin_path = fs.realpathSync(plugin_dir);
if (real_path.indexOf(real_plugin_path) !== 0) { throw new CordovaError('File "' + src + '" is located outside the plugin directory "' + plugin_dir + '"'); }
if (!isPathInside(real_path, real_plugin_path)) { throw new CordovaError('File "' + src + '" is located outside the plugin directory "' + plugin_dir + '"'); }
dest = path.resolve(project_dir, dest);
// check that dest path is located in project directory
if (dest.indexOf(project_dir) !== 0) { 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));
if (link) {

View File

@@ -20,6 +20,7 @@
var fs = require('fs-extra');
var path = require('path');
const nopt = require('nopt');
const glob = require('fast-glob');
var events = require('cordova-common').events;
var AndroidManifest = require('./AndroidManifest');
var checkReqs = require('./check_reqs');
@@ -31,6 +32,7 @@ var PlatformJson = require('cordova-common').PlatformJson;
var PlatformMunger = require('cordova-common').ConfigChanges.PlatformMunger;
var PluginInfoProvider = require('cordova-common').PluginInfoProvider;
const utils = require('./utils');
const gradleConfigDefaults = require('./gradle-config-defaults');
const GradlePropertiesParser = require('./config/GradlePropertiesParser');
@@ -54,31 +56,11 @@ module.exports.prepare = function (cordovaProject, options) {
this._config = updateConfigFilesFrom(cordovaProject.projectConfig, munger, this.locations);
// Get the min SDK version from config.xml
const minSdkVersion = this._config.getPreference('android-minSdkVersion', 'android');
const maxSdkVersion = this._config.getPreference('android-maxSdkVersion', 'android');
const targetSdkVersion = this._config.getPreference('android-targetSdkVersion', 'android');
const androidXEnabled = this._config.getPreference('AndroidXEnabled', 'android');
const isGradlePluginKotlinEnabled = this._config.getPreference('GradlePluginKotlinEnabled', 'android');
const gradlePluginKotlinCodeStyle = this._config.getPreference('GradlePluginKotlinCodeStyle', 'android');
// Update Gradle cdv-gradle-config.json
updateUserProjectGradleConfig(this);
const gradlePropertiesUserConfig = {};
if (minSdkVersion) gradlePropertiesUserConfig.cdvMinSdkVersion = minSdkVersion;
if (maxSdkVersion) gradlePropertiesUserConfig.cdvMaxSdkVersion = maxSdkVersion;
if (targetSdkVersion) gradlePropertiesUserConfig.cdvTargetSdkVersion = targetSdkVersion;
if (args.jvmargs) gradlePropertiesUserConfig['org.gradle.jvmargs'] = args.jvmargs;
if (isGradlePluginKotlinEnabled) {
gradlePropertiesUserConfig['kotlin.code.style'] = gradlePluginKotlinCodeStyle || 'official';
}
// Both 'useAndroidX' and 'enableJetifier' are linked together.
if (androidXEnabled) {
gradlePropertiesUserConfig['android.useAndroidX'] = androidXEnabled;
gradlePropertiesUserConfig['android.enableJetifier'] = androidXEnabled;
}
const gradlePropertiesParser = new GradlePropertiesParser(this.locations.root);
gradlePropertiesParser.configure(gradlePropertiesUserConfig);
// Update Project's Gradle Properties
updateUserProjectGradlePropertiesConfig(this, args);
// Update own www dir with project's www assets and plugins' assets and js-files
return Promise.resolve(updateWww(cordovaProject, this.locations)).then(function () {
@@ -93,6 +75,77 @@ module.exports.prepare = function (cordovaProject, options) {
});
};
/** @param {PlatformApi} project */
function updateUserProjectGradleConfig (project) {
// Generate project gradle config
const projectGradleConfig = {
...gradleConfigDefaults,
...getUserGradleConfig(project._config)
};
// Write out changes
const projectGradleConfigPath = path.join(project.root, 'cdv-gradle-config.json');
fs.writeJSONSync(projectGradleConfigPath, projectGradleConfig, { spaces: 2 });
}
function getUserGradleConfig (configXml) {
const configXmlToGradleMapping = [
{ xmlKey: 'android-minSdkVersion', gradleKey: 'MIN_SDK_VERSION', type: Number },
{ xmlKey: 'android-maxSdkVersion', gradleKey: 'MAX_SDK_VERSION', type: Number },
{ xmlKey: 'android-targetSdkVersion', gradleKey: 'SDK_VERSION', type: Number },
{ xmlKey: 'android-buildToolsVersion', gradleKey: 'BUILD_TOOLS_VERSION', type: String },
{ xmlKey: 'GradleVersion', gradleKey: 'GRADLE_VERSION', type: String },
{ xmlKey: 'AndroidGradlePluginVersion', gradleKey: 'AGP_VERSION', type: String },
{ xmlKey: 'GradlePluginKotlinVersion', gradleKey: 'KOTLIN_VERSION', type: String },
{ xmlKey: 'AndroidXAppCompatVersion', gradleKey: 'ANDROIDX_APP_COMPAT_VERSION', type: String },
{ xmlKey: 'AndroidXWebKitVersion', gradleKey: 'ANDROIDX_WEBKIT_VERSION', type: String },
{ xmlKey: 'GradlePluginGoogleServicesVersion', gradleKey: 'GRADLE_PLUGIN_GOOGLE_SERVICES_VERSION', type: String },
{ xmlKey: 'GradlePluginGoogleServicesEnabled', gradleKey: 'IS_GRADLE_PLUGIN_GOOGLE_SERVICES_ENABLED', type: Boolean },
{ xmlKey: 'GradlePluginKotlinEnabled', gradleKey: 'IS_GRADLE_PLUGIN_KOTLIN_ENABLED', type: Boolean }
];
return configXmlToGradleMapping.reduce((config, mapping) => {
const rawValue = configXml.getPreference(mapping.xmlKey, 'android');
// ignore missing preferences (which occur as '')
if (rawValue) {
config[mapping.gradleKey] = parseStringAsType(rawValue, mapping.type);
}
return config;
}, {});
}
/** Converts given string to given type */
function parseStringAsType (value, type) {
switch (type) {
case String:
return String(value);
case Number:
return parseFloat(value);
case Boolean:
return value.toLowerCase() === 'true';
default:
throw new CordovaError('Invalid type: ' + type);
}
}
function updateUserProjectGradlePropertiesConfig (project, args) {
const gradlePropertiesUserConfig = {};
// Get the min SDK version from config.xml
if (args.jvmargs) gradlePropertiesUserConfig['org.gradle.jvmargs'] = args.jvmargs;
const isGradlePluginKotlinEnabled = project._config.getPreference('GradlePluginKotlinEnabled', 'android');
if (isGradlePluginKotlinEnabled) {
const gradlePluginKotlinCodeStyle = project._config.getPreference('GradlePluginKotlinCodeStyle', 'android');
gradlePropertiesUserConfig['kotlin.code.style'] = gradlePluginKotlinCodeStyle || 'official';
}
const gradlePropertiesParser = new GradlePropertiesParser(project.root);
gradlePropertiesParser.configure(gradlePropertiesUserConfig);
}
module.exports.clean = function (options) {
// A cordovaProject isn't passed into the clean() function, because it might have
// been called from the platform shell script rather than the CLI. Check for the
@@ -237,9 +290,9 @@ function updateProjectAccordingTo (platformConfig, locations) {
// Java file paths shouldn't be hard coded
const javaDirectory = path.join(locations.javaSrc, manifestId.replace(/\./g, '/'));
const javaPattern = /\.java$/;
const java_files = utils.scanDirectory(javaDirectory, javaPattern, true).filter(function (f) {
return utils.grep(f, /extends\s+CordovaActivity/g) !== null;
const java_files = glob.sync('**/*.java', { cwd: javaDirectory, absolute: true }).filter(f => {
const contents = fs.readFileSync(f, 'utf-8');
return /extends\s+CordovaActivity/.test(contents);
});
if (java_files.length === 0) {
@@ -301,11 +354,12 @@ function default_versionCode (version) {
}
function getImageResourcePath (resourcesDir, type, density, name, sourceName) {
if (/\.9\.png$/.test(sourceName)) {
name = name.replace(/\.png$/, '.9.png');
}
var resourcePath = path.join(resourcesDir, (density ? type + '-' + density : type), name);
return resourcePath;
// Use same extension as source with special case for 9-Patch files
const ext = sourceName.endsWith('.9.png')
? '.9.png' : path.extname(sourceName).toLowerCase();
const subDir = density ? `${type}-${density}` : type;
return path.join(resourcesDir, subDir, name + ext);
}
function getAdaptiveImageResourcePath (resourcesDir, type, density, name, sourceName) {
@@ -316,16 +370,29 @@ function getAdaptiveImageResourcePath (resourcesDir, type, density, name, source
return resourcePath;
}
function makeSplashCleanupMap (projectRoot, resourcesDir) {
// Build an initial resource map that deletes all existing splash screens
const existingSplashPaths = glob.sync(
`${resourcesDir.replace(/\\/g, '/')}/drawable-*/screen.{png,9.png,webp,jpg,jpeg}`,
{ cwd: projectRoot }
);
return makeCleanResourceMap(existingSplashPaths);
}
function updateSplashes (cordovaProject, platformResourcesDir) {
var resources = cordovaProject.projectConfig.getSplashScreens('android');
// if there are "splash" elements in config.xml
// if there are no "splash" elements in config.xml
if (resources.length === 0) {
events.emit('verbose', 'This app does not have splash screens defined');
return;
// We must not return here!
// If the user defines no splash screens, the cleanup map will cause any
// existing splash screen images (e.g. the defaults that we copy into a
// new app) to be removed from the app folder, which is what we want.
}
var resourceMap = mapImageResources(cordovaProject.root, platformResourcesDir, 'drawable', 'screen.png');
// Build an initial resource map that deletes all existing splash screens
const resourceMap = makeSplashCleanupMap(cordovaProject.root, platformResourcesDir);
var hadMdpi = false;
resources.forEach(function (resource) {
@@ -336,14 +403,14 @@ function updateSplashes (cordovaProject, platformResourcesDir) {
hadMdpi = true;
}
var targetPath = getImageResourcePath(
platformResourcesDir, 'drawable', resource.density, 'screen.png', path.basename(resource.src));
platformResourcesDir, 'drawable', resource.density, 'screen', path.basename(resource.src));
resourceMap[targetPath] = resource.src;
});
// There's no "default" drawable, so assume default == mdpi.
if (!hadMdpi && resources.defaultResource) {
var targetPath = getImageResourcePath(
platformResourcesDir, 'drawable', 'mdpi', 'screen.png', path.basename(resources.defaultResource.src));
platformResourcesDir, 'drawable', 'mdpi', 'screen', path.basename(resources.defaultResource.src));
resourceMap[targetPath] = resources.defaultResource.src;
}
@@ -355,7 +422,8 @@ function updateSplashes (cordovaProject, platformResourcesDir) {
function cleanSplashes (projectRoot, projectConfig, platformResourcesDir) {
var resources = projectConfig.getSplashScreens('android');
if (resources.length > 0) {
var resourceMap = mapImageResources(projectRoot, platformResourcesDir, 'drawable', 'screen.png');
const resourceMap = makeSplashCleanupMap(projectRoot, platformResourcesDir);
events.emit('verbose', 'Cleaning splash screens at ' + platformResourcesDir);
// No source paths are specified in the map, so updatePaths() will delete the target files.
@@ -545,13 +613,13 @@ function updateIconResourceForLegacy (preparedIcons, resourceMap, platformResour
// The source paths for icons and splashes are relative to
// project's config.xml location, so we use it as base path.
for (var density in android_icons) {
var targetPath = getImageResourcePath(platformResourcesDir, 'mipmap', density, 'ic_launcher.png', path.basename(android_icons[density].src));
var targetPath = getImageResourcePath(platformResourcesDir, 'mipmap', density, 'ic_launcher', path.basename(android_icons[density].src));
resourceMap[targetPath] = android_icons[density].src;
}
// There's no "default" drawable, so assume default == mdpi.
if (default_icon && !android_icons.mdpi) {
var defaultTargetPath = getImageResourcePath(platformResourcesDir, 'mipmap', 'mdpi', 'ic_launcher.png', path.basename(default_icon.src));
var defaultTargetPath = getImageResourcePath(platformResourcesDir, 'mipmap', 'mdpi', 'ic_launcher', path.basename(default_icon.src));
resourceMap[defaultTargetPath] = default_icon.src;
}
@@ -664,14 +732,24 @@ function cleanIcons (projectRoot, projectConfig, platformResourcesDir) {
*/
function mapImageResources (rootDir, subDir, type, resourceName) {
const pathMap = {};
const pattern = new RegExp(type + '+-.+');
utils.scanDirectory(path.join(rootDir, subDir), pattern).forEach(function (drawableFolder) {
const imagePath = path.join(subDir, path.basename(drawableFolder), resourceName);
const globOptions = { cwd: path.join(rootDir, subDir), onlyDirectories: true };
glob.sync(type + '-*', globOptions).forEach(drawableFolder => {
const imagePath = path.join(subDir, drawableFolder, resourceName);
pathMap[imagePath] = null;
});
return pathMap;
}
/** Returns resource map that deletes all given paths */
function makeCleanResourceMap (resourcePaths) {
const pathMap = {};
resourcePaths.map(path.normalize)
.forEach(resourcePath => {
pathMap[resourcePath] = null;
});
return pathMap;
}
function updateFileResources (cordovaProject, platformDir) {
var files = cordovaProject.projectConfig.getFileResources('android');

View File

@@ -21,7 +21,7 @@
var events = require('cordova-common').events;
/*
/**
* Retry a promise-returning function a number of times, propagating its
* results on success or throwing its error on a failed final attempt.
*
@@ -31,31 +31,13 @@ var events = require('cordova-common').events;
*
* @returns {Promise}
*/
module.exports.retryPromise = function (attemptsLeft, promiseFunction) {
// NOTE:
// get all trailing arguments, by skipping the first two (attemptsLeft and
// promiseFunction) because they shouldn't get passed to promiseFunction
var promiseFunctionArguments = Array.prototype.slice.call(arguments, 2);
return promiseFunction.apply(undefined, promiseFunctionArguments).then(
// on success pass results through
function onFulfilled (value) {
return value;
},
// on rejection either retry, or throw the error
function onRejected (error) {
attemptsLeft -= 1;
if (attemptsLeft < 1) {
throw error;
}
module.exports.retryPromise = async function (attemptsLeft, promiseFunction, ...args) {
while (true) {
try {
return await promiseFunction(...args);
} catch (error) {
if (--attemptsLeft < 1) throw error;
events.emit('verbose', 'A retried call failed. Retrying ' + attemptsLeft + ' more time(s).');
// retry call self again with the same arguments, except attemptsLeft is now lower
var fullArguments = [attemptsLeft, promiseFunction].concat(promiseFunctionArguments);
return module.exports.retryPromise.apply(undefined, fullArguments);
}
);
}
};

83
lib/run.js Normal file
View File

@@ -0,0 +1,83 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
var emulator = require('./emulator');
const target = require('./target');
const build = require('./build');
const PackageType = require('./PackageType');
const AndroidManifest = require('./AndroidManifest');
const { CordovaError, events } = require('cordova-common');
/**
* Builds a target spec from a runOptions object
*
* @param {{target?: string, device?: boolean, emulator?: boolean}} runOptions
* @return {target.TargetSpec}
*/
function buildTargetSpec (runOptions) {
const spec = {};
if (runOptions.target) {
spec.id = runOptions.target;
} else if (runOptions.device) {
spec.type = 'device';
} else if (runOptions.emulator) {
spec.type = 'emulator';
}
return spec;
}
function formatResolvedTarget ({ id, type }) {
return `${type} ${id}`;
}
/**
* Runs the application on a device if available. If no device is found, it will
* use a started emulator. If no started emulators are found it will attempt
* to start an avd. If no avds are found it will error out.
*
* @param {Object} runOptions various run/build options. See Api.js build/run
* methods for reference.
*
* @return {Promise}
*/
module.exports.run = async function (runOptions = {}) {
const { packageType, buildType } = build.parseBuildOptions(runOptions, null, this.root);
// Android app bundles cannot be deployed directly to the device
if (packageType === PackageType.BUNDLE) {
throw new CordovaError('Package type "bundle" is not supported during cordova run.');
}
const buildResults = this._builder.fetchBuildResults(buildType);
if (buildResults.apkPaths.length === 0) {
throw new CordovaError('Could not find any APKs to deploy');
}
const targetSpec = buildTargetSpec(runOptions);
const resolvedTarget = await target.resolve(targetSpec, buildResults);
events.emit('log', `Deploying to ${formatResolvedTarget(resolvedTarget)}`);
if (resolvedTarget.type === 'emulator') {
await emulator.wait_for_boot(resolvedTarget.id);
}
const manifest = new AndroidManifest(this.locations.manifest);
return target.install(resolvedTarget, { manifest, buildResults });
};

173
lib/target.js Normal file
View File

@@ -0,0 +1,173 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
const { inspect } = require('util');
const execa = require('execa');
const Adb = require('./Adb');
const build = require('./build');
const emulator = require('./emulator');
const { compareBy } = require('./utils');
const { retryPromise } = require('./retry');
const { events, CordovaError } = require('cordova-common');
const INSTALL_COMMAND_TIMEOUT = 5 * 60 * 1000;
const NUM_INSTALL_RETRIES = 3;
const EXEC_KILL_SIGNAL = 'SIGKILL';
/**
* @typedef { 'device' | 'emulator' } TargetType
* @typedef { { id: string, type: TargetType } } Target
* @typedef { { id?: string, type?: TargetType } } TargetSpec
* @typedef { { apkPaths: string[] } } BuildResults
*/
/**
* Returns a list of available targets (connected devices & started emulators)
*
* @return {Promise<Target[]>}
*/
exports.list = async () => {
return (await Adb.devices())
.map(id => ({
id,
type: id.startsWith('emulator-') ? 'emulator' : 'device'
}));
};
/**
* @param {TargetSpec?} spec
* @return {Promise<Target>}
*/
async function resolveToOnlineTarget (spec = {}) {
const targetList = await exports.list();
if (targetList.length === 0) return null;
// Sort by type: devices first, then emulators.
targetList.sort(compareBy(t => t.type));
// Find first matching target for spec. {} matches any target.
return targetList.find(target =>
Object.keys(spec).every(k => spec[k] === target[k])
) || null;
}
async function isEmulatorName (name) {
const emus = await emulator.list_images();
return emus.some(avd => avd.name === name);
}
/**
* @param {TargetSpec} spec
* @param {BuildResults} buildResults
* @return {Promise<Target>}
*/
async function resolveToOfflineEmulator ({ id: avdName, type }, { apkPaths }) {
if (type === 'device') return null;
if (avdName) {
if (!await isEmulatorName(avdName)) return null;
} else {
events.emit('verbose', 'Looking for emulator image that best matches the target API');
const targetSdk = await getTargetSdkFromApk(apkPaths[0]);
const bestImage = await emulator.best_image(targetSdk);
if (!bestImage) return null;
avdName = bestImage.name;
}
// try to start an emulator with name avdName
const emulatorId = await emulator.start(avdName);
return { id: emulatorId, type: 'emulator' };
}
async function getTargetSdkFromApk (apkPath) {
const { stdout: targetSdkStr } = await execa('apkanalyzer', [
'manifest', 'target-sdk', apkPath
]);
return Number(targetSdkStr);
}
/**
* @param {TargetSpec?} spec
* @param {BuildResults} buildResults
* @return {Promise<Target & {arch: string}>}
*/
exports.resolve = async (spec, buildResults) => {
events.emit('verbose', `Trying to find target matching ${inspect(spec)}`);
const resolvedTarget =
(await resolveToOnlineTarget(spec)) ||
(await resolveToOfflineEmulator(spec, buildResults));
if (!resolvedTarget) {
throw new CordovaError(`Could not find target matching ${inspect(spec)}`);
}
return {
...resolvedTarget,
arch: await build.detectArchitecture(resolvedTarget.id)
};
};
exports.install = async function ({ id: target, arch, type }, { manifest, buildResults }) {
const apk_path = build.findBestApkForArchitecture(buildResults, arch);
const pkgName = manifest.getPackageId();
const launchName = pkgName + '/.' + manifest.getActivity().getName();
events.emit('log', 'Using apk: ' + apk_path);
events.emit('log', 'Package name: ' + pkgName);
events.emit('verbose', `Installing app on target ${target}`);
async function doInstall (execOptions = {}) {
try {
await Adb.install(target, apk_path, { replace: true, execOptions });
} catch (error) {
// CB-9557 CB-10157 only uninstall and reinstall app if the one that
// is already installed on device was signed w/different certificate
if (!/INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES/.test(error.toString())) throw error;
events.emit('warn', 'Uninstalling app from device and reinstalling it again because the ' +
'installed app already signed with different key');
// This promise is always resolved, even if 'adb uninstall' fails to uninstall app
// or the app doesn't installed at all, so no error catching needed.
await Adb.uninstall(target, pkgName);
await Adb.install(target, apk_path, { replace: true });
}
}
if (type === 'emulator') {
// Work around sporadic emulator hangs: http://issues.apache.org/jira/browse/CB-9119
await retryPromise(NUM_INSTALL_RETRIES, () => doInstall({
timeout: INSTALL_COMMAND_TIMEOUT,
killSignal: EXEC_KILL_SIGNAL
}));
} else {
await doInstall();
}
events.emit('log', 'INSTALL SUCCESS');
events.emit('verbose', 'Unlocking screen...');
await Adb.shell(target, 'input keyevent 82');
await Adb.start(target, launchName);
events.emit('log', 'LAUNCH SUCCESS');
};

View File

@@ -24,6 +24,8 @@
// TODO: Perhaps this should live in cordova-common?
const fs = require('fs-extra');
const which = require('which');
const os = require('os');
/**
* Reads, searches, and replaces the found occurences with replacementString and then writes the file back out.
@@ -32,16 +34,35 @@ const fs = require('fs-extra');
* @param {string} file A file path to a readable & writable file
* @param {RegExp} searchRegex The search regex
* @param {string} replacementString The string to replace the found occurences
* @returns {void}
* @returns void
*/
exports.replaceFileContents = function (file, searchRegex, replacementString) {
// let contents;
try {
var contents = fs.readFileSync(file).toString();
} catch (ex) {
console.log('TRYING TO READ: ', file);
throw ex;
}
let contents = fs.readFileSync(file).toString();
contents = contents.replace(searchRegex, replacementString);
fs.writeFileSync(file, contents);
};
// Some helpers for easier sorting
exports.compare = (a, b) => (a < b && -1) || +(a > b);
exports.compareBy = f => (a, b) => exports.compare(f(a), f(b));
exports.compareByAll = fns => {
const comparators = fns.map(exports.compareBy);
return (a, b) => {
for (const cmp of comparators) {
const result = cmp(a, b);
if (result) return result;
}
return 0;
};
};
exports.forgivingWhichSync = (cmd) => {
const whichResult = which.sync(cmd, { nothrow: true });
// On null, returns empty string to maintain backwards compatibility
// realpathSync follows symlinks
return whichResult === null ? '' : fs.realpathSync(whichResult);
};
exports.isWindows = () => os.platform() === 'win32';
exports.isDarwin = () => os.platform() === 'darwin';

1576
package-lock.json generated

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More