mirror of
https://github.com/apache/cordova-android.git
synced 2026-05-30 00:00:04 +08:00
Compare commits
145 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6cda55b35f | |||
| da3f026974 | |||
| 7427b15f5f | |||
| 28580a2f4c | |||
| 31295566f3 | |||
| 20d2964be7 | |||
| d40f43c144 | |||
| 5fb913d000 | |||
| 5fa4728ebe | |||
| 4a7cbb5eb4 | |||
| bc91c554e6 | |||
| dc9413258e | |||
| 4b574a2863 | |||
| 4b3cc67353 | |||
| 32b72756f3 | |||
| 2fc86e2833 | |||
| fab472859d | |||
| 92caa3a186 | |||
| 26c7a96255 | |||
| e170e463fe | |||
| 899daa9ea7 | |||
| 6d334c05e9 | |||
| 5ac0cc51d3 | |||
| f93c2b161d | |||
| 6b071c0fb2 | |||
| d3245a43d3 | |||
| 90a51c2cc1 | |||
| 61df5e0a37 | |||
| c0312f9b50 | |||
| eb70f05168 | |||
| 505db38232 | |||
| 096e1e3caa | |||
| b5d8b51310 | |||
| c9e7201058 | |||
| 4bf705a3d3 | |||
| ce42568721 | |||
| eb956b2449 | |||
| 1bf4e93da1 | |||
| aba0a8421b | |||
| b5a58e6ca0 | |||
| 44aa7464e1 | |||
| 4ea684dd7a | |||
| 215b7e08f8 | |||
| 754911f346 | |||
| 9873106785 | |||
| d005359f89 | |||
| 1ce52a2845 | |||
| 7e480d1ff9 | |||
| 85877d259c | |||
| 0b86db8748 | |||
| bca7f62efd | |||
| 4953ae84cd | |||
| e96a5a0b3e | |||
| e4678f4709 | |||
| 1def13deb3 | |||
| bce4283239 | |||
| 9ff786d021 | |||
| ee14a67795 | |||
| b63a2e37be | |||
| 84274b4259 | |||
| b6bf5298e6 | |||
| b0d5ffec8f | |||
| 09ff81c411 | |||
| a0293578b1 | |||
| 4595403a99 | |||
| 0f73884c8d | |||
| 2e9cbdcb0d | |||
| a652d892ca | |||
| 581252febc | |||
| b27d283f21 | |||
| f2d7c49acf | |||
| a397a23a9c | |||
| 9f7e179288 | |||
| ad1c3d2438 | |||
| 51adf81918 | |||
| 97718a0a25 | |||
| 1aaba440b5 | |||
| b8f2b8948f | |||
| d96d49329b | |||
| 4db421ca36 | |||
| c3991c8164 | |||
| e904bab206 | |||
| 500ccd8e80 | |||
| 7cf7311a9d | |||
| 0669edddae | |||
| 38a8d7742e | |||
| 32e84d2316 | |||
| 151b86cb7b | |||
| e4c9bebe34 | |||
| 8d5cb00bec | |||
| 15530a4820 | |||
| f6e56b345d | |||
| 56d61eb44f | |||
| 2103da7b9d | |||
| 679069729c | |||
| f764448ccc | |||
| e1828696f7 | |||
| 5b87380749 | |||
| 917d0dfc49 | |||
| 191839f764 | |||
| 316cf057f3 | |||
| 55be212594 | |||
| 489e63f8e7 | |||
| 62c081dc85 | |||
| 023ad9ddf8 | |||
| eccf486162 | |||
| a6da46a00e | |||
| 747d2c97cd | |||
| af2969dec5 | |||
| 53dba8678c | |||
| afdac9b413 | |||
| 1ad280db98 | |||
| 035c3ad319 | |||
| c237a1c0d2 | |||
| f1d093548e | |||
| beab74adf5 | |||
| 2a49e8a931 | |||
| 395857c37c | |||
| 9a34f25edc | |||
| 0af02fb9ae | |||
| dcff8794ad | |||
| 1b4f5b13f1 | |||
| 3950818030 | |||
| d6da2ef096 | |||
| 455298d736 | |||
| d99856c52b | |||
| 087ec11e6a | |||
| 00c0a84e4e | |||
| be229b1ac6 | |||
| 8106981bb6 | |||
| de4d7cd10d | |||
| 804dcac12f | |||
| fb0987b824 | |||
| 88f50a66ff | |||
| 7be600d8e9 | |||
| 11d6b8029f | |||
| f1d4c01190 | |||
| c12d93e77f | |||
| 204130a598 | |||
| dbd45d4173 | |||
| 7e0bfbbad2 | |||
| db18e1480e | |||
| 9baa27508a | |||
| c3267def97 | |||
| 390927772e |
@@ -0,0 +1,4 @@
|
||||
*.properties
|
||||
bin
|
||||
gen
|
||||
proguard-project.txt
|
||||
+3
-1
@@ -1,6 +1,8 @@
|
||||
language: android
|
||||
sudo: false
|
||||
install: npm install
|
||||
install:
|
||||
- npm install
|
||||
- echo y | android update sdk -u --filter android-22
|
||||
script:
|
||||
- npm test
|
||||
- npm run test-build
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
@@ -187,7 +187,7 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
Copyright 2015 Apache Cordova
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -226,11 +226,9 @@ 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/shelljs
|
||||
================================================================================
|
||||
|
||||
Copyright (c) 2012, Artur Adib <aadib@mozilla.com>
|
||||
All rights reserved.
|
||||
|
||||
@@ -247,19 +245,19 @@ modification, are permitted provided that the following conditions are met:
|
||||
names of the contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL ARTUR ADIB BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
================================================================================
|
||||
bin/node_modules/shelljs
|
||||
bin/node_modules/nopt
|
||||
================================================================================
|
||||
Copyright 2009, 2010, 2011 Isaac Z. Schlueter.
|
||||
All rights reserved.
|
||||
@@ -285,3 +283,32 @@ 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/which
|
||||
================================================================================
|
||||
|
||||
Copyright 2009, 2010, 2011 Isaac Z. Schlueter.
|
||||
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.
|
||||
|
||||
|
||||
|
||||
@@ -18,37 +18,31 @@
|
||||
# under the License.
|
||||
#
|
||||
-->
|
||||
Cordova Android
|
||||
===
|
||||
# Cordova Android
|
||||
|
||||
Cordova Android is an Android application library that allows for Cordova-based
|
||||
projects to be built for the Android Platform. Cordova based applications are,
|
||||
at the core, applications written with web technology: HTML, CSS and JavaScript.
|
||||
|
||||
[Apache Cordova](http://cordova.io) is a project of The Apache Software Foundation (ASF).
|
||||
[Apache Cordova](https://cordova.apache.org) is a project of The Apache Software Foundation (ASF).
|
||||
|
||||
|
||||
Requires
|
||||
---
|
||||
## Requires
|
||||
|
||||
- Java JDK 1.5 or greater
|
||||
- Apache Ant 1.8.0 or greater
|
||||
- Java JDK 1.6 or greater
|
||||
- Android SDK [http://developer.android.com](http://developer.android.com)
|
||||
|
||||
|
||||
Cordova Android Developer Tools
|
||||
---
|
||||
## Cordova Android Developer Tools
|
||||
|
||||
The Cordova developer tooling is split between general tooling and project level tooling.
|
||||
We recommend using the [Cordova command-line tool](https://www.npmjs.com/package/cordova) to create projects and be able to easily install plugins.
|
||||
|
||||
General Commands
|
||||
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
|
||||
|
||||
Project Commands
|
||||
|
||||
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
|
||||
@@ -57,35 +51,8 @@ These commands live in a generated Cordova Android project. Any interactions wit
|
||||
./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
|
||||
|
||||
Importing a Cordova Android Project into Eclipse
|
||||
----
|
||||
## Using Android Studio
|
||||
|
||||
1. File > New > Project...
|
||||
2. Android > Android Project
|
||||
3. Create project from existing source (point to the generated app found in tmp/android)
|
||||
4. Right click on libs/cordova.jar and add to build path
|
||||
5. Right click on the project root: Run as > Run Configurations
|
||||
6. Click on the Target tab and select Manual (this way you can choose the emulator or device to build to)
|
||||
1. Create a project
|
||||
2. Import it via "Non-Android Studio Project"
|
||||
|
||||
Building without the Tooling
|
||||
---
|
||||
Note: The Developer Tools handle this. This is only to be done if the tooling fails, or if
|
||||
you are developing directly against the framework.
|
||||
|
||||
|
||||
To create your `cordova.jar` file, run in the framework directory:
|
||||
|
||||
android update project -p . -t android-19
|
||||
ant jar
|
||||
|
||||
|
||||
Running Tests
|
||||
----
|
||||
Please see details under test/README.md.
|
||||
|
||||
Further Reading
|
||||
----
|
||||
|
||||
- [http://developer.android.com](http://developer.android.com)
|
||||
- [http://cordova.apache.org/](http://cordova.apache.org)
|
||||
- [http://wiki.apache.org/cordova/](http://wiki.apache.org/cordova/)
|
||||
|
||||
+120
@@ -19,6 +19,126 @@
|
||||
#
|
||||
-->
|
||||
## Release Notes for Cordova (Android) ##
|
||||
Update these notes using: git log --pretty=format:'* %s' --topo-order --no-merges *remote*/4.1.x...HEAD
|
||||
|
||||
### Release 4.1.1 (Aug 2015) ###
|
||||
|
||||
* CB-9428 update script now bumps up minSdkVersion to 14 if it is less than that
|
||||
* CB-9430 Fixes check_reqs failure when javac returns an extra line
|
||||
|
||||
### Release 4.1.0 (Jul 2015) ###
|
||||
|
||||
* CB-9185 Fixed an issue when unsigned apks couldn't be found. This closes #202
|
||||
* CB-9397 Fixes minor issues with `cordova requirements android`
|
||||
* CB-9389 Fixes build/check_reqs hang
|
||||
* CB-9392 Fixed printing flavored versions. This closes #184.
|
||||
* CB-9382 [Android] Fix KeepRunning setting when Plugin activity is showed. This closes #200
|
||||
* CB-9391 Fixes cdvBuildMultipleApks option casting
|
||||
* CB-9343 Split the Content-Type to obtain a clean mimetype
|
||||
* CB-9255 Make getUriType case insensitive.
|
||||
* CB-9149 Fixes JSHint issue introduced by 899daa9
|
||||
* CB-9372: Remove unused files: 'main.js' & 'master.css'. This closes #198
|
||||
* CB-9149 Make gradle alias subprojects in order to handle libs that depend on libs. This closes #182
|
||||
* Update min SDK version to 14
|
||||
* Update licenses. This closes #190
|
||||
* CB-9185 Fix signed release build exception. This closes #193.
|
||||
* CB-9286 Fixes build failure when ANDROID_HOME is not set.
|
||||
* CB-9284 Fix for handling absolute path for keystore in build.json
|
||||
* CB-9260 Install Android-22 on Travis-CI
|
||||
* Adding .ratignore file.
|
||||
* CB-9119 Adding lib/retry.js for retrying promise-returning functions. Retrying 'adb install' in emulator.js because it sometimes hangs.
|
||||
* CB-9115 android: Grant Lollipop permission req
|
||||
* Remove extra console message
|
||||
* CB-8898 Report expected gradle location properly
|
||||
* CB-8898 Fixes gradle check failure due to missing quotes
|
||||
* CB-9080: -d option is not supported on Android 4.1.1 and lower, removing
|
||||
* CB-8954 Adds `requirements` command support to check_reqs module
|
||||
* Update JS snapshot to version 4.1.0-dev (via coho)
|
||||
* CB-8417 updated platform specific files from cordova.js repo
|
||||
* Adding tests to confirm that preferences aren't changed by Intents
|
||||
* Forgot to remove the method that copied over the intent data
|
||||
* Getting around to removing this old Intent code
|
||||
* Update JS snapshot to version 4.1.0-dev (via coho)
|
||||
* Fix CordovaPluginTest on KitKat (start-up events seem to change)
|
||||
* CB-3360 Allow setting a custom User-Agent (close #162)
|
||||
* CB-8902 Use immersive mode when available when going fullscreen (close #175)
|
||||
* Make BridgeMode methods public (they were always supposed to be)
|
||||
* Simplify: EncodingUtils.getBytes(str) -> str.getBytes()
|
||||
* Don't show warning when gradlew file is read-only
|
||||
* Don't show warning when prepEnv copies gradlew and it's read-only
|
||||
* Make gradle wrapper prepEnv code work even when android-sdk is read-only
|
||||
* CB-8897 Delete drawable/icon.png since it duplicates drawable-mdpi/icon.png
|
||||
* Updating the template to target mininumSdkTarget=14
|
||||
* CB-8894: Updating the template to target mininumSdkTarget=14
|
||||
* CB-8891 Add a note about when the gradle helpers were added
|
||||
* CB-8891 Add a gradle helper for retrieving config.xml preference values
|
||||
* CB-8884 Delete Eclipse tweaks from create script
|
||||
* CB-8834 Don't fail to install on VERSION_DOWNGRADE
|
||||
* Automated tools fail, and you have to remember all four places where this is set.
|
||||
* Update the package.json
|
||||
* CB-9042 coho failed to update version, so here we are
|
||||
* CB9042 - Updating Release Notes
|
||||
* Adding tests to confirm that preferences aren't changed by Intents
|
||||
* updating existing test code
|
||||
* Forgot to remove the method that copied over the intent data
|
||||
* Getting around to removing this old Intent code
|
||||
* CB-8834 Don't fail to install on VERSION_DOWNGRADE
|
||||
|
||||
### Release 4.0.2 (May 2015) ###
|
||||
|
||||
* Removed Intent Functionality from Preferences - Preferences can no longer be set by intents
|
||||
|
||||
### Release 4.0.1 (April 2015) ###
|
||||
|
||||
* Bug fixed where platform failed to install on a version downgrade
|
||||
|
||||
### Release 4.0.0 (March 2015) ###
|
||||
|
||||
This release adds significant functionality, and also introduces a number
|
||||
of breaking changes. Some of the changes to the code base will be of
|
||||
particular interest to plugin developers.
|
||||
|
||||
#### Major Changes ####
|
||||
* Support for pluggable WebViews
|
||||
* The system WebView can be replaced in your app, via a plugin
|
||||
* Core WebView functionality is encapsulated, with extension points exposed
|
||||
via interfaces
|
||||
* Support for Crosswalk to bring the modern Chromium WebView to older devices
|
||||
* Uses the pluggable WebView framework
|
||||
* You will need to add the new [cordova-crosswalk-engine](https://github.com/MobileChromeApps/cordova-crosswalk-engine) plugin
|
||||
* Splash screen functionality is now provided via plugin
|
||||
* You will need to add the new [cordova-plugin-splashscreen](https://github.com/apache/cordova-plugin-splashscreen) plugin to continue using a splash screen
|
||||
* Whitelist functionality is now provided via plugin (CB-7747)
|
||||
* The whitelist has been enhanced to be more secure and configurable
|
||||
* Setting of Content-Security-Policy is now supported by the framework (see details in plugin readme)
|
||||
* You will need to add the new [cordova-plugin-whitelist](https://github.com/apache/cordova-plugin-whitelist) plugin
|
||||
* Legacy whitelist behaviour is still available via plugin (although not recommended).
|
||||
|
||||
Changes For Plugin Developers:
|
||||
|
||||
* Develop in Android Studio
|
||||
* Android Studio is now fully supported, and recommended over Eclipse
|
||||
* Build using Gradle
|
||||
* All builds [use Gradle by default](Android%20Shell%20Tool%20Guide_building_with_gradle), instead of Ant
|
||||
* Plugins can add their own gradle build steps!
|
||||
* Plugins can depend on Maven libraries using `<framework>` tags
|
||||
* New APIs: `onStart`, `onStop`, `onConfigurationChanged`
|
||||
* `"onScrollChanged"` message removed. Use `view.getViewTreeObserver().addOnScrollChangedListener(...)` instead
|
||||
* CB-8702 New API for plugins to override `shouldInterceptRequest` with a stream
|
||||
|
||||
#### Other Changes ####
|
||||
* CB-8378 Removed `hidekeyboard` and `showkeyboard` events (apps should use a plugin instead)
|
||||
* CB-8735 `bin/create` regex relaxed / better support for numbers
|
||||
* CB-8699 Fix CordovaResourceApi `copyResource` creating zero-length files when src=uncompressed asset
|
||||
* CB-8693 CordovaLib should not contain icons / splashscreens
|
||||
* CB-8592 Fix NPE if lifecycle events reach CordovaWebView before `init()` has been called
|
||||
* CB-8588 Add CATEGORY_BROWSABLE to intents from showWebPage openExternal=true
|
||||
* CB-8587 Don't allow WebView navigations within showWebPage that are not whitelisted
|
||||
* CB-7827 Add `--activity-name` for `bin/create`
|
||||
* CB-8548 Use debug-signing.properties and release-signing.properties when they exist
|
||||
* CB-8545 Don't add a layout as a parent of the WebView
|
||||
* CB-7159 BackgroundColor not used when `<html style="opacity:0">`, nor during screen rotation
|
||||
* CB-6630 Removed OkHttp from core library. It's now available as a plugin: [cordova-plugin-okhttp](https://www.npmjs.com/package/cordova-plugin-okhttp)
|
||||
|
||||
### Release 3.7.1 (January 2015) ###
|
||||
* CB-8411 Initialize plugins only after `createViews()` is called (regression in 3.7.0)
|
||||
|
||||
+17
-4
@@ -20,17 +20,30 @@
|
||||
*/
|
||||
var path = require('path');
|
||||
var create = require('./lib/create');
|
||||
var args = require('./lib/simpleargs').getArgs(process.argv);
|
||||
var argv = require('nopt')({
|
||||
'help' : Boolean,
|
||||
'cli' : Boolean,
|
||||
'shared' : Boolean,
|
||||
'link' : Boolean,
|
||||
'activity-name' : [String, undefined]
|
||||
});
|
||||
|
||||
if (args['--help'] || args._.length === 0) {
|
||||
console.log('Usage: ' + path.relative(process.cwd(), path.join(__dirname, 'create')) + ' <path_to_new_project> <package_name> <project_name> [<template_path>] [--link]');
|
||||
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);
|
||||
}
|
||||
|
||||
create.createProject(args._[0], args._[1], args._[2], args._[3], args['--link'] || args['--shared'], args['--cli']).done();
|
||||
var project_path = argv.argv.remain[0];
|
||||
var package_name = argv.argv.remain[1];
|
||||
var project_name = argv.argv.remain[2];
|
||||
var template_path = argv.argv.remain[3];
|
||||
var activity_name = argv['activity-name'];
|
||||
|
||||
create.createProject(project_path, package_name, project_name, activity_name, template_path, argv.link || argv.shared, argv.cli).done();
|
||||
|
||||
|
||||
+100
-21
@@ -33,17 +33,20 @@ var isWindows = process.platform == 'win32';
|
||||
|
||||
function forgivingWhichSync(cmd) {
|
||||
try {
|
||||
// TODO: Should use shelljs.which() here to have one less dependency.
|
||||
return fs.realpathSync(which.sync(cmd));
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function tryCommand(cmd, errMsg) {
|
||||
function tryCommand(cmd, errMsg, catchStderr) {
|
||||
var d = Q.defer();
|
||||
child_process.exec(cmd, function(err, stdout, stderr) {
|
||||
if (err) d.reject(new Error(errMsg));
|
||||
else d.resolve(stdout);
|
||||
// Sometimes it is necessary to return an stderr instead of stdout in case of success, since
|
||||
// some commands prints theirs output to stderr instead of stdout. 'javac' is the example
|
||||
else d.resolve((catchStderr ? stderr : stdout).trim());
|
||||
});
|
||||
return d.promise;
|
||||
}
|
||||
@@ -69,15 +72,23 @@ module.exports.get_target = function() {
|
||||
|
||||
// Returns a promise. Called only by build and clean commands.
|
||||
module.exports.check_ant = function() {
|
||||
return tryCommand('ant -version', 'Failed to run "ant -version", make sure you have ant installed and added to your PATH.');
|
||||
return tryCommand('ant -version', 'Failed to run "ant -version", make sure you have ant installed and added to your PATH.')
|
||||
.then(function (output) {
|
||||
// Parse Ant version from command output
|
||||
return /version ((?:\d+\.)+(?:\d+))/i.exec(output)[1];
|
||||
});
|
||||
};
|
||||
|
||||
// Returns a promise. Called only by build and clean commands.
|
||||
module.exports.check_gradle = function() {
|
||||
var sdkDir = process.env['ANDROID_HOME'];
|
||||
if (!sdkDir)
|
||||
return Q.reject('Could not find gradle wrapper within Android SDK. Could not find Android SDK directory.\n' +
|
||||
'Might need to install Android SDK or set up \'ANDROID_HOME\' env variable.');
|
||||
|
||||
var wrapperDir = path.join(sdkDir, 'tools', 'templates', 'gradle', 'wrapper');
|
||||
if (!fs.existsSync(wrapperDir)) {
|
||||
return Q.reject(new Error('Could not find gradle wrapper within android sdk. Might need to update your Android SDK.\n' +
|
||||
return Q.reject(new Error('Could not find gradle wrapper within Android SDK. Might need to update your Android SDK.\n' +
|
||||
'Looked here: ' + wrapperDir));
|
||||
}
|
||||
return Q.when();
|
||||
@@ -95,9 +106,10 @@ module.exports.check_java = function() {
|
||||
}
|
||||
} else {
|
||||
if (javacPath) {
|
||||
var msg = 'Failed to find \'JAVA_HOME\' environment variable. Try setting setting it manually.';
|
||||
// OS X has a command for finding JAVA_HOME.
|
||||
if (fs.existsSync('/usr/libexec/java_home')) {
|
||||
return tryCommand('/usr/libexec/java_home', 'Failed to run: /usr/libexec/java_home')
|
||||
return tryCommand('/usr/libexec/java_home', msg)
|
||||
.then(function(stdout) {
|
||||
process.env['JAVA_HOME'] = stdout.trim();
|
||||
});
|
||||
@@ -108,7 +120,7 @@ module.exports.check_java = function() {
|
||||
if (fs.existsSync(path.join(maybeJavaHome, 'lib', 'tools.jar'))) {
|
||||
process.env['JAVA_HOME'] = maybeJavaHome;
|
||||
} else {
|
||||
throw new Error('Could not find JAVA_HOME. Try setting the environment variable manually');
|
||||
throw new Error(msg);
|
||||
}
|
||||
}
|
||||
} else if (isWindows) {
|
||||
@@ -139,7 +151,12 @@ module.exports.check_java = function() {
|
||||
}
|
||||
return tryCommand('java -version', msg)
|
||||
.then(function() {
|
||||
return tryCommand('javac -version', msg);
|
||||
// We use tryCommand with catchStderr = true, because
|
||||
// javac writes version info to stderr instead of stdout
|
||||
return tryCommand('javac -version', msg, true);
|
||||
}).then(function (output) {
|
||||
var match = /javac ((?:\d+\.)+(?:\d+))/i.exec(output)[1];
|
||||
return match && match[1];
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -195,20 +212,22 @@ module.exports.check_android = function() {
|
||||
process.env['ANDROID_HOME'] = grandParentDir;
|
||||
hasAndroidHome = true;
|
||||
} else {
|
||||
throw new Error('ANDROID_HOME is not set and no "tools" directory found at ' + parentDir);
|
||||
throw new Error('Failed to find \'ANDROID_HOME\' environment variable. Try setting 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 path to valid SDK directory.');
|
||||
}
|
||||
}
|
||||
if (hasAndroidHome && !adbInPath) {
|
||||
process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'platform-tools');
|
||||
}
|
||||
if (!process.env['ANDROID_HOME']) {
|
||||
throw new Error('ANDROID_HOME is not set and "android" command not in your PATH. You must fulfill at least one of these conditions.');
|
||||
throw new Error('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' +
|
||||
'Failed to find \'android\' command in your \'PATH\'. Try update your \'PATH\' to include path to valid SDK directory.');
|
||||
}
|
||||
if (!fs.existsSync(process.env['ANDROID_HOME'])) {
|
||||
throw new Error('ANDROID_HOME is set to a non-existant path: ' + process.env['ANDROID_HOME']);
|
||||
throw new Error('\'ANDROID_HOME\' environment variable is set to non-existent path: ' + process.env['ANDROID_HOME'] +
|
||||
'\nTry update it manually to point to valid SDK directory.');
|
||||
}
|
||||
// Check that the target sdk level is installed.
|
||||
return module.exports.check_android_target(module.exports.get_target());
|
||||
});
|
||||
};
|
||||
|
||||
@@ -222,27 +241,87 @@ module.exports.check_android_target = function(valid_target) {
|
||||
// android-L
|
||||
// Google Inc.:Google APIs:20
|
||||
// Google Inc.:Glass Development Kit Preview:20
|
||||
if (!valid_target) valid_target = module.exports.get_target();
|
||||
var msg = 'Android SDK not found. Make sure that it is installed. If it is not at the default location, set the ANDROID_HOME environment variable.';
|
||||
return tryCommand('android list targets --compact', msg)
|
||||
.then(function(output) {
|
||||
if (output.split('\n').indexOf(valid_target) == -1) {
|
||||
var androidCmd = module.exports.getAbsoluteAndroidCmd();
|
||||
throw new Error('Please install Android target: "' + valid_target + '".\n\n' +
|
||||
'Hint: Open the SDK manager by running: ' + androidCmd + '\n' +
|
||||
'You will require:\n' +
|
||||
'1. "SDK Platform" for ' + valid_target + '\n' +
|
||||
'2. "Android SDK Platform-tools (latest)\n' +
|
||||
'3. "Android SDK Build-tools" (latest)');
|
||||
var targets = output.split('\n');
|
||||
if (targets.indexOf(valid_target) >= 0) {
|
||||
return targets;
|
||||
}
|
||||
|
||||
var androidCmd = module.exports.getAbsoluteAndroidCmd();
|
||||
throw new Error('Please install Android target: "' + valid_target + '".\n\n' +
|
||||
'Hint: Open the SDK manager by running: ' + androidCmd + '\n' +
|
||||
'You will require:\n' +
|
||||
'1. "SDK Platform" for ' + valid_target + '\n' +
|
||||
'2. "Android SDK Platform-tools (latest)\n' +
|
||||
'3. "Android SDK Build-tools" (latest)');
|
||||
});
|
||||
};
|
||||
|
||||
// Returns a promise.
|
||||
module.exports.run = function() {
|
||||
return Q.all([this.check_java(), this.check_android()])
|
||||
return Q.all([this.check_java(), this.check_android().then(this.check_android_target)])
|
||||
.then(function() {
|
||||
console.log('ANDROID_HOME=' + process.env['ANDROID_HOME']);
|
||||
console.log('JAVA_HOME=' + process.env['JAVA_HOME']);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Object thar represents one of requirements for current platform.
|
||||
* @param {String} id The unique identifier for this requirements.
|
||||
* @param {String} name The name of requirements. Human-readable field.
|
||||
* @param {String} version The version of requirement installed. In some cases could be an array of strings
|
||||
* (for example, check_android_target returns an array of android targets installed)
|
||||
* @param {Boolean} installed Indicates whether the requirement is installed or not
|
||||
*/
|
||||
var Requirement = function (id, name, version, installed) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.installed = installed || false;
|
||||
this.metadata = {
|
||||
version: version,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @return Promise<Requirement[]> Array of requirements. Due to implementation, promise is always fulfilled.
|
||||
*/
|
||||
module.exports.check_all = function() {
|
||||
|
||||
var requirements = [
|
||||
new Requirement('java', 'Java JDK'),
|
||||
new Requirement('androidSdk', 'Android SDK'),
|
||||
new Requirement('androidTarget', 'Android target'),
|
||||
new Requirement('gradle', 'Gradle')
|
||||
];
|
||||
|
||||
var checkFns = [
|
||||
this.check_java,
|
||||
this.check_android,
|
||||
this.check_android_target,
|
||||
this.check_gradle
|
||||
];
|
||||
|
||||
// Then execute requirement checks one-by-one
|
||||
return checkFns.reduce(function (promise, checkFn, idx) {
|
||||
// Update each requirement with results
|
||||
var requirement = requirements[idx];
|
||||
return promise.then(checkFn)
|
||||
.then(function (version) {
|
||||
requirement.installed = true;
|
||||
requirement.metadata.version = version;
|
||||
}, function (err) {
|
||||
requirement.metadata.reason = err instanceof Error ? err.message : err;
|
||||
});
|
||||
}, Q())
|
||||
.then(function () {
|
||||
// When chain is completed, return requirements array to upstream API
|
||||
return requirements;
|
||||
});
|
||||
};
|
||||
|
||||
+36
-28
@@ -39,7 +39,8 @@ function getFrameworkDir(projectPath, shared) {
|
||||
|
||||
function copyJsAndLibrary(projectPath, shared, projectName) {
|
||||
var nestedCordovaLibPath = getFrameworkDir(projectPath, false);
|
||||
shell.cp('-f', path.join(ROOT, 'framework', 'assets', 'www', 'cordova.js'), path.join(projectPath, 'assets', 'www', 'cordova.js'));
|
||||
var srcCordovaJsPath = path.join(ROOT, 'bin', 'templates', 'project', 'assets', 'www', 'cordova.js');
|
||||
shell.cp('-f', srcCordovaJsPath, path.join(projectPath, 'assets', 'www', 'cordova.js'));
|
||||
// Don't fail if there are no old jars.
|
||||
setShellFatal(false, function() {
|
||||
shell.ls(path.join(projectPath, 'libs', 'cordova-*.jar')).forEach(function(oldJar) {
|
||||
@@ -57,7 +58,7 @@ function copyJsAndLibrary(projectPath, shared, projectName) {
|
||||
if (shared) {
|
||||
shell.rm('-rf', nestedCordovaLibPath);
|
||||
} else if (!wasSymlink) {
|
||||
// Delete only the src, since eclipse can't handle its .project file being deleted.
|
||||
// Delete only the src, since Eclipse / Android Studio can't handle their project files being deleted.
|
||||
shell.rm('-rf', path.join(nestedCordovaLibPath, 'src'));
|
||||
}
|
||||
});
|
||||
@@ -71,13 +72,6 @@ function copyJsAndLibrary(projectPath, shared, projectName) {
|
||||
shell.cp('-f', path.join(ROOT, 'framework', 'build.gradle'), nestedCordovaLibPath);
|
||||
shell.cp('-f', path.join(ROOT, 'framework', 'cordova.gradle'), nestedCordovaLibPath);
|
||||
shell.cp('-r', path.join(ROOT, 'framework', 'src'), nestedCordovaLibPath);
|
||||
// Create an eclipse project file and set the name of it to something unique.
|
||||
// Without this, you can't import multiple CordovaLib projects into the same workspace.
|
||||
var eclipseProjectFilePath = path.join(nestedCordovaLibPath, '.project');
|
||||
if (!fs.existsSync(eclipseProjectFilePath)) {
|
||||
var data = '<?xml version="1.0" encoding="UTF-8"?><projectDescription><name>' + projectName + '-' + 'CordovaLib</name></projectDescription>';
|
||||
fs.writeFileSync(eclipseProjectFilePath, data, 'utf8');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +109,11 @@ function writeProjectProperties(projectPath, target_api) {
|
||||
fs.writeFileSync(dstPath, data);
|
||||
}
|
||||
|
||||
function prepBuildFiles(projectPath) {
|
||||
var buildModule = require(path.join(path.resolve(projectPath), 'cordova', 'lib', 'build'));
|
||||
buildModule.prepBuildFiles();
|
||||
}
|
||||
|
||||
function copyBuildRules(projectPath) {
|
||||
var srcDir = path.join(ROOT, 'bin', 'templates', 'project');
|
||||
|
||||
@@ -129,7 +128,7 @@ function copyScripts(projectPath) {
|
||||
// Copy in the new ones.
|
||||
shell.cp('-r', srcScriptsDir, projectPath);
|
||||
shell.cp('-r', path.join(ROOT, 'bin', 'node_modules'), destScriptsDir);
|
||||
shell.cp(path.join(ROOT, 'bin', 'check_reqs'), path.join(destScriptsDir, 'check_reqs'));
|
||||
shell.cp(path.join(ROOT, 'bin', 'check_reqs*'), destScriptsDir);
|
||||
shell.cp(path.join(ROOT, 'bin', 'lib', 'check_reqs.js'), path.join(projectPath, 'cordova', 'lib', 'check_reqs.js'));
|
||||
shell.cp(path.join(ROOT, 'bin', 'android_sdk_version'), path.join(destScriptsDir, 'android_sdk_version'));
|
||||
shell.cp(path.join(ROOT, 'bin', 'lib', 'android_sdk_version.js'), path.join(projectPath, 'cordova', 'lib', 'android_sdk_version.js'));
|
||||
@@ -142,8 +141,9 @@ function copyScripts(projectPath) {
|
||||
*/
|
||||
function validatePackageName(package_name) {
|
||||
//Make the package conform to Java package types
|
||||
//http://developer.android.com/guide/topics/manifest/manifest-element.html#package
|
||||
//Enforce underscore limitation
|
||||
if (!/^[a-zA-Z]+(\.[a-zA-Z0-9][a-zA-Z0-9_]*)+$/.test(package_name)) {
|
||||
if (!/^[a-zA-Z][a-zA-Z0-9_]+(\.[a-zA-Z][a-zA-Z0-9_]*)+$/.test(package_name)) {
|
||||
return Q.reject('Package name must look like: com.company.Name');
|
||||
}
|
||||
|
||||
@@ -189,12 +189,13 @@ function validateProjectName(project_name) {
|
||||
* - `project_path` {String} Path to the new Cordova android project.
|
||||
* - `package_name`{String} Package name, following reverse-domain style convention.
|
||||
* - `project_name` {String} Project name.
|
||||
* - `activity_name` {String} Name for the activity
|
||||
* - 'project_template_dir' {String} Path to project template (override).
|
||||
*
|
||||
* Returns a promise.
|
||||
*/
|
||||
|
||||
exports.createProject = function(project_path, package_name, project_name, project_template_dir, use_shared_project, use_cli_template) {
|
||||
exports.createProject = function(project_path, package_name, project_name, activity_name, project_template_dir, use_shared_project, use_cli_template) {
|
||||
// Set default values for path, package and name
|
||||
project_path = typeof project_path !== 'undefined' ? project_path : 'CordovaExample';
|
||||
project_path = path.relative(process.cwd(), project_path);
|
||||
@@ -206,9 +207,7 @@ exports.createProject = function(project_path, package_name, project_name, proje
|
||||
|
||||
var package_as_path = package_name.replace(/\./g, path.sep);
|
||||
var activity_dir = path.join(project_path, 'src', package_as_path);
|
||||
// safe_activity_name is being hardcoded to avoid issues with unicode app name (https://issues.apache.org/jira/browse/CB-6511)
|
||||
// TODO: provide option to specify activity name via CLI (proposal: https://issues.apache.org/jira/browse/CB-7231)
|
||||
var safe_activity_name = 'MainActivity';
|
||||
var safe_activity_name = typeof activity_name !== 'undefined' ? activity_name : 'MainActivity';
|
||||
var activity_path = path.join(activity_dir, safe_activity_name + '.java');
|
||||
var target_api = check_reqs.get_target();
|
||||
var manifest_path = path.join(project_path, 'AndroidManifest.xml');
|
||||
@@ -228,6 +227,7 @@ exports.createProject = function(project_path, package_name, project_name, proje
|
||||
console.log('\tPath: ' + project_path);
|
||||
console.log('\tPackage: ' + package_name);
|
||||
console.log('\tName: ' + project_name);
|
||||
console.log('\tActivity: ' + safe_activity_name);
|
||||
console.log('\tAndroid target: ' + target_api);
|
||||
|
||||
console.log('Copying template files...');
|
||||
@@ -236,23 +236,11 @@ exports.createProject = function(project_path, package_name, project_name, proje
|
||||
// copy project template
|
||||
shell.cp('-r', path.join(project_template_dir, 'assets'), project_path);
|
||||
shell.cp('-r', path.join(project_template_dir, 'res'), project_path);
|
||||
shell.cp('-r', path.join(ROOT, 'framework', 'res', 'xml'), path.join(project_path, 'res'));
|
||||
shell.cp(path.join(project_template_dir, 'gitignore'), path.join(project_path, '.gitignore'));
|
||||
|
||||
// Manually create directories that would be empty within the template (since git doesn't track directories).
|
||||
shell.mkdir(path.join(project_path, 'libs'));
|
||||
|
||||
// Add in the proper eclipse project file.
|
||||
if (use_cli_template) {
|
||||
var note = 'To show `assets/www` or `res/xml/config.xml`, go to:\n' +
|
||||
' Project -> Properties -> Resource -> Resource Filters\n' +
|
||||
'And delete the exclusion filter.\n';
|
||||
shell.cp(path.join(project_template_dir, 'eclipse-project-CLI'), path.join(project_path, '.project'));
|
||||
fs.writeFileSync(path.join(project_path, 'assets', '_where-is-www.txt'), note);
|
||||
} else {
|
||||
shell.cp(path.join(project_template_dir, 'eclipse-project'), path.join(project_path, '.project'));
|
||||
}
|
||||
|
||||
// copy cordova.js, cordova.jar
|
||||
copyJsAndLibrary(project_path, use_shared_project, safe_activity_name);
|
||||
|
||||
@@ -261,7 +249,6 @@ exports.createProject = function(project_path, package_name, project_name, proje
|
||||
shell.cp('-f', path.join(project_template_dir, 'Activity.java'), activity_path);
|
||||
shell.sed('-i', /__ACTIVITY__/, safe_activity_name, activity_path);
|
||||
shell.sed('-i', /__NAME__/, project_name, path.join(project_path, 'res', 'values', 'strings.xml'));
|
||||
shell.sed('-i', /__NAME__/, project_name, path.join(project_path, '.project'));
|
||||
shell.sed('-i', /__ID__/, package_name, activity_path);
|
||||
|
||||
shell.cp('-f', path.join(project_template_dir, 'AndroidManifest.xml'), manifest_path);
|
||||
@@ -273,6 +260,7 @@ exports.createProject = function(project_path, package_name, project_name, proje
|
||||
});
|
||||
// Link it to local android install.
|
||||
writeProjectProperties(project_path, target_api);
|
||||
prepBuildFiles(project_path);
|
||||
console.log(generateDoneMessage('create', use_shared_project));
|
||||
});
|
||||
};
|
||||
@@ -302,17 +290,37 @@ function extractProjectNameFromManifest(projectPath) {
|
||||
return m[1];
|
||||
}
|
||||
|
||||
// Cordova-android updates sometimes drop support for older versions. Need to update minSDK in existing projects.
|
||||
function updateMinSDKInManifest(projectPath) {
|
||||
var manifestPath = path.join(projectPath, 'AndroidManifest.xml');
|
||||
var manifestData = fs.readFileSync(manifestPath, 'utf8');
|
||||
var minSDKVersion = 14;
|
||||
|
||||
//grab minSdkVersion from Android.
|
||||
var m = /android:minSdkVersion\s*=\s*"(.*?)"/i.exec(manifestData);
|
||||
if (!m) {
|
||||
throw new Error('Could not find minSDKVersion in ' + manifestPath);
|
||||
}
|
||||
//if minSDKVersion in Android.manifest is less than our current min, replace it
|
||||
if(Number(m[1]) < minSDKVersion) {
|
||||
console.log('Updating minSdkVersion from ' + m[1] + ' to ' + minSDKVersion + ' in AndroidManifest.xml');
|
||||
shell.sed('-i', /android:minSdkVersion\s*=\s*"(.*?)"/, 'android:minSdkVersion="'+minSDKVersion+'"', manifestPath);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a promise.
|
||||
exports.updateProject = function(projectPath, shared) {
|
||||
return Q()
|
||||
.then(function() {
|
||||
var projectName = extractProjectNameFromManifest(projectPath);
|
||||
var target_api = check_reqs.get_target();
|
||||
updateMinSDKInManifest(projectPath);
|
||||
copyJsAndLibrary(projectPath, shared, projectName);
|
||||
copyScripts(projectPath);
|
||||
copyBuildRules(projectPath);
|
||||
removeDebuggableFromManifest(projectPath);
|
||||
writeProjectProperties(projectPath, target_api);
|
||||
prepBuildFiles(projectPath);
|
||||
console.log(generateDoneMessage('update', shared));
|
||||
});
|
||||
};
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
Copyright 2009, 2010, 2011 Isaac Z. Schlueter.
|
||||
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.
|
||||
+414
@@ -0,0 +1,414 @@
|
||||
// info about each config option.
|
||||
|
||||
var debug = process.env.DEBUG_NOPT || process.env.NOPT_DEBUG
|
||||
? function () { console.error.apply(console, arguments) }
|
||||
: function () {}
|
||||
|
||||
var url = require("url")
|
||||
, path = require("path")
|
||||
, Stream = require("stream").Stream
|
||||
, abbrev = require("abbrev")
|
||||
|
||||
module.exports = exports = nopt
|
||||
exports.clean = clean
|
||||
|
||||
exports.typeDefs =
|
||||
{ String : { type: String, validate: validateString }
|
||||
, Boolean : { type: Boolean, validate: validateBoolean }
|
||||
, url : { type: url, validate: validateUrl }
|
||||
, Number : { type: Number, validate: validateNumber }
|
||||
, path : { type: path, validate: validatePath }
|
||||
, Stream : { type: Stream, validate: validateStream }
|
||||
, Date : { type: Date, validate: validateDate }
|
||||
}
|
||||
|
||||
function nopt (types, shorthands, args, slice) {
|
||||
args = args || process.argv
|
||||
types = types || {}
|
||||
shorthands = shorthands || {}
|
||||
if (typeof slice !== "number") slice = 2
|
||||
|
||||
debug(types, shorthands, args, slice)
|
||||
|
||||
args = args.slice(slice)
|
||||
var data = {}
|
||||
, key
|
||||
, remain = []
|
||||
, cooked = args
|
||||
, original = args.slice(0)
|
||||
|
||||
parse(args, data, remain, types, shorthands)
|
||||
// now data is full
|
||||
clean(data, types, exports.typeDefs)
|
||||
data.argv = {remain:remain,cooked:cooked,original:original}
|
||||
Object.defineProperty(data.argv, 'toString', { value: function () {
|
||||
return this.original.map(JSON.stringify).join(" ")
|
||||
}, enumerable: false })
|
||||
return data
|
||||
}
|
||||
|
||||
function clean (data, types, typeDefs) {
|
||||
typeDefs = typeDefs || exports.typeDefs
|
||||
var remove = {}
|
||||
, typeDefault = [false, true, null, String, Array]
|
||||
|
||||
Object.keys(data).forEach(function (k) {
|
||||
if (k === "argv") return
|
||||
var val = data[k]
|
||||
, isArray = Array.isArray(val)
|
||||
, type = types[k]
|
||||
if (!isArray) val = [val]
|
||||
if (!type) type = typeDefault
|
||||
if (type === Array) type = typeDefault.concat(Array)
|
||||
if (!Array.isArray(type)) type = [type]
|
||||
|
||||
debug("val=%j", val)
|
||||
debug("types=", type)
|
||||
val = val.map(function (val) {
|
||||
// if it's an unknown value, then parse false/true/null/numbers/dates
|
||||
if (typeof val === "string") {
|
||||
debug("string %j", val)
|
||||
val = val.trim()
|
||||
if ((val === "null" && ~type.indexOf(null))
|
||||
|| (val === "true" &&
|
||||
(~type.indexOf(true) || ~type.indexOf(Boolean)))
|
||||
|| (val === "false" &&
|
||||
(~type.indexOf(false) || ~type.indexOf(Boolean)))) {
|
||||
val = JSON.parse(val)
|
||||
debug("jsonable %j", val)
|
||||
} else if (~type.indexOf(Number) && !isNaN(val)) {
|
||||
debug("convert to number", val)
|
||||
val = +val
|
||||
} else if (~type.indexOf(Date) && !isNaN(Date.parse(val))) {
|
||||
debug("convert to date", val)
|
||||
val = new Date(val)
|
||||
}
|
||||
}
|
||||
|
||||
if (!types.hasOwnProperty(k)) {
|
||||
return val
|
||||
}
|
||||
|
||||
// allow `--no-blah` to set 'blah' to null if null is allowed
|
||||
if (val === false && ~type.indexOf(null) &&
|
||||
!(~type.indexOf(false) || ~type.indexOf(Boolean))) {
|
||||
val = null
|
||||
}
|
||||
|
||||
var d = {}
|
||||
d[k] = val
|
||||
debug("prevalidated val", d, val, types[k])
|
||||
if (!validate(d, k, val, types[k], typeDefs)) {
|
||||
if (exports.invalidHandler) {
|
||||
exports.invalidHandler(k, val, types[k], data)
|
||||
} else if (exports.invalidHandler !== false) {
|
||||
debug("invalid: "+k+"="+val, types[k])
|
||||
}
|
||||
return remove
|
||||
}
|
||||
debug("validated val", d, val, types[k])
|
||||
return d[k]
|
||||
}).filter(function (val) { return val !== remove })
|
||||
|
||||
if (!val.length) delete data[k]
|
||||
else if (isArray) {
|
||||
debug(isArray, data[k], val)
|
||||
data[k] = val
|
||||
} else data[k] = val[0]
|
||||
|
||||
debug("k=%s val=%j", k, val, data[k])
|
||||
})
|
||||
}
|
||||
|
||||
function validateString (data, k, val) {
|
||||
data[k] = String(val)
|
||||
}
|
||||
|
||||
function validatePath (data, k, val) {
|
||||
if (val === true) return false
|
||||
if (val === null) return true
|
||||
|
||||
val = String(val)
|
||||
var homePattern = process.platform === 'win32' ? /^~(\/|\\)/ : /^~\//
|
||||
if (val.match(homePattern) && process.env.HOME) {
|
||||
val = path.resolve(process.env.HOME, val.substr(2))
|
||||
}
|
||||
data[k] = path.resolve(String(val))
|
||||
return true
|
||||
}
|
||||
|
||||
function validateNumber (data, k, val) {
|
||||
debug("validate Number %j %j %j", k, val, isNaN(val))
|
||||
if (isNaN(val)) return false
|
||||
data[k] = +val
|
||||
}
|
||||
|
||||
function validateDate (data, k, val) {
|
||||
debug("validate Date %j %j %j", k, val, Date.parse(val))
|
||||
var s = Date.parse(val)
|
||||
if (isNaN(s)) return false
|
||||
data[k] = new Date(val)
|
||||
}
|
||||
|
||||
function validateBoolean (data, k, val) {
|
||||
if (val instanceof Boolean) val = val.valueOf()
|
||||
else if (typeof val === "string") {
|
||||
if (!isNaN(val)) val = !!(+val)
|
||||
else if (val === "null" || val === "false") val = false
|
||||
else val = true
|
||||
} else val = !!val
|
||||
data[k] = val
|
||||
}
|
||||
|
||||
function validateUrl (data, k, val) {
|
||||
val = url.parse(String(val))
|
||||
if (!val.host) return false
|
||||
data[k] = val.href
|
||||
}
|
||||
|
||||
function validateStream (data, k, val) {
|
||||
if (!(val instanceof Stream)) return false
|
||||
data[k] = val
|
||||
}
|
||||
|
||||
function validate (data, k, val, type, typeDefs) {
|
||||
// arrays are lists of types.
|
||||
if (Array.isArray(type)) {
|
||||
for (var i = 0, l = type.length; i < l; i ++) {
|
||||
if (type[i] === Array) continue
|
||||
if (validate(data, k, val, type[i], typeDefs)) return true
|
||||
}
|
||||
delete data[k]
|
||||
return false
|
||||
}
|
||||
|
||||
// an array of anything?
|
||||
if (type === Array) return true
|
||||
|
||||
// NaN is poisonous. Means that something is not allowed.
|
||||
if (type !== type) {
|
||||
debug("Poison NaN", k, val, type)
|
||||
delete data[k]
|
||||
return false
|
||||
}
|
||||
|
||||
// explicit list of values
|
||||
if (val === type) {
|
||||
debug("Explicitly allowed %j", val)
|
||||
// if (isArray) (data[k] = data[k] || []).push(val)
|
||||
// else data[k] = val
|
||||
data[k] = val
|
||||
return true
|
||||
}
|
||||
|
||||
// now go through the list of typeDefs, validate against each one.
|
||||
var ok = false
|
||||
, types = Object.keys(typeDefs)
|
||||
for (var i = 0, l = types.length; i < l; i ++) {
|
||||
debug("test type %j %j %j", k, val, types[i])
|
||||
var t = typeDefs[types[i]]
|
||||
if (t && type === t.type) {
|
||||
var d = {}
|
||||
ok = false !== t.validate(d, k, val)
|
||||
val = d[k]
|
||||
if (ok) {
|
||||
// if (isArray) (data[k] = data[k] || []).push(val)
|
||||
// else data[k] = val
|
||||
data[k] = val
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
debug("OK? %j (%j %j %j)", ok, k, val, types[i])
|
||||
|
||||
if (!ok) delete data[k]
|
||||
return ok
|
||||
}
|
||||
|
||||
function parse (args, data, remain, types, shorthands) {
|
||||
debug("parse", args, data, remain)
|
||||
|
||||
var key = null
|
||||
, abbrevs = abbrev(Object.keys(types))
|
||||
, shortAbbr = abbrev(Object.keys(shorthands))
|
||||
|
||||
for (var i = 0; i < args.length; i ++) {
|
||||
var arg = args[i]
|
||||
debug("arg", arg)
|
||||
|
||||
if (arg.match(/^-{2,}$/)) {
|
||||
// done with keys.
|
||||
// the rest are args.
|
||||
remain.push.apply(remain, args.slice(i + 1))
|
||||
args[i] = "--"
|
||||
break
|
||||
}
|
||||
var hadEq = false
|
||||
if (arg.charAt(0) === "-" && arg.length > 1) {
|
||||
if (arg.indexOf("=") !== -1) {
|
||||
hadEq = true
|
||||
var v = arg.split("=")
|
||||
arg = v.shift()
|
||||
v = v.join("=")
|
||||
args.splice.apply(args, [i, 1].concat([arg, v]))
|
||||
}
|
||||
|
||||
// see if it's a shorthand
|
||||
// if so, splice and back up to re-parse it.
|
||||
var shRes = resolveShort(arg, shorthands, shortAbbr, abbrevs)
|
||||
debug("arg=%j shRes=%j", arg, shRes)
|
||||
if (shRes) {
|
||||
debug(arg, shRes)
|
||||
args.splice.apply(args, [i, 1].concat(shRes))
|
||||
if (arg !== shRes[0]) {
|
||||
i --
|
||||
continue
|
||||
}
|
||||
}
|
||||
arg = arg.replace(/^-+/, "")
|
||||
var no = null
|
||||
while (arg.toLowerCase().indexOf("no-") === 0) {
|
||||
no = !no
|
||||
arg = arg.substr(3)
|
||||
}
|
||||
|
||||
if (abbrevs[arg]) arg = abbrevs[arg]
|
||||
|
||||
var isArray = types[arg] === Array ||
|
||||
Array.isArray(types[arg]) && types[arg].indexOf(Array) !== -1
|
||||
|
||||
// allow unknown things to be arrays if specified multiple times.
|
||||
if (!types.hasOwnProperty(arg) && data.hasOwnProperty(arg)) {
|
||||
if (!Array.isArray(data[arg]))
|
||||
data[arg] = [data[arg]]
|
||||
isArray = true
|
||||
}
|
||||
|
||||
var val
|
||||
, la = args[i + 1]
|
||||
|
||||
var isBool = typeof no === 'boolean' ||
|
||||
types[arg] === Boolean ||
|
||||
Array.isArray(types[arg]) && types[arg].indexOf(Boolean) !== -1 ||
|
||||
(typeof types[arg] === 'undefined' && !hadEq) ||
|
||||
(la === "false" &&
|
||||
(types[arg] === null ||
|
||||
Array.isArray(types[arg]) && ~types[arg].indexOf(null)))
|
||||
|
||||
if (isBool) {
|
||||
// just set and move along
|
||||
val = !no
|
||||
// however, also support --bool true or --bool false
|
||||
if (la === "true" || la === "false") {
|
||||
val = JSON.parse(la)
|
||||
la = null
|
||||
if (no) val = !val
|
||||
i ++
|
||||
}
|
||||
|
||||
// also support "foo":[Boolean, "bar"] and "--foo bar"
|
||||
if (Array.isArray(types[arg]) && la) {
|
||||
if (~types[arg].indexOf(la)) {
|
||||
// an explicit type
|
||||
val = la
|
||||
i ++
|
||||
} else if ( la === "null" && ~types[arg].indexOf(null) ) {
|
||||
// null allowed
|
||||
val = null
|
||||
i ++
|
||||
} else if ( !la.match(/^-{2,}[^-]/) &&
|
||||
!isNaN(la) &&
|
||||
~types[arg].indexOf(Number) ) {
|
||||
// number
|
||||
val = +la
|
||||
i ++
|
||||
} else if ( !la.match(/^-[^-]/) && ~types[arg].indexOf(String) ) {
|
||||
// string
|
||||
val = la
|
||||
i ++
|
||||
}
|
||||
}
|
||||
|
||||
if (isArray) (data[arg] = data[arg] || []).push(val)
|
||||
else data[arg] = val
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if (types[arg] === String && la === undefined)
|
||||
la = ""
|
||||
|
||||
if (la && la.match(/^-{2,}$/)) {
|
||||
la = undefined
|
||||
i --
|
||||
}
|
||||
|
||||
val = la === undefined ? true : la
|
||||
if (isArray) (data[arg] = data[arg] || []).push(val)
|
||||
else data[arg] = val
|
||||
|
||||
i ++
|
||||
continue
|
||||
}
|
||||
remain.push(arg)
|
||||
}
|
||||
}
|
||||
|
||||
function resolveShort (arg, shorthands, shortAbbr, abbrevs) {
|
||||
// handle single-char shorthands glommed together, like
|
||||
// npm ls -glp, but only if there is one dash, and only if
|
||||
// all of the chars are single-char shorthands, and it's
|
||||
// not a match to some other abbrev.
|
||||
arg = arg.replace(/^-+/, '')
|
||||
|
||||
// if it's an exact known option, then don't go any further
|
||||
if (abbrevs[arg] === arg)
|
||||
return null
|
||||
|
||||
// if it's an exact known shortopt, same deal
|
||||
if (shorthands[arg]) {
|
||||
// make it an array, if it's a list of words
|
||||
if (shorthands[arg] && !Array.isArray(shorthands[arg]))
|
||||
shorthands[arg] = shorthands[arg].split(/\s+/)
|
||||
|
||||
return shorthands[arg]
|
||||
}
|
||||
|
||||
// first check to see if this arg is a set of single-char shorthands
|
||||
var singles = shorthands.___singles
|
||||
if (!singles) {
|
||||
singles = Object.keys(shorthands).filter(function (s) {
|
||||
return s.length === 1
|
||||
}).reduce(function (l,r) {
|
||||
l[r] = true
|
||||
return l
|
||||
}, {})
|
||||
shorthands.___singles = singles
|
||||
debug('shorthand singles', singles)
|
||||
}
|
||||
|
||||
var chrs = arg.split("").filter(function (c) {
|
||||
return singles[c]
|
||||
})
|
||||
|
||||
if (chrs.join("") === arg) return chrs.map(function (c) {
|
||||
return shorthands[c]
|
||||
}).reduce(function (l, r) {
|
||||
return l.concat(r)
|
||||
}, [])
|
||||
|
||||
|
||||
// if it's an arg abbrev, and not a literal shorthand, then prefer the arg
|
||||
if (abbrevs[arg] && !shorthands[arg])
|
||||
return null
|
||||
|
||||
// if it's an abbr for a shorthand, then use that
|
||||
if (shortAbbr[arg])
|
||||
arg = shortAbbr[arg]
|
||||
|
||||
// make it an array, if it's a list of words
|
||||
if (shorthands[arg] && !Array.isArray(shorthands[arg]))
|
||||
shorthands[arg] = shorthands[arg].split(/\s+/)
|
||||
|
||||
return shorthands[arg]
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
Copyright 2009, 2010, 2011 Isaac Z. Schlueter.
|
||||
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.
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
|
||||
module.exports = exports = abbrev.abbrev = abbrev
|
||||
|
||||
abbrev.monkeyPatch = monkeyPatch
|
||||
|
||||
function monkeyPatch () {
|
||||
Object.defineProperty(Array.prototype, 'abbrev', {
|
||||
value: function () { return abbrev(this) },
|
||||
enumerable: false, configurable: true, writable: true
|
||||
})
|
||||
|
||||
Object.defineProperty(Object.prototype, 'abbrev', {
|
||||
value: function () { return abbrev(Object.keys(this)) },
|
||||
enumerable: false, configurable: true, writable: true
|
||||
})
|
||||
}
|
||||
|
||||
function abbrev (list) {
|
||||
if (arguments.length !== 1 || !Array.isArray(list)) {
|
||||
list = Array.prototype.slice.call(arguments, 0)
|
||||
}
|
||||
for (var i = 0, l = list.length, args = [] ; i < l ; i ++) {
|
||||
args[i] = typeof list[i] === "string" ? list[i] : String(list[i])
|
||||
}
|
||||
|
||||
// sort them lexicographically, so that they're next to their nearest kin
|
||||
args = args.sort(lexSort)
|
||||
|
||||
// walk through each, seeing how much it has in common with the next and previous
|
||||
var abbrevs = {}
|
||||
, prev = ""
|
||||
for (var i = 0, l = args.length ; i < l ; i ++) {
|
||||
var current = args[i]
|
||||
, next = args[i + 1] || ""
|
||||
, nextMatches = true
|
||||
, prevMatches = true
|
||||
if (current === next) continue
|
||||
for (var j = 0, cl = current.length ; j < cl ; j ++) {
|
||||
var curChar = current.charAt(j)
|
||||
nextMatches = nextMatches && curChar === next.charAt(j)
|
||||
prevMatches = prevMatches && curChar === prev.charAt(j)
|
||||
if (!nextMatches && !prevMatches) {
|
||||
j ++
|
||||
break
|
||||
}
|
||||
}
|
||||
prev = current
|
||||
if (j === cl) {
|
||||
abbrevs[current] = current
|
||||
continue
|
||||
}
|
||||
for (var a = current.substr(0, j) ; j <= cl ; j ++) {
|
||||
abbrevs[a] = current
|
||||
a += current.charAt(j)
|
||||
}
|
||||
}
|
||||
return abbrevs
|
||||
}
|
||||
|
||||
function lexSort (a, b) {
|
||||
return a === b ? 0 : a > b ? 1 : -1
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "abbrev",
|
||||
"version": "1.0.5",
|
||||
"description": "Like ruby's abbrev module, but in js",
|
||||
"author": {
|
||||
"name": "Isaac Z. Schlueter",
|
||||
"email": "i@izs.me"
|
||||
},
|
||||
"main": "abbrev.js",
|
||||
"scripts": {
|
||||
"test": "node test.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/isaacs/abbrev-js"
|
||||
},
|
||||
"license": {
|
||||
"type": "MIT",
|
||||
"url": "https://github.com/isaacs/abbrev-js/raw/master/LICENSE"
|
||||
},
|
||||
"readme": "# abbrev-js\n\nJust like [ruby's Abbrev](http://apidock.com/ruby/Abbrev).\n\nUsage:\n\n var abbrev = require(\"abbrev\");\n abbrev(\"foo\", \"fool\", \"folding\", \"flop\");\n \n // returns:\n { fl: 'flop'\n , flo: 'flop'\n , flop: 'flop'\n , fol: 'folding'\n , fold: 'folding'\n , foldi: 'folding'\n , foldin: 'folding'\n , folding: 'folding'\n , foo: 'foo'\n , fool: 'fool'\n }\n\nThis is handy for command-line scripts, or other cases where you want to be able to accept shorthands.\n",
|
||||
"readmeFilename": "README.md",
|
||||
"bugs": {
|
||||
"url": "https://github.com/isaacs/abbrev-js/issues"
|
||||
},
|
||||
"homepage": "https://github.com/isaacs/abbrev-js",
|
||||
"_id": "abbrev@1.0.5",
|
||||
"_shasum": "5d8257bd9ebe435e698b2fa431afde4fe7b10b03",
|
||||
"_from": "abbrev@1",
|
||||
"_resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.5.tgz"
|
||||
}
|
||||
+41
File diff suppressed because one or more lines are too long
Vendored
+287
-94
@@ -31,9 +31,12 @@ var shell = require('shelljs'),
|
||||
var check_reqs = require('./check_reqs');
|
||||
var exec = require('./exec');
|
||||
|
||||
var LOCAL_PROPERTIES_TEMPLATE =
|
||||
|
||||
var SIGNING_PROPERTIES = '-signing.properties';
|
||||
var MARKER = 'YOUR CHANGES WILL BE ERASED!';
|
||||
var TEMPLATE =
|
||||
'# This file is automatically generated.\n' +
|
||||
'# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n';
|
||||
'# Do not modify this file -- ' + MARKER + '\n';
|
||||
|
||||
function findApks(directory) {
|
||||
var ret = [];
|
||||
@@ -56,6 +59,14 @@ function sortFilesByDate(files) {
|
||||
}).map(function(p) { return p.p; });
|
||||
}
|
||||
|
||||
function isAutoGenerated(file) {
|
||||
if(fs.existsSync(file)) {
|
||||
var fileContents = fs.readFileSync(file, 'utf8');
|
||||
return fileContents.indexOf(MARKER) > 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function findOutputApksHelper(dir, build_type, arch) {
|
||||
var ret = findApks(dir).filter(function(candidate) {
|
||||
// Need to choose between release and debug .apk.
|
||||
@@ -71,7 +82,7 @@ function findOutputApksHelper(dir, build_type, arch) {
|
||||
if (ret.length === 0) {
|
||||
return ret;
|
||||
}
|
||||
// Assume arch-specific build if newest api has -x86 or -arm.
|
||||
// Assume arch-specific build if newest apk has -x86 or -arm.
|
||||
var archSpecific = !!/-x86|-arm/.exec(ret[0]);
|
||||
// And show only arch-specific ones (or non-arch-specific)
|
||||
ret = ret.filter(function(p) {
|
||||
@@ -79,11 +90,12 @@ function findOutputApksHelper(dir, build_type, arch) {
|
||||
return !!/-x86|-arm/.exec(p) == archSpecific;
|
||||
/*jshint +W018 */
|
||||
});
|
||||
if (arch && ret.length > 1) {
|
||||
if (archSpecific && ret.length > 1) {
|
||||
ret = ret.filter(function(p) {
|
||||
return p.indexOf('-' + arch) != -1;
|
||||
});
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -91,6 +103,19 @@ function hasCustomRules() {
|
||||
return fs.existsSync(path.join(ROOT, 'custom_rules.xml'));
|
||||
}
|
||||
|
||||
function extractRealProjectNameFromManifest(projectPath) {
|
||||
var manifestPath = path.join(projectPath, 'AndroidManifest.xml');
|
||||
var manifestData = fs.readFileSync(manifestPath, 'utf8');
|
||||
var m = /<manifest[\s\S]*?package\s*=\s*"(.*?)"/i.exec(manifestData);
|
||||
if (!m) {
|
||||
throw new Error('Could not find package name in ' + manifestPath);
|
||||
}
|
||||
|
||||
var packageName=m[1];
|
||||
var lastDotIndex = packageName.lastIndexOf('.');
|
||||
return packageName.substring(lastDotIndex + 1);
|
||||
}
|
||||
|
||||
function extractProjectNameFromManifest(projectPath) {
|
||||
var manifestPath = path.join(projectPath, 'AndroidManifest.xml');
|
||||
var manifestData = fs.readFileSync(manifestPath, 'utf8');
|
||||
@@ -121,16 +146,19 @@ function readProjectProperties() {
|
||||
|
||||
var builders = {
|
||||
ant: {
|
||||
getArgs: function(cmd) {
|
||||
getArgs: function(cmd, opts) {
|
||||
var args = [cmd, '-f', path.join(ROOT, 'build.xml')];
|
||||
// custom_rules.xml is required for incremental builds.
|
||||
if (hasCustomRules()) {
|
||||
args.push('-Dout.dir=ant-build', '-Dgen.absolute.dir=ant-gen');
|
||||
}
|
||||
if(opts.packageInfo) {
|
||||
args.push('-propertyfile=' + path.join(ROOT, opts.buildType + SIGNING_PROPERTIES));
|
||||
}
|
||||
return args;
|
||||
},
|
||||
|
||||
prepEnv: function() {
|
||||
prepEnv: function(opts) {
|
||||
return check_reqs.check_ant()
|
||||
.then(function() {
|
||||
// Copy in build.xml on each build so that:
|
||||
@@ -142,7 +170,7 @@ var builders = {
|
||||
var newData = buildTemplate.replace('PROJECT_NAME', extractProjectNameFromManifest(ROOT));
|
||||
fs.writeFileSync(path.join(projectPath, 'build.xml'), newData);
|
||||
if (!fs.existsSync(path.join(projectPath, 'local.properties'))) {
|
||||
fs.writeFileSync(path.join(projectPath, 'local.properties'), LOCAL_PROPERTIES_TEMPLATE);
|
||||
fs.writeFileSync(path.join(projectPath, 'local.properties'), TEMPLATE);
|
||||
}
|
||||
}
|
||||
writeBuildXml(ROOT);
|
||||
@@ -154,6 +182,14 @@ var builders = {
|
||||
if (propertiesObj.systemLibs.length > 0) {
|
||||
throw new Error('Project contains at least one plugin that requires a system library. This is not supported with ANT. Please build using gradle.');
|
||||
}
|
||||
|
||||
var propertiesFile = opts.buildType + SIGNING_PROPERTIES;
|
||||
var propertiesFilePath = path.join(ROOT, propertiesFile);
|
||||
if (opts.packageInfo) {
|
||||
fs.writeFileSync(propertiesFilePath, TEMPLATE + opts.packageInfo.toProperties());
|
||||
} else if(isAutoGenerated(propertiesFilePath)) {
|
||||
shell.rm('-f', propertiesFilePath);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -161,23 +197,24 @@ var builders = {
|
||||
* Builds the project with ant.
|
||||
* Returns a promise.
|
||||
*/
|
||||
build: function(build_type) {
|
||||
build: function(opts) {
|
||||
// Without our custom_rules.xml, we need to clean before building.
|
||||
var ret = Q();
|
||||
if (!hasCustomRules()) {
|
||||
// clean will call check_ant() for us.
|
||||
ret = this.clean();
|
||||
ret = this.clean(opts);
|
||||
}
|
||||
|
||||
var args = this.getArgs(build_type == 'debug' ? 'debug' : 'release');
|
||||
var args = this.getArgs(opts.buildType == 'debug' ? 'debug' : 'release', opts);
|
||||
return check_reqs.check_ant()
|
||||
.then(function() {
|
||||
console.log('Executing: ant ' + args.join(' '));
|
||||
return spawn('ant', args);
|
||||
});
|
||||
},
|
||||
|
||||
clean: function() {
|
||||
var args = this.getArgs('clean');
|
||||
clean: function(opts) {
|
||||
var args = this.getArgs('clean', opts);
|
||||
return check_reqs.check_ant()
|
||||
.then(function() {
|
||||
return spawn('ant', args);
|
||||
@@ -190,28 +227,100 @@ var builders = {
|
||||
}
|
||||
},
|
||||
gradle: {
|
||||
getArgs: function(cmd, arch, extraArgs) {
|
||||
getArgs: function(cmd, opts) {
|
||||
if (cmd == 'release') {
|
||||
cmd = 'cdvBuildRelease';
|
||||
} else if (cmd == 'debug') {
|
||||
cmd = 'cdvBuildDebug';
|
||||
}
|
||||
var args = [cmd, '-b', path.join(ROOT, 'build.gradle')];
|
||||
if (arch) {
|
||||
args.push('-PcdvBuildArch=' + arch);
|
||||
if (opts.arch) {
|
||||
args.push('-PcdvBuildArch=' + opts.arch);
|
||||
}
|
||||
|
||||
// 10 seconds -> 6 seconds
|
||||
args.push('-Dorg.gradle.daemon=true');
|
||||
args.push.apply(args, extraArgs);
|
||||
args.push.apply(args, opts.extraArgs);
|
||||
// Shaves another 100ms, but produces a "try at own risk" warning. Not worth it (yet):
|
||||
// args.push('-Dorg.gradle.parallel=true');
|
||||
return args;
|
||||
},
|
||||
|
||||
prepEnv: function() {
|
||||
// Makes the project buildable, minus the gradle wrapper.
|
||||
prepBuildFiles: function() {
|
||||
var projectPath = ROOT;
|
||||
// Update the version of build.gradle in each dependent library.
|
||||
var pluginBuildGradle = path.join(projectPath, 'cordova', 'lib', 'plugin-build.gradle');
|
||||
var propertiesObj = readProjectProperties();
|
||||
var subProjects = propertiesObj.libs;
|
||||
for (var i = 0; i < subProjects.length; ++i) {
|
||||
if (subProjects[i] !== 'CordovaLib') {
|
||||
shell.cp('-f', pluginBuildGradle, path.join(ROOT, subProjects[i], 'build.gradle'));
|
||||
}
|
||||
}
|
||||
|
||||
var name = extractRealProjectNameFromManifest(ROOT);
|
||||
//Remove the proj.id/name- prefix from projects: https://issues.apache.org/jira/browse/CB-9149
|
||||
var settingsGradlePaths = subProjects.map(function(p){
|
||||
var realDir=p.replace(/[/\\]/g, ':');
|
||||
var libName=realDir.replace(name+'-','');
|
||||
var str='include ":'+libName+'"\n';
|
||||
if(realDir.indexOf(name+'-')!==-1)
|
||||
str+='project(":'+libName+'").projectDir = new File("'+p+'")\n';
|
||||
return str;
|
||||
});
|
||||
|
||||
// Write the settings.gradle file.
|
||||
fs.writeFileSync(path.join(projectPath, 'settings.gradle'),
|
||||
'// GENERATED FILE - DO NOT EDIT\n' +
|
||||
'include ":"\n' + settingsGradlePaths.join(''));
|
||||
// Update dependencies within build.gradle.
|
||||
var buildGradle = fs.readFileSync(path.join(projectPath, 'build.gradle'), 'utf8');
|
||||
var depsList = '';
|
||||
subProjects.forEach(function(p) {
|
||||
var libName=p.replace(/[/\\]/g, ':').replace(name+'-','');
|
||||
depsList += ' debugCompile project(path: "' + libName + '", configuration: "debug")\n';
|
||||
depsList += ' releaseCompile project(path: "' + libName + '", configuration: "release")\n';
|
||||
});
|
||||
// For why we do this mapping: https://issues.apache.org/jira/browse/CB-8390
|
||||
var SYSTEM_LIBRARY_MAPPINGS = [
|
||||
[/^\/?extras\/android\/support\/(.*)$/, 'com.android.support:support-$1:+'],
|
||||
[/^\/?google\/google_play_services\/libproject\/google-play-services_lib\/?$/, 'com.google.android.gms:play-services:+']
|
||||
];
|
||||
propertiesObj.systemLibs.forEach(function(p) {
|
||||
var mavenRef;
|
||||
// It's already in gradle form if it has two ':'s
|
||||
if (/:.*:/.exec(p)) {
|
||||
mavenRef = p;
|
||||
} else {
|
||||
for (var i = 0; i < SYSTEM_LIBRARY_MAPPINGS.length; ++i) {
|
||||
var pair = SYSTEM_LIBRARY_MAPPINGS[i];
|
||||
if (pair[0].exec(p)) {
|
||||
mavenRef = p.replace(pair[0], pair[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!mavenRef) {
|
||||
throw new Error('Unsupported system library (does not work with gradle): ' + p);
|
||||
}
|
||||
}
|
||||
depsList += ' compile "' + mavenRef + '"\n';
|
||||
});
|
||||
buildGradle = buildGradle.replace(/(SUB-PROJECT DEPENDENCIES START)[\s\S]*(\/\/ SUB-PROJECT DEPENDENCIES END)/, '$1\n' + depsList + ' $2');
|
||||
var includeList = '';
|
||||
propertiesObj.gradleIncludes.forEach(function(includePath) {
|
||||
includeList += 'apply from: "' + includePath + '"\n';
|
||||
});
|
||||
buildGradle = buildGradle.replace(/(PLUGIN GRADLE EXTENSIONS START)[\s\S]*(\/\/ PLUGIN GRADLE EXTENSIONS END)/, '$1\n' + includeList + '$2');
|
||||
fs.writeFileSync(path.join(projectPath, 'build.gradle'), buildGradle);
|
||||
},
|
||||
|
||||
prepEnv: function(opts) {
|
||||
var self = this;
|
||||
return check_reqs.check_gradle()
|
||||
.then(function() {
|
||||
return self.prepBuildFiles();
|
||||
}).then(function() {
|
||||
// Copy the gradle wrapper on each build so that:
|
||||
// A) we don't require the Android SDK at project creation time, and
|
||||
// B) we always use the SDK's latest version of it.
|
||||
@@ -220,9 +329,11 @@ var builders = {
|
||||
var sdkDir = process.env['ANDROID_HOME'];
|
||||
var wrapperDir = path.join(sdkDir, 'tools', 'templates', 'gradle', 'wrapper');
|
||||
if (process.platform == 'win32') {
|
||||
shell.cp('-f', path.join(wrapperDir, 'gradlew.bat'), projectPath);
|
||||
shell.rm('-f', path.join(projectPath, 'gradlew.bat'));
|
||||
shell.cp(path.join(wrapperDir, 'gradlew.bat'), projectPath);
|
||||
} else {
|
||||
shell.cp('-f', path.join(wrapperDir, 'gradlew'), projectPath);
|
||||
shell.rm('-f', path.join(projectPath, 'gradlew'));
|
||||
shell.cp(path.join(wrapperDir, 'gradlew'), projectPath);
|
||||
}
|
||||
shell.rm('-rf', path.join(projectPath, 'gradle', 'wrapper'));
|
||||
shell.mkdir('-p', path.join(projectPath, 'gradle'));
|
||||
@@ -234,62 +345,16 @@ var builders = {
|
||||
var distributionUrlRegex = /distributionUrl.*zip/;
|
||||
var distributionUrl = 'distributionUrl=http\\://services.gradle.org/distributions/gradle-2.2.1-all.zip';
|
||||
var gradleWrapperPropertiesPath = path.join(projectPath, 'gradle', 'wrapper', 'gradle-wrapper.properties');
|
||||
shell.chmod('u+w', gradleWrapperPropertiesPath);
|
||||
shell.sed('-i', distributionUrlRegex, distributionUrl, gradleWrapperPropertiesPath);
|
||||
|
||||
// Update the version of build.gradle in each dependent library.
|
||||
var pluginBuildGradle = path.join(projectPath, 'cordova', 'lib', 'plugin-build.gradle');
|
||||
var propertiesObj = readProjectProperties();
|
||||
var subProjects = propertiesObj.libs;
|
||||
for (var i = 0; i < subProjects.length; ++i) {
|
||||
if (subProjects[i] !== 'CordovaLib') {
|
||||
shell.cp('-f', pluginBuildGradle, path.join(ROOT, subProjects[i], 'build.gradle'));
|
||||
}
|
||||
var propertiesFile = opts.buildType + SIGNING_PROPERTIES;
|
||||
var propertiesFilePath = path.join(ROOT, propertiesFile);
|
||||
if (opts.packageInfo) {
|
||||
fs.writeFileSync(propertiesFilePath, TEMPLATE + opts.packageInfo.toProperties());
|
||||
} else if (isAutoGenerated(propertiesFilePath)) {
|
||||
shell.rm('-f', propertiesFilePath);
|
||||
}
|
||||
|
||||
var subProjectsAsGradlePaths = subProjects.map(function(p) { return ':' + p.replace(/[/\\]/g, ':'); });
|
||||
// Write the settings.gradle file.
|
||||
fs.writeFileSync(path.join(projectPath, 'settings.gradle'),
|
||||
'// GENERATED FILE - DO NOT EDIT\n' +
|
||||
'include ":"\n' +
|
||||
'include "' + subProjectsAsGradlePaths.join('"\ninclude "') + '"\n');
|
||||
// Update dependencies within build.gradle.
|
||||
var buildGradle = fs.readFileSync(path.join(projectPath, 'build.gradle'), 'utf8');
|
||||
var depsList = '';
|
||||
subProjectsAsGradlePaths.forEach(function(p) {
|
||||
depsList += ' debugCompile project(path: "' + p + '", configuration: "debug")\n';
|
||||
depsList += ' releaseCompile project(path: "' + p + '", configuration: "release")\n';
|
||||
});
|
||||
// For why we do this mapping: https://issues.apache.org/jira/browse/CB-8390
|
||||
var SYSTEM_LIBRARY_MAPPINGS = [
|
||||
[/^\/?extras\/android\/support\/(.*)$/, 'com.android.support:support-$1:+'],
|
||||
[/^\/?google\/google_play_services\/libproject\/google-play-services_lib\/?$/, 'com.google.android.gms:play-services:+']
|
||||
];
|
||||
propertiesObj.systemLibs.forEach(function(p) {
|
||||
var mavenRef;
|
||||
// It's already in gradle form if it has two ':'s
|
||||
if (/:.*:/.exec(p)) {
|
||||
mavenRef = p;
|
||||
} else {
|
||||
for (var i = 0; i < SYSTEM_LIBRARY_MAPPINGS.length; ++i) {
|
||||
var pair = SYSTEM_LIBRARY_MAPPINGS[i];
|
||||
if (pair[0].exec(p)) {
|
||||
mavenRef = p.replace(pair[0], pair[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!mavenRef) {
|
||||
throw new Error('Unsupported system library (does not work with gradle): ' + p);
|
||||
}
|
||||
}
|
||||
depsList += ' compile "' + mavenRef + '"\n';
|
||||
});
|
||||
buildGradle = buildGradle.replace(/(SUB-PROJECT DEPENDENCIES START)[\s\S]*(\/\/ SUB-PROJECT DEPENDENCIES END)/, '$1\n' + depsList + ' $2');
|
||||
var includeList = '';
|
||||
propertiesObj.gradleIncludes.forEach(function(includePath) {
|
||||
includeList += 'apply from: "' + includePath + '"\n';
|
||||
});
|
||||
buildGradle = buildGradle.replace(/(PLUGIN GRADLE EXTENSIONS START)[\s\S]*(\/\/ PLUGIN GRADLE EXTENSIONS END)/, '$1\n' + includeList + '$2');
|
||||
fs.writeFileSync(path.join(projectPath, 'build.gradle'), buildGradle);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -297,21 +362,21 @@ var builders = {
|
||||
* Builds the project with gradle.
|
||||
* Returns a promise.
|
||||
*/
|
||||
build: function(build_type, arch, extraArgs) {
|
||||
build: function(opts) {
|
||||
var wrapper = path.join(ROOT, 'gradlew');
|
||||
var args = this.getArgs(build_type == 'debug' ? 'debug' : 'release', arch, extraArgs);
|
||||
var args = this.getArgs(opts.buildType == 'debug' ? 'debug' : 'release', opts);
|
||||
return Q().then(function() {
|
||||
console.log('Running: ' + wrapper + ' ' + args.concat(extraArgs).join(' '));
|
||||
console.log('Running: ' + wrapper + ' ' + args.join(' '));
|
||||
return spawn(wrapper, args);
|
||||
});
|
||||
},
|
||||
|
||||
clean: function(extraArgs) {
|
||||
clean: function(opts) {
|
||||
var builder = this;
|
||||
var wrapper = path.join(ROOT, 'gradlew');
|
||||
var args = builder.getArgs('clean', null, extraArgs);
|
||||
var args = builder.getArgs('clean', opts);
|
||||
return Q().then(function() {
|
||||
console.log('Running: ' + wrapper + ' ' + args.concat(extraArgs).join(' '));
|
||||
console.log('Running: ' + wrapper + ' ' + args.join(' '));
|
||||
return spawn(wrapper, args);
|
||||
});
|
||||
},
|
||||
@@ -339,6 +404,10 @@ var builders = {
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.isBuildFlag = function(flag) {
|
||||
return /^--(debug|release|ant|gradle|nobuild|versionCode=|minSdkVersion=|gradleArg=|keystore=|alias=|password=|storePassword=|keystoreType=|buildConfig=)/.exec(flag);
|
||||
};
|
||||
|
||||
function parseOpts(options, resolvedTarget) {
|
||||
// Backwards-compatibility: Allow a single string argument
|
||||
if (typeof options == 'string') options = [options];
|
||||
@@ -353,9 +422,16 @@ function parseOpts(options, resolvedTarget) {
|
||||
var multiValueArgs = {
|
||||
'versionCode': true,
|
||||
'minSdkVersion': true,
|
||||
'gradleArg': true
|
||||
'gradleArg': true,
|
||||
'keystore' : true,
|
||||
'alias' : true,
|
||||
'password' : true,
|
||||
'storePassword' : true,
|
||||
'keystoreType' : true,
|
||||
'buildConfig' : true
|
||||
};
|
||||
|
||||
var packageArgs = {};
|
||||
var buildConfig;
|
||||
// Iterate through command line options
|
||||
for (var i=0; options && (i < options.length); ++i) {
|
||||
if (/^--/.exec(options[i])) {
|
||||
@@ -380,6 +456,9 @@ function parseOpts(options, resolvedTarget) {
|
||||
// Don't need to do anything special to when building for device vs emulator.
|
||||
// iOS uses this flag to switch on architecture.
|
||||
break;
|
||||
case 'prepenv' :
|
||||
ret.prepEnv = true;
|
||||
break;
|
||||
case 'nobuild' :
|
||||
ret.buildMethod = 'none';
|
||||
break;
|
||||
@@ -392,6 +471,18 @@ function parseOpts(options, resolvedTarget) {
|
||||
case 'gradleArg':
|
||||
ret.extraArgs.push(flagValue);
|
||||
break;
|
||||
case 'keystore':
|
||||
packageArgs.keystore = path.relative(ROOT, path.resolve(flagValue));
|
||||
break;
|
||||
case 'alias':
|
||||
case 'storePassword':
|
||||
case 'password':
|
||||
case 'keystoreType':
|
||||
packageArgs[flagName] = flagValue;
|
||||
break;
|
||||
case 'buildConfig':
|
||||
buildConfig = flagValue;
|
||||
break;
|
||||
default :
|
||||
console.warn('Build option --\'' + flagName + '\' not recognized (ignoring).');
|
||||
}
|
||||
@@ -400,6 +491,39 @@ function parseOpts(options, resolvedTarget) {
|
||||
}
|
||||
}
|
||||
|
||||
// If some values are not specified as command line arguments - use build config to supplement them.
|
||||
// Command line arguemnts have precedence over build config.
|
||||
if (buildConfig) {
|
||||
if (!fs.existsSync(buildConfig)) {
|
||||
throw new Error('Specified build config file does not exist: ' + buildConfig);
|
||||
}
|
||||
console.log('Reading build config file: '+ path.resolve(buildConfig));
|
||||
var config = JSON.parse(fs.readFileSync(buildConfig, 'utf8'));
|
||||
if (config.android && config.android[ret.buildType]) {
|
||||
var androidInfo = config.android[ret.buildType];
|
||||
if(androidInfo.keystore && !packageArgs.keystore) {
|
||||
if(path.isAbsolute(androidInfo.keystore)) {
|
||||
packageArgs.keystore = androidInfo.keystore;
|
||||
} else {
|
||||
packageArgs.keystore = path.relative(ROOT, path.join(path.dirname(buildConfig), androidInfo.keystore));
|
||||
}
|
||||
}
|
||||
|
||||
['alias', 'storePassword', 'password','keystoreType'].forEach(function (key){
|
||||
packageArgs[key] = packageArgs[key] || androidInfo[key];
|
||||
});
|
||||
}
|
||||
}
|
||||
if (packageArgs.keystore && packageArgs.alias) {
|
||||
ret.packageInfo = new PackageInfo(packageArgs.keystore, packageArgs.alias, packageArgs.storePassword,
|
||||
packageArgs.password, packageArgs.keystoreType);
|
||||
}
|
||||
|
||||
if(!ret.packageInfo) {
|
||||
if(Object.keys(packageArgs).length > 0) {
|
||||
console.warn('\'keystore\' and \'alias\' need to be specified to generate a signed archive.');
|
||||
}
|
||||
}
|
||||
ret.arch = resolvedTarget && resolvedTarget.arch;
|
||||
|
||||
return ret;
|
||||
@@ -412,11 +536,18 @@ function parseOpts(options, resolvedTarget) {
|
||||
module.exports.runClean = function(options) {
|
||||
var opts = parseOpts(options);
|
||||
var builder = builders[opts.buildMethod];
|
||||
return builder.prepEnv()
|
||||
return builder.prepEnv(opts)
|
||||
.then(function() {
|
||||
return builder.clean(opts.extraArgs);
|
||||
return builder.clean(opts);
|
||||
}).then(function() {
|
||||
shell.rm('-rf', path.join(ROOT, 'out'));
|
||||
|
||||
['debug', 'release'].forEach(function(config) {
|
||||
var propertiesFilePath = path.join(ROOT, config + SIGNING_PROPERTIES);
|
||||
if(isAutoGenerated(propertiesFilePath)){
|
||||
shell.rm('-f', propertiesFilePath);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -427,21 +558,32 @@ module.exports.runClean = function(options) {
|
||||
module.exports.run = function(options, optResolvedTarget) {
|
||||
var opts = parseOpts(options, optResolvedTarget);
|
||||
var builder = builders[opts.buildMethod];
|
||||
return builder.prepEnv()
|
||||
return builder.prepEnv(opts)
|
||||
.then(function() {
|
||||
return builder.build(opts.buildType, opts.arch, opts.extraArgs);
|
||||
}).then(function() {
|
||||
var apkPaths = builder.findOutputApks(opts.buildType, opts.arch);
|
||||
console.log('Built the following apk(s):');
|
||||
console.log(' ' + apkPaths.join('\n '));
|
||||
return {
|
||||
apkPaths: apkPaths,
|
||||
buildType: opts.buildType,
|
||||
buildMethod: opts.buildMethod
|
||||
};
|
||||
if (opts.prepEnv) {
|
||||
console.log('Build file successfully prepared.');
|
||||
return;
|
||||
}
|
||||
return builder.build(opts)
|
||||
.then(function() {
|
||||
var apkPaths = builder.findOutputApks(opts.buildType, opts.arch);
|
||||
console.log('Built the following apk(s):');
|
||||
console.log(' ' + apkPaths.join('\n '));
|
||||
return {
|
||||
apkPaths: apkPaths,
|
||||
buildType: opts.buildType,
|
||||
buildMethod: opts.buildMethod
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Called by plugman after installing plugins, and by create script after creating project.
|
||||
module.exports.prepBuildFiles = function() {
|
||||
var builder = builders['gradle'];
|
||||
return builder.prepBuildFiles();
|
||||
};
|
||||
|
||||
/*
|
||||
* Detects the architecture of a device/emulator
|
||||
* Returns "arm" or "x86".
|
||||
@@ -509,16 +651,67 @@ module.exports.findBestApkForArchitecture = function(buildResults, arch) {
|
||||
throw new Error('Could not find apk architecture: ' + arch + ' build-type: ' + buildResults.buildType);
|
||||
};
|
||||
|
||||
function PackageInfo(keystore, alias, storePassword, password, keystoreType) {
|
||||
this.keystore = {
|
||||
'name': 'key.store',
|
||||
'value': keystore
|
||||
};
|
||||
this.alias = {
|
||||
'name': 'key.alias',
|
||||
'value': alias
|
||||
};
|
||||
if (storePassword) {
|
||||
this.storePassword = {
|
||||
'name': 'key.store.password',
|
||||
'value': storePassword
|
||||
};
|
||||
}
|
||||
if (password) {
|
||||
this.password = {
|
||||
'name': 'key.alias.password',
|
||||
'value': password
|
||||
};
|
||||
}
|
||||
if (keystoreType) {
|
||||
this.keystoreType = {
|
||||
'name': 'key.store.type',
|
||||
'value': keystoreType
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
PackageInfo.prototype = {
|
||||
toProperties: function() {
|
||||
var self = this;
|
||||
var result = '';
|
||||
Object.keys(self).forEach(function(key) {
|
||||
result += self[key].name;
|
||||
result += '=';
|
||||
result += self[key].value.replace(/\\/g, '\\\\');
|
||||
result += '\n';
|
||||
});
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.help = function() {
|
||||
console.log('Usage: ' + path.relative(process.cwd(), path.join(ROOT, 'cordova', 'build')) + ' [flags]');
|
||||
console.log('Usage: ' + path.relative(process.cwd(), path.join(ROOT, 'cordova', '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(' \'--ant\': will build project with ant');
|
||||
console.log(' \'--gradle\': will build project with gradle (default)');
|
||||
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. Requires --gradle.');
|
||||
console.log(' \'--minSdkVersion=#\': Override minSdkVersion for this build. Useful for uploading multiple APKs. Requires --gradle.');
|
||||
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('');
|
||||
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);
|
||||
};
|
||||
|
||||
+68
-30
@@ -21,14 +21,22 @@
|
||||
|
||||
/* jshint sub:true */
|
||||
|
||||
var exec = require('./exec'),
|
||||
Q = require('q'),
|
||||
os = require('os'),
|
||||
appinfo = require('./appinfo'),
|
||||
build = require('./build'),
|
||||
child_process = require('child_process');
|
||||
var exec = require('./exec');
|
||||
var appinfo = require('./appinfo');
|
||||
var retry = require('./retry');
|
||||
var build = require('./build');
|
||||
var check_reqs = require('./check_reqs');
|
||||
|
||||
var Q = require('q');
|
||||
var os = require('os');
|
||||
var child_process = require('child_process');
|
||||
|
||||
// constants
|
||||
var ONE_SECOND = 1000; // in milliseconds
|
||||
var INSTALL_COMMAND_TIMEOUT = 120 * ONE_SECOND; // in milliseconds
|
||||
var NUM_INSTALL_RETRIES = 3;
|
||||
var EXEC_KILL_SIGNAL = 'SIGKILL';
|
||||
|
||||
/**
|
||||
* Returns a Promise for a list of emulator images in the form of objects
|
||||
* {
|
||||
@@ -298,37 +306,67 @@ module.exports.resolveTarget = function(target) {
|
||||
* If no started emulators are found, error out.
|
||||
* Returns a promise.
|
||||
*/
|
||||
module.exports.install = function(target, buildResults) {
|
||||
return Q().then(function() {
|
||||
if (target && typeof target == 'object') {
|
||||
return target;
|
||||
module.exports.install = function(givenTarget, buildResults) {
|
||||
|
||||
var target;
|
||||
|
||||
// resolve the target emulator
|
||||
return Q().then(function () {
|
||||
if (givenTarget && typeof givenTarget == 'object') {
|
||||
return givenTarget;
|
||||
} else {
|
||||
return module.exports.resolveTarget(givenTarget);
|
||||
}
|
||||
return module.exports.resolveTarget(target);
|
||||
}).then(function(resolvedTarget) {
|
||||
var apk_path = build.findBestApkForArchitecture(buildResults, resolvedTarget.arch);
|
||||
|
||||
// set the resolved target
|
||||
}).then(function (resolvedTarget) {
|
||||
target = resolvedTarget;
|
||||
|
||||
// install the app
|
||||
}).then(function () {
|
||||
|
||||
var apk_path = build.findBestApkForArchitecture(buildResults, target.arch);
|
||||
var execOptions = {
|
||||
timeout: INSTALL_COMMAND_TIMEOUT, // in milliseconds
|
||||
killSignal: EXEC_KILL_SIGNAL
|
||||
};
|
||||
|
||||
console.log('Installing app on emulator...');
|
||||
console.log('Using apk: ' + apk_path);
|
||||
return exec('adb -s ' + resolvedTarget.target + ' install -r "' + apk_path + '"', os.tmpdir())
|
||||
.then(function(output) {
|
||||
|
||||
var retriedInstall = retry.retryPromise(
|
||||
NUM_INSTALL_RETRIES,
|
||||
exec, 'adb -s ' + target.target + ' install -r -d "' + apk_path + '"', os.tmpdir(), execOptions
|
||||
);
|
||||
|
||||
return retriedInstall.then(function (output) {
|
||||
if (output.match(/Failure/)) {
|
||||
return Q.reject('Failed to install apk to emulator: ' + output);
|
||||
} else {
|
||||
console.log('INSTALL SUCCESS');
|
||||
}
|
||||
return Q();
|
||||
}, function(err) {
|
||||
}, function (err) {
|
||||
return Q.reject('Failed to install apk to emulator: ' + err);
|
||||
}).then(function() {
|
||||
//unlock screen
|
||||
return exec('adb -s ' + resolvedTarget.target + ' shell input keyevent 82', os.tmpdir());
|
||||
}).then(function() {
|
||||
// launch the application
|
||||
console.log('Launching application...');
|
||||
var launchName = appinfo.getActivityName();
|
||||
var cmd = 'adb -s ' + resolvedTarget.target + ' shell am start -W -a android.intent.action.MAIN -n ' + launchName;
|
||||
return exec(cmd, os.tmpdir());
|
||||
}).then(function(output) {
|
||||
console.log('LAUNCH SUCCESS');
|
||||
}, function(err) {
|
||||
return Q.reject('Failed to launch app on emulator: ' + err);
|
||||
});
|
||||
|
||||
// unlock screen
|
||||
}).then(function () {
|
||||
|
||||
console.log('Unlocking screen...');
|
||||
return exec('adb -s ' + target.target + ' shell input keyevent 82', os.tmpdir());
|
||||
|
||||
// launch the application
|
||||
}).then(function () {
|
||||
|
||||
console.log('Launching application...');
|
||||
var launchName = appinfo.getActivityName();
|
||||
var cmd = 'adb -s ' + target.target + ' shell am start -W -a android.intent.action.MAIN -n ' + launchName;
|
||||
return exec(cmd, os.tmpdir());
|
||||
|
||||
// report success or failure
|
||||
}).then(function (output) {
|
||||
console.log('LAUNCH SUCCESS');
|
||||
}, function (err) {
|
||||
return Q.reject('Failed to launch app on emulator: ' + err);
|
||||
});
|
||||
};
|
||||
|
||||
Vendored
+33
-6
@@ -19,23 +19,50 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
var child_process = require('child_process'),
|
||||
Q = require('q');
|
||||
var child_process = require("child_process");
|
||||
var Q = require("q");
|
||||
|
||||
// constants
|
||||
var DEFAULT_MAX_BUFFER = 1024000;
|
||||
|
||||
// Takes a command and optional current working directory.
|
||||
// Returns a promise that either resolves with the stdout, or
|
||||
// rejects with an error message and the stderr.
|
||||
module.exports = function(cmd, opt_cwd) {
|
||||
//
|
||||
// WARNING:
|
||||
// opt_cwd is an artifact of an old design, and must
|
||||
// be removed in the future; the correct solution is
|
||||
// to pass the options object the same way that
|
||||
// child_process.exec expects
|
||||
//
|
||||
// NOTE:
|
||||
// exec documented here - https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback
|
||||
module.exports = function(cmd, opt_cwd, options) {
|
||||
|
||||
var d = Q.defer();
|
||||
|
||||
if (typeof options === "undefined") {
|
||||
options = {};
|
||||
}
|
||||
|
||||
// override cwd to preserve old opt_cwd behavior
|
||||
options.cwd = opt_cwd;
|
||||
|
||||
// set maxBuffer
|
||||
if (typeof options.maxBuffer === "undefined") {
|
||||
options.maxBuffer = DEFAULT_MAX_BUFFER;
|
||||
}
|
||||
|
||||
try {
|
||||
child_process.exec(cmd, {cwd: opt_cwd, maxBuffer: 1024000}, function(err, stdout, stderr) {
|
||||
if (err) d.reject('Error executing "' + cmd + '": ' + stderr);
|
||||
child_process.exec(cmd, options, function(err, stdout, stderr) {
|
||||
if (err) d.reject("Error executing \"" + cmd + "\": " + stderr);
|
||||
else d.resolve(stdout);
|
||||
});
|
||||
} catch(e) {
|
||||
console.error('error caught: ' + e);
|
||||
console.error("error caught: " + e);
|
||||
d.reject(e);
|
||||
}
|
||||
|
||||
return d.promise;
|
||||
};
|
||||
|
||||
|
||||
Vendored
+66
@@ -0,0 +1,66 @@
|
||||
#!/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.
|
||||
*/
|
||||
|
||||
/* jshint node: true */
|
||||
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
* Retry a promise-returning function a number of times, propagating its
|
||||
* results on success or throwing its error on a failed final attempt.
|
||||
*
|
||||
* @arg {Number} attemts_left - The number of times to retry the passed call.
|
||||
* @arg {Function} promiseFunction - A function that returns a promise.
|
||||
* @arg {...} - Arguments to pass to promiseFunction.
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
module.exports.retryPromise = function (attemts_left, promiseFunction) {
|
||||
|
||||
// NOTE:
|
||||
// get all trailing arguments, by skipping the first two (attemts_left 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) {
|
||||
|
||||
attemts_left -= 1;
|
||||
|
||||
if (attemts_left < 1) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
console.log("A retried call failed. Retrying " + attemts_left + " more time(s).");
|
||||
|
||||
// retry call self again with the same arguments, except attemts_left is now lower
|
||||
var fullArguments = [attemts_left, promiseFunction].concat(promiseFunctionArguments);
|
||||
return module.exports.retryPromise.apply(undefined, fullArguments);
|
||||
}
|
||||
);
|
||||
};
|
||||
Vendored
+1
-1
@@ -41,7 +41,7 @@ var path = require('path'),
|
||||
var list = false;
|
||||
|
||||
for (var i=2; i<args.length; i++) {
|
||||
if (/^--(debug|release|ant|gradle|nobuild|versionCode=|minSdkVersion=|gradleArg=)/.exec(args[i])) {
|
||||
if (build.isBuildFlag(args[i])) {
|
||||
buildFlags.push(args[i]);
|
||||
} else if (args[i] == '--device') {
|
||||
install_target = '--device';
|
||||
|
||||
@@ -20,6 +20,6 @@
|
||||
*/
|
||||
|
||||
// Coho updates this line:
|
||||
var VERSION = "4.0.0-dev";
|
||||
var VERSION = "4.1.1";
|
||||
|
||||
console.log(VERSION);
|
||||
|
||||
@@ -47,5 +47,5 @@
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
<uses-sdk android:minSdkVersion="10" android:targetSdkVersion="__APILEVEL__"/>
|
||||
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="__APILEVEL__"/>
|
||||
</manifest>
|
||||
|
||||
+42
-38
@@ -1,5 +1,5 @@
|
||||
// Platform: android
|
||||
// fc4db9145934bd0053161cbf9ffc0caf83b770c6
|
||||
// 2c29e187e4206a6a77fba940ef6f77aef5c7eb8c
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
@@ -19,7 +19,7 @@
|
||||
under the License.
|
||||
*/
|
||||
;(function() {
|
||||
var PLATFORM_VERSION_BUILD_LABEL = '4.0.0-dev';
|
||||
var PLATFORM_VERSION_BUILD_LABEL = '4.1.1';
|
||||
// file: src/scripts/require.js
|
||||
|
||||
/*jshint -W079 */
|
||||
@@ -101,10 +101,15 @@ if (typeof module === "object" && typeof require === "function") {
|
||||
// file: src/cordova.js
|
||||
define("cordova", function(require, exports, module) {
|
||||
|
||||
if(window.cordova){
|
||||
throw new Error("cordova already defined");
|
||||
}
|
||||
|
||||
|
||||
var channel = require('cordova/channel');
|
||||
var platform = require('cordova/platform');
|
||||
|
||||
|
||||
/**
|
||||
* Intercept calls to addEventListener + removeEventListener and handle deviceready,
|
||||
* resume, and pause events.
|
||||
@@ -323,7 +328,7 @@ module.exports = cordova;
|
||||
|
||||
});
|
||||
|
||||
// file: src/android/android/nativeapiprovider.js
|
||||
// file: /Users/steveng/repo/cordova/cordova-android/cordova-js-src/android/nativeapiprovider.js
|
||||
define("cordova/android/nativeapiprovider", function(require, exports, module) {
|
||||
|
||||
/**
|
||||
@@ -346,7 +351,7 @@ module.exports = {
|
||||
|
||||
});
|
||||
|
||||
// file: src/android/android/promptbasednativeapi.js
|
||||
// file: /Users/steveng/repo/cordova/cordova-android/cordova-js-src/android/promptbasednativeapi.js
|
||||
define("cordova/android/promptbasednativeapi", function(require, exports, module) {
|
||||
|
||||
/**
|
||||
@@ -371,7 +376,6 @@ module.exports = {
|
||||
// file: src/common/argscheck.js
|
||||
define("cordova/argscheck", function(require, exports, module) {
|
||||
|
||||
var exec = require('cordova/exec');
|
||||
var utils = require('cordova/utils');
|
||||
|
||||
var moduleExports = module.exports;
|
||||
@@ -856,7 +860,7 @@ module.exports = channel;
|
||||
|
||||
});
|
||||
|
||||
// file: src/android/exec.js
|
||||
// file: /Users/steveng/repo/cordova/cordova-android/cordova-js-src/exec.js
|
||||
define("cordova/exec", function(require, exports, module) {
|
||||
|
||||
/**
|
||||
@@ -891,11 +895,7 @@ var cordova = require('cordova'),
|
||||
// For the ONLINE_EVENT to be viable, it would need to intercept all event
|
||||
// listeners (both through addEventListener and window.ononline) as well
|
||||
// as set the navigator property itself.
|
||||
ONLINE_EVENT: 2,
|
||||
// Uses reflection to access private APIs of the WebView that can send JS
|
||||
// to be executed.
|
||||
// Requires Android 3.2.4 or above.
|
||||
PRIVATE_API: 3
|
||||
ONLINE_EVENT: 2
|
||||
},
|
||||
jsToNativeBridgeMode, // Set lazily.
|
||||
nativeToJsBridgeMode = nativeToJsModes.ONLINE_EVENT,
|
||||
@@ -1229,6 +1229,7 @@ if (!window.console.warn) {
|
||||
// Register pause, resume and deviceready channels as events on document.
|
||||
channel.onPause = cordova.addDocumentEventHandler('pause');
|
||||
channel.onResume = cordova.addDocumentEventHandler('resume');
|
||||
channel.onActivated = cordova.addDocumentEventHandler('activated');
|
||||
channel.onDeviceReady = cordova.addStickyDocumentEventHandler('deviceready');
|
||||
|
||||
// Listen for DOMContentLoaded and notify our channel subscribers.
|
||||
@@ -1356,6 +1357,7 @@ if (!window.console.warn) {
|
||||
// Register pause, resume and deviceready channels as events on document.
|
||||
channel.onPause = cordova.addDocumentEventHandler('pause');
|
||||
channel.onResume = cordova.addDocumentEventHandler('resume');
|
||||
channel.onActivated = cordova.addDocumentEventHandler('activated');
|
||||
channel.onDeviceReady = cordova.addStickyDocumentEventHandler('deviceready');
|
||||
|
||||
// Listen for DOMContentLoaded and notify our channel subscribers.
|
||||
@@ -1499,7 +1501,7 @@ exports.reset();
|
||||
|
||||
});
|
||||
|
||||
// file: src/android/platform.js
|
||||
// file: /Users/steveng/repo/cordova/cordova-android/cordova-js-src/platform.js
|
||||
define("cordova/platform", function(require, exports, module) {
|
||||
|
||||
module.exports = {
|
||||
@@ -1575,7 +1577,7 @@ function onMessageFromNative(msg) {
|
||||
|
||||
});
|
||||
|
||||
// file: src/android/plugin/android/app.js
|
||||
// file: /Users/steveng/repo/cordova/cordova-android/cordova-js-src/plugin/android/app.js
|
||||
define("cordova/plugin/android/app", function(require, exports, module) {
|
||||
|
||||
var exec = require('cordova/exec');
|
||||
@@ -1671,6 +1673,10 @@ module.exports = {
|
||||
// file: src/common/pluginloader.js
|
||||
define("cordova/pluginloader", function(require, exports, module) {
|
||||
|
||||
/*
|
||||
NOTE: this file is NOT used when we use the browserify workflow
|
||||
*/
|
||||
|
||||
var modulemapper = require('cordova/modulemapper');
|
||||
var urlutil = require('cordova/urlutil');
|
||||
|
||||
@@ -1859,15 +1865,14 @@ utils.typeName = function(val) {
|
||||
/**
|
||||
* Returns an indication of whether the argument is an array or not
|
||||
*/
|
||||
utils.isArray = function(a) {
|
||||
return utils.typeName(a) == 'Array';
|
||||
};
|
||||
utils.isArray = Array.isArray ||
|
||||
function(a) {return utils.typeName(a) == 'Array';};
|
||||
|
||||
/**
|
||||
* Returns an indication of whether the argument is a Date or not
|
||||
*/
|
||||
utils.isDate = function(d) {
|
||||
return utils.typeName(d) == 'Date';
|
||||
return (d instanceof Date);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1901,17 +1906,25 @@ utils.clone = function(obj) {
|
||||
* Returns a wrapped version of the function
|
||||
*/
|
||||
utils.close = function(context, func, params) {
|
||||
if (typeof params == 'undefined') {
|
||||
return function() {
|
||||
return func.apply(context, arguments);
|
||||
};
|
||||
} else {
|
||||
return function() {
|
||||
return func.apply(context, params);
|
||||
};
|
||||
}
|
||||
return function() {
|
||||
var args = params || arguments;
|
||||
return func.apply(context, args);
|
||||
};
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
function UUIDcreatePart(length) {
|
||||
var uuidpart = "";
|
||||
for (var i=0; i<length; i++) {
|
||||
var uuidchar = parseInt((Math.random() * 256), 10).toString(16);
|
||||
if (uuidchar.length == 1) {
|
||||
uuidchar = "0" + uuidchar;
|
||||
}
|
||||
uuidpart += uuidchar;
|
||||
}
|
||||
return uuidpart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a UUID
|
||||
*/
|
||||
@@ -1923,6 +1936,7 @@ utils.createUUID = function() {
|
||||
UUIDcreatePart(6);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Extends a child object from a parent object using classical inheritance
|
||||
* pattern.
|
||||
@@ -1932,6 +1946,7 @@ utils.extend = (function() {
|
||||
var F = function() {};
|
||||
// extend Child from Parent
|
||||
return function(Child, Parent) {
|
||||
|
||||
F.prototype = Parent.prototype;
|
||||
Child.prototype = new F();
|
||||
Child.__super__ = Parent.prototype;
|
||||
@@ -1951,18 +1966,7 @@ utils.alert = function(msg) {
|
||||
};
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
function UUIDcreatePart(length) {
|
||||
var uuidpart = "";
|
||||
for (var i=0; i<length; i++) {
|
||||
var uuidchar = parseInt((Math.random() * 256), 10).toString(16);
|
||||
if (uuidchar.length == 1) {
|
||||
uuidchar = "0" + uuidchar;
|
||||
}
|
||||
uuidpart += uuidchar;
|
||||
}
|
||||
return uuidpart;
|
||||
}
|
||||
|
||||
|
||||
|
||||
});
|
||||
@@ -19,10 +19,11 @@
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
|
||||
<link rel="stylesheet" type="text/css" href="css/index.css" />
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: https://ssl.gstatic.com/accessibility/javascript/android/; style-src 'self' 'unsafe-inline'; media-src *">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="msapplication-tap-highlight" content="no">
|
||||
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">
|
||||
<link rel="stylesheet" type="text/css" href="css/index.css">
|
||||
<title>Hello World</title>
|
||||
</head>
|
||||
<body>
|
||||
@@ -35,8 +36,5 @@
|
||||
</div>
|
||||
<script type="text/javascript" src="cordova.js"></script>
|
||||
<script type="text/javascript" src="js/index.js"></script>
|
||||
<script type="text/javascript">
|
||||
app.initialize();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -47,3 +47,5 @@ var app = {
|
||||
console.log('Received Event: ' + id);
|
||||
}
|
||||
};
|
||||
|
||||
app.initialize();
|
||||
@@ -1,165 +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 deviceInfo = function() {
|
||||
document.getElementById("platform").innerHTML = device.platform;
|
||||
document.getElementById("version").innerHTML = device.version;
|
||||
document.getElementById("uuid").innerHTML = device.uuid;
|
||||
document.getElementById("name").innerHTML = device.name;
|
||||
document.getElementById("width").innerHTML = screen.width;
|
||||
document.getElementById("height").innerHTML = screen.height;
|
||||
document.getElementById("colorDepth").innerHTML = screen.colorDepth;
|
||||
};
|
||||
|
||||
var getLocation = function() {
|
||||
var suc = function(p) {
|
||||
alert(p.coords.latitude + " " + p.coords.longitude);
|
||||
};
|
||||
var locFail = function() {
|
||||
};
|
||||
navigator.geolocation.getCurrentPosition(suc, locFail);
|
||||
};
|
||||
|
||||
var beep = function() {
|
||||
navigator.notification.beep(2);
|
||||
};
|
||||
|
||||
var vibrate = function() {
|
||||
navigator.notification.vibrate(0);
|
||||
};
|
||||
|
||||
function roundNumber(num) {
|
||||
var dec = 3;
|
||||
var result = Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
|
||||
return result;
|
||||
}
|
||||
|
||||
var accelerationWatch = null;
|
||||
|
||||
function updateAcceleration(a) {
|
||||
document.getElementById('x').innerHTML = roundNumber(a.x);
|
||||
document.getElementById('y').innerHTML = roundNumber(a.y);
|
||||
document.getElementById('z').innerHTML = roundNumber(a.z);
|
||||
}
|
||||
|
||||
var toggleAccel = function() {
|
||||
if (accelerationWatch !== null) {
|
||||
navigator.accelerometer.clearWatch(accelerationWatch);
|
||||
updateAcceleration({
|
||||
x : "",
|
||||
y : "",
|
||||
z : ""
|
||||
});
|
||||
accelerationWatch = null;
|
||||
} else {
|
||||
var options = {};
|
||||
options.frequency = 1000;
|
||||
accelerationWatch = navigator.accelerometer.watchAcceleration(
|
||||
updateAcceleration, function(ex) {
|
||||
alert("accel fail (" + ex.name + ": " + ex.message + ")");
|
||||
}, options);
|
||||
}
|
||||
};
|
||||
|
||||
var preventBehavior = function(e) {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
function dump_pic(data) {
|
||||
var viewport = document.getElementById('viewport');
|
||||
console.log(data);
|
||||
viewport.style.display = "";
|
||||
viewport.style.position = "absolute";
|
||||
viewport.style.top = "10px";
|
||||
viewport.style.left = "10px";
|
||||
document.getElementById("test_img").src = data;
|
||||
}
|
||||
|
||||
function fail(msg) {
|
||||
alert(msg);
|
||||
}
|
||||
|
||||
function show_pic() {
|
||||
navigator.camera.getPicture(dump_pic, fail, {
|
||||
quality : 50
|
||||
});
|
||||
}
|
||||
|
||||
function close() {
|
||||
var viewport = document.getElementById('viewport');
|
||||
viewport.style.position = "relative";
|
||||
viewport.style.display = "none";
|
||||
}
|
||||
|
||||
function contacts_success(contacts) {
|
||||
alert(contacts.length
|
||||
+ ' contacts returned.'
|
||||
+ (contacts[2] && contacts[2].name ? (' Third contact is ' + contacts[2].name.formatted)
|
||||
: ''));
|
||||
}
|
||||
|
||||
function get_contacts() {
|
||||
var obj = new ContactFindOptions();
|
||||
obj.filter = "";
|
||||
obj.multiple = true;
|
||||
navigator.contacts.find(
|
||||
[ "displayName", "name" ], contacts_success,
|
||||
fail, obj);
|
||||
}
|
||||
|
||||
function check_network() {
|
||||
var networkState = navigator.network.connection.type;
|
||||
|
||||
var states = {};
|
||||
states[Connection.UNKNOWN] = 'Unknown connection';
|
||||
states[Connection.ETHERNET] = 'Ethernet connection';
|
||||
states[Connection.WIFI] = 'WiFi connection';
|
||||
states[Connection.CELL_2G] = 'Cell 2G connection';
|
||||
states[Connection.CELL_3G] = 'Cell 3G connection';
|
||||
states[Connection.CELL_4G] = 'Cell 4G connection';
|
||||
states[Connection.NONE] = 'No network connection';
|
||||
|
||||
confirm('Connection type:\n ' + states[networkState]);
|
||||
}
|
||||
|
||||
var watchID = null;
|
||||
|
||||
function updateHeading(h) {
|
||||
document.getElementById('h').innerHTML = h.magneticHeading;
|
||||
}
|
||||
|
||||
function toggleCompass() {
|
||||
if (watchID !== null) {
|
||||
navigator.compass.clearWatch(watchID);
|
||||
watchID = null;
|
||||
updateHeading({ magneticHeading : "Off"});
|
||||
} else {
|
||||
var options = { frequency: 1000 };
|
||||
watchID = navigator.compass.watchHeading(updateHeading, function(e) {
|
||||
alert('Compass Error: ' + e.code);
|
||||
}, options);
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
// the next line makes it impossible to see Contacts on the HTC Evo since it
|
||||
// doesn't have a scroll button
|
||||
// document.addEventListener("touchmove", preventBehavior, false);
|
||||
document.addEventListener("deviceready", deviceInfo, true);
|
||||
}
|
||||
@@ -1,116 +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.
|
||||
*/
|
||||
|
||||
|
||||
body {
|
||||
background:#222 none repeat scroll 0 0;
|
||||
color:#666;
|
||||
font-family:Helvetica;
|
||||
font-size:72%;
|
||||
line-height:1.5em;
|
||||
margin:0;
|
||||
border-top:1px solid #393939;
|
||||
}
|
||||
|
||||
#info{
|
||||
background:#ffa;
|
||||
border: 1px solid #ffd324;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
clear:both;
|
||||
margin:15px 6px 0;
|
||||
width:295px;
|
||||
padding:4px 0px 2px 10px;
|
||||
}
|
||||
|
||||
#info > h4{
|
||||
font-size:.95em;
|
||||
margin:5px 0;
|
||||
}
|
||||
|
||||
#stage.theme{
|
||||
padding-top:3px;
|
||||
}
|
||||
|
||||
/* Definition List */
|
||||
#stage.theme > dl{
|
||||
padding-top:10px;
|
||||
clear:both;
|
||||
margin:0;
|
||||
list-style-type:none;
|
||||
padding-left:10px;
|
||||
overflow:auto;
|
||||
}
|
||||
|
||||
#stage.theme > dl > dt{
|
||||
font-weight:bold;
|
||||
float:left;
|
||||
margin-left:5px;
|
||||
}
|
||||
|
||||
#stage.theme > dl > dd{
|
||||
width:45px;
|
||||
float:left;
|
||||
color:#a87;
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
/* Content Styling */
|
||||
#stage.theme > h1, #stage.theme > h2, #stage.theme > p{
|
||||
margin:1em 0 .5em 13px;
|
||||
}
|
||||
|
||||
#stage.theme > h1{
|
||||
color:#eee;
|
||||
font-size:1.6em;
|
||||
text-align:center;
|
||||
margin:0;
|
||||
margin-top:15px;
|
||||
padding:0;
|
||||
}
|
||||
|
||||
#stage.theme > h2{
|
||||
clear:both;
|
||||
margin:0;
|
||||
padding:3px;
|
||||
font-size:1em;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
/* Stage Buttons */
|
||||
#stage.theme a.btn{
|
||||
border: 1px solid #555;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
text-align:center;
|
||||
display:block;
|
||||
float:left;
|
||||
background:#444;
|
||||
width:150px;
|
||||
color:#9ab;
|
||||
font-size:1.1em;
|
||||
text-decoration:none;
|
||||
padding:1.2em 0;
|
||||
margin:3px 0px 3px 5px;
|
||||
}
|
||||
#stage.theme a.btn.large{
|
||||
width:308px;
|
||||
padding:1.2em 0;
|
||||
}
|
||||
|
||||
@@ -110,9 +110,15 @@ if (ext.cdvCompileSdkVersion == null) {
|
||||
if (ext.cdvBuildToolsVersion == null) {
|
||||
ext.cdvBuildToolsVersion = privateHelpers.findLatestInstalledBuildTools()
|
||||
}
|
||||
if (ext.cdvDebugSigningPropertiesFile == null && file('debug-signing.properties').exists()) {
|
||||
ext.cdvDebugSigningPropertiesFile = 'debug-signing.properties'
|
||||
}
|
||||
if (ext.cdvReleaseSigningPropertiesFile == null && file('release-signing.properties').exists()) {
|
||||
ext.cdvReleaseSigningPropertiesFile = 'release-signing.properties'
|
||||
}
|
||||
|
||||
// Cast to appropriate types.
|
||||
ext.cdvBuildMultipleApks = !!cdvBuildMultipleApks;
|
||||
ext.cdvBuildMultipleApks = cdvBuildMultipleApks == null ? false : cdvBuildMultipleApks.toBoolean();
|
||||
ext.cdvMinSdkVersion = cdvMinSdkVersion == null ? null : Integer.parseInt('' + cdvMinSdkVersion)
|
||||
ext.cdvVersionCode = cdvVersionCode == null ? null : Integer.parseInt('' + cdvVersionCode)
|
||||
|
||||
@@ -146,11 +152,8 @@ task cdvPrintProps << {
|
||||
println('cdvDebugSigningPropertiesFile=' + cdvDebugSigningPropertiesFile)
|
||||
println('cdvBuildArch=' + cdvBuildArch)
|
||||
println('computedVersionCode=' + android.defaultConfig.versionCode)
|
||||
if (android.productFlavors.has('armv7')) {
|
||||
println('computedArmv7VersionCode=' + android.productFlavors.armv7.versionCode)
|
||||
}
|
||||
if (android.productFlavors.has('x86')) {
|
||||
println('computedx86VersionCode=' + android.productFlavors.x86.versionCode)
|
||||
android.productFlavors.each { flavor ->
|
||||
println('computed' + flavor.name.capitalize() + 'VersionCode=' + flavor.versionCode)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +167,7 @@ android {
|
||||
renderscript.srcDirs = ['src']
|
||||
res.srcDirs = ['res']
|
||||
assets.srcDirs = ['assets']
|
||||
jniLibs.srcDirs = ['libs']
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,27 +272,28 @@ def addSigningProps(propsFilePath, signingConfig) {
|
||||
propsFile.withReader { reader ->
|
||||
props.load(reader)
|
||||
}
|
||||
def storeFile = new File(privateHelpers.ensureValueExists(propsFilePath, props, 'storeFile'))
|
||||
|
||||
def storeFile = new File(props.get('key.store') ?: privateHelpers.ensureValueExists(propsFilePath, props, 'storeFile'))
|
||||
if (!storeFile.isAbsolute()) {
|
||||
storeFile = RelativePath.parse(true, storeFile.toString()).getFile(propsFile.getParentFile())
|
||||
}
|
||||
if (!storeFile.exists()) {
|
||||
throw new FileNotFoundException('Keystore file does not exist: ' + storeFile.getAbsolutePath())
|
||||
}
|
||||
signingConfig.keyAlias = privateHelpers.ensureValueExists(propsFilePath, props, 'keyAlias')
|
||||
signingConfig.keyPassword = props.get('keyPassword', signingConfig.keyPassword)
|
||||
signingConfig.keyAlias = props.get('key.alias') ?: privateHelpers.ensureValueExists(propsFilePath, props, 'keyAlias')
|
||||
signingConfig.keyPassword = props.get('keyPassword', props.get('key.alias.password', signingConfig.keyPassword))
|
||||
signingConfig.storeFile = storeFile
|
||||
signingConfig.storePassword = props.get('storePassword', signingConfig.storePassword)
|
||||
def storeType = props.get('storeType')
|
||||
signingConfig.storePassword = props.get('storePassword', props.get('key.store.password', signingConfig.storePassword))
|
||||
def storeType = props.get('storeType', props.get('key.store.type', ''))
|
||||
if (!storeType) {
|
||||
def filename = storeFile.getName().toLowerCase();
|
||||
if (filename.endsWith('.p12') || filename.endsWith('.pfx')) {
|
||||
storeType = 'pkcs12'
|
||||
} else {
|
||||
storeType = signingConfig.storeType // "jks"
|
||||
}
|
||||
}
|
||||
if (storeType) {
|
||||
signingConfig.storeType = storeType
|
||||
}
|
||||
signingConfig.storeType = storeType
|
||||
}
|
||||
|
||||
for (def func : cdvPluginPostBuildExtras) {
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>__NAME__</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
<filteredResources>
|
||||
<filter>
|
||||
<id>1388696068187</id>
|
||||
<name></name>
|
||||
<type>10</type>
|
||||
<matcher>
|
||||
<id>org.eclipse.ui.ide.multiFilter</id>
|
||||
<arguments>1.0-name-matches-false-true-CordovaLib|platform_www|cordova</arguments>
|
||||
</matcher>
|
||||
</filter>
|
||||
</filteredResources>
|
||||
</projectDescription>
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>__NAME__</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
<linkedResources>
|
||||
<link>
|
||||
<name>config.xml</name>
|
||||
<type>1</type>
|
||||
<locationURI>$%7BPARENT-2-PROJECT_LOC%7D/config.xml</locationURI>
|
||||
</link>
|
||||
<link>
|
||||
<name>www</name>
|
||||
<type>2</type>
|
||||
<locationURI>$%7BPARENT-2-PROJECT_LOC%7D/www</locationURI>
|
||||
</link>
|
||||
<link>
|
||||
<name>merges</name>
|
||||
<type>2</type>
|
||||
<locationURI>$%7BPARENT-2-PROJECT_LOC%7D/merges</locationURI>
|
||||
</link>
|
||||
</linkedResources>
|
||||
<filteredResources>
|
||||
<filter>
|
||||
<id>1390880034107</id>
|
||||
<name></name>
|
||||
<type>30</type>
|
||||
<matcher>
|
||||
<id>org.eclipse.ui.ide.multiFilter</id>
|
||||
<arguments>1.0-projectRelativePath-matches-false-true-^(build.xml|ant-gen|ant-build|custom_rules.xml|CordovaLib|platform_www|cordova)</arguments>
|
||||
</matcher>
|
||||
</filter>
|
||||
<filter>
|
||||
<id>1390880034108</id>
|
||||
<name></name>
|
||||
<type>30</type>
|
||||
<matcher>
|
||||
<id>org.eclipse.ui.ide.multiFilter</id>
|
||||
<arguments>1.0-projectRelativePath-matches-false-true-^(assets/www|res/xml/config.xml)</arguments>
|
||||
</matcher>
|
||||
</filter>
|
||||
</filteredResources>
|
||||
</projectDescription>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 7.5 KiB |
@@ -30,28 +30,26 @@
|
||||
Apache Cordova Team
|
||||
</author>
|
||||
|
||||
<!-- Allow access to arbitrary URLs in the Cordova WebView. This is a
|
||||
development mode setting, and should be changed for production. -->
|
||||
<access origin="http://*/*"/>
|
||||
<access origin="https://*/*"/>
|
||||
<!-- <content src="http://mysite.com/myapp.html" /> for external pages -->
|
||||
<content src="index.html" />
|
||||
|
||||
<!-- Whitelist docs: https://github.com/apache/cordova-plugin-whitelist -->
|
||||
<access origin="*" />
|
||||
<!-- Grant certain URLs the ability to launch external applications. This
|
||||
behaviour is set to match that of Cordova versions before 3.6.0, and
|
||||
should be reviewed before launching an application in production. It
|
||||
may be changed in the future. -->
|
||||
<access origin="tel:*" launch-external="yes"/>
|
||||
<access origin="geo:*" launch-external="yes"/>
|
||||
<access origin="mailto:*" launch-external="yes"/>
|
||||
<access origin="sms:*" launch-external="yes"/>
|
||||
<access origin="market:*" launch-external="yes"/>
|
||||
|
||||
<!-- <content src="http://mysite.com/myapp.html" /> for external pages -->
|
||||
<content src="index.html" />
|
||||
<allow-intent href="http://*/*" />
|
||||
<allow-intent href="https://*/*" />
|
||||
<allow-intent href="tel:*" />
|
||||
<allow-intent href="sms:*" />
|
||||
<allow-intent href="mailto:*" />
|
||||
<allow-intent href="geo:*" />
|
||||
<allow-intent href="market:*" />
|
||||
|
||||
<preference name="loglevel" value="DEBUG" />
|
||||
|
||||
<!--
|
||||
<preference name="splashscreen" value="resourceName" />
|
||||
<preference name="splashscreen" value="splash" />
|
||||
<preference name="backgroundColor" value="0xFFF" />
|
||||
<preference name="loadUrlTimeoutValue" value="20000" />
|
||||
<preference name="InAppBrowserStorageEnabled" value="true" />
|
||||
Vendored
+1
-5
@@ -51,11 +51,7 @@ var cordova = require('cordova'),
|
||||
// For the ONLINE_EVENT to be viable, it would need to intercept all event
|
||||
// listeners (both through addEventListener and window.ononline) as well
|
||||
// as set the navigator property itself.
|
||||
ONLINE_EVENT: 2,
|
||||
// Uses reflection to access private APIs of the WebView that can send JS
|
||||
// to be executed.
|
||||
// Requires Android 3.2.4 or above.
|
||||
PRIVATE_API: 3
|
||||
ONLINE_EVENT: 2
|
||||
},
|
||||
jsToNativeBridgeMode, // Set lazily.
|
||||
nativeToJsBridgeMode = nativeToJsModes.ONLINE_EVENT,
|
||||
|
||||
@@ -19,5 +19,5 @@
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.apache.cordova" android:versionName="1.0" android:versionCode="1">
|
||||
<uses-sdk android:minSdkVersion="10" />
|
||||
<uses-sdk android:minSdkVersion="14" />
|
||||
</manifest>
|
||||
|
||||
@@ -152,6 +152,26 @@ def doPromptForPassword(msg) {
|
||||
}
|
||||
}
|
||||
|
||||
def doGetConfigXml() {
|
||||
def xml = file("res/xml/config.xml").getText()
|
||||
// Disable namespace awareness since Cordova doesn't use them properly
|
||||
return new XmlParser(false, false).parseText(xml)
|
||||
}
|
||||
|
||||
def doGetConfigPreference(name, defaultValue) {
|
||||
name = name.toLowerCase()
|
||||
def root = doGetConfigXml()
|
||||
|
||||
def ret = defaultValue
|
||||
root.preference.each { it ->
|
||||
def attrName = it.attribute("name")
|
||||
if (attrName && attrName.toLowerCase() == name) {
|
||||
ret = it.attribute("value")
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Properties exported here are visible to all plugins.
|
||||
ext {
|
||||
// These helpers are shared, but are not guaranteed to be stable / unchanged.
|
||||
@@ -161,5 +181,12 @@ ext {
|
||||
privateHelpers.extractIntFromManifest = { name -> doExtractIntFromManifest(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.
|
||||
cdvHelpers = {}
|
||||
// Returns a XmlParser for the config.xml. Added in 4.1.0.
|
||||
cdvHelpers.getConfigXml = { doGetConfigXml() }
|
||||
// Returns the value for the desired <preference>. Added in 4.1.0.
|
||||
cdvHelpers.getConfigPreference = { name, defaultValue -> doGetConfigPreference(name, defaultValue) }
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
# Indicates whether an apk should be generated for each density.
|
||||
split.density=false
|
||||
# Project target.
|
||||
target=android-21
|
||||
target=android-22
|
||||
apk-configurations=
|
||||
renderscript.opt.level=O0
|
||||
android.library=true
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.9 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.0 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 92 KiB |
@@ -1,29 +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.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
>
|
||||
<WebView android:id="@+id/appView"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_width="fill_parent"
|
||||
/>
|
||||
</LinearLayout>
|
||||
@@ -1,23 +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.
|
||||
-->
|
||||
<resources>
|
||||
<string name="app_name">Cordova</string>
|
||||
<string name="go">Snap</string>
|
||||
</resources>
|
||||
@@ -1,814 +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.
|
||||
*/
|
||||
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.webkit.WebBackForwardList;
|
||||
import android.webkit.WebHistoryItem;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebSettings.LayoutAlgorithm;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.webkit.CookieManager;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
|
||||
/*
|
||||
* This class is our web view.
|
||||
*
|
||||
* @see <a href="http://developer.android.com/guide/webapps/webview.html">WebView guide</a>
|
||||
* @see <a href="http://developer.android.com/reference/android/webkit/WebView.html">WebView</a>
|
||||
*/
|
||||
public class AndroidWebView extends WebView implements CordovaWebView {
|
||||
|
||||
public static final String TAG = "AndroidWebView";
|
||||
|
||||
private HashSet<Integer> boundKeyCodes = new HashSet<Integer>();
|
||||
|
||||
PluginManager pluginManager;
|
||||
AndroidCookieManager cookieManager;
|
||||
|
||||
private BroadcastReceiver receiver;
|
||||
|
||||
|
||||
/** Activities and other important classes **/
|
||||
private CordovaInterface cordova;
|
||||
AndroidWebViewClient viewClient;
|
||||
private AndroidChromeClient chromeClient;
|
||||
|
||||
// Flag to track that a loadUrl timeout occurred
|
||||
int loadUrlTimeout = 0;
|
||||
|
||||
private long lastMenuEventTime = 0;
|
||||
|
||||
CordovaBridge bridge;
|
||||
|
||||
/** custom view created by the browser (a video player for example) */
|
||||
private View mCustomView;
|
||||
private WebChromeClient.CustomViewCallback mCustomViewCallback;
|
||||
|
||||
private CordovaResourceApi resourceApi;
|
||||
private CordovaPreferences preferences;
|
||||
private CoreAndroid appPlugin;
|
||||
private CordovaUriHelper helper;
|
||||
// The URL passed to loadUrl(), not necessarily the URL of the current page.
|
||||
String loadedUrl;
|
||||
|
||||
static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
|
||||
new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
Gravity.CENTER);
|
||||
|
||||
/** Used when created via reflection. */
|
||||
public AndroidWebView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
/** Required to allow view to be used within XML layouts. */
|
||||
public AndroidWebView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
// Use two-phase init so that the control will work with XML layouts.
|
||||
@Override
|
||||
public void init(final CordovaInterface cordova, List<PluginEntry> pluginEntries,
|
||||
CordovaPreferences preferences) {
|
||||
if (this.cordova != null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
this.cordova = cordova;
|
||||
this.preferences = preferences;
|
||||
this.helper = new CordovaUriHelper(cordova, this);
|
||||
|
||||
pluginManager = new PluginManager(this, this.cordova, pluginEntries);
|
||||
cookieManager = new AndroidCookieManager(this);
|
||||
resourceApi = new CordovaResourceApi(this.getContext(), pluginManager);
|
||||
NativeToJsMessageQueue nativeToJsMessageQueue = new NativeToJsMessageQueue();
|
||||
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.NoOpBridgeMode());
|
||||
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.LoadUrlBridgeMode(this, cordova));
|
||||
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode.OnlineEventsBridgeModeDelegate() {
|
||||
@Override
|
||||
public void setNetworkAvailable(boolean value) {
|
||||
AndroidWebView.this.setNetworkAvailable(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runOnUiThread(Runnable r) {
|
||||
cordova.getActivity().runOnUiThread(r);
|
||||
}
|
||||
}));
|
||||
bridge = new CordovaBridge(pluginManager, nativeToJsMessageQueue, this.cordova.getActivity().getPackageName(), helper);
|
||||
initWebViewSettings();
|
||||
pluginManager.addService(CoreAndroid.PLUGIN_NAME, CoreAndroid.class.getCanonicalName());
|
||||
pluginManager.init();
|
||||
|
||||
if (this.viewClient == null) {
|
||||
setWebViewClient(Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH ?
|
||||
new AndroidWebViewClient(cordova, this) :
|
||||
new IceCreamCordovaWebViewClient(cordova, this));
|
||||
}
|
||||
|
||||
|
||||
if (this.chromeClient == null) {
|
||||
setWebChromeClient(new AndroidChromeClient(cordova, this));
|
||||
}
|
||||
|
||||
exposeJsInterface();
|
||||
|
||||
if (preferences.getBoolean("DisallowOverscroll", false)) {
|
||||
setOverScrollMode(View.OVER_SCROLL_NEVER);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@SuppressWarnings("deprecation")
|
||||
private void initWebViewSettings() {
|
||||
this.setInitialScale(0);
|
||||
this.setVerticalScrollBarEnabled(false);
|
||||
// TODO: The Activity is the one that should call requestFocus().
|
||||
if (shouldRequestFocusOnInit()) {
|
||||
this.requestFocusFromTouch();
|
||||
}
|
||||
this.setInitialScale(0);
|
||||
this.setVerticalScrollBarEnabled(false);
|
||||
if (shouldRequestFocusOnInit()) {
|
||||
this.requestFocusFromTouch();
|
||||
}
|
||||
// Enable JavaScript
|
||||
final WebSettings settings = this.getSettings();
|
||||
settings.setJavaScriptEnabled(true);
|
||||
settings.setJavaScriptCanOpenWindowsAutomatically(true);
|
||||
settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
|
||||
|
||||
// Set the nav dump for HTC 2.x devices (disabling for ICS, deprecated entirely for Jellybean 4.2)
|
||||
try {
|
||||
Method gingerbread_getMethod = WebSettings.class.getMethod("setNavDump", new Class[] { boolean.class });
|
||||
|
||||
String manufacturer = android.os.Build.MANUFACTURER;
|
||||
Log.d(TAG, "CordovaWebView is running on device made by: " + manufacturer);
|
||||
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB &&
|
||||
android.os.Build.MANUFACTURER.contains("HTC"))
|
||||
{
|
||||
gingerbread_getMethod.invoke(settings, true);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
Log.d(TAG, "We are on a modern version of Android, we will deprecate HTC 2.3 devices in 2.8");
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.d(TAG, "Doing the NavDump failed with bad arguments");
|
||||
} catch (IllegalAccessException e) {
|
||||
Log.d(TAG, "This should never happen: IllegalAccessException means this isn't Android anymore");
|
||||
} catch (InvocationTargetException e) {
|
||||
Log.d(TAG, "This should never happen: InvocationTargetException means this isn't Android anymore.");
|
||||
}
|
||||
|
||||
//We don't save any form data in the application
|
||||
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
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
|
||||
Level16Apis.enableUniversalAccess(settings);
|
||||
}
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
Level17Apis.setMediaPlaybackRequiresUserGesture(settings, false);
|
||||
}
|
||||
// Enable database
|
||||
// We keep this disabled because we use or shim to get around DOM_EXCEPTION_ERROR_16
|
||||
String databasePath = 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 = getContext().getApplicationContext().getApplicationInfo();
|
||||
if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0 &&
|
||||
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
|
||||
enableRemoteDebugging();
|
||||
}
|
||||
|
||||
settings.setGeolocationDatabasePath(databasePath);
|
||||
|
||||
// Enable DOM storage
|
||||
settings.setDomStorageEnabled(true);
|
||||
|
||||
// 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
|
||||
settings.getUserAgentString();
|
||||
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
|
||||
if (this.receiver == null) {
|
||||
this.receiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
settings.getUserAgentString();
|
||||
}
|
||||
};
|
||||
getContext().registerReceiver(this.receiver, intentFilter);
|
||||
}
|
||||
// end CB-1405
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||
private void enableRemoteDebugging() {
|
||||
try {
|
||||
WebView.setWebContentsDebuggingEnabled(true);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.d(TAG, "You have one job! To turn on Remote Web Debugging! YOU HAVE FAILED! ");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method to decide whether or not you need to request the
|
||||
* focus when your application start
|
||||
*
|
||||
* @return true unless this method is overriden to return a different value
|
||||
*/
|
||||
protected boolean shouldRequestFocusOnInit() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void exposeJsInterface() {
|
||||
if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)) {
|
||||
Log.i(TAG, "Disabled addJavascriptInterface() bridge since Android version is old.");
|
||||
// Bug being that Java Strings do not get converted to JS strings automatically.
|
||||
// This isn't hard to work-around on the JS side, but it's easier to just
|
||||
// use the prompt bridge instead.
|
||||
return;
|
||||
}
|
||||
AndroidExposedJsApi exposedJsApi = new AndroidExposedJsApi(bridge);
|
||||
this.addJavascriptInterface(exposedJsApi, "_cordovaNative");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWebViewClient(WebViewClient client) {
|
||||
this.viewClient = (AndroidWebViewClient)client;
|
||||
super.setWebViewClient(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWebChromeClient(WebChromeClient client) {
|
||||
this.chromeClient = (AndroidChromeClient)client;
|
||||
super.setWebChromeClient(client);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the url into the webview.
|
||||
*/
|
||||
@Override
|
||||
public void loadUrl(String url) {
|
||||
this.loadUrlIntoView(url, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the url into the webview.
|
||||
*/
|
||||
@Override
|
||||
public void loadUrlIntoView(final String url, boolean recreatePlugins) {
|
||||
if (url.equals("about:blank") || url.startsWith("javascript:")) {
|
||||
this.loadUrlNow(url);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.d(TAG, ">>> loadUrl(" + url + ")");
|
||||
recreatePlugins = recreatePlugins || (loadedUrl == null);
|
||||
|
||||
if (recreatePlugins) {
|
||||
// Don't re-initialize on first load.
|
||||
if (loadedUrl != null) {
|
||||
this.pluginManager.init();
|
||||
}
|
||||
this.loadedUrl = url;
|
||||
}
|
||||
|
||||
// Create a timeout timer for loadUrl
|
||||
final AndroidWebView me = this;
|
||||
final int currentLoadUrlTimeout = me.loadUrlTimeout;
|
||||
final int loadUrlTimeoutValue = preferences.getInteger("LoadUrlTimeoutValue", 20000);
|
||||
|
||||
// Timeout error method
|
||||
final Runnable loadError = new Runnable() {
|
||||
public void run() {
|
||||
me.stopLoading();
|
||||
LOG.e(TAG, "CordovaWebView: TIMEOUT ERROR!");
|
||||
if (viewClient != null) {
|
||||
viewClient.onReceivedError(AndroidWebView.this, -6, "The connection to the server was unsuccessful.", url);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Timeout timer method
|
||||
final Runnable timeoutCheck = new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
synchronized (this) {
|
||||
wait(loadUrlTimeoutValue);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// If timeout, then stop loading and handle error
|
||||
if (me.loadUrlTimeout == currentLoadUrlTimeout) {
|
||||
me.cordova.getActivity().runOnUiThread(loadError);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Load url
|
||||
this.cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
cordova.getThreadPool().execute(timeoutCheck);
|
||||
me.loadUrlNow(url);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load URL in webview.
|
||||
*/
|
||||
private void loadUrlNow(String url) {
|
||||
if (LOG.isLoggable(LOG.DEBUG) && !url.startsWith("javascript:")) {
|
||||
LOG.d(TAG, ">>> loadUrlNow()");
|
||||
}
|
||||
if (url.startsWith("javascript:") || helper.shouldAllowNavigation(url)) {
|
||||
super.loadUrl(url);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopLoading() {
|
||||
//viewClient.isCurrentlyLoading = false;
|
||||
super.stopLoading();
|
||||
}
|
||||
|
||||
public void onScrollChanged(int l, int t, int oldl, int oldt)
|
||||
{
|
||||
super.onScrollChanged(l, t, oldl, oldt);
|
||||
//We should post a message that the scroll changed
|
||||
ScrollEvent myEvent = new ScrollEvent(l, t, oldl, oldt, this);
|
||||
pluginManager.postMessage("onScrollChanged", myEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send JavaScript statement back to JavaScript.
|
||||
* (This is a convenience method)
|
||||
*/
|
||||
public void sendJavascript(String statement) {
|
||||
bridge.getMessageQueue().addJavaScript(statement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a plugin result back to JavaScript.
|
||||
*/
|
||||
public void sendPluginResult(PluginResult result, String callbackId) {
|
||||
bridge.getMessageQueue().addPluginResult(result, callbackId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to previous page in history. (We manage our own history)
|
||||
*
|
||||
* @return true if we went back, false if we are already at top
|
||||
*/
|
||||
public boolean backHistory() {
|
||||
|
||||
// Check webview first to see if there is a history
|
||||
// This is needed to support curPage#diffLink, since they are added to appView's history, but not our history url array (JQMobile behavior)
|
||||
if (super.canGoBack()) {
|
||||
printBackForwardList();
|
||||
super.goBack();
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load the specified URL in the Cordova webview or a new browser instance.
|
||||
*
|
||||
* NOTE: If openExternal is false, only URLs listed in whitelist can be loaded.
|
||||
*
|
||||
* @param url The url to load.
|
||||
* @param openExternal Load url in browser instead of Cordova webview.
|
||||
* @param clearHistory Clear the history stack, so new page becomes top of history
|
||||
* @param params Parameters for new app
|
||||
*/
|
||||
public void showWebPage(String url, boolean openExternal, boolean clearHistory, HashMap<String, Object> params) {
|
||||
LOG.d(TAG, "showWebPage(%s, %b, %b, HashMap", url, openExternal, clearHistory);
|
||||
|
||||
// If clearing history
|
||||
if (clearHistory) {
|
||||
this.clearHistory();
|
||||
}
|
||||
|
||||
// If loading into our webview
|
||||
if (!openExternal) {
|
||||
|
||||
// Make sure url is in whitelist
|
||||
if (helper.shouldAllowNavigation(url)) {
|
||||
// TODO: What about params?
|
||||
// Load new URL
|
||||
loadUrlIntoView(url, true);
|
||||
return;
|
||||
}
|
||||
// Load in default viewer if not
|
||||
LOG.w(TAG, "showWebPage: Cannot load URL into webview since it is not in white list. Loading into browser instead. (URL=" + url + ")");
|
||||
}
|
||||
try {
|
||||
// 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.
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
Uri uri = Uri.parse(url);
|
||||
if ("file".equals(uri.getScheme())) {
|
||||
intent.setDataAndType(uri, resourceApi.getMimeType(uri));
|
||||
} else {
|
||||
intent.setData(uri);
|
||||
}
|
||||
cordova.getActivity().startActivity(intent);
|
||||
} catch (android.content.ActivityNotFoundException e) {
|
||||
LOG.e(TAG, "Error loading url " + url, e);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* onKeyDown
|
||||
*/
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event)
|
||||
{
|
||||
if(boundKeyCodes.contains(keyCode))
|
||||
{
|
||||
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
|
||||
sendJavascriptEvent("volumedownbutton");
|
||||
return true;
|
||||
}
|
||||
else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
|
||||
sendJavascriptEvent("volumeupbutton");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
}
|
||||
else if(keyCode == KeyEvent.KEYCODE_BACK)
|
||||
{
|
||||
return !(this.startOfHistory()) || isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK);
|
||||
|
||||
}
|
||||
else if(keyCode == KeyEvent.KEYCODE_MENU)
|
||||
{
|
||||
//How did we get here? Is there a childView?
|
||||
View childView = this.getFocusedChild();
|
||||
if(childView != null)
|
||||
{
|
||||
//Make sure we close the keyboard if it's present
|
||||
InputMethodManager imm = (InputMethodManager) cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(childView.getWindowToken(), 0);
|
||||
cordova.getActivity().openOptionsMenu();
|
||||
return true;
|
||||
} else {
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
}
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event)
|
||||
{
|
||||
// If back key
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
// A custom view is currently displayed (e.g. playing a video)
|
||||
if(mCustomView != null) {
|
||||
this.hideCustomView();
|
||||
return true;
|
||||
} else {
|
||||
// The webview is currently displayed
|
||||
// If back key is bound, then send event to JavaScript
|
||||
if (isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK)) {
|
||||
sendJavascriptEvent("backbutton");
|
||||
return true;
|
||||
} else {
|
||||
// If not bound
|
||||
// Go to previous page in webview if it is possible to go back
|
||||
if (this.backHistory()) {
|
||||
return true;
|
||||
}
|
||||
// If not, then invoke default behavior
|
||||
}
|
||||
}
|
||||
}
|
||||
// Legacy
|
||||
else if (keyCode == KeyEvent.KEYCODE_MENU) {
|
||||
if (this.lastMenuEventTime < event.getEventTime()) {
|
||||
sendJavascriptEvent("menubutton");
|
||||
}
|
||||
this.lastMenuEventTime = event.getEventTime();
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
// If search key
|
||||
else if (keyCode == KeyEvent.KEYCODE_SEARCH) {
|
||||
sendJavascriptEvent("searchbutton");
|
||||
return true;
|
||||
}
|
||||
|
||||
//Does webkit change this behavior?
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
|
||||
private void sendJavascriptEvent(String event) {
|
||||
if (appPlugin == null) {
|
||||
appPlugin = (CoreAndroid)this.pluginManager.getPlugin(CoreAndroid.PLUGIN_NAME);
|
||||
}
|
||||
|
||||
if (appPlugin == null) {
|
||||
LOG.w(TAG, "Unable to fire event without existing plugin");
|
||||
return;
|
||||
}
|
||||
appPlugin.fireJavascriptEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setButtonPlumbedToJs(int keyCode, boolean override) {
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_VOLUME_DOWN:
|
||||
case KeyEvent.KEYCODE_VOLUME_UP:
|
||||
case KeyEvent.KEYCODE_BACK:
|
||||
// TODO: Why are search and menu buttons handled separately?
|
||||
if (override) {
|
||||
boundKeyCodes.add(keyCode);
|
||||
} else {
|
||||
boundKeyCodes.remove(keyCode);
|
||||
}
|
||||
return;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported keycode: " + keyCode);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isButtonPlumbedToJs(int keyCode)
|
||||
{
|
||||
return boundKeyCodes.contains(keyCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePause(boolean keepRunning)
|
||||
{
|
||||
LOG.d(TAG, "Handle the pause");
|
||||
// Send pause event to JavaScript
|
||||
sendJavascriptEvent("pause");
|
||||
|
||||
// Forward to plugins
|
||||
if (this.pluginManager != null) {
|
||||
this.pluginManager.onPause(keepRunning);
|
||||
}
|
||||
|
||||
// If app doesn't want to run in background
|
||||
if (!keepRunning) {
|
||||
// Pause JavaScript timers. This affects all webviews within the app!
|
||||
this.pauseTimers();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResume(boolean keepRunning)
|
||||
{
|
||||
// Resume JavaScript timers. This affects all webviews within the app!
|
||||
this.resumeTimers();
|
||||
|
||||
sendJavascriptEvent("resume");
|
||||
|
||||
// Forward to plugins
|
||||
if (this.pluginManager != null) {
|
||||
this.pluginManager.onResume(keepRunning);
|
||||
}
|
||||
}
|
||||
|
||||
public void handleDestroy()
|
||||
{
|
||||
// Cancel pending timeout timer.
|
||||
loadUrlTimeout++;
|
||||
|
||||
// Load blank page so that JavaScript onunload is called
|
||||
this.loadUrl("about:blank");
|
||||
|
||||
//Remove last AlertDialog
|
||||
this.chromeClient.destroyLastDialog();
|
||||
|
||||
// Forward to plugins
|
||||
if (this.pluginManager != null) {
|
||||
this.pluginManager.onDestroy();
|
||||
}
|
||||
|
||||
// unregister the receiver
|
||||
if (this.receiver != null) {
|
||||
try {
|
||||
getContext().unregisterReceiver(this.receiver);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error unregistering configuration receiver: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onNewIntent(Intent intent)
|
||||
{
|
||||
//Forward to plugins
|
||||
if (this.pluginManager != null) {
|
||||
this.pluginManager.onNewIntent(intent);
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapping these functions in their own class prevents warnings in adb like:
|
||||
// VFY: unable to resolve virtual method 285: Landroid/webkit/WebSettings;.setAllowUniversalAccessFromFileURLs
|
||||
@TargetApi(16)
|
||||
private static class Level16Apis {
|
||||
static void enableUniversalAccess(WebSettings settings) {
|
||||
settings.setAllowUniversalAccessFromFileURLs(true);
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(17)
|
||||
private static final class Level17Apis {
|
||||
static void setMediaPlaybackRequiresUserGesture(WebSettings settings, boolean value) {
|
||||
settings.setMediaPlaybackRequiresUserGesture(value);
|
||||
}
|
||||
}
|
||||
public void printBackForwardList() {
|
||||
WebBackForwardList currentList = this.copyBackForwardList();
|
||||
int currentSize = currentList.getSize();
|
||||
for(int i = 0; i < currentSize; ++i)
|
||||
{
|
||||
WebHistoryItem item = currentList.getItemAtIndex(i);
|
||||
String url = item.getUrl();
|
||||
LOG.d(TAG, "The URL at index: " + Integer.toString(i) + " is " + url );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Can Go Back is BROKEN!
|
||||
public boolean startOfHistory()
|
||||
{
|
||||
WebBackForwardList currentList = this.copyBackForwardList();
|
||||
WebHistoryItem item = currentList.getItemAtIndex(0);
|
||||
if( item!=null){ // Null-fence in case they haven't called loadUrl yet (CB-2458)
|
||||
String url = item.getUrl();
|
||||
String currentUrl = this.getUrl();
|
||||
LOG.d(TAG, "The current URL is: " + currentUrl);
|
||||
LOG.d(TAG, "The URL at item 0 is: " + url);
|
||||
return currentUrl.equals(url);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void showCustomView(View view, WebChromeClient.CustomViewCallback callback) {
|
||||
// This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
|
||||
Log.d(TAG, "showing Custom View");
|
||||
// if a view already exists then immediately terminate the new one
|
||||
if (mCustomView != null) {
|
||||
callback.onCustomViewHidden();
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the view and its callback for later (to kill it properly)
|
||||
mCustomView = view;
|
||||
mCustomViewCallback = callback;
|
||||
|
||||
// Add the custom view to its container.
|
||||
ViewGroup parent = (ViewGroup) this.getParent();
|
||||
parent.addView(view, COVER_SCREEN_GRAVITY_CENTER);
|
||||
|
||||
// Hide the content view.
|
||||
this.setVisibility(View.GONE);
|
||||
|
||||
// Finally show the custom view container.
|
||||
parent.setVisibility(View.VISIBLE);
|
||||
parent.bringToFront();
|
||||
}
|
||||
|
||||
public void hideCustomView() {
|
||||
// This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
|
||||
Log.d(TAG, "Hiding Custom View");
|
||||
if (mCustomView == null) return;
|
||||
|
||||
// Hide the custom view.
|
||||
mCustomView.setVisibility(View.GONE);
|
||||
|
||||
// Remove the custom view from its container.
|
||||
ViewGroup parent = (ViewGroup) this.getParent();
|
||||
parent.removeView(mCustomView);
|
||||
mCustomView = null;
|
||||
mCustomViewCallback.onCustomViewHidden();
|
||||
|
||||
// Show the content view.
|
||||
this.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* if the video overlay is showing then we need to know
|
||||
* as it effects back button handling
|
||||
*
|
||||
* @return true if custom view is showing
|
||||
*/
|
||||
public boolean isCustomViewShowing() {
|
||||
return mCustomView != null;
|
||||
}
|
||||
|
||||
public WebBackForwardList restoreState(Bundle savedInstanceState)
|
||||
{
|
||||
WebBackForwardList myList = super.restoreState(savedInstanceState);
|
||||
Log.d(TAG, "WebView restoration crew now restoring!");
|
||||
//Initialize the plugin manager once more
|
||||
this.pluginManager.init();
|
||||
return myList;
|
||||
}
|
||||
|
||||
public CordovaResourceApi getResourceApi() {
|
||||
return resourceApi;
|
||||
}
|
||||
|
||||
void onPageReset() {
|
||||
boundKeyCodes.clear();
|
||||
pluginManager.onReset();
|
||||
bridge.reset(loadedUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginManager getPluginManager() {
|
||||
return this.pluginManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CordovaPreferences getPreferences() {
|
||||
return preferences;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICordovaCookieManager getCookieManager() {
|
||||
return cookieManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postMessage(String id, Object data) {
|
||||
return pluginManager.postMessage(id, data);
|
||||
}
|
||||
}
|
||||
@@ -36,8 +36,8 @@ public class Config {
|
||||
public static void init(Activity action) {
|
||||
parser = new ConfigXmlParser();
|
||||
parser.parse(action);
|
||||
//TODO: Add feature to bring this back. Some preferences should be overridden by intents, but not all
|
||||
parser.getPreferences().setPreferencesBundle(action.getIntent().getExtras());
|
||||
parser.getPreferences().copyIntoIntentExtras(action);
|
||||
}
|
||||
|
||||
// Intended to be used for testing only; creates an empty configuration.
|
||||
|
||||
@@ -25,11 +25,10 @@ import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.cordova.LOG;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.res.XmlResourceParser;
|
||||
import android.content.Context;
|
||||
|
||||
public class ConfigXmlParser {
|
||||
private static String TAG = "ConfigXmlParser";
|
||||
@@ -50,7 +49,7 @@ public class ConfigXmlParser {
|
||||
return launchUrl;
|
||||
}
|
||||
|
||||
public void parse(Activity action) {
|
||||
public void parse(Context action) {
|
||||
// First checking the class namespace for config.xml
|
||||
int id = action.getResources().getIdentifier("config", "xml", action.getClass().getPackage().getName());
|
||||
if (id == 0) {
|
||||
@@ -68,14 +67,14 @@ public class ConfigXmlParser {
|
||||
String service = "", pluginClass = "", paramType = "";
|
||||
boolean onload = false;
|
||||
|
||||
public void parse(XmlResourceParser xml) {
|
||||
public void parse(XmlPullParser xml) {
|
||||
int eventType = -1;
|
||||
|
||||
while (eventType != XmlResourceParser.END_DOCUMENT) {
|
||||
if (eventType == XmlResourceParser.START_TAG) {
|
||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
handleStartTag(xml);
|
||||
}
|
||||
else if (eventType == XmlResourceParser.END_TAG)
|
||||
else if (eventType == XmlPullParser.END_TAG)
|
||||
{
|
||||
handleEndTag(xml);
|
||||
}
|
||||
@@ -89,7 +88,7 @@ public class ConfigXmlParser {
|
||||
}
|
||||
}
|
||||
|
||||
public void handleStartTag(XmlResourceParser xml) {
|
||||
public void handleStartTag(XmlPullParser xml) {
|
||||
String strNode = xml.getName();
|
||||
if (strNode.equals("feature")) {
|
||||
//Check for supported feature sets aka. plugins (Accelerometer, Geolocation, etc)
|
||||
@@ -119,7 +118,7 @@ public class ConfigXmlParser {
|
||||
}
|
||||
}
|
||||
|
||||
public void handleEndTag(XmlResourceParser xml) {
|
||||
public void handleEndTag(XmlPullParser xml) {
|
||||
String strNode = xml.getName();
|
||||
if (strNode.equals("feature")) {
|
||||
pluginEntries.add(new PluginEntry(service, pluginClass, onload));
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
|
||||
@@ -28,22 +26,22 @@ import org.json.JSONObject;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Color;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
/**
|
||||
* This class is the main Android activity that represents the Cordova
|
||||
@@ -85,20 +83,21 @@ public class CordovaActivity extends Activity {
|
||||
private static int ACTIVITY_STARTING = 0;
|
||||
private static int ACTIVITY_RUNNING = 1;
|
||||
private static int ACTIVITY_EXITING = 2;
|
||||
private int activityState = 0; // 0=starting, 1=running (after 1st resume), 2=shutting down
|
||||
|
||||
// Keep app running when pause is received. (default = true)
|
||||
// If true, then the JavaScript and native code continue to run in the background
|
||||
// when another application (activity) is started.
|
||||
protected boolean keepRunning = true;
|
||||
|
||||
// Flag to keep immersive mode if set to fullscreen
|
||||
protected boolean immersiveMode;
|
||||
|
||||
// Read from config.xml:
|
||||
protected CordovaPreferences preferences;
|
||||
protected String launchUrl;
|
||||
protected ArrayList<PluginEntry> pluginEntries;
|
||||
protected CordovaInterfaceImpl cordovaInterface;
|
||||
|
||||
|
||||
/**
|
||||
* Called when the activity is first created.
|
||||
*/
|
||||
@@ -113,15 +112,23 @@ public class CordovaActivity extends Activity {
|
||||
{
|
||||
getWindow().requestFeature(Window.FEATURE_NO_TITLE);
|
||||
}
|
||||
|
||||
|
||||
if(preferences.getBoolean("SetFullscreen", false))
|
||||
{
|
||||
Log.d(TAG, "The SetFullscreen configuration is deprecated in favor of Fullscreen, and will be removed in a future version.");
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
} else if (preferences.getBoolean("Fullscreen", false)) {
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||
preferences.set("Fullscreen", true);
|
||||
}
|
||||
if(preferences.getBoolean("Fullscreen", false))
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
|
||||
{
|
||||
immersiveMode = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
}
|
||||
} else {
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN,
|
||||
WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
|
||||
@@ -139,9 +146,10 @@ public class CordovaActivity extends Activity {
|
||||
protected void init() {
|
||||
appView = makeWebView();
|
||||
createViews();
|
||||
//TODO: Add null check against CordovaInterfaceImpl, since this can be fragile
|
||||
appView.init(cordovaInterface, pluginEntries, preferences);
|
||||
cordovaInterface.setPluginManager(appView.getPluginManager());
|
||||
if (!appView.isInitialized()) {
|
||||
appView.init(cordovaInterface, pluginEntries, preferences);
|
||||
}
|
||||
cordovaInterface.onCordovaInit(appView.getPluginManager());
|
||||
|
||||
// Wire the hardware volume controls to control media if desired.
|
||||
String volumePref = preferences.getString("DefaultVolumeStream", "");
|
||||
@@ -156,7 +164,6 @@ public class CordovaActivity extends Activity {
|
||||
parser.parse(this);
|
||||
preferences = parser.getPreferences();
|
||||
preferences.setPreferencesBundle(getIntent().getExtras());
|
||||
preferences.copyIntoIntentExtras(this);
|
||||
launchUrl = parser.getLaunchUrl();
|
||||
pluginEntries = parser.getPluginEntries();
|
||||
Config.parser = parser;
|
||||
@@ -165,30 +172,21 @@ public class CordovaActivity extends Activity {
|
||||
//Suppressing warnings in AndroidStudio
|
||||
@SuppressWarnings({"deprecation", "ResourceType"})
|
||||
protected void createViews() {
|
||||
LinearLayout root = new LinearLayout(this);
|
||||
root.setOrientation(LinearLayout.VERTICAL);
|
||||
root.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, 0.0F));
|
||||
|
||||
//Why are we setting a constant as the ID? This should be investigated
|
||||
appView.getView().setId(100);
|
||||
appView.getView().setLayoutParams(new LinearLayout.LayoutParams(
|
||||
appView.getView().setLayoutParams(new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
1.0F));
|
||||
ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
// need to remove appView from any existing parent before invoking root.addView(appView)
|
||||
ViewParent parent = appView.getView().getParent();
|
||||
if ((parent != null) && (parent != root)) {
|
||||
LOG.d(TAG, "removing appView from existing parent");
|
||||
ViewGroup parentGroup = (ViewGroup) parent;
|
||||
parentGroup.removeView(appView.getView());
|
||||
setContentView(appView.getView());
|
||||
|
||||
if (preferences.contains("BackgroundColor")) {
|
||||
int backgroundColor = preferences.getInteger("BackgroundColor", Color.BLACK);
|
||||
// Background of activity:
|
||||
appView.getView().setBackgroundColor(backgroundColor);
|
||||
}
|
||||
root.addView(appView.getView());
|
||||
setContentView(root);
|
||||
|
||||
int backgroundColor = preferences.getInteger("BackgroundColor", Color.BLACK);
|
||||
root.setBackgroundColor(backgroundColor);
|
||||
appView.getView().requestFocusFromTouch();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -197,16 +195,11 @@ public class CordovaActivity extends Activity {
|
||||
* Override this to customize the webview that is used.
|
||||
*/
|
||||
protected CordovaWebView makeWebView() {
|
||||
String webViewClassName = preferences.getString("webView", AndroidWebView.class.getCanonicalName());
|
||||
CordovaWebView ret;
|
||||
try {
|
||||
Class<?> webViewClass = Class.forName(webViewClassName);
|
||||
Constructor<?> constructor = webViewClass.getConstructor(Context.class);
|
||||
ret = (CordovaWebView) constructor.newInstance((Context)this);
|
||||
return ret;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to create webview. ", e);
|
||||
}
|
||||
return new CordovaWebViewImpl(makeWebViewEngine());
|
||||
}
|
||||
|
||||
protected CordovaWebViewEngine makeWebViewEngine() {
|
||||
return CordovaWebViewImpl.createEngine(this, preferences);
|
||||
}
|
||||
|
||||
protected CordovaInterfaceImpl makeCordovaInterface() {
|
||||
@@ -241,13 +234,11 @@ public class CordovaActivity extends Activity {
|
||||
super.onPause();
|
||||
LOG.d(TAG, "Paused the activity.");
|
||||
|
||||
// Don't process pause if shutting down, since onDestroy() will be called
|
||||
if (this.activityState == ACTIVITY_EXITING) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.appView != null) {
|
||||
this.appView.handlePause(this.keepRunning);
|
||||
// CB-9382 If there is an activity that started for result and main activity is waiting for callback
|
||||
// result, we shoudn't stop WebView Javascript timers, as activity for result might be using them
|
||||
boolean keepRunning = this.keepRunning || this.cordovaInterface.activityResultCallback != null;
|
||||
this.appView.handlePause(keepRunning);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,11 +261,6 @@ public class CordovaActivity extends Activity {
|
||||
super.onResume();
|
||||
LOG.d(TAG, "Resumed the activity.");
|
||||
|
||||
if (this.activityState == ACTIVITY_STARTING) {
|
||||
this.activityState = ACTIVITY_RUNNING;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.appView == null) {
|
||||
return;
|
||||
}
|
||||
@@ -285,6 +271,34 @@ public class CordovaActivity extends Activity {
|
||||
this.appView.handleResume(this.keepRunning);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity is no longer visible to the user.
|
||||
*/
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
LOG.d(TAG, "Stopped the activity.");
|
||||
|
||||
if (this.appView == null) {
|
||||
return;
|
||||
}
|
||||
this.appView.handleStop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity is becoming visible to the user.
|
||||
*/
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
LOG.d(TAG, "Started the activity.");
|
||||
|
||||
if (this.appView == null) {
|
||||
return;
|
||||
}
|
||||
this.appView.handleStart();
|
||||
}
|
||||
|
||||
/**
|
||||
* The final call you receive before your activity is destroyed.
|
||||
*/
|
||||
@@ -296,22 +310,24 @@ public class CordovaActivity extends Activity {
|
||||
if (this.appView != null) {
|
||||
appView.handleDestroy();
|
||||
}
|
||||
else {
|
||||
this.activityState = ACTIVITY_EXITING;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* End this activity by calling finish for activity
|
||||
* Called when view focus is changed
|
||||
*/
|
||||
public void endActivity() {
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
this.activityState = ACTIVITY_EXITING;
|
||||
super.finish();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -350,7 +366,6 @@ public class CordovaActivity extends Activity {
|
||||
|
||||
// If errorUrl specified, then load it
|
||||
final String errorUrl = preferences.getString("errorUrl", null);
|
||||
CordovaUriHelper helper = new CordovaUriHelper(this.cordovaInterface, appView);
|
||||
if ((errorUrl != null) && (!failingUrl.equals(errorUrl)) && (appView != null)) {
|
||||
// Load URL on UI thread
|
||||
me.runOnUiThread(new Runnable() {
|
||||
@@ -390,7 +405,7 @@ public class CordovaActivity extends Activity {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
if (exit) {
|
||||
me.endActivity();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -438,10 +453,6 @@ public class CordovaActivity extends Activity {
|
||||
* @return Object or null
|
||||
*/
|
||||
public Object onMessage(String id, Object data) {
|
||||
if (!"onScrollChanged".equals(id)) {
|
||||
LOG.d(TAG, "onMessage(" + id + "," + data + ")");
|
||||
}
|
||||
|
||||
if ("onReceivedError".equals(id)) {
|
||||
JSONObject d = (JSONObject) data;
|
||||
try {
|
||||
@@ -450,14 +461,31 @@ public class CordovaActivity extends Activity {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else if ("exit".equals(id)) {
|
||||
this.endActivity();
|
||||
finish();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void onSaveInstanceState(Bundle outState)
|
||||
{
|
||||
super.onSaveInstanceState(outState);
|
||||
cordovaInterface.onSaveInstanceState(outState);
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the system when the device configuration changes while your activity is running.
|
||||
*
|
||||
* @param newConfig The new device configuration
|
||||
*/
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
if (this.appView == null) {
|
||||
return;
|
||||
}
|
||||
PluginManager pm = this.appView.getPluginManager();
|
||||
if (pm != null) {
|
||||
pm.onConfigurationChanged(newConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ package org.apache.cordova;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import org.apache.cordova.PluginManager;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
||||
@@ -36,15 +35,10 @@ public class CordovaBridge {
|
||||
private PluginManager pluginManager;
|
||||
private NativeToJsMessageQueue jsMessageQueue;
|
||||
private volatile int expectedBridgeSecret = -1; // written by UI thread, read by JS thread.
|
||||
private String loadedUrl;
|
||||
private String appContentUrlPrefix;
|
||||
protected CordovaUriHelper helper;
|
||||
|
||||
public CordovaBridge(PluginManager pluginManager, NativeToJsMessageQueue jsMessageQueue, String packageName, CordovaUriHelper helper) {
|
||||
public CordovaBridge(PluginManager pluginManager, NativeToJsMessageQueue jsMessageQueue) {
|
||||
this.pluginManager = pluginManager;
|
||||
this.jsMessageQueue = jsMessageQueue;
|
||||
this.appContentUrlPrefix = "content://" + packageName + ".";
|
||||
this.helper = helper;
|
||||
}
|
||||
|
||||
public String jsExec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
|
||||
@@ -113,6 +107,10 @@ public class CordovaBridge {
|
||||
expectedBridgeSecret = -1;
|
||||
}
|
||||
|
||||
public boolean isSecretEstablished() {
|
||||
return expectedBridgeSecret != -1;
|
||||
}
|
||||
|
||||
/** Called by cordova.js to initialize the bridge. */
|
||||
int generateBridgeSecret() {
|
||||
SecureRandom randGen = new SecureRandom();
|
||||
@@ -120,10 +118,9 @@ public class CordovaBridge {
|
||||
return expectedBridgeSecret;
|
||||
}
|
||||
|
||||
public void reset(String loadedUrl) {
|
||||
public void reset() {
|
||||
jsMessageQueue.reset();
|
||||
clearBridgeSecret();
|
||||
this.loadedUrl = loadedUrl;
|
||||
}
|
||||
|
||||
public String promptOnJsPrompt(String origin, String message, String defaultValue) {
|
||||
@@ -169,11 +166,8 @@ public class CordovaBridge {
|
||||
}
|
||||
else if (defaultValue != null && defaultValue.startsWith("gap_init:")) {
|
||||
// Protect against random iframes being able to talk through the bridge.
|
||||
// Trust only file URLs and pages which the app would have been allowed
|
||||
// to navigate to anyway.
|
||||
if (origin.startsWith("file:") ||
|
||||
origin.startsWith(this.appContentUrlPrefix) ||
|
||||
helper.shouldAllowNavigation(origin)) {
|
||||
// Trust only pages which the app would have been allowed to navigate to anyway.
|
||||
if (pluginManager.shouldAllowBridgeAccess(origin)) {
|
||||
// Enable the bridge
|
||||
int bridgeMode = Integer.parseInt(defaultValue.substring(9));
|
||||
jsMessageQueue.setBridgeMode(bridgeMode);
|
||||
@@ -187,8 +181,4 @@ public class CordovaBridge {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public NativeToJsMessageQueue getMessageQueue() {
|
||||
return jsMessageQueue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.view.KeyEvent;
|
||||
import android.widget.EditText;
|
||||
|
||||
/**
|
||||
* Helper class for WebViews to implement prompt(), alert(), confirm() dialogs.
|
||||
*/
|
||||
public class CordovaDialogsHelper {
|
||||
private final Context context;
|
||||
private AlertDialog lastHandledDialog;
|
||||
|
||||
public CordovaDialogsHelper(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void showAlert(String message, final Result result) {
|
||||
AlertDialog.Builder dlg = new AlertDialog.Builder(context);
|
||||
dlg.setMessage(message);
|
||||
dlg.setTitle("Alert");
|
||||
//Don't let alerts break the back button
|
||||
dlg.setCancelable(true);
|
||||
dlg.setPositiveButton(android.R.string.ok,
|
||||
new AlertDialog.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
result.gotResult(true, null);
|
||||
}
|
||||
});
|
||||
dlg.setOnCancelListener(
|
||||
new DialogInterface.OnCancelListener() {
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
result.gotResult(false, null);
|
||||
}
|
||||
});
|
||||
dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {
|
||||
//DO NOTHING
|
||||
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK)
|
||||
{
|
||||
result.gotResult(true, null);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return true;
|
||||
}
|
||||
});
|
||||
lastHandledDialog = dlg.show();
|
||||
}
|
||||
|
||||
public void showConfirm(String message, final Result result) {
|
||||
AlertDialog.Builder dlg = new AlertDialog.Builder(context);
|
||||
dlg.setMessage(message);
|
||||
dlg.setTitle("Confirm");
|
||||
dlg.setCancelable(true);
|
||||
dlg.setPositiveButton(android.R.string.ok,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
result.gotResult(true, null);
|
||||
}
|
||||
});
|
||||
dlg.setNegativeButton(android.R.string.cancel,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
result.gotResult(false, null);
|
||||
}
|
||||
});
|
||||
dlg.setOnCancelListener(
|
||||
new DialogInterface.OnCancelListener() {
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
result.gotResult(false, null);
|
||||
}
|
||||
});
|
||||
dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {
|
||||
//DO NOTHING
|
||||
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK)
|
||||
{
|
||||
result.gotResult(false, null);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return true;
|
||||
}
|
||||
});
|
||||
lastHandledDialog = dlg.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the client to display a prompt dialog to the user.
|
||||
* If the client returns true, WebView will assume that the client will
|
||||
* handle the prompt dialog and call the appropriate JsPromptResult method.
|
||||
*
|
||||
* Since we are hacking prompts for our own purposes, we should not be using them for
|
||||
* this purpose, perhaps we should hack console.log to do this instead!
|
||||
*/
|
||||
public void showPrompt(String message, String defaultValue, final Result result) {
|
||||
// Returning false would also show a dialog, but the default one shows the origin (ugly).
|
||||
AlertDialog.Builder dlg = new AlertDialog.Builder(context);
|
||||
dlg.setMessage(message);
|
||||
final EditText input = new EditText(context);
|
||||
if (defaultValue != null) {
|
||||
input.setText(defaultValue);
|
||||
}
|
||||
dlg.setView(input);
|
||||
dlg.setCancelable(false);
|
||||
dlg.setPositiveButton(android.R.string.ok,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
String userText = input.getText().toString();
|
||||
result.gotResult(true, userText);
|
||||
}
|
||||
});
|
||||
dlg.setNegativeButton(android.R.string.cancel,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
result.gotResult(false, null);
|
||||
}
|
||||
});
|
||||
lastHandledDialog = dlg.show();
|
||||
}
|
||||
|
||||
public void destroyLastDialog(){
|
||||
if (lastHandledDialog != null){
|
||||
lastHandledDialog.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public interface Result {
|
||||
public void gotResult(boolean success, String value);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
package org.apache.cordova;
|
||||
|
||||
import android.app.Activity;
|
||||
@@ -17,8 +36,9 @@ public class CordovaInterfaceImpl implements CordovaInterface {
|
||||
protected ExecutorService threadPool;
|
||||
protected PluginManager pluginManager;
|
||||
|
||||
protected ActivityResultHolder savedResult;
|
||||
protected CordovaPlugin activityResultCallback;
|
||||
protected String initCallbackClass;
|
||||
protected String initCallbackService;
|
||||
protected int activityResultRequestCode;
|
||||
|
||||
public CordovaInterfaceImpl(Activity activity) {
|
||||
@@ -30,10 +50,6 @@ public class CordovaInterfaceImpl implements CordovaInterface {
|
||||
this.threadPool = threadPool;
|
||||
}
|
||||
|
||||
public void setPluginManager(PluginManager pluginManager) {
|
||||
this.pluginManager = pluginManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startActivityForResult(CordovaPlugin command, Intent intent, int requestCode) {
|
||||
setActivityResultCallback(command);
|
||||
@@ -72,25 +88,39 @@ public class CordovaInterfaceImpl implements CordovaInterface {
|
||||
return threadPool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches any pending onActivityResult callbacks.
|
||||
*/
|
||||
public void onCordovaInit(PluginManager pluginManager) {
|
||||
this.pluginManager = pluginManager;
|
||||
if (savedResult != null) {
|
||||
onActivityResult(savedResult.requestCode, savedResult.resultCode, savedResult.intent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Routes the result to the awaiting plugin. Returns false if no plugin was waiting.
|
||||
*/
|
||||
public boolean onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||
CordovaPlugin callback = activityResultCallback;
|
||||
if(callback == null && initCallbackClass != null) {
|
||||
if(callback == null && initCallbackService != null) {
|
||||
// The application was restarted, but had defined an initial callback
|
||||
// before being shut down.
|
||||
callback = pluginManager.getPlugin(initCallbackClass);
|
||||
savedResult = new ActivityResultHolder(requestCode, resultCode, intent);
|
||||
if (pluginManager != null) {
|
||||
callback = pluginManager.getPlugin(initCallbackService);
|
||||
}
|
||||
}
|
||||
initCallbackClass = null;
|
||||
activityResultCallback = null;
|
||||
|
||||
if (callback != null) {
|
||||
Log.d(TAG, "Sending activity result to plugin");
|
||||
initCallbackService = null;
|
||||
savedResult = null;
|
||||
callback.onActivityResult(requestCode, resultCode, intent);
|
||||
return true;
|
||||
}
|
||||
Log.w(TAG, "Got an activity result, but no plugin was registered to receive it.");
|
||||
Log.w(TAG, "Got an activity result, but no plugin was registered to receive it" + (savedResult != null ? " yet!": "."));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -108,8 +138,8 @@ public class CordovaInterfaceImpl implements CordovaInterface {
|
||||
*/
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
if (activityResultCallback != null) {
|
||||
String cClass = activityResultCallback.getClass().getName();
|
||||
outState.putString("callbackClass", cClass);
|
||||
String serviceName = activityResultCallback.getServiceName();
|
||||
outState.putString("callbackService", serviceName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +147,18 @@ public class CordovaInterfaceImpl implements CordovaInterface {
|
||||
* Call this from onCreate() so that any saved startActivityForResult parameters will be restored.
|
||||
*/
|
||||
public void restoreInstanceState(Bundle savedInstanceState) {
|
||||
initCallbackClass = savedInstanceState.getString("callbackClass");
|
||||
initCallbackService = savedInstanceState.getString("callbackService");
|
||||
}
|
||||
|
||||
private static class ActivityResultHolder {
|
||||
private int requestCode;
|
||||
private int resultCode;
|
||||
private Intent intent;
|
||||
|
||||
public ActivityResultHolder(int requestCode, int resultCode, Intent intent) {
|
||||
this.requestCode = requestCode;
|
||||
this.resultCode = resultCode;
|
||||
this.intent = intent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,12 @@ import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Plugins must extend this class and override one of the execute methods.
|
||||
*/
|
||||
@@ -35,13 +39,15 @@ public class CordovaPlugin {
|
||||
public CordovaWebView webView;
|
||||
public CordovaInterface cordova;
|
||||
protected CordovaPreferences preferences;
|
||||
private String serviceName;
|
||||
|
||||
/**
|
||||
* Call this after constructing to initialize the plugin.
|
||||
* Final because we want to be able to change args without breaking plugins.
|
||||
*/
|
||||
public final void privateInitialize(CordovaInterface cordova, CordovaWebView webView, CordovaPreferences preferences) {
|
||||
public final void privateInitialize(String serviceName, CordovaInterface cordova, CordovaWebView webView, CordovaPreferences preferences) {
|
||||
assert this.cordova == null;
|
||||
this.serviceName = serviceName;
|
||||
this.cordova = cordova;
|
||||
this.webView = webView;
|
||||
this.preferences = preferences;
|
||||
@@ -62,6 +68,13 @@ public class CordovaPlugin {
|
||||
*/
|
||||
protected void pluginInitialize() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the plugin's service name (what you'd use when calling pluginManger.getPlugin())
|
||||
*/
|
||||
public String getServiceName() {
|
||||
return serviceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the request.
|
||||
@@ -135,6 +148,18 @@ public class CordovaPlugin {
|
||||
public void onResume(boolean multitasking) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity is becoming visible to the user.
|
||||
*/
|
||||
public void onStart() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity is no longer visible to the user.
|
||||
*/
|
||||
public void onStop() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity receives a new intent.
|
||||
*/
|
||||
@@ -192,7 +217,8 @@ public class CordovaPlugin {
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for blocking navigation by the Cordova WebView.
|
||||
* Hook for blocking navigation by the Cordova WebView. This applies both to top-level and
|
||||
* iframe navigations.
|
||||
*
|
||||
* This will be called when the WebView's needs to know whether to navigate
|
||||
* to a new page. Return false to block the navigation: if any plugin
|
||||
@@ -204,6 +230,15 @@ public class CordovaPlugin {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for allowing page to call exec(). By default, this returns the result of
|
||||
* shouldAllowNavigation(). It's generally unsafe to allow untrusted content to be loaded
|
||||
* into a CordovaWebView, even within an iframe, so it's best not to touch this.
|
||||
*/
|
||||
public Boolean shouldAllowBridgeAccess(String url) {
|
||||
return shouldAllowNavigation(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for blocking the launching of Intents by the Cordova application.
|
||||
*
|
||||
@@ -219,7 +254,7 @@ public class CordovaPlugin {
|
||||
}
|
||||
|
||||
/**
|
||||
* By specifying a <url-filter> in config.xml you can map a URL (using startsWith atm) to this method.
|
||||
* Allows plugins to handle a link being clicked. Return true here to cancel the navigation.
|
||||
*
|
||||
* @param url The URL that is trying to be loaded in the Cordova webview.
|
||||
* @return Return true to prevent the URL from loading. Default is false.
|
||||
@@ -230,11 +265,53 @@ public class CordovaPlugin {
|
||||
|
||||
/**
|
||||
* Hook for redirecting requests. Applies to WebView requests as well as requests made by plugins.
|
||||
* To handle the request directly, return a URI in the form:
|
||||
*
|
||||
* cdvplugin://pluginId/...
|
||||
*
|
||||
* And implement handleOpenForRead().
|
||||
* To make this easier, use the toPluginUri() and fromPluginUri() helpers:
|
||||
*
|
||||
* public Uri remapUri(Uri uri) { return toPluginUri(uri); }
|
||||
*
|
||||
* public CordovaResourceApi.OpenForReadResult handleOpenForRead(Uri uri) throws IOException {
|
||||
* Uri origUri = fromPluginUri(uri);
|
||||
* ...
|
||||
* }
|
||||
*/
|
||||
public Uri remapUri(Uri uri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called to handle CordovaResourceApi.openForRead() calls for a cdvplugin://pluginId/ URL.
|
||||
* Should never return null.
|
||||
* Added in cordova-android@4.0.0
|
||||
*/
|
||||
public CordovaResourceApi.OpenForReadResult handleOpenForRead(Uri uri) throws IOException {
|
||||
throw new FileNotFoundException("Plugin can't handle uri: " + uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refer to remapUri()
|
||||
* Added in cordova-android@4.0.0
|
||||
*/
|
||||
protected Uri toPluginUri(Uri origUri) {
|
||||
return new Uri.Builder()
|
||||
.scheme(CordovaResourceApi.PLUGIN_URI_SCHEME)
|
||||
.authority(serviceName)
|
||||
.appendQueryParameter("origUri", origUri.toString())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Refer to remapUri()
|
||||
* Added in cordova-android@4.0.0
|
||||
*/
|
||||
protected Uri fromPluginUri(Uri pluginUri) {
|
||||
return Uri.parse(pluginUri.getQueryParameter("origUri"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the WebView does a top-level navigation or refreshes.
|
||||
*
|
||||
@@ -274,4 +351,12 @@ public class CordovaPlugin {
|
||||
public boolean onReceivedClientCertRequest(CordovaWebView view, ICordovaClientCertRequest request) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the system when the device configuration changes while your activity is running.
|
||||
*
|
||||
* @param newConfig The new device configuration
|
||||
*/
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,30 +61,21 @@ public class CordovaPreferences {
|
||||
String value = prefs.get(name);
|
||||
if (value != null) {
|
||||
return Boolean.parseBoolean(value);
|
||||
} else if (preferencesBundleExtras != null) {
|
||||
Object bundleValue = preferencesBundleExtras.get(name);
|
||||
if (bundleValue instanceof String) {
|
||||
return "true".equals(bundleValue);
|
||||
}
|
||||
// Gives a nice warning if type is wrong.
|
||||
return preferencesBundleExtras.getBoolean(name, defaultValue);
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// Added in 4.0.0
|
||||
public boolean contains(String name) {
|
||||
return getString(name, null) != null;
|
||||
}
|
||||
|
||||
public int getInteger(String name, int defaultValue) {
|
||||
name = name.toLowerCase(Locale.ENGLISH);
|
||||
String value = prefs.get(name);
|
||||
if (value != null) {
|
||||
// Use Integer.decode() can't handle it if the highest bit is set.
|
||||
return (int)(long)Long.decode(value);
|
||||
} else if (preferencesBundleExtras != null) {
|
||||
Object bundleValue = preferencesBundleExtras.get(name);
|
||||
if (bundleValue instanceof String) {
|
||||
return Integer.valueOf((String)bundleValue);
|
||||
}
|
||||
// Gives a nice warning if type is wrong.
|
||||
return preferencesBundleExtras.getInt(name, defaultValue);
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
@@ -94,13 +85,6 @@ public class CordovaPreferences {
|
||||
String value = prefs.get(name);
|
||||
if (value != null) {
|
||||
return Double.valueOf(value);
|
||||
} else if (preferencesBundleExtras != null) {
|
||||
Object bundleValue = preferencesBundleExtras.get(name);
|
||||
if (bundleValue instanceof String) {
|
||||
return Double.valueOf((String)bundleValue);
|
||||
}
|
||||
// Gives a nice warning if type is wrong.
|
||||
return preferencesBundleExtras.getDouble(name, defaultValue);
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
@@ -110,69 +94,8 @@ public class CordovaPreferences {
|
||||
String value = prefs.get(name);
|
||||
if (value != null) {
|
||||
return value;
|
||||
} else if (preferencesBundleExtras != null && !"errorurl".equals(name)) {
|
||||
Object bundleValue = preferencesBundleExtras.get(name);
|
||||
if (bundleValue != null) {
|
||||
return bundleValue.toString();
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// Plugins should not rely on values within the intent since this does not work
|
||||
// for apps with multiple webviews. Instead, they should retrieve prefs from the
|
||||
// Config object associated with their webview.
|
||||
public void copyIntoIntentExtras(Activity action) {
|
||||
for (String name : prefs.keySet()) {
|
||||
String value = prefs.get(name);
|
||||
if (value == null) {
|
||||
continue;
|
||||
}
|
||||
if (name.equals("loglevel")) {
|
||||
LOG.setLogLevel(value);
|
||||
} else if (name.equals("splashscreen")) {
|
||||
// Note: We should probably pass in the classname for the variable splash on splashscreen!
|
||||
int resource = action.getResources().getIdentifier(value, "drawable", action.getClass().getPackage().getName());
|
||||
if(resource == 0) {
|
||||
resource = action.getResources().getIdentifier(value, "drawable", action.getPackageName());
|
||||
}
|
||||
action.getIntent().putExtra(name, resource);
|
||||
}
|
||||
else if(name.equals("backgroundcolor")) {
|
||||
int asInt = (int)(long)Long.decode(value);
|
||||
action.getIntent().putExtra(name, asInt);
|
||||
}
|
||||
else if(name.equals("loadurltimeoutvalue")) {
|
||||
int asInt = Integer.decode(value);
|
||||
action.getIntent().putExtra(name, asInt);
|
||||
}
|
||||
else if(name.equals("splashscreendelay")) {
|
||||
int asInt = Integer.decode(value);
|
||||
action.getIntent().putExtra(name, asInt);
|
||||
}
|
||||
else if(name.equals("keeprunning"))
|
||||
{
|
||||
boolean asBool = Boolean.parseBoolean(value);
|
||||
action.getIntent().putExtra(name, asBool);
|
||||
}
|
||||
else if(name.equals("inappbrowserstorageenabled"))
|
||||
{
|
||||
boolean asBool = Boolean.parseBoolean(value);
|
||||
action.getIntent().putExtra(name, asBool);
|
||||
}
|
||||
else if(name.equals("disallowoverscroll"))
|
||||
{
|
||||
boolean asBool = Boolean.parseBoolean(value);
|
||||
action.getIntent().putExtra(name, asBool);
|
||||
}
|
||||
else
|
||||
{
|
||||
action.getIntent().putExtra(name, value);
|
||||
}
|
||||
}
|
||||
// In the normal case, the intent extras are null until the first call to putExtra().
|
||||
if (preferencesBundleExtras == null) {
|
||||
preferencesBundleExtras = action.getIntent().getExtras();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,6 @@ import android.os.Looper;
|
||||
import android.util.Base64;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import org.apache.http.util.EncodingUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
@@ -38,6 +36,7 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.channels.FileChannel;
|
||||
@@ -72,8 +71,11 @@ public class CordovaResourceApi {
|
||||
public static final int URI_TYPE_DATA = 4;
|
||||
public static final int URI_TYPE_HTTP = 5;
|
||||
public static final int URI_TYPE_HTTPS = 6;
|
||||
public static final int URI_TYPE_PLUGIN = 7;
|
||||
public static final int URI_TYPE_UNKNOWN = -1;
|
||||
|
||||
|
||||
public static final String PLUGIN_URI_SCHEME = "cdvplugin";
|
||||
|
||||
private static final String[] LOCAL_FILE_PROJECTION = { "_data" };
|
||||
|
||||
public static Thread jsThread;
|
||||
@@ -102,27 +104,30 @@ public class CordovaResourceApi {
|
||||
public static int getUriType(Uri uri) {
|
||||
assertNonRelative(uri);
|
||||
String scheme = uri.getScheme();
|
||||
if (ContentResolver.SCHEME_CONTENT.equals(scheme)) {
|
||||
if (ContentResolver.SCHEME_CONTENT.equalsIgnoreCase(scheme)) {
|
||||
return URI_TYPE_CONTENT;
|
||||
}
|
||||
if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) {
|
||||
if (ContentResolver.SCHEME_ANDROID_RESOURCE.equalsIgnoreCase(scheme)) {
|
||||
return URI_TYPE_RESOURCE;
|
||||
}
|
||||
if (ContentResolver.SCHEME_FILE.equals(scheme)) {
|
||||
if (ContentResolver.SCHEME_FILE.equalsIgnoreCase(scheme)) {
|
||||
if (uri.getPath().startsWith("/android_asset/")) {
|
||||
return URI_TYPE_ASSET;
|
||||
}
|
||||
return URI_TYPE_FILE;
|
||||
}
|
||||
if ("data".equals(scheme)) {
|
||||
if ("data".equalsIgnoreCase(scheme)) {
|
||||
return URI_TYPE_DATA;
|
||||
}
|
||||
if ("http".equals(scheme)) {
|
||||
if ("http".equalsIgnoreCase(scheme)) {
|
||||
return URI_TYPE_HTTP;
|
||||
}
|
||||
if ("https".equals(scheme)) {
|
||||
if ("https".equalsIgnoreCase(scheme)) {
|
||||
return URI_TYPE_HTTPS;
|
||||
}
|
||||
if (PLUGIN_URI_SCHEME.equalsIgnoreCase(scheme)) {
|
||||
return URI_TYPE_PLUGIN;
|
||||
}
|
||||
return URI_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
@@ -183,7 +188,11 @@ public class CordovaResourceApi {
|
||||
HttpURLConnection conn = (HttpURLConnection)new URL(uri.toString()).openConnection();
|
||||
conn.setDoInput(false);
|
||||
conn.setRequestMethod("HEAD");
|
||||
return conn.getHeaderField("Content-Type");
|
||||
String mimeType = conn.getHeaderField("Content-Type");
|
||||
if (mimeType != null) {
|
||||
mimeType = mimeType.split(";")[0];
|
||||
}
|
||||
return mimeType;
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
@@ -278,10 +287,21 @@ public class CordovaResourceApi {
|
||||
HttpURLConnection conn = (HttpURLConnection)new URL(uri.toString()).openConnection();
|
||||
conn.setDoInput(true);
|
||||
String mimeType = conn.getHeaderField("Content-Type");
|
||||
if (mimeType != null) {
|
||||
mimeType = mimeType.split(";")[0];
|
||||
}
|
||||
int length = conn.getContentLength();
|
||||
InputStream inputStream = conn.getInputStream();
|
||||
return new OpenForReadResult(uri, inputStream, mimeType, length, null);
|
||||
}
|
||||
case URI_TYPE_PLUGIN: {
|
||||
String pluginId = uri.getHost();
|
||||
CordovaPlugin plugin = pluginManager.getPlugin(pluginId);
|
||||
if (plugin == null) {
|
||||
throw new FileNotFoundException("Invalid plugin ID in URI: " + uri);
|
||||
}
|
||||
return plugin.handleOpenForRead(uri);
|
||||
}
|
||||
}
|
||||
throw new FileNotFoundException("URI not supported by CordovaResourceApi: " + uri);
|
||||
}
|
||||
@@ -336,7 +356,10 @@ public class CordovaResourceApi {
|
||||
if (input.assetFd != null) {
|
||||
offset = input.assetFd.getStartOffset();
|
||||
}
|
||||
outChannel.transferFrom(inChannel, offset, length);
|
||||
// transferFrom()'s 2nd arg is a relative position. Need to set the absolute
|
||||
// position first.
|
||||
inChannel.position(offset);
|
||||
outChannel.transferFrom(inChannel, 0, length);
|
||||
} else {
|
||||
final int BUFFER_SIZE = 8192;
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
@@ -410,7 +433,16 @@ public class CordovaResourceApi {
|
||||
}
|
||||
}
|
||||
String dataPartAsString = uriAsString.substring(commaPos + 1);
|
||||
byte[] data = base64 ? Base64.decode(dataPartAsString, Base64.DEFAULT) : EncodingUtils.getBytes(dataPartAsString, "UTF-8");
|
||||
byte[] data;
|
||||
if (base64) {
|
||||
data = Base64.decode(dataPartAsString, Base64.DEFAULT);
|
||||
} else {
|
||||
try {
|
||||
data = dataPartAsString.getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
data = dataPartAsString.getBytes();
|
||||
}
|
||||
}
|
||||
InputStream inputStream = new ByteArrayInputStream(data);
|
||||
return new OpenForReadResult(uri, inputStream, contentType, data.length, null);
|
||||
}
|
||||
@@ -428,7 +460,7 @@ public class CordovaResourceApi {
|
||||
public final long length;
|
||||
public final AssetFileDescriptor assetFd;
|
||||
|
||||
OpenForReadResult(Uri uri, InputStream inputStream, String mimeType, long length, AssetFileDescriptor assetFd) {
|
||||
public OpenForReadResult(Uri uri, InputStream inputStream, String mimeType, long length, AssetFileDescriptor assetFd) {
|
||||
this.uri = uri;
|
||||
this.inputStream = inputStream;
|
||||
this.mimeType = mimeType;
|
||||
|
||||
@@ -1,146 +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.
|
||||
*/
|
||||
|
||||
package org.apache.cordova;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.webkit.WebView;
|
||||
|
||||
public class CordovaUriHelper {
|
||||
|
||||
private static final String TAG = "CordovaUriHelper";
|
||||
|
||||
private CordovaWebView appView;
|
||||
private CordovaInterface cordova;
|
||||
|
||||
public CordovaUriHelper(CordovaInterface cdv, CordovaWebView webView)
|
||||
{
|
||||
appView = webView;
|
||||
cordova = cdv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the webview should be allowed to navigate to a given URL.
|
||||
*
|
||||
* This method implements the default whitelist policy when no plugins override
|
||||
* shouldAllowNavigation
|
||||
*/
|
||||
public boolean shouldAllowNavigation(String url) {
|
||||
Boolean pluginManagerAllowsNavigation = this.appView.getPluginManager().shouldAllowNavigation(url);
|
||||
if (pluginManagerAllowsNavigation == null) {
|
||||
// Default policy:
|
||||
// Internal urls on file:// or data:// that do not contain "/app_webview/" are allowed for navigation
|
||||
if(url.startsWith("file://") || url.startsWith("data:"))
|
||||
{
|
||||
//This directory on WebKit/Blink based webviews contains SQLite databases!
|
||||
//DON'T CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING!
|
||||
return !url.contains("/app_webview/");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return pluginManagerAllowsNavigation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the webview should be allowed to launch an intent for a given URL.
|
||||
*
|
||||
* This method implements the default whitelist policy when no plugins override
|
||||
* shouldOpenExternalUrl
|
||||
*/
|
||||
public boolean shouldOpenExternalUrl(String url) {
|
||||
Boolean pluginManagerAllowsExternalUrl = this.appView.getPluginManager().shouldOpenExternalUrl(url);
|
||||
if (pluginManagerAllowsExternalUrl == null) {
|
||||
// Default policy:
|
||||
// External URLs are not allowed
|
||||
return false;
|
||||
}
|
||||
return pluginManagerAllowsExternalUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the webview should be allowed to request a resource from a given URL.
|
||||
*
|
||||
* This method implements the default whitelist policy when no plugins override
|
||||
* shouldAllowRequest
|
||||
*/
|
||||
public boolean shouldAllowRequest(String url) {
|
||||
|
||||
Boolean pluginManagerAllowsRequest = this.appView.getPluginManager().shouldAllowRequest(url);
|
||||
if (pluginManagerAllowsRequest == null) {
|
||||
// Default policy:
|
||||
// Internal urls on file:// or data:// that do not contain "/app_webview/" are allowed for navigation
|
||||
if(url.startsWith("file://") || url.startsWith("data:"))
|
||||
{
|
||||
//This directory on WebKit/Blink based webviews contains SQLite databases!
|
||||
//DON'T CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING!
|
||||
return !url.contains("/app_webview/");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return pluginManagerAllowsRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Give the host application a chance to take over the control when a new url
|
||||
* is about to be loaded in the current WebView.
|
||||
*
|
||||
* This method implements the default whitelist policy when no plugins override
|
||||
* the whitelist methods:
|
||||
* Internal urls on file:// or data:// that do not contain "app_webview" are allowed for navigation
|
||||
* External urls are not allowed.
|
||||
*
|
||||
* @param view The WebView that is initiating the callback.
|
||||
* @param url The url to be loaded.
|
||||
* @return true to override, false for default behavior
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
|
||||
public boolean shouldOverrideUrlLoading(String url) {
|
||||
// Give plugins the chance to handle the url
|
||||
if (shouldAllowNavigation(url)) {
|
||||
// Allow internal navigation
|
||||
return false;
|
||||
}
|
||||
if (shouldOpenExternalUrl(url)) {
|
||||
// Do nothing other than what the plugins wanted.
|
||||
// If any returned false, then the request was either blocked
|
||||
// completely, or handled out-of-band by the plugin. If they all
|
||||
// returned true, then we should open the URL here.
|
||||
try {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(url));
|
||||
intent.addCategory(Intent.CATEGORY_BROWSABLE);
|
||||
intent.setComponent(null);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
|
||||
intent.setSelector(null);
|
||||
}
|
||||
this.cordova.getActivity().startActivity(intent);
|
||||
return true;
|
||||
} catch (android.content.ActivityNotFoundException e) {
|
||||
Log.e(TAG, "Error loading url " + url, e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// Block by default
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,41 @@
|
||||
/*
|
||||
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 java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.view.View;
|
||||
import android.webkit.WebChromeClient.CustomViewCallback;
|
||||
|
||||
/**
|
||||
* Main interface for interacting with a Cordova webview - implemented by CordovaWebViewImpl.
|
||||
* This is an interface so that it can be easily mocked in tests.
|
||||
* Methods may be added to this interface without a major version bump, as plugins & embedders
|
||||
* are not expected to implement it.
|
||||
*/
|
||||
public interface CordovaWebView {
|
||||
public static final String CORDOVA_VERSION = "4.0.0-dev";
|
||||
public static final String CORDOVA_VERSION = "4.1.1";
|
||||
|
||||
void init(CordovaInterface cordova, List<PluginEntry> pluginEntries,
|
||||
CordovaPreferences preferences);
|
||||
void init(CordovaInterface cordova, List<PluginEntry> pluginEntries, CordovaPreferences preferences);
|
||||
|
||||
boolean isInitialized();
|
||||
|
||||
View getView();
|
||||
|
||||
@@ -23,6 +45,10 @@ public interface CordovaWebView {
|
||||
|
||||
boolean canGoBack();
|
||||
|
||||
void clearCache();
|
||||
|
||||
/** Use parameter-less overload */
|
||||
@Deprecated
|
||||
void clearCache(boolean b);
|
||||
|
||||
void clearHistory();
|
||||
@@ -35,13 +61,15 @@ public interface CordovaWebView {
|
||||
|
||||
void handleResume(boolean keepRunning);
|
||||
|
||||
void handleStart();
|
||||
|
||||
void handleStop();
|
||||
|
||||
void handleDestroy();
|
||||
|
||||
/**
|
||||
* Send JavaScript statement back to JavaScript.
|
||||
* (This is a convenience method)
|
||||
*
|
||||
* @param statement
|
||||
* Deprecated (https://issues.apache.org/jira/browse/CB-6851)
|
||||
* Instead of executing snippets of JS, you should use the exec bridge
|
||||
* to create a Java->JS communication channel.
|
||||
@@ -63,12 +91,34 @@ public interface CordovaWebView {
|
||||
@Deprecated
|
||||
void sendJavascript(String statememt);
|
||||
|
||||
void showWebPage(String errorUrl, boolean b, boolean c, HashMap<String, Object> params);
|
||||
/**
|
||||
* Load the specified URL in the Cordova webview or a new browser instance.
|
||||
*
|
||||
* NOTE: If openExternal is false, only whitelisted URLs can be loaded.
|
||||
*
|
||||
* @param url The url to load.
|
||||
* @param openExternal Load url in browser instead of Cordova webview.
|
||||
* @param clearHistory Clear the history stack, so new page becomes top of history
|
||||
* @param params Parameters for new app
|
||||
*/
|
||||
void showWebPage(String url, boolean openExternal, boolean clearHistory, Map<String, Object> params);
|
||||
|
||||
/**
|
||||
* Deprecated in 4.0.0. Use your own View-toggling logic.
|
||||
*/
|
||||
@Deprecated
|
||||
boolean isCustomViewShowing();
|
||||
|
||||
/**
|
||||
* Deprecated in 4.0.0. Use your own View-toggling logic.
|
||||
*/
|
||||
@Deprecated
|
||||
void showCustomView(View view, CustomViewCallback callback);
|
||||
|
||||
/**
|
||||
* Deprecated in 4.0.0. Use your own View-toggling logic.
|
||||
*/
|
||||
@Deprecated
|
||||
void hideCustomView();
|
||||
|
||||
CordovaResourceApi getResourceApi();
|
||||
@@ -79,12 +129,10 @@ public interface CordovaWebView {
|
||||
void sendPluginResult(PluginResult cr, String callbackId);
|
||||
|
||||
PluginManager getPluginManager();
|
||||
|
||||
CordovaWebViewEngine getEngine();
|
||||
CordovaPreferences getPreferences();
|
||||
ICordovaCookieManager getCookieManager();
|
||||
|
||||
void setNetworkAvailable(boolean online);
|
||||
|
||||
String getUrl();
|
||||
|
||||
// TODO: Work on deleting these by removing refs from plugins.
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* Interfcae for all Cordova engines.
|
||||
* No methods will be added to this class (in order to be compatible with existing engines).
|
||||
* Instead, we will create a new interface: e.g. CordovaWebViewEngineV2
|
||||
*/
|
||||
public interface CordovaWebViewEngine {
|
||||
void init(CordovaWebView parentWebView, CordovaInterface cordova, Client client,
|
||||
CordovaResourceApi resourceApi, PluginManager pluginManager,
|
||||
NativeToJsMessageQueue nativeToJsMessageQueue);
|
||||
|
||||
CordovaWebView getCordovaWebView();
|
||||
ICordovaCookieManager getCookieManager();
|
||||
View getView();
|
||||
|
||||
void loadUrl(String url, boolean clearNavigationStack);
|
||||
|
||||
void stopLoading();
|
||||
|
||||
/** Return the currently loaded URL */
|
||||
String getUrl();
|
||||
|
||||
void clearCache();
|
||||
|
||||
/** After calling clearHistory(), canGoBack() should be false. */
|
||||
void clearHistory();
|
||||
|
||||
boolean canGoBack();
|
||||
|
||||
/** Returns whether a navigation occurred */
|
||||
boolean goBack();
|
||||
|
||||
/** Pauses / resumes the WebView's event loop. */
|
||||
void setPaused(boolean value);
|
||||
|
||||
/** Clean up all resources associated with the WebView. */
|
||||
void destroy();
|
||||
|
||||
/**
|
||||
* Used to retrieve the associated CordovaWebView given a View without knowing the type of Engine.
|
||||
* E.g. ((CordovaWebView.EngineView)activity.findViewById(android.R.id.webView)).getCordovaWebView();
|
||||
*/
|
||||
public interface EngineView {
|
||||
CordovaWebView getCordovaWebView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains methods that an engine uses to communicate with the parent CordovaWebView.
|
||||
* Methods may be added in future cordova versions, but never removed.
|
||||
*/
|
||||
public interface Client {
|
||||
Boolean onDispatchKeyEvent(KeyEvent event);
|
||||
void clearLoadTimeoutTimer();
|
||||
void onPageStarted(String newUrl);
|
||||
void onReceivedError(int errorCode, String description, String failingUrl);
|
||||
void onPageFinishedLoading(String url);
|
||||
boolean onNavigationAttempt(String url);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,609 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import org.apache.cordova.engine.SystemWebViewEngine;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Main class for interacting with a Cordova webview. Manages plugins, events, and a CordovaWebViewEngine.
|
||||
* Class uses two-phase initialization. You must call init() before calling any other methods.
|
||||
*/
|
||||
public class CordovaWebViewImpl implements CordovaWebView {
|
||||
|
||||
public static final String TAG = "CordovaWebViewImpl";
|
||||
|
||||
private PluginManager pluginManager;
|
||||
|
||||
protected final CordovaWebViewEngine engine;
|
||||
private CordovaInterface cordova;
|
||||
|
||||
// Flag to track that a loadUrl timeout occurred
|
||||
private int loadUrlTimeout = 0;
|
||||
|
||||
private CordovaResourceApi resourceApi;
|
||||
private CordovaPreferences preferences;
|
||||
private CoreAndroid appPlugin;
|
||||
private NativeToJsMessageQueue nativeToJsMessageQueue;
|
||||
private EngineClient engineClient = new EngineClient();
|
||||
private boolean hasPausedEver;
|
||||
|
||||
// The URL passed to loadUrl(), not necessarily the URL of the current page.
|
||||
String loadedUrl;
|
||||
|
||||
/** custom view created by the browser (a video player for example) */
|
||||
private View mCustomView;
|
||||
private WebChromeClient.CustomViewCallback mCustomViewCallback;
|
||||
|
||||
private Set<Integer> boundKeyCodes = new HashSet<Integer>();
|
||||
|
||||
public static CordovaWebViewEngine createEngine(Context context, CordovaPreferences preferences) {
|
||||
String className = preferences.getString("webview", SystemWebViewEngine.class.getCanonicalName());
|
||||
try {
|
||||
Class<?> webViewClass = Class.forName(className);
|
||||
Constructor<?> constructor = webViewClass.getConstructor(Context.class, CordovaPreferences.class);
|
||||
return (CordovaWebViewEngine) constructor.newInstance(context, preferences);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to create webview. ", e);
|
||||
}
|
||||
}
|
||||
|
||||
public CordovaWebViewImpl(CordovaWebViewEngine cordovaWebViewEngine) {
|
||||
this.engine = cordovaWebViewEngine;
|
||||
}
|
||||
|
||||
// Convenience method for when creating programmatically (not from Config.xml).
|
||||
public void init(CordovaInterface cordova) {
|
||||
init(cordova, new ArrayList<PluginEntry>(), new CordovaPreferences());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(CordovaInterface cordova, List<PluginEntry> pluginEntries, CordovaPreferences preferences) {
|
||||
if (this.cordova != null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
this.cordova = cordova;
|
||||
this.preferences = preferences;
|
||||
pluginManager = new PluginManager(this, this.cordova, pluginEntries);
|
||||
resourceApi = new CordovaResourceApi(engine.getView().getContext(), pluginManager);
|
||||
nativeToJsMessageQueue = new NativeToJsMessageQueue();
|
||||
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.NoOpBridgeMode());
|
||||
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.LoadUrlBridgeMode(engine, cordova));
|
||||
|
||||
if (preferences.getBoolean("DisallowOverscroll", false)) {
|
||||
engine.getView().setOverScrollMode(View.OVER_SCROLL_NEVER);
|
||||
}
|
||||
engine.init(this, cordova, engineClient, resourceApi, pluginManager, nativeToJsMessageQueue);
|
||||
// This isn't enforced by the compiler, so assert here.
|
||||
assert engine.getView() instanceof CordovaWebViewEngine.EngineView;
|
||||
|
||||
pluginManager.addService(CoreAndroid.PLUGIN_NAME, "org.apache.cordova.CoreAndroid");
|
||||
pluginManager.init();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return cordova != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadUrlIntoView(final String url, boolean recreatePlugins) {
|
||||
LOG.d(TAG, ">>> loadUrl(" + url + ")");
|
||||
if (url.equals("about:blank") || url.startsWith("javascript:")) {
|
||||
engine.loadUrl(url, false);
|
||||
return;
|
||||
}
|
||||
|
||||
recreatePlugins = recreatePlugins || (loadedUrl == null);
|
||||
|
||||
if (recreatePlugins) {
|
||||
// Don't re-initialize on first load.
|
||||
if (loadedUrl != null) {
|
||||
pluginManager.init();
|
||||
}
|
||||
loadedUrl = url;
|
||||
}
|
||||
|
||||
// Create a timeout timer for loadUrl
|
||||
final int currentLoadUrlTimeout = loadUrlTimeout;
|
||||
final int loadUrlTimeoutValue = preferences.getInteger("LoadUrlTimeoutValue", 20000);
|
||||
|
||||
// Timeout error method
|
||||
final Runnable loadError = new Runnable() {
|
||||
public void run() {
|
||||
stopLoading();
|
||||
LOG.e(TAG, "CordovaWebView: TIMEOUT ERROR!");
|
||||
|
||||
// Handle other errors by passing them to the webview in JS
|
||||
JSONObject data = new JSONObject();
|
||||
try {
|
||||
data.put("errorCode", -6);
|
||||
data.put("description", "The connection to the server was unsuccessful.");
|
||||
data.put("url", url);
|
||||
} catch (JSONException e) {
|
||||
// Will never happen.
|
||||
}
|
||||
pluginManager.postMessage("onReceivedError", data);
|
||||
}
|
||||
};
|
||||
|
||||
// Timeout timer method
|
||||
final Runnable timeoutCheck = new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
synchronized (this) {
|
||||
wait(loadUrlTimeoutValue);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// If timeout, then stop loading and handle error
|
||||
if (loadUrlTimeout == currentLoadUrlTimeout) {
|
||||
cordova.getActivity().runOnUiThread(loadError);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final boolean _recreatePlugins = recreatePlugins;
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
if (loadUrlTimeoutValue > 0) {
|
||||
cordova.getThreadPool().execute(timeoutCheck);
|
||||
}
|
||||
engine.loadUrl(url, _recreatePlugins);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void loadUrl(String url) {
|
||||
loadUrlIntoView(url, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showWebPage(String url, boolean openExternal, boolean clearHistory, Map<String, Object> params) {
|
||||
LOG.d(TAG, "showWebPage(%s, %b, %b, HashMap)", url, openExternal, clearHistory);
|
||||
|
||||
// If clearing history
|
||||
if (clearHistory) {
|
||||
engine.clearHistory();
|
||||
}
|
||||
|
||||
// If loading into our webview
|
||||
if (!openExternal) {
|
||||
// Make sure url is in whitelist
|
||||
if (pluginManager.shouldAllowNavigation(url)) {
|
||||
// TODO: What about params?
|
||||
// Load new URL
|
||||
loadUrlIntoView(url, true);
|
||||
} else {
|
||||
LOG.w(TAG, "showWebPage: Refusing to load URL into webview since it is not in the <allow-navigation> whitelist. URL=" + url);
|
||||
}
|
||||
}
|
||||
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);
|
||||
return;
|
||||
}
|
||||
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));
|
||||
} else {
|
||||
intent.setData(uri);
|
||||
}
|
||||
cordova.getActivity().startActivity(intent);
|
||||
} catch (android.content.ActivityNotFoundException e) {
|
||||
LOG.e(TAG, "Error loading url " + url, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void showCustomView(View view, WebChromeClient.CustomViewCallback callback) {
|
||||
// This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
|
||||
Log.d(TAG, "showing Custom View");
|
||||
// if a view already exists then immediately terminate the new one
|
||||
if (mCustomView != null) {
|
||||
callback.onCustomViewHidden();
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the view and its callback for later (to kill it properly)
|
||||
mCustomView = view;
|
||||
mCustomViewCallback = callback;
|
||||
|
||||
// Add the custom view to its container.
|
||||
ViewGroup parent = (ViewGroup) engine.getView().getParent();
|
||||
parent.addView(view, new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
Gravity.CENTER));
|
||||
|
||||
// Hide the content view.
|
||||
engine.getView().setVisibility(View.GONE);
|
||||
|
||||
// Finally show the custom view container.
|
||||
parent.setVisibility(View.VISIBLE);
|
||||
parent.bringToFront();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void hideCustomView() {
|
||||
// This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
|
||||
if (mCustomView == null) return;
|
||||
Log.d(TAG, "Hiding Custom View");
|
||||
|
||||
// Hide the custom view.
|
||||
mCustomView.setVisibility(View.GONE);
|
||||
|
||||
// Remove the custom view from its container.
|
||||
ViewGroup parent = (ViewGroup) engine.getView().getParent();
|
||||
parent.removeView(mCustomView);
|
||||
mCustomView = null;
|
||||
mCustomViewCallback.onCustomViewHidden();
|
||||
|
||||
// Show the content view.
|
||||
engine.getView().setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public boolean isCustomViewShowing() {
|
||||
return mCustomView != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void sendJavascript(String statement) {
|
||||
nativeToJsMessageQueue.addJavaScript(statement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPluginResult(PluginResult cr, String callbackId) {
|
||||
nativeToJsMessageQueue.addPluginResult(cr, callbackId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginManager getPluginManager() {
|
||||
return pluginManager;
|
||||
}
|
||||
@Override
|
||||
public CordovaPreferences getPreferences() {
|
||||
return preferences;
|
||||
}
|
||||
@Override
|
||||
public ICordovaCookieManager getCookieManager() {
|
||||
return engine.getCookieManager();
|
||||
}
|
||||
@Override
|
||||
public CordovaResourceApi getResourceApi() {
|
||||
return resourceApi;
|
||||
}
|
||||
@Override
|
||||
public CordovaWebViewEngine getEngine() {
|
||||
return engine;
|
||||
}
|
||||
@Override
|
||||
public View getView() {
|
||||
return engine.getView();
|
||||
}
|
||||
@Override
|
||||
public Context getContext() {
|
||||
return engine.getView().getContext();
|
||||
}
|
||||
|
||||
private void sendJavascriptEvent(String event) {
|
||||
if (appPlugin == null) {
|
||||
appPlugin = (CoreAndroid)pluginManager.getPlugin(CoreAndroid.PLUGIN_NAME);
|
||||
}
|
||||
|
||||
if (appPlugin == null) {
|
||||
LOG.w(TAG, "Unable to fire event without existing plugin");
|
||||
return;
|
||||
}
|
||||
appPlugin.fireJavascriptEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setButtonPlumbedToJs(int keyCode, boolean override) {
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_VOLUME_DOWN:
|
||||
case KeyEvent.KEYCODE_VOLUME_UP:
|
||||
case KeyEvent.KEYCODE_BACK:
|
||||
// TODO: Why are search and menu buttons handled separately?
|
||||
if (override) {
|
||||
boundKeyCodes.add(keyCode);
|
||||
} else {
|
||||
boundKeyCodes.remove(keyCode);
|
||||
}
|
||||
return;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported keycode: " + keyCode);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isButtonPlumbedToJs(int keyCode) {
|
||||
return boundKeyCodes.contains(keyCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postMessage(String id, Object data) {
|
||||
return pluginManager.postMessage(id, data);
|
||||
}
|
||||
|
||||
// Engine method proxies:
|
||||
@Override
|
||||
public String getUrl() {
|
||||
return engine.getUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopLoading() {
|
||||
// Clear timeout flag
|
||||
loadUrlTimeout++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canGoBack() {
|
||||
return engine.canGoBack();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearCache() {
|
||||
engine.clearCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void clearCache(boolean b) {
|
||||
engine.clearCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearHistory() {
|
||||
engine.clearHistory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean backHistory() {
|
||||
return engine.goBack();
|
||||
}
|
||||
|
||||
/////// LifeCycle methods ///////
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
if (this.pluginManager != null) {
|
||||
this.pluginManager.onNewIntent(intent);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void handlePause(boolean keepRunning) {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
hasPausedEver = true;
|
||||
pluginManager.onPause(keepRunning);
|
||||
sendJavascriptEvent("pause");
|
||||
|
||||
// If app doesn't want to run in background
|
||||
if (!keepRunning) {
|
||||
// Pause JavaScript timers. This affects all webviews within the app!
|
||||
engine.setPaused(true);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void handleResume(boolean keepRunning) {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Resume JavaScript timers. This affects all webviews within the app!
|
||||
engine.setPaused(false);
|
||||
this.pluginManager.onResume(keepRunning);
|
||||
// To be the same as other platforms, fire this event only when resumed after a "pause".
|
||||
if (hasPausedEver) {
|
||||
sendJavascriptEvent("resume");
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void handleStart() {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
pluginManager.onStart();
|
||||
}
|
||||
@Override
|
||||
public void handleStop() {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
pluginManager.onStop();
|
||||
}
|
||||
@Override
|
||||
public void handleDestroy() {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
// Cancel pending timeout timer.
|
||||
loadUrlTimeout++;
|
||||
|
||||
// Forward to plugins
|
||||
this.pluginManager.onDestroy();
|
||||
|
||||
// TODO: about:blank is a bit special (and the default URL for new frames)
|
||||
// We should use a blank data: url instead so it's more obvious
|
||||
this.loadUrl("about:blank");
|
||||
|
||||
// TODO: Should not destroy webview until after about:blank is done loading.
|
||||
engine.destroy();
|
||||
hideCustomView();
|
||||
}
|
||||
|
||||
protected class EngineClient implements CordovaWebViewEngine.Client {
|
||||
@Override
|
||||
public void clearLoadTimeoutTimer() {
|
||||
loadUrlTimeout++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageStarted(String newUrl) {
|
||||
LOG.d(TAG, "onPageDidNavigate(" + newUrl + ")");
|
||||
boundKeyCodes.clear();
|
||||
pluginManager.onReset();
|
||||
pluginManager.postMessage("onPageStarted", newUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedError(int errorCode, String description, String failingUrl) {
|
||||
clearLoadTimeoutTimer();
|
||||
JSONObject data = new JSONObject();
|
||||
try {
|
||||
data.put("errorCode", errorCode);
|
||||
data.put("description", description);
|
||||
data.put("url", failingUrl);
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
pluginManager.postMessage("onReceivedError", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFinishedLoading(String url) {
|
||||
LOG.d(TAG, "onPageFinished(" + url + ")");
|
||||
|
||||
clearLoadTimeoutTimer();
|
||||
|
||||
// Broadcast message that page has loaded
|
||||
pluginManager.postMessage("onPageFinished", url);
|
||||
|
||||
// Make app visible after 2 sec in case there was a JS error and Cordova JS never initialized correctly
|
||||
if (engine.getView().getVisibility() != View.VISIBLE) {
|
||||
Thread t = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
pluginManager.postMessage("spinner", "stop");
|
||||
}
|
||||
});
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
});
|
||||
t.start();
|
||||
}
|
||||
|
||||
// Shutdown if blank loaded
|
||||
if (url.equals("about:blank")) {
|
||||
pluginManager.postMessage("exit", null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean onDispatchKeyEvent(KeyEvent event) {
|
||||
int keyCode = event.getKeyCode();
|
||||
boolean isBackButton = keyCode == KeyEvent.KEYCODE_BACK;
|
||||
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||
if (isBackButton && mCustomView != null) {
|
||||
return true;
|
||||
} else if (boundKeyCodes.contains(keyCode)) {
|
||||
return true;
|
||||
} else if (isBackButton) {
|
||||
return engine.canGoBack();
|
||||
}
|
||||
} else if (event.getAction() == KeyEvent.ACTION_UP) {
|
||||
if (isBackButton && mCustomView != null) {
|
||||
hideCustomView();
|
||||
return true;
|
||||
} else if (boundKeyCodes.contains(keyCode)) {
|
||||
String eventName = null;
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_VOLUME_DOWN:
|
||||
eventName = "volumedownbutton";
|
||||
break;
|
||||
case KeyEvent.KEYCODE_VOLUME_UP:
|
||||
eventName = "volumeupbutton";
|
||||
break;
|
||||
case KeyEvent.KEYCODE_SEARCH:
|
||||
eventName = "searchbutton";
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MENU:
|
||||
eventName = "menubutton";
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BACK:
|
||||
eventName = "backbutton";
|
||||
break;
|
||||
}
|
||||
if (eventName != null) {
|
||||
sendJavascriptEvent(eventName);
|
||||
return true;
|
||||
}
|
||||
} else if (isBackButton) {
|
||||
return engine.goBack();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onNavigationAttempt(String url) {
|
||||
// Give plugins the chance to handle the url
|
||||
if (pluginManager.onOverrideUrlLoading(url)) {
|
||||
return true;
|
||||
} else if (pluginManager.shouldAllowNavigation(url)) {
|
||||
return false;
|
||||
} else if (pluginManager.shouldOpenExternalUrl(url)) {
|
||||
showWebPage(url, true, false, null);
|
||||
return true;
|
||||
}
|
||||
LOG.w(TAG, "Blocked (possibly sub-frame) navigation to non-allowed URL: " + url);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
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.json.JSONException;
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.cordova.CordovaInterface;
|
||||
import org.apache.cordova.CordovaResourceApi.OpenForReadResult;
|
||||
import org.apache.cordova.LOG;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.webkit.WebResourceResponse;
|
||||
import android.webkit.WebView;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
public class IceCreamCordovaWebViewClient extends AndroidWebViewClient {
|
||||
|
||||
private static final String TAG = "IceCreamCordovaWebViewClient";
|
||||
|
||||
public IceCreamCordovaWebViewClient(CordovaInterface cordova, AndroidWebView view) {
|
||||
super(cordova, view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
|
||||
try {
|
||||
// Check the against the whitelist and lock out access to the WebView directory
|
||||
// Changing this will cause problems for your application
|
||||
if (!helper.shouldAllowRequest(url)) {
|
||||
LOG.w(TAG, "URL blocked by whitelist: " + url);
|
||||
// Results in a 404.
|
||||
return new WebResourceResponse("text/plain", "UTF-8", null);
|
||||
}
|
||||
|
||||
CordovaResourceApi resourceApi = appView.getResourceApi();
|
||||
Uri origUri = Uri.parse(url);
|
||||
// Allow plugins to intercept WebView requests.
|
||||
Uri remappedUri = resourceApi.remapUri(origUri);
|
||||
|
||||
if (!origUri.equals(remappedUri) || needsSpecialsInAssetUrlFix(origUri) || needsKitKatContentUrlFix(origUri)) {
|
||||
OpenForReadResult result = resourceApi.openForRead(remappedUri, true);
|
||||
return new WebResourceResponse(result.mimeType, "UTF-8", result.inputStream);
|
||||
}
|
||||
// If we don't need to special-case the request, let the browser load it.
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
if (!(e instanceof FileNotFoundException)) {
|
||||
LOG.e("IceCreamCordovaWebViewClient", "Error occurred while loading a file (returning a 404).", e);
|
||||
}
|
||||
// Results in a 404.
|
||||
return new WebResourceResponse("text/plain", "UTF-8", null);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean needsKitKatContentUrlFix(Uri uri) {
|
||||
return android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && "content".equals(uri.getScheme());
|
||||
}
|
||||
|
||||
private static boolean needsSpecialsInAssetUrlFix(Uri uri) {
|
||||
if (CordovaResourceApi.getUriType(uri) != CordovaResourceApi.URI_TYPE_ASSET) {
|
||||
return false;
|
||||
}
|
||||
if (uri.getQuery() != null || uri.getFragment() != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!uri.toString().contains("%")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch(android.os.Build.VERSION.SDK_INT){
|
||||
case android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH:
|
||||
case android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -270,9 +270,9 @@ public class NativeToJsMessageQueue {
|
||||
}
|
||||
|
||||
public static abstract class BridgeMode {
|
||||
abstract void onNativeToJsMessageAvailable(NativeToJsMessageQueue queue);
|
||||
void notifyOfFlush(NativeToJsMessageQueue queue, boolean fromOnlineEvent) {}
|
||||
void reset() {}
|
||||
public abstract void onNativeToJsMessageAvailable(NativeToJsMessageQueue queue);
|
||||
public void notifyOfFlush(NativeToJsMessageQueue queue, boolean fromOnlineEvent) {}
|
||||
public void reset() {}
|
||||
}
|
||||
|
||||
/** Uses JS polls for messages on a timer.. */
|
||||
@@ -283,11 +283,11 @@ public class NativeToJsMessageQueue {
|
||||
|
||||
/** Uses webView.loadUrl("javascript:") to execute messages. */
|
||||
public static class LoadUrlBridgeMode extends BridgeMode {
|
||||
private final CordovaWebView webView;
|
||||
private final CordovaWebViewEngine engine;
|
||||
private final CordovaInterface cordova;
|
||||
|
||||
public LoadUrlBridgeMode(CordovaWebView webView, CordovaInterface cordova) {
|
||||
this.webView = webView;
|
||||
public LoadUrlBridgeMode(CordovaWebViewEngine engine, CordovaInterface cordova) {
|
||||
this.engine = engine;
|
||||
this.cordova = cordova;
|
||||
}
|
||||
|
||||
@@ -297,7 +297,7 @@ public class NativeToJsMessageQueue {
|
||||
public void run() {
|
||||
String js = queue.popAndEncodeAsJs();
|
||||
if (js != null) {
|
||||
webView.loadUrl("javascript:" + js);
|
||||
engine.loadUrl("javascript:" + js, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.util.LinkedHashMap;
|
||||
import org.json.JSONException;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.net.Uri;
|
||||
import android.os.Debug;
|
||||
import android.util.Log;
|
||||
@@ -44,6 +45,7 @@ public class PluginManager {
|
||||
|
||||
private final CordovaInterface ctx;
|
||||
private final CordovaWebView app;
|
||||
private boolean isInitialized;
|
||||
|
||||
public PluginManager(CordovaWebView cordovaWebView, CordovaInterface cordova, Collection<PluginEntry> pluginEntries) {
|
||||
this.ctx = cordova;
|
||||
@@ -56,13 +58,18 @@ public class PluginManager {
|
||||
}
|
||||
|
||||
public void setPluginEntries(Collection<PluginEntry> pluginEntries) {
|
||||
this.onPause(false);
|
||||
this.onDestroy();
|
||||
pluginMap.clear();
|
||||
entryMap.clear();
|
||||
if (isInitialized) {
|
||||
this.onPause(false);
|
||||
this.onDestroy();
|
||||
pluginMap.clear();
|
||||
entryMap.clear();
|
||||
}
|
||||
for (PluginEntry entry : pluginEntries) {
|
||||
addService(entry);
|
||||
}
|
||||
if (isInitialized) {
|
||||
startupPlugins();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,6 +77,7 @@ public class PluginManager {
|
||||
*/
|
||||
public void init() {
|
||||
LOG.d(TAG, "init()");
|
||||
isInitialized = true;
|
||||
this.onPause(false);
|
||||
this.onDestroy();
|
||||
pluginMap.clear();
|
||||
@@ -158,7 +166,7 @@ public class PluginManager {
|
||||
} else {
|
||||
ret = instantiatePlugin(pe.pluginClass);
|
||||
}
|
||||
ret.privateInitialize(ctx, app, app.getPreferences());
|
||||
ret.privateInitialize(service, ctx, app, app.getPreferences());
|
||||
pluginMap.put(service, ret);
|
||||
}
|
||||
return ret;
|
||||
@@ -185,7 +193,7 @@ public class PluginManager {
|
||||
public void addService(PluginEntry entry) {
|
||||
this.entryMap.put(entry.service, entry);
|
||||
if (entry.plugin != null) {
|
||||
entry.plugin.privateInitialize(ctx, app, app.getPreferences());
|
||||
entry.plugin.privateInitialize(entry.service, ctx, app, app.getPreferences());
|
||||
pluginMap.put(entry.service, entry.plugin);
|
||||
}
|
||||
}
|
||||
@@ -217,7 +225,7 @@ 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(view, handler, host, realm)) {
|
||||
if (plugin != null && plugin.onReceivedHttpAuthRequest(app, handler, host, realm)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -236,7 +244,7 @@ public class PluginManager {
|
||||
*/
|
||||
public boolean onReceivedClientCertRequest(CordovaWebView view, ICordovaClientCertRequest request) {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null && plugin.onReceivedClientCertRequest(view, request)) {
|
||||
if (plugin != null && plugin.onReceivedClientCertRequest(app, request)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -256,6 +264,28 @@ 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The final call you receive before your activity is destroyed.
|
||||
*/
|
||||
@@ -300,105 +330,111 @@ public class PluginManager {
|
||||
/**
|
||||
* Called when the webview is going to request an external resource.
|
||||
*
|
||||
* This delegates to the installed plugins, which must all return true for
|
||||
* this method to return true.
|
||||
* This delegates to the installed plugins, and returns true/false for the
|
||||
* first plugin to provide a non-null result. If no plugins respond, then
|
||||
* the default policy is applied.
|
||||
*
|
||||
* @param url The URL that is being requested.
|
||||
* @return Tri-State:
|
||||
* null: All plugins returned null (the default). This
|
||||
* indicates that the default policy should be
|
||||
* followed.
|
||||
* true: All plugins returned true (allow the resource
|
||||
* to load)
|
||||
* false: At least one plugin returned false (block the
|
||||
* resource)
|
||||
* @return Returns true to allow the resource to load,
|
||||
* false to block the resource.
|
||||
*/
|
||||
public Boolean shouldAllowRequest(String url) {
|
||||
Boolean anyResponded = null;
|
||||
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) {
|
||||
anyResponded = true;
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
// This will be true if all plugins allow the request, or null if no plugins override the method
|
||||
return anyResponded;
|
||||
|
||||
// Default policy:
|
||||
if (url.startsWith("blob:") || url.startsWith("data:") || url.startsWith("about:blank")) {
|
||||
return true;
|
||||
}
|
||||
// TalkBack requires this, so allow it by default.
|
||||
if (url.startsWith("https://ssl.gstatic.com/accessibility/javascript/android/")) {
|
||||
return true;
|
||||
}
|
||||
if (url.startsWith("file://")) {
|
||||
//This directory on WebKit/Blink based webviews contains SQLite databases!
|
||||
//DON'T CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING!
|
||||
return !url.contains("/app_webview/");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the webview is going to change the URL of the loaded content.
|
||||
*
|
||||
* This delegates to the installed plugins, which must all return true for
|
||||
* this method to return true. A true result will allow the new page to load;
|
||||
* a false result will prevent the page from loading.
|
||||
* This delegates to the installed plugins, and returns true/false for the
|
||||
* first plugin to provide a non-null result. If no plugins respond, then
|
||||
* the default policy is applied.
|
||||
*
|
||||
* @param url The URL that is being requested.
|
||||
* @return Tri-State:
|
||||
* null: All plugins returned null (the default). This
|
||||
* indicates that the default policy should be
|
||||
* followed.
|
||||
* true: All plugins returned true (allow the navigation)
|
||||
* false: At least one plugin returned false (block the
|
||||
* navigation)
|
||||
* @return Returns true to allow the navigation,
|
||||
* false to block the navigation.
|
||||
*/
|
||||
public Boolean shouldAllowNavigation(String url) {
|
||||
Boolean anyResponded = null;
|
||||
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) {
|
||||
anyResponded = true;
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
// This will be true if all plugins allow the request, or null if no plugins override the method
|
||||
return anyResponded;
|
||||
|
||||
// Default policy:
|
||||
return url.startsWith("file://") || url.startsWith("about:blank");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default policy:
|
||||
return url.startsWith("file://");
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the webview is going not going to navigate, but may launch
|
||||
* an Intent for an URL.
|
||||
*
|
||||
* This delegates to the installed plugins, which must all return true for
|
||||
* this method to return true. A true result will allow the URL to launch;
|
||||
* a false result will prevent the URL from loading.
|
||||
* This delegates to the installed plugins, and returns true/false for the
|
||||
* first plugin to provide a non-null result. If no plugins respond, then
|
||||
* the default policy is applied.
|
||||
*
|
||||
* @param url The URL that is being requested.
|
||||
* @return Tri-State:
|
||||
* null: All plugins returned null (the default). This
|
||||
* indicates that the default policy should be
|
||||
* followed.
|
||||
* true: All plugins returned true (allow the URL to
|
||||
* launch an intent)
|
||||
* false: At least one plugin returned false (block the
|
||||
* intent)
|
||||
* @return Returns true to allow the URL to launch an intent,
|
||||
* false to block the intent.
|
||||
*/
|
||||
public Boolean shouldOpenExternalUrl(String url) {
|
||||
Boolean anyResponded = null;
|
||||
for (PluginEntry entry : this.entryMap.values()) {
|
||||
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||
if (plugin != null) {
|
||||
Boolean result = plugin.shouldOpenExternalUrl(url);
|
||||
if (result != null) {
|
||||
anyResponded = true;
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
// This will be true if all plugins allow the request, or null if no plugins override the method
|
||||
return anyResponded;
|
||||
// Default policy:
|
||||
// External URLs are not allowed
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -408,10 +444,6 @@ 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) {
|
||||
// Deprecated way to intercept URLs. (process <url-filter> tags).
|
||||
// Instead, plugins should not include <url-filter> and instead ensure
|
||||
// that they are loaded before this function is called (either by setting
|
||||
// the onload <param> or by making an exec() call to them)
|
||||
for (PluginEntry entry : this.entryMap.values()) {
|
||||
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||
if (plugin != null && plugin.onOverrideUrlLoading(url)) {
|
||||
@@ -463,4 +495,17 @@ public class PluginManager {
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the system when the device configuration changes while your activity is running.
|
||||
*
|
||||
* @param newConfig The new device configuration
|
||||
*/
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onConfigurationChanged(newConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,67 +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.
|
||||
*/
|
||||
|
||||
package org.apache.cordova;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
/*
|
||||
* This can be used by any view, including native views
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
public class ScrollEvent {
|
||||
|
||||
public int l, t, nl, nt;
|
||||
private View targetView;
|
||||
|
||||
/*
|
||||
* ScrollEvent constructor
|
||||
* No idea why it uses l and t instead of x and y
|
||||
*
|
||||
* @param x
|
||||
* @param y
|
||||
* @param nx
|
||||
* @param ny
|
||||
* @param view
|
||||
*/
|
||||
|
||||
public ScrollEvent(int nx, int ny, int x, int y, View view)
|
||||
{
|
||||
l = x; y = t; nl = nx; nt = ny;
|
||||
targetView = view;
|
||||
}
|
||||
|
||||
public int dl()
|
||||
{
|
||||
return nl - l;
|
||||
}
|
||||
|
||||
public int dt()
|
||||
{
|
||||
return nt - t;
|
||||
}
|
||||
|
||||
public View getTargetView()
|
||||
{
|
||||
return targetView;
|
||||
}
|
||||
|
||||
}
|
||||
+14
-12
@@ -17,45 +17,47 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
package org.apache.cordova;
|
||||
package org.apache.cordova.engine;
|
||||
|
||||
import android.os.Build;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.WebView;
|
||||
|
||||
class AndroidCookieManager implements ICordovaCookieManager {
|
||||
import org.apache.cordova.ICordovaCookieManager;
|
||||
|
||||
protected WebView webView;
|
||||
class SystemCookieManager implements ICordovaCookieManager {
|
||||
|
||||
public AndroidCookieManager(WebView webview) {
|
||||
protected final WebView webView;
|
||||
private final CookieManager cookieManager;
|
||||
|
||||
public SystemCookieManager(WebView webview) {
|
||||
webView = webview;
|
||||
cookieManager = CookieManager.getInstance();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
|
||||
{
|
||||
CookieManager cookieManager = CookieManager.getInstance();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
cookieManager.setAcceptThirdPartyCookies(webView, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void setCookiesEnabled(boolean accept) {
|
||||
CookieManager.getInstance().setAcceptCookie(accept);
|
||||
cookieManager.setAcceptCookie(accept);
|
||||
}
|
||||
|
||||
public void setCookie(final String url, final String value) {
|
||||
CookieManager.getInstance().setCookie(url, value);
|
||||
cookieManager.setCookie(url, value);
|
||||
}
|
||||
|
||||
public String getCookie(final String url) {
|
||||
return CookieManager.getInstance().getCookie(url);
|
||||
return cookieManager.getCookie(url);
|
||||
}
|
||||
|
||||
public void clearCookies() {
|
||||
CookieManager.getInstance().removeAllCookie();
|
||||
cookieManager.removeAllCookie();
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
CookieManager.getInstance().flush();
|
||||
cookieManager.flush();
|
||||
}
|
||||
}
|
||||
};
|
||||
+6
-3
@@ -16,9 +16,12 @@
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
package org.apache.cordova.engine;
|
||||
|
||||
import android.webkit.JavascriptInterface;
|
||||
|
||||
import org.apache.cordova.CordovaBridge;
|
||||
import org.apache.cordova.ExposedJsApi;
|
||||
import org.json.JSONException;
|
||||
|
||||
/**
|
||||
@@ -26,10 +29,10 @@ import org.json.JSONException;
|
||||
* an equivalent entry in CordovaChromeClient.java, and be added to
|
||||
* cordova-js/lib/android/plugin/android/promptbasednativeapi.js
|
||||
*/
|
||||
class AndroidExposedJsApi implements ExposedJsApi {
|
||||
class SystemExposedJsApi implements ExposedJsApi {
|
||||
private final CordovaBridge bridge;
|
||||
|
||||
AndroidExposedJsApi(CordovaBridge bridge) {
|
||||
SystemExposedJsApi(CordovaBridge bridge) {
|
||||
this.bridge = bridge;
|
||||
}
|
||||
|
||||
+57
-132
@@ -16,157 +16,94 @@
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import org.apache.cordova.CordovaInterface;
|
||||
import org.apache.cordova.LOG;
|
||||
package org.apache.cordova.engine;
|
||||
|
||||
import java.util.Arrays;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
import android.webkit.ConsoleMessage;
|
||||
import android.webkit.GeolocationPermissions.Callback;
|
||||
import android.webkit.JsPromptResult;
|
||||
import android.webkit.JsResult;
|
||||
import android.webkit.ValueCallback;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebStorage;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.GeolocationPermissions.Callback;
|
||||
import android.widget.EditText;
|
||||
import android.webkit.PermissionRequest;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
import org.apache.cordova.CordovaDialogsHelper;
|
||||
import org.apache.cordova.CordovaPlugin;
|
||||
import org.apache.cordova.LOG;
|
||||
|
||||
/**
|
||||
* This class is the WebChromeClient that implements callbacks for our web view.
|
||||
* The kind of callbacks that happen here are on the chrome outside the document,
|
||||
* such as onCreateWindow(), onConsoleMessage(), onProgressChanged(), etc. Related
|
||||
* to but different than CordovaWebViewClient.
|
||||
*
|
||||
* @see <a href="http://developer.android.com/reference/android/webkit/WebChromeClient.html">WebChromeClient</a>
|
||||
* @see <a href="http://developer.android.com/guide/webapps/webview.html">WebView guide</a>
|
||||
* @see CordovaWebViewClient
|
||||
* @see CordovaWebView
|
||||
*/
|
||||
public class AndroidChromeClient extends WebChromeClient {
|
||||
public class SystemWebChromeClient extends WebChromeClient {
|
||||
|
||||
public static final int FILECHOOSER_RESULTCODE = 5173;
|
||||
private static final String LOG_TAG = "AndroidChromeClient";
|
||||
private static final int FILECHOOSER_RESULTCODE = 5173;
|
||||
private static final String LOG_TAG = "SystemWebChromeClient";
|
||||
private long MAX_QUOTA = 100 * 1024 * 1024;
|
||||
protected final CordovaInterface cordova;
|
||||
protected final AndroidWebView appView;
|
||||
protected final SystemWebViewEngine parentEngine;
|
||||
|
||||
// the video progress view
|
||||
private View mVideoProgressView;
|
||||
|
||||
//Keep track of last AlertDialog showed
|
||||
private AlertDialog lastHandledDialog;
|
||||
private CordovaDialogsHelper dialogsHelper;
|
||||
|
||||
public AndroidChromeClient(CordovaInterface ctx, AndroidWebView app) {
|
||||
this.cordova = ctx;
|
||||
this.appView = app;
|
||||
private WebChromeClient.CustomViewCallback mCustomViewCallback;
|
||||
private View mCustomView;
|
||||
|
||||
public SystemWebChromeClient(SystemWebViewEngine parentEngine) {
|
||||
this.parentEngine = parentEngine;
|
||||
dialogsHelper = new CordovaDialogsHelper(parentEngine.webView.getContext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the client to display a javascript alert dialog.
|
||||
*
|
||||
* @param view
|
||||
* @param url
|
||||
* @param message
|
||||
* @param result
|
||||
* @see Other implementation in the Dialogs plugin.
|
||||
*/
|
||||
@Override
|
||||
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
|
||||
AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity());
|
||||
dlg.setMessage(message);
|
||||
dlg.setTitle("Alert");
|
||||
//Don't let alerts break the back button
|
||||
dlg.setCancelable(true);
|
||||
dlg.setPositiveButton(android.R.string.ok,
|
||||
new AlertDialog.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
result.confirm();
|
||||
}
|
||||
});
|
||||
dlg.setOnCancelListener(
|
||||
new DialogInterface.OnCancelListener() {
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
result.cancel();
|
||||
}
|
||||
});
|
||||
dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {
|
||||
//DO NOTHING
|
||||
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK)
|
||||
{
|
||||
dialogsHelper.showAlert(message, new CordovaDialogsHelper.Result() {
|
||||
@Override public void gotResult(boolean success, String value) {
|
||||
if (success) {
|
||||
result.confirm();
|
||||
return false;
|
||||
} else {
|
||||
result.cancel();
|
||||
}
|
||||
else
|
||||
return true;
|
||||
}
|
||||
});
|
||||
lastHandledDialog = dlg.show();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the client to display a confirm dialog to the user.
|
||||
*
|
||||
* @param view
|
||||
* @param url
|
||||
* @param message
|
||||
* @param result
|
||||
* @see Other implementation in the Dialogs plugin.
|
||||
*/
|
||||
@Override
|
||||
public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
|
||||
AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity());
|
||||
dlg.setMessage(message);
|
||||
dlg.setTitle("Confirm");
|
||||
dlg.setCancelable(true);
|
||||
dlg.setPositiveButton(android.R.string.ok,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
result.confirm();
|
||||
}
|
||||
});
|
||||
dlg.setNegativeButton(android.R.string.cancel,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
result.cancel();
|
||||
}
|
||||
});
|
||||
dlg.setOnCancelListener(
|
||||
new DialogInterface.OnCancelListener() {
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
result.cancel();
|
||||
}
|
||||
});
|
||||
dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {
|
||||
//DO NOTHING
|
||||
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK)
|
||||
{
|
||||
dialogsHelper.showConfirm(message, new CordovaDialogsHelper.Result() {
|
||||
@Override
|
||||
public void gotResult(boolean success, String value) {
|
||||
if (success) {
|
||||
result.confirm();
|
||||
} else {
|
||||
result.cancel();
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return true;
|
||||
}
|
||||
});
|
||||
lastHandledDialog = dlg.show();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -177,40 +114,24 @@ public class AndroidChromeClient extends WebChromeClient {
|
||||
*
|
||||
* Since we are hacking prompts for our own purposes, we should not be using them for
|
||||
* this purpose, perhaps we should hack console.log to do this instead!
|
||||
*
|
||||
* @see Other implementation in the Dialogs plugin.
|
||||
*/
|
||||
@Override
|
||||
public boolean onJsPrompt(WebView view, String origin, String message, String defaultValue, JsPromptResult result) {
|
||||
public boolean onJsPrompt(WebView view, String origin, String message, String defaultValue, final JsPromptResult result) {
|
||||
// Unlike the @JavascriptInterface bridge, this method is always called on the UI thread.
|
||||
String handledRet = appView.bridge.promptOnJsPrompt(origin, message, defaultValue);
|
||||
String handledRet = parentEngine.bridge.promptOnJsPrompt(origin, message, defaultValue);
|
||||
if (handledRet != null) {
|
||||
result.confirm(handledRet);
|
||||
} else {
|
||||
// Returning false would also show a dialog, but the default one shows the origin (ugly).
|
||||
final JsPromptResult res = result;
|
||||
AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity());
|
||||
dlg.setMessage(message);
|
||||
final EditText input = new EditText(this.cordova.getActivity());
|
||||
if (defaultValue != null) {
|
||||
input.setText(defaultValue);
|
||||
}
|
||||
dlg.setView(input);
|
||||
dlg.setCancelable(false);
|
||||
dlg.setPositiveButton(android.R.string.ok,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
String usertext = input.getText().toString();
|
||||
res.confirm(usertext);
|
||||
}
|
||||
});
|
||||
dlg.setNegativeButton(android.R.string.cancel,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
res.cancel();
|
||||
}
|
||||
});
|
||||
lastHandledDialog = dlg.show();
|
||||
dialogsHelper.showPrompt(message, defaultValue, new CordovaDialogsHelper.Result() {
|
||||
@Override
|
||||
public void gotResult(boolean success, String value) {
|
||||
if (success) {
|
||||
result.confirm(value);
|
||||
} else {
|
||||
result.cancel();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -264,14 +185,14 @@ public class AndroidChromeClient extends WebChromeClient {
|
||||
// API level 7 is required for this, see if we could lower this using something else
|
||||
@Override
|
||||
public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
|
||||
this.appView.showCustomView(view, callback);
|
||||
parentEngine.getCordovaWebView().showCustomView(view, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHideCustomView() {
|
||||
this.appView.hideCustomView();
|
||||
parentEngine.getCordovaWebView().hideCustomView();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
/**
|
||||
* Ask the host application for a custom progress view to show while
|
||||
@@ -284,13 +205,13 @@ public class AndroidChromeClient extends WebChromeClient {
|
||||
// Create a new Loading view programmatically.
|
||||
|
||||
// create the linear layout
|
||||
LinearLayout layout = new LinearLayout(this.appView.getContext());
|
||||
LinearLayout layout = new LinearLayout(parentEngine.getView().getContext());
|
||||
layout.setOrientation(LinearLayout.VERTICAL);
|
||||
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
||||
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
|
||||
layout.setLayoutParams(layoutParams);
|
||||
// the proress bar
|
||||
ProgressBar bar = new ProgressBar(this.appView.getContext());
|
||||
ProgressBar bar = new ProgressBar(parentEngine.getView().getContext());
|
||||
LinearLayout.LayoutParams barLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
||||
barLayoutParams.gravity = Gravity.CENTER;
|
||||
bar.setLayoutParams(barLayoutParams);
|
||||
@@ -317,7 +238,7 @@ public class AndroidChromeClient extends WebChromeClient {
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intent.setType("*/*");
|
||||
cordova.startActivityForResult(new CordovaPlugin() {
|
||||
parentEngine.cordova.startActivityForResult(new CordovaPlugin() {
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||
Uri result = intent == null || resultCode != Activity.RESULT_OK ? null : intent.getData();
|
||||
@@ -332,7 +253,7 @@ public class AndroidChromeClient extends WebChromeClient {
|
||||
public boolean onShowFileChooser(WebView webView, final ValueCallback<Uri[]> filePathsCallback, final WebChromeClient.FileChooserParams fileChooserParams) {
|
||||
Intent intent = fileChooserParams.createIntent();
|
||||
try {
|
||||
cordova.startActivityForResult(new CordovaPlugin() {
|
||||
parentEngine.cordova.startActivityForResult(new CordovaPlugin() {
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||
Uri[] result = WebChromeClient.FileChooserParams.parseResult(resultCode, intent);
|
||||
@@ -347,10 +268,14 @@ public class AndroidChromeClient extends WebChromeClient {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void destroyLastDialog(){
|
||||
if(lastHandledDialog != null){
|
||||
lastHandledDialog.cancel();
|
||||
}
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
@Override
|
||||
public void onPermissionRequest(final PermissionRequest request) {
|
||||
Log.d(LOG_TAG, "onPermissionRequest: " + Arrays.toString(request.getResources()));
|
||||
request.grant(request.getResources());
|
||||
}
|
||||
|
||||
public void destroyLastDialog(){
|
||||
dialogsHelper.destroyLastDialog();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
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.engine;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.KeyEvent;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
|
||||
import org.apache.cordova.CordovaInterface;
|
||||
import org.apache.cordova.CordovaWebView;
|
||||
import org.apache.cordova.CordovaWebViewEngine;
|
||||
|
||||
/**
|
||||
* Custom WebView subclass that enables us to capture events needed for Cordova.
|
||||
*/
|
||||
public class SystemWebView extends WebView implements CordovaWebViewEngine.EngineView {
|
||||
private SystemWebViewClient viewClient;
|
||||
SystemWebChromeClient chromeClient;
|
||||
private SystemWebViewEngine parentEngine;
|
||||
private CordovaInterface cordova;
|
||||
|
||||
public SystemWebView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public SystemWebView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
// Package visibility to enforce that only SystemWebViewEngine should call this method.
|
||||
void init(SystemWebViewEngine parentEngine, CordovaInterface cordova) {
|
||||
this.cordova = cordova;
|
||||
this.parentEngine = parentEngine;
|
||||
if (this.viewClient == null) {
|
||||
setWebViewClient(new SystemWebViewClient(parentEngine));
|
||||
}
|
||||
|
||||
if (this.chromeClient == null) {
|
||||
setWebChromeClient(new SystemWebChromeClient(parentEngine));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CordovaWebView getCordovaWebView() {
|
||||
return parentEngine != null ? parentEngine.getCordovaWebView() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWebViewClient(WebViewClient client) {
|
||||
viewClient = (SystemWebViewClient)client;
|
||||
super.setWebViewClient(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWebChromeClient(WebChromeClient client) {
|
||||
chromeClient = (SystemWebChromeClient)client;
|
||||
super.setWebChromeClient(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
Boolean ret = parentEngine.client.onDispatchKeyEvent(event);
|
||||
if (ret != null) {
|
||||
return ret.booleanValue();
|
||||
}
|
||||
return super.dispatchKeyEvent(event);
|
||||
}
|
||||
}
|
||||
+92
-79
@@ -16,26 +16,34 @@
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.util.Hashtable;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
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.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.net.http.SslError;
|
||||
import android.view.View;
|
||||
import android.os.Build;
|
||||
import android.webkit.ClientCertRequest;
|
||||
import android.webkit.HttpAuthHandler;
|
||||
import android.webkit.SslErrorHandler;
|
||||
import android.webkit.WebResourceResponse;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
|
||||
import org.apache.cordova.AuthenticationToken;
|
||||
import org.apache.cordova.CordovaClientCertRequest;
|
||||
import org.apache.cordova.CordovaHttpAuthHandler;
|
||||
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.util.Hashtable;
|
||||
|
||||
|
||||
/**
|
||||
* This class is the WebViewClient that implements callbacks for our web view.
|
||||
@@ -43,28 +51,19 @@ import android.webkit.WebViewClient;
|
||||
* document instead of the chrome surrounding it, such as onPageStarted(),
|
||||
* shouldOverrideUrlLoading(), etc. Related to but different than
|
||||
* CordovaChromeClient.
|
||||
*
|
||||
* @see <a href="http://developer.android.com/reference/android/webkit/WebViewClient.html">WebViewClient</a>
|
||||
* @see <a href="http://developer.android.com/guide/webapps/webview.html">WebView guide</a>
|
||||
* @see CordovaChromeClient
|
||||
* @see CordovaWebView
|
||||
*/
|
||||
public class AndroidWebViewClient extends WebViewClient {
|
||||
public class SystemWebViewClient extends WebViewClient {
|
||||
|
||||
private static final String TAG = "AndroidWebViewClient";
|
||||
protected final CordovaInterface cordova;
|
||||
protected final AndroidWebView appView;
|
||||
protected final CordovaUriHelper helper;
|
||||
private static final String TAG = "SystemWebViewClient";
|
||||
protected final SystemWebViewEngine parentEngine;
|
||||
private boolean doClearHistory = false;
|
||||
boolean isCurrentlyLoading;
|
||||
|
||||
/** The authorization tokens. */
|
||||
private Hashtable<String, AuthenticationToken> authenticationTokens = new Hashtable<String, AuthenticationToken>();
|
||||
|
||||
public AndroidWebViewClient(CordovaInterface cordova, AndroidWebView view) {
|
||||
this.cordova = cordova;
|
||||
this.appView = view;
|
||||
helper = new CordovaUriHelper(cordova, view);
|
||||
public SystemWebViewClient(SystemWebViewEngine parentEngine) {
|
||||
this.parentEngine = parentEngine;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,9 +76,9 @@ public class AndroidWebViewClient extends WebViewClient {
|
||||
*/
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
return helper.shouldOverrideUrlLoading(url);
|
||||
return parentEngine.client.onNavigationAttempt(url);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* On received http auth request.
|
||||
* The method reacts on all registered authentication tokens. There is one and only one authentication token for any host + realm combination
|
||||
@@ -95,9 +94,9 @@ public class AndroidWebViewClient extends WebViewClient {
|
||||
}
|
||||
|
||||
// Check if there is some plugin which can resolve this auth challenge
|
||||
PluginManager pluginManager = this.appView.pluginManager;
|
||||
if (pluginManager != null && pluginManager.onReceivedHttpAuthRequest(this.appView, new CordovaHttpAuthHandler(handler), host, realm)) {
|
||||
this.appView.loadUrlTimeout++;
|
||||
PluginManager pluginManager = this.parentEngine.pluginManager;
|
||||
if (pluginManager != null && pluginManager.onReceivedHttpAuthRequest(null, new CordovaHttpAuthHandler(handler), host, realm)) {
|
||||
parentEngine.client.clearLoadTimeoutTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -118,9 +117,9 @@ public class AndroidWebViewClient extends WebViewClient {
|
||||
{
|
||||
|
||||
// Check if there is some plugin which can resolve this certificate request
|
||||
PluginManager pluginManager = this.appView.pluginManager;
|
||||
if (pluginManager != null && pluginManager.onReceivedClientCertRequest(this.appView, new CordovaClientCertRequest(request))) {
|
||||
this.appView.loadUrlTimeout++;
|
||||
PluginManager pluginManager = this.parentEngine.pluginManager;
|
||||
if (pluginManager != null && pluginManager.onReceivedClientCertRequest(null, new CordovaClientCertRequest(request))) {
|
||||
parentEngine.client.clearLoadTimeoutTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -141,12 +140,9 @@ public class AndroidWebViewClient extends WebViewClient {
|
||||
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
||||
super.onPageStarted(view, url, favicon);
|
||||
isCurrentlyLoading = true;
|
||||
LOG.d(TAG, "onPageStarted(" + url + ")");
|
||||
// Flush stale messages & reset plugins.
|
||||
this.appView.onPageReset();
|
||||
|
||||
// Broadcast message that page has loaded
|
||||
this.appView.getPluginManager().postMessage("onPageStarted", url);
|
||||
parentEngine.bridge.reset();
|
||||
parentEngine.client.onPageStarted(url);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,7 +161,6 @@ public class AndroidWebViewClient extends WebViewClient {
|
||||
return;
|
||||
}
|
||||
isCurrentlyLoading = false;
|
||||
LOG.d(TAG, "onPageFinished(" + url + ")");
|
||||
|
||||
/**
|
||||
* Because of a timing issue we need to clear this history in onPageFinished as well as
|
||||
@@ -177,35 +172,8 @@ public class AndroidWebViewClient extends WebViewClient {
|
||||
view.clearHistory();
|
||||
this.doClearHistory = false;
|
||||
}
|
||||
parentEngine.client.onPageFinishedLoading(url);
|
||||
|
||||
// Clear timeout flag
|
||||
appView.loadUrlTimeout++;
|
||||
|
||||
// Broadcast message that page has loaded
|
||||
this.appView.getPluginManager().postMessage("onPageFinished", url);
|
||||
|
||||
// Make app visible after 2 sec in case there was a JS error and Cordova JS never initialized correctly
|
||||
if (this.appView.getVisibility() == View.INVISIBLE) {
|
||||
Thread t = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
appView.getPluginManager().postMessage("spinner", "stop");
|
||||
}
|
||||
});
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
});
|
||||
t.start();
|
||||
}
|
||||
|
||||
// Shutdown if blank loaded
|
||||
if (url.equals("about:blank")) {
|
||||
appView.getPluginManager().postMessage("exit", null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -225,13 +193,12 @@ public class AndroidWebViewClient extends WebViewClient {
|
||||
}
|
||||
LOG.d(TAG, "CordovaWebViewClient.onReceivedError: Error code=%s Description=%s URL=%s", errorCode, description, failingUrl);
|
||||
|
||||
// Clear timeout flag
|
||||
appView.loadUrlTimeout++;
|
||||
|
||||
// If this is a "Protocol Not Supported" error, then revert to the previous
|
||||
// page. If there was no previous page, then punt. The application's config
|
||||
// is likely incorrect (start page set to sms: or something like that)
|
||||
if (errorCode == WebViewClient.ERROR_UNSUPPORTED_SCHEME) {
|
||||
parentEngine.client.clearLoadTimeoutTimer();
|
||||
|
||||
if (view.canGoBack()) {
|
||||
view.goBack();
|
||||
return;
|
||||
@@ -239,17 +206,7 @@ public class AndroidWebViewClient extends WebViewClient {
|
||||
super.onReceivedError(view, errorCode, description, failingUrl);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle other errors by passing them to the webview in JS
|
||||
JSONObject data = new JSONObject();
|
||||
try {
|
||||
data.put("errorCode", errorCode);
|
||||
data.put("description", description);
|
||||
data.put("url", failingUrl);
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
this.appView.getPluginManager().postMessage("onReceivedError", data);
|
||||
parentEngine.client.onReceivedError(errorCode, description, failingUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -266,8 +223,8 @@ public class AndroidWebViewClient extends WebViewClient {
|
||||
@Override
|
||||
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
|
||||
|
||||
final String packageName = this.cordova.getActivity().getPackageName();
|
||||
final PackageManager pm = this.cordova.getActivity().getPackageManager();
|
||||
final String packageName = parentEngine.cordova.getActivity().getPackageName();
|
||||
final PackageManager pm = parentEngine.cordova.getActivity().getPackageManager();
|
||||
|
||||
ApplicationInfo appInfo;
|
||||
try {
|
||||
@@ -358,4 +315,60 @@ public class AndroidWebViewClient extends WebViewClient {
|
||||
public void clearAuthenticationTokens() {
|
||||
this.authenticationTokens.clear();
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
@Override
|
||||
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
|
||||
try {
|
||||
// Check the against the whitelist 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);
|
||||
// Results in a 404.
|
||||
return new WebResourceResponse("text/plain", "UTF-8", null);
|
||||
}
|
||||
|
||||
CordovaResourceApi resourceApi = parentEngine.resourceApi;
|
||||
Uri origUri = Uri.parse(url);
|
||||
// Allow plugins to intercept WebView requests.
|
||||
Uri remappedUri = resourceApi.remapUri(origUri);
|
||||
|
||||
if (!origUri.equals(remappedUri) || needsSpecialsInAssetUrlFix(origUri) || needsKitKatContentUrlFix(origUri)) {
|
||||
CordovaResourceApi.OpenForReadResult result = resourceApi.openForRead(remappedUri, true);
|
||||
return new WebResourceResponse(result.mimeType, "UTF-8", result.inputStream);
|
||||
}
|
||||
// If we don't need to special-case the request, let the browser load it.
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
if (!(e instanceof FileNotFoundException)) {
|
||||
LOG.e(TAG, "Error occurred while loading a file (returning a 404).", e);
|
||||
}
|
||||
// Results in a 404.
|
||||
return new WebResourceResponse("text/plain", "UTF-8", null);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean needsKitKatContentUrlFix(Uri uri) {
|
||||
return android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && "content".equals(uri.getScheme());
|
||||
}
|
||||
|
||||
private static boolean needsSpecialsInAssetUrlFix(Uri uri) {
|
||||
if (CordovaResourceApi.getUriType(uri) != CordovaResourceApi.URI_TYPE_ASSET) {
|
||||
return false;
|
||||
}
|
||||
if (uri.getQuery() != null || uri.getFragment() != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!uri.toString().contains("%")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch(android.os.Build.VERSION.SDK_INT){
|
||||
case android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH:
|
||||
case android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
/*
|
||||
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.engine;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebSettings.LayoutAlgorithm;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import org.apache.cordova.CordovaBridge;
|
||||
import org.apache.cordova.CordovaInterface;
|
||||
import org.apache.cordova.CordovaPreferences;
|
||||
import org.apache.cordova.CordovaResourceApi;
|
||||
import org.apache.cordova.CordovaWebView;
|
||||
import org.apache.cordova.CordovaWebViewEngine;
|
||||
import org.apache.cordova.ICordovaCookieManager;
|
||||
import org.apache.cordova.NativeToJsMessageQueue;
|
||||
import org.apache.cordova.PluginManager;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
|
||||
/**
|
||||
* Glue class between CordovaWebView (main Cordova logic) and SystemWebView (the actual View).
|
||||
* We make the Engine separate from the actual View so that:
|
||||
* A) We don't need to worry about WebView methods clashing with CordovaWebViewEngine methods
|
||||
* (e.g.: goBack() is void for WebView, and boolean for CordovaWebViewEngine)
|
||||
* B) Separating the actual View from the Engine makes API surfaces smaller.
|
||||
* Class uses two-phase initialization. However, CordovaWebView is responsible for calling .init().
|
||||
*/
|
||||
public class SystemWebViewEngine implements CordovaWebViewEngine {
|
||||
public static final String TAG = "SystemWebViewEngine";
|
||||
|
||||
protected final SystemWebView webView;
|
||||
protected final SystemCookieManager cookieManager;
|
||||
protected CordovaPreferences preferences;
|
||||
protected CordovaBridge bridge;
|
||||
protected CordovaWebViewEngine.Client client;
|
||||
protected CordovaWebView parentWebView;
|
||||
protected CordovaInterface cordova;
|
||||
protected PluginManager pluginManager;
|
||||
protected CordovaResourceApi resourceApi;
|
||||
protected NativeToJsMessageQueue nativeToJsMessageQueue;
|
||||
private BroadcastReceiver receiver;
|
||||
|
||||
/** Used when created via reflection. */
|
||||
public SystemWebViewEngine(Context context, CordovaPreferences preferences) {
|
||||
this(new SystemWebView(context), preferences);
|
||||
}
|
||||
|
||||
public SystemWebViewEngine(SystemWebView webView) {
|
||||
this(webView, null);
|
||||
}
|
||||
|
||||
public SystemWebViewEngine(SystemWebView webView, CordovaPreferences preferences) {
|
||||
this.preferences = preferences;
|
||||
this.webView = webView;
|
||||
cookieManager = new SystemCookieManager(webView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(CordovaWebView parentWebView, CordovaInterface cordova, CordovaWebViewEngine.Client client,
|
||||
CordovaResourceApi resourceApi, PluginManager pluginManager,
|
||||
NativeToJsMessageQueue nativeToJsMessageQueue) {
|
||||
if (this.cordova != null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
// Needed when prefs are not passed by the constructor
|
||||
if (preferences == null) {
|
||||
preferences = parentWebView.getPreferences();
|
||||
}
|
||||
this.parentWebView = parentWebView;
|
||||
this.cordova = cordova;
|
||||
this.client = client;
|
||||
this.resourceApi = resourceApi;
|
||||
this.pluginManager = pluginManager;
|
||||
this.nativeToJsMessageQueue = nativeToJsMessageQueue;
|
||||
webView.init(this, cordova);
|
||||
|
||||
initWebViewSettings();
|
||||
|
||||
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode.OnlineEventsBridgeModeDelegate() {
|
||||
@Override
|
||||
public void setNetworkAvailable(boolean value) {
|
||||
webView.setNetworkAvailable(value);
|
||||
}
|
||||
@Override
|
||||
public void runOnUiThread(Runnable r) {
|
||||
SystemWebViewEngine.this.cordova.getActivity().runOnUiThread(r);
|
||||
}
|
||||
}));
|
||||
bridge = new CordovaBridge(pluginManager, nativeToJsMessageQueue);
|
||||
exposeJsInterface(webView, bridge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CordovaWebView getCordovaWebView() {
|
||||
return parentWebView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICordovaCookieManager getCookieManager() {
|
||||
return cookieManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView() {
|
||||
return webView;
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@SuppressWarnings("deprecation")
|
||||
private void initWebViewSettings() {
|
||||
webView.setInitialScale(0);
|
||||
webView.setVerticalScrollBarEnabled(false);
|
||||
// Enable JavaScript
|
||||
final WebSettings settings = webView.getSettings();
|
||||
settings.setJavaScriptEnabled(true);
|
||||
settings.setJavaScriptCanOpenWindowsAutomatically(true);
|
||||
settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
|
||||
|
||||
// Set the nav dump for HTC 2.x devices (disabling for ICS, deprecated entirely for Jellybean 4.2)
|
||||
try {
|
||||
Method gingerbread_getMethod = WebSettings.class.getMethod("setNavDump", new Class[] { boolean.class });
|
||||
|
||||
String manufacturer = android.os.Build.MANUFACTURER;
|
||||
Log.d(TAG, "CordovaWebView is running on device made by: " + manufacturer);
|
||||
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB &&
|
||||
android.os.Build.MANUFACTURER.contains("HTC"))
|
||||
{
|
||||
gingerbread_getMethod.invoke(settings, true);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
Log.d(TAG, "We are on a modern version of Android, we will deprecate HTC 2.3 devices in 2.8");
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.d(TAG, "Doing the NavDump failed with bad arguments");
|
||||
} catch (IllegalAccessException e) {
|
||||
Log.d(TAG, "This should never happen: IllegalAccessException means this isn't Android anymore");
|
||||
} catch (InvocationTargetException e) {
|
||||
Log.d(TAG, "This should never happen: InvocationTargetException means this isn't Android anymore.");
|
||||
}
|
||||
|
||||
//We don't save any form data in the application
|
||||
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
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
|
||||
settings.setAllowUniversalAccessFromFileURLs(true);
|
||||
}
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
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();
|
||||
if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0 &&
|
||||
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
|
||||
enableRemoteDebugging();
|
||||
}
|
||||
|
||||
settings.setGeolocationDatabasePath(databasePath);
|
||||
|
||||
// Enable DOM storage
|
||||
settings.setDomStorageEnabled(true);
|
||||
|
||||
// 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();
|
||||
|
||||
// Fix for CB-3360
|
||||
String overrideUserAgent = preferences.getString("OverrideUserAgent", null);
|
||||
if (overrideUserAgent != null) {
|
||||
settings.setUserAgentString(overrideUserAgent);
|
||||
} else {
|
||||
String appendUserAgent = preferences.getString("AppendUserAgent", null);
|
||||
if (appendUserAgent != null) {
|
||||
settings.setUserAgentString(defaultUserAgent + " " + appendUserAgent);
|
||||
}
|
||||
}
|
||||
// End CB-3360
|
||||
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
|
||||
if (this.receiver == null) {
|
||||
this.receiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
settings.getUserAgentString();
|
||||
}
|
||||
};
|
||||
webView.getContext().registerReceiver(this.receiver, intentFilter);
|
||||
}
|
||||
// end CB-1405
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||
private void enableRemoteDebugging() {
|
||||
try {
|
||||
WebView.setWebContentsDebuggingEnabled(true);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.d(TAG, "You have one job! To turn on Remote Web Debugging! YOU HAVE FAILED! ");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static void exposeJsInterface(WebView webView, CordovaBridge bridge) {
|
||||
if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)) {
|
||||
Log.i(TAG, "Disabled addJavascriptInterface() bridge since Android version is old.");
|
||||
// Bug being that Java Strings do not get converted to JS strings automatically.
|
||||
// This isn't hard to work-around on the JS side, but it's easier to just
|
||||
// use the prompt bridge instead.
|
||||
return;
|
||||
}
|
||||
SystemExposedJsApi exposedJsApi = new SystemExposedJsApi(bridge);
|
||||
webView.addJavascriptInterface(exposedJsApi, "_cordovaNative");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load the url into the webview.
|
||||
*/
|
||||
@Override
|
||||
public void loadUrl(final String url, boolean clearNavigationStack) {
|
||||
webView.loadUrl(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl() {
|
||||
return webView.getUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopLoading() {
|
||||
webView.stopLoading();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearCache() {
|
||||
webView.clearCache(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearHistory() {
|
||||
webView.clearHistory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canGoBack() {
|
||||
return webView.canGoBack();
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to previous page in history. (We manage our own history)
|
||||
*
|
||||
* @return true if we went back, false if we are already at top
|
||||
*/
|
||||
@Override
|
||||
public boolean goBack() {
|
||||
// Check webview first to see if there is a history
|
||||
// This is needed to support curPage#diffLink, since they are added to parentEngine's history, but not our history url array (JQMobile behavior)
|
||||
if (webView.canGoBack()) {
|
||||
webView.goBack();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPaused(boolean value) {
|
||||
if (value) {
|
||||
webView.pauseTimers();
|
||||
} else {
|
||||
webView.resumeTimers();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
webView.chromeClient.destroyLastDialog();
|
||||
webView.destroy();
|
||||
// unregister the receiver
|
||||
if (receiver != null) {
|
||||
try {
|
||||
webView.getContext().unregisterReceiver(receiver);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error unregistering configuration receiver: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +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.
|
||||
*/
|
||||
import org.junit.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.apache.cordova.PreferenceNode;
|
||||
|
||||
public class PreferenceNodeTest {
|
||||
@Test
|
||||
public void testConstructor() {
|
||||
PreferenceNode foo = new org.apache.cordova.PreferenceNode("fullscreen", "false", false);
|
||||
assertEquals("fullscreen", foo.name);
|
||||
assertEquals("false", foo.value);
|
||||
assertEquals(false, foo.readonly);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameAssignment() {
|
||||
PreferenceNode foo = new org.apache.cordova.PreferenceNode("fullscreen", "false", false);
|
||||
foo.name = "widescreen";
|
||||
assertEquals("widescreen", foo.name);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValueAssignment() {
|
||||
PreferenceNode foo = new org.apache.cordova.PreferenceNode("fullscreen", "false", false);
|
||||
foo.value = "maybe";
|
||||
assertEquals("maybe", foo.value);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadonlyAssignment() {
|
||||
PreferenceNode foo = new org.apache.cordova.PreferenceNode("fullscreen", "false", false);
|
||||
foo.readonly = true;
|
||||
assertEquals(true, foo.readonly);
|
||||
}
|
||||
}
|
||||
@@ -1,73 +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.
|
||||
*/
|
||||
import org.junit.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.apache.cordova.PreferenceNode;
|
||||
import org.apache.cordova.PreferenceSet;
|
||||
|
||||
public class PreferenceSetTest {
|
||||
private PreferenceSet preferences;
|
||||
private PreferenceNode screen;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
preferences = new PreferenceSet();
|
||||
screen = new PreferenceNode("fullscreen", "true", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddition() {
|
||||
preferences.add(screen);
|
||||
assertEquals(1, preferences.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClear() {
|
||||
preferences.add(screen);
|
||||
preferences.clear();
|
||||
assertEquals(0, preferences.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPreferenceRetrieval() {
|
||||
preferences.add(screen);
|
||||
assertEquals("true", preferences.pref("fullscreen"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoPreferenceRetrieval() {
|
||||
// return null if the preference is not defined
|
||||
assertEquals(null, preferences.pref("antigravity"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsetPreferenceChecking() {
|
||||
PreferenceSet emptySet = new PreferenceSet();
|
||||
boolean value = emptySet.prefMatches("fullscreen", "true");
|
||||
assertEquals(false, value);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetPreferenceChecking() {
|
||||
preferences.add(screen);
|
||||
boolean value = preferences.prefMatches("fullscreen", "true");
|
||||
assertEquals(true, value);
|
||||
}
|
||||
}
|
||||
+4
-5
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cordova-android",
|
||||
"version": "4.0.0-dev",
|
||||
"version": "4.1.1",
|
||||
"description": "cordova-android release",
|
||||
"main": "bin/create",
|
||||
"repository": {
|
||||
@@ -21,12 +21,11 @@
|
||||
"license": "Apache version 2.0",
|
||||
"dependencies": {
|
||||
"q": "^0.9.0",
|
||||
"shelljs": "^0.2.6",
|
||||
"which": "^1.0.5"
|
||||
"shelljs": "^0.2.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jasmine-node": "~1",
|
||||
"jasmine-node": "^1.14.5",
|
||||
"jshint": "^2.6.0",
|
||||
"promise-matchers": "~0"
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-1
@@ -27,7 +27,8 @@ describe("create", function () {
|
||||
var valid = [
|
||||
"org.apache.mobilespec"
|
||||
, "com.example"
|
||||
, "com.42floors.package"
|
||||
, "com.floors42.package"
|
||||
, "ball8.ball8.ball8ball"
|
||||
];
|
||||
var invalid = [
|
||||
""
|
||||
@@ -40,6 +41,10 @@ describe("create", function () {
|
||||
, "_underscore.anything"
|
||||
, "underscore._something"
|
||||
, "_underscore._all._the._things"
|
||||
, "8.ball"
|
||||
, "8ball.ball"
|
||||
, "ball8.8ball"
|
||||
, "ball8.com.8ball"
|
||||
];
|
||||
|
||||
valid.forEach(function(package_name) {
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
||||
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
|
||||
|
||||
<uses-sdk android:minSdkVersion="10" android:targetSdkVersion="19"/>
|
||||
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="19"/>
|
||||
|
||||
<instrumentation
|
||||
android:name="android.test.InstrumentationTestRunner"
|
||||
|
||||
+7
-12
@@ -21,7 +21,6 @@
|
||||
# Android Native Tests
|
||||
|
||||
These tests are designed to verify Android native features and other Android specific features.
|
||||
They currently are in disrepair, and don't pass / work on KitKat+ :(.
|
||||
|
||||
## Initial Setup
|
||||
|
||||
@@ -41,14 +40,6 @@ Copy it from a freshly created project:
|
||||
(cd foo && cordova/build --gradle; cp -r gradlew gradle ..)
|
||||
rm -r foo
|
||||
|
||||
### Robotium
|
||||
|
||||
Robotium has to be installed for the onScrollChanged tests to work correctly. It can be
|
||||
found at https://code.google.com/p/robotium/ and the jar should be put in the
|
||||
'androidTest/libs' directory'.
|
||||
|
||||
mkdir -p androidTest/libs && curl 'http://dl.bintray.com/robotium/generic/robotium-solo-5.2.1.jar' > androidTest/libs/robotium-solo-5.2.1.jar
|
||||
|
||||
## Running
|
||||
|
||||
To run manual tests:
|
||||
@@ -59,8 +50,12 @@ To run unit tests:
|
||||
|
||||
./gradlew connectedAndroidTest
|
||||
|
||||
`BUILD SUCCESSFUL` means that the tests all passed :)
|
||||
|
||||
## Android Studio
|
||||
|
||||
1. Use "Non-Android Studio Project" to import the `test` directory.
|
||||
2. Right click on the `junit` package in the left-side nav
|
||||
3. Select "Debug"`->`_The one with the Android icon_
|
||||
1. Use "Import Project" and import the `test` directory.
|
||||
2. Right click on the `org.apache.cordova.test` package on the left-hand nav.
|
||||
3. Select `Create Run Configuration` -> `Tests in ...` (The one with the Android icon)
|
||||
4. Review options (mainly - target device)
|
||||
5. Click the bug icon in the top toolbar to run with debugger attached
|
||||
|
||||
@@ -37,14 +37,11 @@ public class BaseCordovaIntegrationTest extends ActivityInstrumentationTestCase2
|
||||
super(MainTestActivity.class);
|
||||
}
|
||||
|
||||
protected void setUpWithStartUrl(String url) {
|
||||
setUpWithStartUrl(url, null, null);
|
||||
}
|
||||
protected void setUpWithStartUrl(String url, String prefKey, String prefValue) {
|
||||
protected void setUpWithStartUrl(String url, String... prefsAndValues) {
|
||||
Intent intent = new Intent(getInstrumentation().getContext(), MainTestActivity.class);
|
||||
intent.putExtra("testStartUrl", url);
|
||||
if (prefKey != null) {
|
||||
intent.putExtra(prefKey, prefValue);
|
||||
for (int i = 0; i < prefsAndValues.length; i += 2) {
|
||||
intent.putExtra(prefsAndValues[i], prefsAndValues[i + 1]);
|
||||
}
|
||||
setActivityIntent(intent);
|
||||
testActivity = getActivity();
|
||||
|
||||
@@ -23,7 +23,8 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import org.apache.cordova.AndroidWebView;
|
||||
import org.apache.cordova.CordovaWebViewEngine;
|
||||
import org.apache.cordova.engine.SystemWebView;
|
||||
|
||||
public class CordovaActivityTest extends BaseCordovaIntegrationTest {
|
||||
private ViewGroup innerContainer;
|
||||
@@ -32,13 +33,12 @@ public class CordovaActivityTest extends BaseCordovaIntegrationTest {
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
setUpWithStartUrl(null);
|
||||
innerContainer = (ViewGroup)containerView.getChildAt(0);
|
||||
testView = innerContainer.getChildAt(0);
|
||||
testView = (ViewGroup)containerView.getChildAt(0);
|
||||
}
|
||||
|
||||
public void testBasicLoad() throws Exception {
|
||||
assertTrue(testView instanceof AndroidWebView);
|
||||
assertTrue(innerContainer instanceof LinearLayout);
|
||||
assertTrue(testView instanceof SystemWebView);
|
||||
assertTrue(((CordovaWebViewEngine.EngineView)testView).getCordovaWebView() != null);
|
||||
String onPageFinishedUrl = testActivity.onPageFinishedUrl.take();
|
||||
assertEquals(MainTestActivity.START_URL, onPageFinishedUrl);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
*
|
||||
* 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.test;
|
||||
|
||||
|
||||
import android.app.Activity;
|
||||
import android.test.ActivityInstrumentationTestCase2;
|
||||
|
||||
import org.apache.cordova.CordovaWebView;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class CordovaPluginTest extends BaseCordovaIntegrationTest {
|
||||
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
setUpWithStartUrl(null);
|
||||
}
|
||||
|
||||
private void invokeBlockingCallToLifeCycleEvent(final String lifeCycleEventName) {
|
||||
testActivity.runOnUiThread( new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
Method method = getInstrumentation().getClass().getMethod(lifeCycleEventName, Activity.class);
|
||||
method.invoke(getInstrumentation(), testActivity);
|
||||
} catch (Exception e) {
|
||||
fail("An Exception occurred in invokeBlockingCallToLifeCycleEvent while invoking " + lifeCycleEventName);
|
||||
}
|
||||
}
|
||||
});
|
||||
getInstrumentation().waitForIdleSync();
|
||||
}
|
||||
|
||||
public void testPluginLifeCycle() throws IOException {
|
||||
//TODO: add coverage for both cases where handleOnStart is called in CordovaActivity (onStart and init)
|
||||
//currently only one of the cases is covered
|
||||
//TODO: add coverage for both cases where onStart is called in CordovaWebViewImpl (handleOnStart and init)
|
||||
//currently only one of the cases is covered
|
||||
LifeCyclePlugin testPlugin = (LifeCyclePlugin)cordovaWebView.getPluginManager().getPlugin("LifeCycle");
|
||||
testPlugin.calls = "";
|
||||
// testOnStart
|
||||
invokeBlockingCallToLifeCycleEvent("callActivityOnStart");
|
||||
invokeBlockingCallToLifeCycleEvent("callActivityOnResume");
|
||||
invokeBlockingCallToLifeCycleEvent("callActivityOnPause");
|
||||
invokeBlockingCallToLifeCycleEvent("callActivityOnStop");
|
||||
assertEquals("start,resume,pause,stop,", testPlugin.calls);
|
||||
}
|
||||
}
|
||||
@@ -36,9 +36,11 @@ import org.apache.cordova.PluginEntry;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Scanner;
|
||||
|
||||
public class CordovaResourceApiTest extends BaseCordovaIntegrationTest {
|
||||
@@ -58,8 +60,18 @@ public class CordovaResourceApiTest extends BaseCordovaIntegrationTest {
|
||||
return cordovaWebView.getResourceApi().remapUri(
|
||||
Uri.parse("data:text/plain;charset=utf-8,pass"));
|
||||
}
|
||||
if (uri.getQuery() != null && uri.getQuery().contains("pluginUri")) {
|
||||
return toPluginUri(uri);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
public OpenForReadResult handleOpenForRead(Uri uri) throws IOException {
|
||||
Uri orig = fromPluginUri(uri);
|
||||
ByteArrayInputStream retStream = new ByteArrayInputStream(orig.toString().getBytes(StandardCharsets.UTF_8));
|
||||
return new OpenForReadResult(uri, retStream, "text/plain", retStream.available(), null);
|
||||
}
|
||||
@Override
|
||||
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
|
||||
synchronized (CordovaResourceApiTest.this) {
|
||||
execPayload = args.getString(0);
|
||||
@@ -214,6 +226,16 @@ public class CordovaResourceApiTest extends BaseCordovaIntegrationTest {
|
||||
String data = new Scanner(readResult.inputStream, "UTF-8").useDelimiter("\\A").next();
|
||||
assertEquals("pass", data);
|
||||
}
|
||||
// testPluginUris
|
||||
{
|
||||
String origUri = "http://orig/foo?pluginUri";
|
||||
Uri uri = resourceApi.remapUri(Uri.parse(origUri));
|
||||
OpenForReadResult readResult = resourceApi.openForRead(uri);
|
||||
assertEquals("openForRead mime-type", "text/plain", readResult.mimeType);
|
||||
String data = new Scanner(readResult.inputStream, "UTF-8").useDelimiter("\\A").next();
|
||||
assertEquals(origUri, data);
|
||||
assertEquals(origUri.length(), readResult.length);
|
||||
}
|
||||
}
|
||||
|
||||
public void testWebViewRequestIntercept() throws Throwable
|
||||
|
||||
@@ -24,10 +24,12 @@ package org.apache.cordova.test;
|
||||
public class ErrorUrlTest extends BaseCordovaIntegrationTest {
|
||||
private static final String START_URL = "file:///android_asset/www/htmlnotfound/index.html";
|
||||
private static final String ERROR_URL = "file:///android_asset/www/htmlnotfound/error.html";
|
||||
private static final String INVALID_URL = "file:///android_asset/www/invalid.html";
|
||||
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
setUpWithStartUrl(START_URL, "testErrorUrl", ERROR_URL);
|
||||
// INVALID_URL tests that errorUrl and url are *not* settable via the intent.
|
||||
setUpWithStartUrl(START_URL, "testErrorUrl", ERROR_URL, "errorurl", INVALID_URL, "url", INVALID_URL);
|
||||
}
|
||||
|
||||
public void testUrl() throws Throwable {
|
||||
|
||||
@@ -31,10 +31,9 @@ public class HtmlNotFoundTest extends BaseCordovaIntegrationTest {
|
||||
public void testUrl() throws Throwable
|
||||
{
|
||||
assertEquals(START_URL, testActivity.onPageFinishedUrl.take());
|
||||
// TODO: Should this be null? Or some other way to indicate it didn't actually load?
|
||||
runTestOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
assertEquals(START_URL, testActivity.getCordovaWebView().getUrl());
|
||||
assertFalse(START_URL.equals(testActivity.getCordovaWebView().getUrl()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import org.apache.cordova.AndroidWebView;
|
||||
import org.apache.cordova.engine.SystemWebView;
|
||||
|
||||
public class InflateLayoutTest extends ActivityInstrumentationTestCase2<CordovaWebViewTestActivity> {
|
||||
|
||||
@@ -48,7 +48,7 @@ public class InflateLayoutTest extends ActivityInstrumentationTestCase2<CordovaW
|
||||
}
|
||||
|
||||
public void testBasicLoad() throws Exception {
|
||||
assertTrue(testView instanceof AndroidWebView);
|
||||
assertTrue(testView instanceof SystemWebView);
|
||||
assertTrue(innerContainer instanceof LinearLayout);
|
||||
String onPageFinishedUrl = testActivity.onPageFinishedUrl.take();
|
||||
assertEquals(CordovaWebViewTestActivity.START_URL, onPageFinishedUrl);
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.apache.cordova.test;
|
||||
/*
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
import android.graphics.Color;
|
||||
|
||||
import org.apache.cordova.CordovaPreferences;
|
||||
|
||||
public class IntentPreferenceTest extends BaseCordovaIntegrationTest {
|
||||
private static final String GREEN = Integer.toHexString(Color.GREEN);
|
||||
private static final String START_URL = "file:///android_asset/www/index.html";
|
||||
|
||||
CordovaPreferences prefs;
|
||||
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
// INVALID_URL tests that errorUrl and url are *not* settable via the intent.
|
||||
setUpWithStartUrl(START_URL, "backgroundcolor", GREEN);
|
||||
prefs = cordovaWebView.getPreferences();
|
||||
}
|
||||
|
||||
public void testUrl() throws Throwable {
|
||||
assertEquals(START_URL, testActivity.onPageFinishedUrl.take());
|
||||
assertFalse(prefs.getInteger("backgroundcolor", Color.BLACK) == Color.GREEN);
|
||||
}
|
||||
}
|
||||
+8
-2
@@ -17,8 +17,6 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
// GENERATED FILE! DO NOT EDIT!
|
||||
|
||||
apply plugin: 'android'
|
||||
|
||||
buildscript {
|
||||
@@ -73,3 +71,11 @@ dependencies {
|
||||
androidTestCompile fileTree(dir: 'androidTest/libs', include: '*.jar')
|
||||
}
|
||||
|
||||
task copyCordovaJs (type: Copy) {
|
||||
from '../bin/templates/project/assets/www'
|
||||
into 'assets/www'
|
||||
include('cordova.js')
|
||||
}
|
||||
preBuild.dependsOn copyCordovaJs
|
||||
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<org.apache.cordova.AndroidWebView
|
||||
<org.apache.cordova.engine.SystemWebView
|
||||
android:id="@+id/cordovaWebView"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent" />
|
||||
|
||||
@@ -38,4 +38,8 @@
|
||||
<feature name="Activity">
|
||||
<param name="android-package" value="org.apache.cordova.test.ActivityPlugin" />
|
||||
</feature>
|
||||
<feature name="LifeCycle">
|
||||
<param name="android-package" value="org.apache.cordova.test.LifeCyclePlugin" />
|
||||
<param name="onload" value="true" />
|
||||
</feature>
|
||||
</widget>
|
||||
|
||||
@@ -21,15 +21,13 @@ package org.apache.cordova.test;
|
||||
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
|
||||
import org.apache.cordova.AndroidChromeClient;
|
||||
import org.apache.cordova.AndroidWebView;
|
||||
import org.apache.cordova.AndroidWebViewClient;
|
||||
import org.apache.cordova.Config;
|
||||
import org.apache.cordova.ConfigXmlParser;
|
||||
import org.apache.cordova.CordovaInterfaceImpl;
|
||||
import org.apache.cordova.CordovaWebView;
|
||||
import org.apache.cordova.CordovaInterface;
|
||||
import org.apache.cordova.CordovaPlugin;
|
||||
import org.apache.cordova.test.R;
|
||||
import org.apache.cordova.CordovaWebViewImpl;
|
||||
import org.apache.cordova.engine.SystemWebView;
|
||||
import org.apache.cordova.engine.SystemWebViewEngine;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
@@ -58,12 +56,14 @@ public class CordovaWebViewTestActivity extends Activity {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.main);
|
||||
|
||||
//CB-7238: This has to be added now, because it got removed from somewhere else
|
||||
Config.init(this);
|
||||
|
||||
AndroidWebView webView = (AndroidWebView) findViewById(R.id.cordovaWebView);
|
||||
cordovaWebView = webView;
|
||||
cordovaWebView.init(cordovaInterface, Config.getPluginEntries(), Config.getPreferences());
|
||||
//Set up the webview
|
||||
ConfigXmlParser parser = new ConfigXmlParser();
|
||||
parser.parse(this);
|
||||
|
||||
SystemWebView webView = (SystemWebView) findViewById(R.id.cordovaWebView);
|
||||
cordovaWebView = new CordovaWebViewImpl(new SystemWebViewEngine(webView));
|
||||
cordovaWebView.init(cordovaInterface, parser.getPluginEntries(), parser.getPreferences());
|
||||
|
||||
cordovaWebView.loadUrl(START_URL);
|
||||
}
|
||||
|
||||
+32
-10
@@ -1,4 +1,4 @@
|
||||
<!--
|
||||
/*
|
||||
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
|
||||
@@ -15,13 +15,35 @@
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
<script src="cordova.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
*/
|
||||
package org.apache.cordova.test;
|
||||
|
||||
</body>
|
||||
</html>
|
||||
import org.apache.cordova.CordovaPlugin;
|
||||
import org.apache.cordova.LOG;
|
||||
|
||||
public class LifeCyclePlugin extends CordovaPlugin {
|
||||
|
||||
static String TAG = "LifeCyclePlugin";
|
||||
String calls = "";
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
calls += "start,";
|
||||
LOG.d(TAG, "onStart");
|
||||
}
|
||||
@Override
|
||||
public void onPause(boolean multitasking) {
|
||||
calls += "pause,";
|
||||
LOG.d(TAG, "onPause");
|
||||
}
|
||||
@Override
|
||||
public void onResume(boolean multitasking) {
|
||||
calls += "resume,";
|
||||
LOG.d(TAG, "onResume");
|
||||
}
|
||||
@Override
|
||||
public void onStop() {
|
||||
calls += "stop,";
|
||||
LOG.d(TAG, "onStop");
|
||||
}
|
||||
}
|
||||
@@ -18,15 +18,8 @@
|
||||
*/
|
||||
package org.apache.cordova.test;
|
||||
|
||||
import org.apache.cordova.CordovaActivity;
|
||||
import org.apache.cordova.CordovaInterfaceImpl;
|
||||
import org.apache.cordova.CordovaWebView;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
|
||||
public class MainTestActivity extends BaseTestCordovaActivity {
|
||||
public static final String START_URL = "file:///android_asset/www/index.html";
|
||||
|
||||
@@ -41,9 +34,9 @@ public class MainTestActivity extends BaseTestCordovaActivity {
|
||||
super.loadUrl(url);
|
||||
}
|
||||
|
||||
@Override protected void loadConfig() {
|
||||
@Override protected void loadConfig() {
|
||||
super.loadConfig();
|
||||
// Need to set this explicitly in prefs since it's not settable via bundle extras (for security reasons).
|
||||
// Need to set this explicitly in prefs since it's not settable via bundle extras.
|
||||
String errorUrl = getIntent().getStringExtra("testErrorUrl");
|
||||
if (errorUrl != null) {
|
||||
preferences.set("errorUrl", errorUrl);
|
||||
|
||||
@@ -23,6 +23,9 @@ import android.webkit.WebView;
|
||||
import android.webkit.GeolocationPermissions.Callback;
|
||||
|
||||
import org.apache.cordova.*;
|
||||
import org.apache.cordova.engine.SystemWebChromeClient;
|
||||
import org.apache.cordova.engine.SystemWebViewClient;
|
||||
import org.apache.cordova.engine.SystemWebViewEngine;
|
||||
|
||||
public class userwebview extends MainTestActivity {
|
||||
|
||||
@@ -32,17 +35,19 @@ public class userwebview extends MainTestActivity {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
testViewClient = new TestViewClient(cordovaInterface, ((AndroidWebView)appView));
|
||||
testChromeClient = new TestChromeClient(cordovaInterface, ((AndroidWebView)appView));
|
||||
SystemWebViewEngine engine = (SystemWebViewEngine)appView.getEngine();
|
||||
testViewClient = new TestViewClient(engine);
|
||||
testChromeClient = new TestChromeClient(engine);
|
||||
super.init();
|
||||
((AndroidWebView)appView).setWebViewClient(testViewClient);
|
||||
((AndroidWebView)appView).setWebChromeClient(testChromeClient);
|
||||
WebView webView = (WebView)engine.getView();
|
||||
webView.setWebViewClient(testViewClient);
|
||||
webView.setWebChromeClient(testChromeClient);
|
||||
super.loadUrl("file:///android_asset/www/userwebview/index.html");
|
||||
}
|
||||
|
||||
public class TestChromeClient extends AndroidChromeClient {
|
||||
public TestChromeClient(CordovaInterface ctx, AndroidWebView app) {
|
||||
super(ctx, app);
|
||||
public class TestChromeClient extends SystemWebChromeClient {
|
||||
public TestChromeClient(SystemWebViewEngine parentEngine) {
|
||||
super(parentEngine);
|
||||
LOG.d("userwebview", "TestChromeClient()");
|
||||
}
|
||||
|
||||
@@ -57,9 +62,9 @@ public class userwebview extends MainTestActivity {
|
||||
/**
|
||||
* This class can be used to override the GapViewClient and receive notification of webview events.
|
||||
*/
|
||||
public class TestViewClient extends AndroidWebViewClient {
|
||||
public TestViewClient(CordovaInterface ctx, AndroidWebView app) {
|
||||
super(ctx, app);
|
||||
public class TestViewClient extends SystemWebViewClient {
|
||||
public TestViewClient(SystemWebViewEngine parentEngine) {
|
||||
super(parentEngine);
|
||||
LOG.d("userwebview", "TestViewClient()");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user