mirror of
https://github.com/apache/cordova-android.git
synced 2026-01-30 00:05:28 +08:00
Compare commits
187 Commits
CB-7291
...
update-gra
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ed2272e51 | ||
|
|
d022be547b | ||
|
|
3f83fdbfc1 | ||
|
|
949152532c | ||
|
|
215adab1f9 | ||
|
|
7ce46ed60c | ||
|
|
c32bcca67b | ||
|
|
cb442364ca | ||
|
|
6bdc01290d | ||
|
|
ac34bf1e54 | ||
|
|
6fb164d200 | ||
|
|
a5d300c6ff | ||
|
|
6eb4409a72 | ||
|
|
533677df8b | ||
|
|
8f27b2ab56 | ||
|
|
25be42d385 | ||
|
|
00f6d30e08 | ||
|
|
090822eb41 | ||
|
|
d9900a725d | ||
|
|
a10106c61a | ||
|
|
5cb01f2ae9 | ||
|
|
4c1efe7ad4 | ||
|
|
4be92f285a | ||
|
|
f9b89e98c2 | ||
|
|
be01ce03d0 | ||
|
|
f221441877 | ||
|
|
18fda7ec68 | ||
|
|
f2e8c00f49 | ||
|
|
30e8b818f5 | ||
|
|
525ce0e0ad | ||
|
|
3cd567dc95 | ||
|
|
2f7ffa3636 | ||
|
|
d99386ef1e | ||
|
|
9ae3d2c074 | ||
|
|
dd5a337a49 | ||
|
|
51e634ccb4 | ||
|
|
31b1a821ca | ||
|
|
bf13fd48ce | ||
|
|
3b99760a42 | ||
|
|
0e78dc35d8 | ||
|
|
c8bbdb23de | ||
|
|
7ee8117186 | ||
|
|
8237c41143 | ||
|
|
d52ca93ba6 | ||
|
|
8354651059 | ||
|
|
81cc3c260f | ||
|
|
4dc32e194b | ||
|
|
5a82dd5110 | ||
|
|
f20708a5e7 | ||
|
|
91cf78f183 | ||
|
|
0cde8819cf | ||
|
|
07632b0eeb | ||
|
|
4a7f825cfe | ||
|
|
0b6b068097 | ||
|
|
4bc2051f44 | ||
|
|
623b2306ca | ||
|
|
34dde53506 | ||
|
|
7a09182446 | ||
|
|
233e513860 | ||
|
|
eb8cf56e8e | ||
|
|
12a27643db | ||
|
|
c6ccde0558 | ||
|
|
16e3ebd87b | ||
|
|
94c096dd5b | ||
|
|
2e3e4ec3b2 | ||
|
|
7caa96abcd | ||
|
|
b2776269cf | ||
|
|
b6c5a5fc9a | ||
|
|
94943a9a84 | ||
|
|
4c1942e3fe | ||
|
|
71e72f215d | ||
|
|
58cdfd86d0 | ||
|
|
dfa66b9dd4 | ||
|
|
d56ea25816 | ||
|
|
c91b272648 | ||
|
|
ca8bb75b40 | ||
|
|
36eab713a1 | ||
|
|
7133576fe9 | ||
|
|
effffcba1d | ||
|
|
404ce8bc3e | ||
|
|
a91bd095b0 | ||
|
|
fd6a1e5ed0 | ||
|
|
7d6ac87033 | ||
|
|
8aa813b862 | ||
|
|
95aa5c9f1c | ||
|
|
4319447cb5 | ||
|
|
50ea162251 | ||
|
|
9c239804d3 | ||
|
|
a7ccb9243d | ||
|
|
320e31bb10 | ||
|
|
f9b8f9a45f | ||
|
|
5054b714e2 | ||
|
|
05868b541b | ||
|
|
a40424e75c | ||
|
|
a99c8219bd | ||
|
|
a03fdaba39 | ||
|
|
6f301576eb | ||
|
|
e2b3f76a10 | ||
|
|
b277202838 | ||
|
|
a4f6d9f6e7 | ||
|
|
1d4aa44d3d | ||
|
|
b52fcb8aa9 | ||
|
|
f0da63a8ff | ||
|
|
f38c460588 | ||
|
|
9b9c59766f | ||
|
|
fc2a202afa | ||
|
|
4b4b71ff32 | ||
|
|
9358838dab | ||
|
|
a4d9f702e4 | ||
|
|
efcedabee0 | ||
|
|
25a7b66296 | ||
|
|
ac194cd34f | ||
|
|
eca05e6bad | ||
|
|
84bf20152b | ||
|
|
200e9f1a8e | ||
|
|
7dc09b4019 | ||
|
|
dbb196a17e | ||
|
|
05a95c699f | ||
|
|
9c5e340fb8 | ||
|
|
67006add53 | ||
|
|
1571b26a65 | ||
|
|
bdf2f22f81 | ||
|
|
a8330773ca | ||
|
|
4ca2305693 | ||
|
|
428e1bc14d | ||
|
|
d66bb84924 | ||
|
|
4ce5123a12 | ||
|
|
96a1192474 | ||
|
|
c052f40ef8 | ||
|
|
98246c0e35 | ||
|
|
8ac067da89 | ||
|
|
0ffb5d253a | ||
|
|
3a9898a6a6 | ||
|
|
693ec14df5 | ||
|
|
fa189b3234 | ||
|
|
3b27cd093b | ||
|
|
6abb9da88a | ||
|
|
d5e8807756 | ||
|
|
7e9fdb3555 | ||
|
|
b42faea2eb | ||
|
|
635a6279a9 | ||
|
|
404d3e0959 | ||
|
|
f77b20bbca | ||
|
|
1d0a1664e6 | ||
|
|
410afbf9a1 | ||
|
|
aaddfa6f3a | ||
|
|
2d9a16e857 | ||
|
|
1dcba51092 | ||
|
|
7c63b30de1 | ||
|
|
c0eae1ad52 | ||
|
|
c012b98223 | ||
|
|
559493babd | ||
|
|
990ab2c7ef | ||
|
|
437003de29 | ||
|
|
22b1959333 | ||
|
|
97008305ff | ||
|
|
1a17083e8c | ||
|
|
b6664cc859 | ||
|
|
e595c313a1 | ||
|
|
955da2e360 | ||
|
|
04b3fc0268 | ||
|
|
105ccc81a5 | ||
|
|
3571307df5 | ||
|
|
df05f3a3c0 | ||
|
|
8e31ef7be6 | ||
|
|
f4555f7c96 | ||
|
|
8408da55ea | ||
|
|
4a67dd2e28 | ||
|
|
bd806a34d8 | ||
|
|
2f7e833a79 | ||
|
|
c17503ab78 | ||
|
|
19f76d34db | ||
|
|
25c8b2fabb | ||
|
|
bfd8bf9ca4 | ||
|
|
7a5405d2ab | ||
|
|
b9a24f00ad | ||
|
|
dbfc292353 | ||
|
|
a09255b2ff | ||
|
|
9d1c72cc07 | ||
|
|
09ac30ef2e | ||
|
|
79e313a0c0 | ||
|
|
9f4c75d1c2 | ||
|
|
b37492644c | ||
|
|
04a792a8c2 | ||
|
|
35ec24c3f0 | ||
|
|
61b23677d1 | ||
|
|
90037dc6cd |
5
.travis.yml
Normal file
5
.travis.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
language: android
|
||||
install: npm install
|
||||
script:
|
||||
- npm test
|
||||
- npm run test-build
|
||||
129
RELEASENOTES.md
129
RELEASENOTES.md
@@ -20,6 +20,135 @@
|
||||
-->
|
||||
## Release Notes for Cordova (Android) ##
|
||||
|
||||
### 3.6.0 (Sept 2014) ###
|
||||
|
||||
* Set VERSION to 3.6.0 (via coho)
|
||||
* CB-7410 fix the menu test
|
||||
* CB-7410 Fix the errorUrl test
|
||||
* CB-7410 Fix Basic Authentication test
|
||||
* CB-3445: Allow build and run scripts to select APK by architecture
|
||||
* CB-3445: Add environment variable 'BUILD_MULTIPLE_APKS' for splitting APKs based on architecture
|
||||
* CB-3445: Ensure that JAR files in libs directory are included
|
||||
* CB-7267 update RELEASENOTES for 3.5.1
|
||||
* CB-7410 clarify the title
|
||||
* CB-7385 update cordova.js for testing prior to branch/tag
|
||||
* CB-7410 add whitelist entries to get iframe/GoogleMaps working
|
||||
* CB-7291 propogate change in method signature to the native tests
|
||||
* CB-7291: Restrict meaning of "\*" in internal whitelist to just http and https
|
||||
* CB-7291: Only add file, content and data URLs to internal whitelist
|
||||
* CB-7291: Add defaults to external whitelist
|
||||
* CB-7291: Add external-launch-whitelist and use it for filtering intent launches
|
||||
* CB-3445: Read project.properties to configure gradle libraries
|
||||
* CB-7325 Fix error message in android_sdk_version.js when missing SDK on windows
|
||||
* CB-7335 Add a .gitignore to android project template
|
||||
* CB-7330 Fix dangling function call in last commit (broke gradle builds)
|
||||
* CB-7330 Don't run "android update" during creation
|
||||
* CB-3445 Add gradle support clean command (plus some code cleanup)
|
||||
* CB-7044 Fix typo in prev commit causing check_reqs to always fail.
|
||||
* CB-3445 Copy gradle wrapper in build instead of create
|
||||
* CB-3445 Add .gradle template files for "update" as well as "create"
|
||||
* CB-7044 Add JAVA_HOME when not set. Be stricter about ANDROID_HOME
|
||||
* CB-3445 Speed up gradle building (incremental builds go from 10s -> 1.5s for me)
|
||||
* CB-3445: android: Copy Gradle wrapper from Android SDK rather than bundling a JAR
|
||||
* CB-3445: Add which to checked-in node_modules
|
||||
* CB-3445: Add option to build and install with gradle
|
||||
* CB-3445: Add an initial set of Gradle build scripts
|
||||
* CB-7321 Don't require ant for create script
|
||||
* CB-7044, CB-7299 Fix up PATH problems when possible.
|
||||
* Change in test's AndroidManifest.xml needed for the test to run properly. Forgot the manifest.
|
||||
* Change in test's AndroidManifest.xml needed for the test to run properly
|
||||
* Adding tests related to 3.5.1
|
||||
* CB-7261 Fix setNativeToJsBridgeMode sometimes crashing when switching to ONLINE_EVENT
|
||||
* CB-7265 Fix crash when navigating to custom protocol (introduced in 3.5.1)
|
||||
* Filter out non-launchable intents
|
||||
* Handle unsupported protocol errors in webview better
|
||||
* CB-7238: I should have collapsed this, but Config.init() must go before the creation of CordovaWebView
|
||||
* CB-7238: Minor band-aid to get tests running again, this has to go away before 3.6.0 is released, since this is an API change.
|
||||
* Extend whitelist to handle URLs without // chars
|
||||
* CB-7172 Force window to have focus after resume
|
||||
* CB-7159 Set background color of webView as well as its parent
|
||||
* CB-7018 Fix setButtonPlumbedToJs never un-listening
|
||||
* Undeprecate some just-deprecated symbols in PluginManager.
|
||||
* @Deprecate methods of PluginManager that were never meant to be public
|
||||
* Move plugin instantiation and instance storing logic PluginEntry->PluginManager
|
||||
* Fix broken unit test due to missing Config.init() call
|
||||
* Update to check for Google Glass APIs
|
||||
* Fix for `android` not being in PATH check on Windows
|
||||
* Displaying error when regex does not match.
|
||||
* Fix broken compile due to previous commit :(
|
||||
* Tweak CordovaPlugin.initialize method to be less deprecated.
|
||||
* Un-deprecate CordovaActivity.init() - it's needed to tweak prefs in onCreate
|
||||
* Tweak log messages in CordovaBridge with bridgeSecret is wrong
|
||||
* Backport CordovaBridge from 4.0.x -> master
|
||||
* Update unit tests to not use most deprecated things (e.g. DroidGap)
|
||||
* Add non-String overloades for CordovaPreferences.set()
|
||||
* Make CordovaWebview resilient to init() not being called (for backwards-compatibility)
|
||||
* Add node_module licenses to LICENSE
|
||||
* Update cordova.js snapshot to work with bridge changes
|
||||
* Provide CordovaPlugin with CordovaPreferences. Add new Plugin.initialize()
|
||||
* Convert usages of Config.\* to use the non-static versions
|
||||
* Change getProperty -> prefs.get\* within CordovaActivity
|
||||
* Make CordovaUriHelper class package-private
|
||||
* Fix PluginManager.setPluginEntries not removing old entries
|
||||
* Move registration of App plugin from config.xml -> code
|
||||
* Make setWebViewClient an override instead of an overload. Delete Location-change JS->Native bridge mode (missed some of it).
|
||||
* CB-4404 Revert setting android:windowSoftInputMode to "adjustPan"
|
||||
* Refactor: Use ConfigXmlParser in activity. Adds CordovaWebView.init()
|
||||
* Deprecate some convenience methods on CordovaActivity
|
||||
* Fix CordovaPreferences not correctly parsing hex values (valueOf->decode)
|
||||
* Refactor: Move url-filter information into PluginEntry.
|
||||
* Don't re-parse config.xml in onResume.
|
||||
* Move handling of Fullscreen preference to CordovaActivity
|
||||
* Delete dead code from CordovaActivity
|
||||
* Update .classpath to make Eclipse happy (just re-orders one line)
|
||||
* Delete "CB-3064: The errorUrl is..." Log message left over from debugging presumably
|
||||
* Refactor Config into ConfigXmlParser, CordovaPreferences
|
||||
* Delete Location-change JS->Native bridge mode
|
||||
* CB-5988 Allow exec() only from file: or start-up URL's domain
|
||||
* CB-6761 Fix native->JS bridge ceasing to fire when page changes and online is set to false and the JS loads quickly
|
||||
* Update the errorurl to no longer use intents
|
||||
* This breaks running the JUnit tests, we'll bring it back soon
|
||||
* Refactoring the URI handling on Cordova, removing dead code
|
||||
* CB-7018 Clean up and deprecation of some button-related functions
|
||||
* CB-7017 Fix onload=true being set on all subsequent plugins
|
||||
* CB-5971: Fix package / project validation
|
||||
* CB-5971: Add unit tests to cordova-android
|
||||
* CB-5971: Factor out package/project name validation logic
|
||||
* Delete explicit activity.finish() in back button handling. No change in behaviour.
|
||||
* CB-5971: This would have been a good first bug, too bad
|
||||
* CB-4404: Changing where android:windowSoftInputMode is in the manifest so it works
|
||||
* Add documentation referencing other implementation.
|
||||
* CB-6851 Deprecate WebView.sendJavascript()
|
||||
* CB-6876 Show the correct executable name
|
||||
* CB-6876 Fix the "print usage"
|
||||
* Trivial spelling fix in comments when reading CordovaResourceApi
|
||||
* CB-6818: I want to remove this code, because Square didn't do their headers properly
|
||||
* CB-6860 Add activity_name and launcher_name to AndroidManifest.xml & strings.xml
|
||||
* Add a comment to custom_rules.xml saying why we move AndroidManifest.xml
|
||||
* Remove +x from README.md
|
||||
* CB-6784 Add missing licenses
|
||||
* CB-6784 Add license to CONTRIBUTING.md
|
||||
* Revert "defaults.xml: Add AndroidLaunchMode preference"
|
||||
* updated RELEASENOTES
|
||||
* CB-6315: Wrapping this so it runs on the UI thread
|
||||
* CB-6723 Update package name for Robotium
|
||||
* CB-6707 Update minSdkVersion to 10 consistently
|
||||
* CB-5652 make visible cordova version
|
||||
* Update JS snapshot to version 3.6.0-dev (via coho)
|
||||
* Update JS snapshot to version 3.6.0-dev (via coho)
|
||||
* Set VERSION to 3.6.0-dev (via coho)
|
||||
|
||||
### 3.5.1 (August 2014) ###
|
||||
|
||||
This was a security update to address CVE-2014-3500, CVE-2014-3501,
|
||||
and CVE-2014-3502. For more information, see
|
||||
http://cordova.apache.org/announcements/2014/08/04/android-351.html
|
||||
|
||||
* Filter out non-launchable intents
|
||||
* Handle unsupported protocol errors in webview better
|
||||
* Update the errorurl to no longer use intents
|
||||
* Refactoring the URI handling on Cordova, removing dead code
|
||||
|
||||
### 3.5.0 (May 2014) ###
|
||||
|
||||
* OkHttp has broken headers. Updating for ASF compliance.
|
||||
|
||||
@@ -51,7 +51,7 @@ get_sdks = function() {
|
||||
|
||||
return Q();
|
||||
}, function(stderr) {
|
||||
if (stderr.match(/command\snot\sfound/)) {
|
||||
if (stderr.match(/command\snot\sfound/) || stderr.match(/'android' is not recognized/)) {
|
||||
return Q.reject(new Error('The command \"android\" failed. Make sure you have the latest Android SDK installed, and the \"android\" command (inside the tools/ folder) is added to your path.'));
|
||||
} else {
|
||||
return Q.reject(new Error('An error occurred while listing Android targets'));
|
||||
|
||||
@@ -19,82 +19,211 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
var shell = require('shelljs'),
|
||||
var shelljs = require('shelljs'),
|
||||
child_process = require('child_process'),
|
||||
Q = require('q'),
|
||||
path = require('path'),
|
||||
fs = require('fs'),
|
||||
which = require('which'),
|
||||
ROOT = path.join(__dirname, '..', '..');
|
||||
|
||||
// Get valid target from framework/project.properties
|
||||
module.exports.get_target = function() {
|
||||
if(fs.existsSync(path.join(ROOT, 'framework', 'project.properties'))) {
|
||||
var target = shell.grep(/target=android-[\d+]/, path.join(ROOT, 'framework', 'project.properties'));
|
||||
return target.split('=')[1].replace('\n', '').replace('\r', '').replace(' ', '');
|
||||
} else if (fs.existsSync(path.join(ROOT, 'project.properties'))) {
|
||||
// if no target found, we're probably in a project and project.properties is in ROOT.
|
||||
// this is called on the project itself, and can support Google APIs AND Vanilla Android
|
||||
var target = shell.grep(/target=android-[\d+]/, path.join(ROOT, 'project.properties')) ||
|
||||
shell.grep(/target=Google Inc.:Google APIs:[\d+]/, path.join(ROOT, 'project.properties'));
|
||||
if(target == "" || !target) {
|
||||
// Try Google Glass APIs
|
||||
target = shell.grep(/target=Google Inc.:Glass Development Kit Preview:[\d+]/, path.join(ROOT, 'project.properties'));
|
||||
}
|
||||
return target.split('=')[1].replace('\n', '').replace('\r', '');
|
||||
var isWindows = process.platform == 'win32';
|
||||
|
||||
function forgivingWhichSync(cmd) {
|
||||
try {
|
||||
return which.sync(cmd);
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a promise.
|
||||
module.exports.check_ant = function() {
|
||||
function tryCommand(cmd, errMsg) {
|
||||
var d = Q.defer();
|
||||
child_process.exec('ant -version', function(err, stdout, stderr) {
|
||||
if (err) d.reject(new Error('ERROR : executing command \'ant\', make sure you have ant installed and added to your path.'));
|
||||
else d.resolve();
|
||||
child_process.exec(cmd, function(err, stdout, stderr) {
|
||||
if (err) d.reject(new Error(errMsg));
|
||||
else d.resolve(stdout);
|
||||
});
|
||||
return d.promise;
|
||||
}
|
||||
|
||||
// Get valid target from framework/project.properties
|
||||
module.exports.get_target = function() {
|
||||
function extractFromFile(filePath) {
|
||||
var target = shelljs.grep(/\btarget=/, filePath);
|
||||
if (!target) {
|
||||
throw new Error('Could not find android target within: ' + filePath);
|
||||
}
|
||||
return target.split('=')[1].trim();
|
||||
}
|
||||
if (fs.existsSync(path.join(ROOT, 'framework', 'project.properties'))) {
|
||||
return extractFromFile(path.join(ROOT, 'framework', 'project.properties'));
|
||||
}
|
||||
if (fs.existsSync(path.join(ROOT, 'project.properties'))) {
|
||||
// if no target found, we're probably in a project and project.properties is in ROOT.
|
||||
return extractFromFile(path.join(ROOT, 'project.properties'));
|
||||
}
|
||||
throw new Error('Could not find android target. File missing: ' + path.join(ROOT, 'project.properties'));
|
||||
}
|
||||
|
||||
// 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.');
|
||||
};
|
||||
|
||||
// Returns a promise. Called only by build and clean commands.
|
||||
module.exports.check_gradle = function() {
|
||||
var sdkDir = process.env['ANDROID_HOME'];
|
||||
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' +
|
||||
'Looked here: ' + wrapperDir));
|
||||
}
|
||||
return Q.when();
|
||||
};
|
||||
|
||||
// Returns a promise.
|
||||
module.exports.check_java = function() {
|
||||
var d = Q.defer();
|
||||
child_process.exec('java -version', function(err, stdout, stderr) {
|
||||
if(err) {
|
||||
var msg =
|
||||
'Failed to run \'java -version\', make sure your java environment is set up\n' +
|
||||
'including JDK and JRE.\n' +
|
||||
'Your JAVA_HOME variable is ' + process.env.JAVA_HOME + '\n';
|
||||
d.reject(new Error(msg + err));
|
||||
var javacPath = forgivingWhichSync('javac');
|
||||
var hasJavaHome = !!process.env['JAVA_HOME'];
|
||||
return Q().then(function() {
|
||||
if (hasJavaHome) {
|
||||
// Windows java installer doesn't add javac to PATH, nor set JAVA_HOME (ugh).
|
||||
if (!javacPath) {
|
||||
process.env['PATH'] += path.delimiter + path.join(process.env['JAVA_HOME'], 'bin');
|
||||
}
|
||||
} else {
|
||||
if (javacPath) {
|
||||
// OS X has a command for finding JAVA_HOME.
|
||||
if (fs.existsSync('/usr/libexec/java_home')) {
|
||||
return tryCommand('/usr/libexec/java_home', 'Failed to run: /usr/libexec/java_home')
|
||||
.then(function(stdout) {
|
||||
process.env['JAVA_HOME'] = stdout.trim();
|
||||
});
|
||||
} else {
|
||||
// See if we can derive it from javac's location.
|
||||
var maybeJavaHome = path.dirname(path.dirname(javacPath));
|
||||
if (fs.existsSync(path.join(maybeJavaHome, 'lib', 'tools.jar'))) {
|
||||
process.env['JAVA_HOME'] = maybeJavaHome;
|
||||
} else {
|
||||
throw new Error('Could not find JAVA_HOME. Try setting the environment variable manually');
|
||||
}
|
||||
}
|
||||
} else if (isWindows) {
|
||||
// Try to auto-detect java in the default install paths.
|
||||
var oldSilent = shelljs.config.silent;
|
||||
shelljs.config.silent = true;
|
||||
var firstJdkDir =
|
||||
shelljs.ls(process.env['ProgramFiles'] + '\\java\\jdk*')[0] ||
|
||||
shelljs.ls('C:\\Program Files\\java\\jdk*')[0] ||
|
||||
shelljs.ls('C:\\Program Files (x86)\\java\\jdk*')[0];
|
||||
shelljs.config.silent = oldSilent;
|
||||
if (firstJdkDir) {
|
||||
// shelljs always uses / in paths.
|
||||
firstJdkDir = firstJdkDir.replace(/\//g, path.sep);
|
||||
if (!javacPath) {
|
||||
process.env['PATH'] += path.delimiter + path.join(firstJdkDir, 'bin');
|
||||
}
|
||||
process.env['JAVA_HOME'] = firstJdkDir;
|
||||
}
|
||||
}
|
||||
}
|
||||
else d.resolve();
|
||||
}).then(function() {
|
||||
var msg =
|
||||
'Failed to run "java -version", make sure that you have a JDK installed.\n' +
|
||||
'You can get it from: http://www.oracle.com/technetwork/java/javase/downloads.\n';
|
||||
if (process.env['JAVA_HOME']) {
|
||||
msg += 'Your JAVA_HOME is invalid: ' + process.env['JAVA_HOME'] + '\n';
|
||||
}
|
||||
return tryCommand('java -version', msg)
|
||||
.then(function() {
|
||||
return tryCommand('javac -version', msg);
|
||||
});
|
||||
});
|
||||
return d.promise;
|
||||
}
|
||||
|
||||
// Returns a promise.
|
||||
module.exports.check_android = function() {
|
||||
var valid_target = this.get_target();
|
||||
var d = Q.defer();
|
||||
child_process.exec('android list targets', function(err, stdout, stderr) {
|
||||
if (err) d.reject(stderr);
|
||||
else d.resolve(stdout);
|
||||
return Q().then(function() {
|
||||
var androidCmdPath = forgivingWhichSync('android');
|
||||
var adbInPath = !!forgivingWhichSync('adb');
|
||||
var hasAndroidHome = !!process.env['ANDROID_HOME'] && fs.existsSync(process.env['ANDROID_HOME']);
|
||||
function maybeSetAndroidHome(value) {
|
||||
if (!hasAndroidHome && fs.existsSync(value)) {
|
||||
hasAndroidHome = true;
|
||||
process.env['ANDROID_HOME'] = value;
|
||||
}
|
||||
}
|
||||
if (!hasAndroidHome && !androidCmdPath) {
|
||||
if (isWindows) {
|
||||
// Android Studio installer.
|
||||
maybeSetAndroidHome(path.join(process.env['LOCALAPPDATA'], 'Android', 'android-studio', 'sdk'));
|
||||
maybeSetAndroidHome(path.join(process.env['ProgramFiles'], 'Android', 'android-studio', 'sdk'));
|
||||
// Stand-alone installer.
|
||||
maybeSetAndroidHome(path.join(process.env['LOCALAPPDATA'], 'Android', 'android-sdk'));
|
||||
maybeSetAndroidHome(path.join(process.env['ProgramFiles'], 'Android', 'android-sdk'));
|
||||
} else if (process.platform == 'darwin') {
|
||||
maybeSetAndroidHome('/Applications/Android Studio.app/sdk');
|
||||
// Stand-alone zip file that user might think to put under /Applications
|
||||
maybeSetAndroidHome('/Applications/android-sdk-macosx');
|
||||
maybeSetAndroidHome('/Applications/android-sdk');
|
||||
}
|
||||
if (process.env['HOME']) {
|
||||
// or their HOME directory.
|
||||
maybeSetAndroidHome(path.join(process.env['HOME'], 'android-sdk-macosx'));
|
||||
maybeSetAndroidHome(path.join(process.env['HOME'], 'android-sdk'));
|
||||
}
|
||||
}
|
||||
if (hasAndroidHome && !androidCmdPath) {
|
||||
process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'tools');
|
||||
}
|
||||
if (androidCmdPath && !hasAndroidHome) {
|
||||
var parentDir = path.dirname(androidCmdPath);
|
||||
if (path.basename(parentDir) == 'tools') {
|
||||
process.env['ANDROID_HOME'] = path.dirname(parentDir);
|
||||
hasAndroidHome = true;
|
||||
}
|
||||
}
|
||||
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.');
|
||||
}
|
||||
if (!fs.existsSync(process.env['ANDROID_HOME'])) {
|
||||
throw new Error('ANDROID_HOME is set to a non-existant path: ' + process.env['ANDROID_HOME']);
|
||||
}
|
||||
// Check that the target sdk level is installed.
|
||||
return module.exports.check_android_target(module.exports.get_target());
|
||||
});
|
||||
};
|
||||
|
||||
return d.promise.then(function(output) {
|
||||
if (!output.match(valid_target)) {
|
||||
return Q.reject(new Error('Please install Android target ' + valid_target.split('-')[1] + ' (the Android newest SDK). Make sure you have the latest Android tools installed as well. Run \"android\" from your command-line to install/update any missing SDKs or tools.'));
|
||||
}
|
||||
return Q();
|
||||
}, function(stderr) {
|
||||
if (stderr.match(/command\snot\sfound/) || stderr.match(/is not recognized as an internal or external command/)) {
|
||||
return Q.reject(new Error('The command \"android\" failed. Make sure you have the latest Android SDK installed, and the \"android\" command (inside the tools/ folder) is added to your path.'));
|
||||
} else {
|
||||
return Q.reject(new Error('An error occurred while listing Android targets. Error: ' + stderr ));
|
||||
module.exports.getAbsoluteAndroidCmd = function() {
|
||||
return forgivingWhichSync('android').replace(/(\s)/g, '\\$1');
|
||||
};
|
||||
|
||||
module.exports.check_android_target = function(valid_target) {
|
||||
// valid_target can look like:
|
||||
// android-19
|
||||
// android-L
|
||||
// Google Inc.:Google APIs:20
|
||||
// Google Inc.:Glass Development Kit Preview:20
|
||||
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)');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Returns a promise.
|
||||
module.exports.run = function() {
|
||||
return Q.all([this.check_ant(), this.check_java(), this.check_android()]);
|
||||
return Q.all([this.check_java(), this.check_android()]);
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ function copyJsAndLibrary(projectPath, shared, projectName) {
|
||||
shell.mkdir('-p', nestedCordovaLibPath);
|
||||
shell.cp('-f', path.join(ROOT, 'framework', 'AndroidManifest.xml'), nestedCordovaLibPath);
|
||||
shell.cp('-f', path.join(ROOT, 'framework', 'project.properties'), nestedCordovaLibPath);
|
||||
shell.cp('-f', path.join(ROOT, 'framework', 'build.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.
|
||||
@@ -82,14 +83,46 @@ function copyJsAndLibrary(projectPath, shared, projectName) {
|
||||
}
|
||||
}
|
||||
|
||||
function runAndroidUpdate(projectPath, target_api, shared) {
|
||||
var targetFrameworkDir = getFrameworkDir(projectPath, shared);
|
||||
return exec('android update project --subprojects --path "' + projectPath + '" --target ' + target_api + ' --library "' + path.relative(projectPath, targetFrameworkDir) + '"');
|
||||
function extractSubProjectPaths(data) {
|
||||
var ret = {};
|
||||
var r = /^\s*android\.library\.reference\.\d+=(.*)(?:\s|$)/mg
|
||||
var m;
|
||||
while (m = r.exec(data)) {
|
||||
ret[m[1]] = 1;
|
||||
}
|
||||
return Object.keys(ret);
|
||||
}
|
||||
|
||||
function copyAntRules(projectPath) {
|
||||
function writeProjectProperties(projectPath, target_api, shared) {
|
||||
var dstPath = path.join(projectPath, 'project.properties');
|
||||
var templatePath = path.join(ROOT, 'bin', 'templates', 'project', 'project.properties');
|
||||
var srcPath = fs.existsSync(dstPath) ? dstPath : templatePath;
|
||||
var data = fs.readFileSync(srcPath, 'utf8');
|
||||
data = data.replace(/^target=.*/m, 'target=' + target_api);
|
||||
var subProjects = extractSubProjectPaths(data);
|
||||
subProjects = subProjects.filter(function(p) {
|
||||
return !(/^CordovaLib$/m.exec(p) ||
|
||||
/[\\\/]cordova-android[\\\/]framework$/m.exec(p) ||
|
||||
/^(\.\.[\\\/])+framework$/m.exec(p)
|
||||
);
|
||||
});
|
||||
subProjects.unshift(shared ? path.relative(projectPath, path.join(ROOT, 'framework')) : 'CordovaLib');
|
||||
data = data.replace(/^\s*android\.library\.reference\.\d+=.*\n/mg, '');
|
||||
if (!/\n$/.exec(data)) {
|
||||
data += '\n';
|
||||
}
|
||||
for (var i = 0; i < subProjects.length; ++i) {
|
||||
data += 'android.library.reference.' + (i+1) + '=' + subProjects[i] + '\n';
|
||||
}
|
||||
fs.writeFileSync(dstPath, data);
|
||||
}
|
||||
|
||||
function copyBuildRules(projectPath) {
|
||||
var srcDir = path.join(ROOT, 'bin', 'templates', 'project');
|
||||
shell.cp('-f', path.join(srcDir, 'custom_rules.xml'), projectPath);
|
||||
|
||||
shell.cp('-f', path.join(srcDir, 'build.gradle'), projectPath);
|
||||
shell.cp('-f', path.join(srcDir, 'settings.gradle'), projectPath);
|
||||
shell.cp('-f', path.join(srcDir, 'cordova.gradle'), projectPath);
|
||||
}
|
||||
|
||||
function copyScripts(projectPath) {
|
||||
@@ -193,10 +226,6 @@ exports.createProject = function(project_path, package_name, project_name, proje
|
||||
return validatePackageName(package_name)
|
||||
.then(function() {
|
||||
validateProjectName(project_name);
|
||||
})
|
||||
// Check that requirements are met and proper targets are installed
|
||||
.then(function() {
|
||||
check_reqs.run();
|
||||
}).then(function() {
|
||||
// Log the given values for the project
|
||||
console.log('Creating Cordova project for the Android platform:');
|
||||
@@ -212,6 +241,7 @@ exports.createProject = function(project_path, package_name, project_name, proje
|
||||
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'));
|
||||
@@ -243,10 +273,10 @@ exports.createProject = function(project_path, package_name, project_name, proje
|
||||
shell.sed('-i', /__PACKAGE__/, package_name, manifest_path);
|
||||
shell.sed('-i', /__APILEVEL__/, target_api.split('-')[1], manifest_path);
|
||||
copyScripts(project_path);
|
||||
copyAntRules(project_path);
|
||||
copyBuildRules(project_path);
|
||||
});
|
||||
// Link it to local android install.
|
||||
return runAndroidUpdate(project_path, target_api, use_shared_project);
|
||||
writeProjectProperties(project_path, target_api);
|
||||
}).then(function() {
|
||||
console.log('Project successfully created.');
|
||||
});
|
||||
@@ -269,22 +299,19 @@ function extractProjectNameFromManifest(projectPath) {
|
||||
}
|
||||
|
||||
// Returns a promise.
|
||||
exports.updateProject = function(projectPath) {
|
||||
var version = fs.readFileSync(path.join(ROOT, 'VERSION'), 'utf-8').trim();
|
||||
// Check that requirements are met and proper targets are installed
|
||||
return check_reqs.run()
|
||||
exports.updateProject = function(projectPath, shared) {
|
||||
var newVersion = fs.readFileSync(path.join(ROOT, 'VERSION'), 'utf-8').trim();
|
||||
return Q()
|
||||
.then(function() {
|
||||
var projectName = extractProjectNameFromManifest(projectPath);
|
||||
var target_api = check_reqs.get_target();
|
||||
copyJsAndLibrary(projectPath, false, projectName);
|
||||
copyJsAndLibrary(projectPath, shared, projectName);
|
||||
copyScripts(projectPath);
|
||||
copyAntRules(projectPath);
|
||||
copyBuildRules(projectPath);
|
||||
removeDebuggableFromManifest(projectPath);
|
||||
return runAndroidUpdate(projectPath, target_api, false)
|
||||
.then(function() {
|
||||
console.log('Android project is now at version ' + version);
|
||||
console.log('If you updated from a pre-3.2.0 version and use an IDE, we now require that you import the "CordovaLib" library project.');
|
||||
});
|
||||
writeProjectProperties(projectPath, target_api, shared);
|
||||
console.log('Android project is now at version ' + newVersion);
|
||||
console.log('If you updated from a pre-3.2.0 version and use an IDE, we now require that you import the "CordovaLib" library project.');
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
23
bin/node_modules/which/LICENSE
generated
vendored
Normal file
23
bin/node_modules/which/LICENSE
generated
vendored
Normal file
@@ -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.
|
||||
5
bin/node_modules/which/README.md
generated
vendored
Normal file
5
bin/node_modules/which/README.md
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
The "which" util from npm's guts.
|
||||
|
||||
Finds the first instance of a specified executable in the PATH
|
||||
environment variable. Does not cache the results, so `hash -r` is not
|
||||
needed when the PATH changes.
|
||||
14
bin/node_modules/which/bin/which
generated
vendored
Executable file
14
bin/node_modules/which/bin/which
generated
vendored
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env node
|
||||
var which = require("../")
|
||||
if (process.argv.length < 3) {
|
||||
console.error("Usage: which <thing>")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
which(process.argv[2], function (er, thing) {
|
||||
if (er) {
|
||||
console.error(er.message)
|
||||
process.exit(er.errno || 127)
|
||||
}
|
||||
console.log(thing)
|
||||
})
|
||||
31
bin/node_modules/which/package.json
generated
vendored
Normal file
31
bin/node_modules/which/package.json
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"author": {
|
||||
"name": "Isaac Z. Schlueter",
|
||||
"email": "i@izs.me",
|
||||
"url": "http://blog.izs.me"
|
||||
},
|
||||
"name": "which",
|
||||
"description": "Like which(1) unix command. Find the first instance of an executable in the PATH.",
|
||||
"version": "1.0.5",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/isaacs/node-which.git"
|
||||
},
|
||||
"main": "which.js",
|
||||
"bin": {
|
||||
"which": "./bin/which"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {},
|
||||
"readme": "The \"which\" util from npm's guts.\n\nFinds the first instance of a specified executable in the PATH\nenvironment variable. Does not cache the results, so `hash -r` is not\nneeded when the PATH changes.\n",
|
||||
"readmeFilename": "README.md",
|
||||
"bugs": {
|
||||
"url": "https://github.com/isaacs/node-which/issues"
|
||||
},
|
||||
"homepage": "https://github.com/isaacs/node-which",
|
||||
"_id": "which@1.0.5",
|
||||
"_from": "which@"
|
||||
}
|
||||
104
bin/node_modules/which/which.js
generated
vendored
Normal file
104
bin/node_modules/which/which.js
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
module.exports = which
|
||||
which.sync = whichSync
|
||||
|
||||
var path = require("path")
|
||||
, fs
|
||||
, COLON = process.platform === "win32" ? ";" : ":"
|
||||
, isExe
|
||||
|
||||
try {
|
||||
fs = require("graceful-fs")
|
||||
} catch (ex) {
|
||||
fs = require("fs")
|
||||
}
|
||||
|
||||
if (process.platform == "win32") {
|
||||
// On windows, there is no good way to check that a file is executable
|
||||
isExe = function isExe () { return true }
|
||||
} else {
|
||||
isExe = function isExe (mod, uid, gid) {
|
||||
//console.error(mod, uid, gid);
|
||||
//console.error("isExe?", (mod & 0111).toString(8))
|
||||
var ret = (mod & 0001)
|
||||
|| (mod & 0010) && process.getgid && gid === process.getgid()
|
||||
|| (mod & 0100) && process.getuid && uid === process.getuid()
|
||||
//console.error("isExe?", ret)
|
||||
return ret
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function which (cmd, cb) {
|
||||
if (isAbsolute(cmd)) return cb(null, cmd)
|
||||
var pathEnv = (process.env.PATH || "").split(COLON)
|
||||
, pathExt = [""]
|
||||
if (process.platform === "win32") {
|
||||
pathEnv.push(process.cwd())
|
||||
pathExt = (process.env.PATHEXT || ".EXE").split(COLON)
|
||||
if (cmd.indexOf(".") !== -1) pathExt.unshift("")
|
||||
}
|
||||
//console.error("pathEnv", pathEnv)
|
||||
;(function F (i, l) {
|
||||
if (i === l) return cb(new Error("not found: "+cmd))
|
||||
var p = path.resolve(pathEnv[i], cmd)
|
||||
;(function E (ii, ll) {
|
||||
if (ii === ll) return F(i + 1, l)
|
||||
var ext = pathExt[ii]
|
||||
//console.error(p + ext)
|
||||
fs.stat(p + ext, function (er, stat) {
|
||||
if (!er &&
|
||||
stat &&
|
||||
stat.isFile() &&
|
||||
isExe(stat.mode, stat.uid, stat.gid)) {
|
||||
//console.error("yes, exe!", p + ext)
|
||||
return cb(null, p + ext)
|
||||
}
|
||||
return E(ii + 1, ll)
|
||||
})
|
||||
})(0, pathExt.length)
|
||||
})(0, pathEnv.length)
|
||||
}
|
||||
|
||||
function whichSync (cmd) {
|
||||
if (isAbsolute(cmd)) return cmd
|
||||
var pathEnv = (process.env.PATH || "").split(COLON)
|
||||
, pathExt = [""]
|
||||
if (process.platform === "win32") {
|
||||
pathEnv.push(process.cwd())
|
||||
pathExt = (process.env.PATHEXT || ".EXE").split(COLON)
|
||||
if (cmd.indexOf(".") !== -1) pathExt.unshift("")
|
||||
}
|
||||
for (var i = 0, l = pathEnv.length; i < l; i ++) {
|
||||
var p = path.join(pathEnv[i], cmd)
|
||||
for (var j = 0, ll = pathExt.length; j < ll; j ++) {
|
||||
var cur = p + pathExt[j]
|
||||
var stat
|
||||
try { stat = fs.statSync(cur) } catch (ex) {}
|
||||
if (stat &&
|
||||
stat.isFile() &&
|
||||
isExe(stat.mode, stat.uid, stat.gid)) return cur
|
||||
}
|
||||
}
|
||||
throw new Error("not found: "+cmd)
|
||||
}
|
||||
|
||||
var isAbsolute = process.platform === "win32" ? absWin : absUnix
|
||||
|
||||
function absWin (p) {
|
||||
if (absUnix(p)) return true
|
||||
// pull off the device/UNC bit from a windows path.
|
||||
// from node's lib/path.js
|
||||
var splitDeviceRe =
|
||||
/^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/][^\\\/]+)?([\\\/])?/
|
||||
, result = splitDeviceRe.exec(p)
|
||||
, device = result[1] || ''
|
||||
, isUnc = device && device.charAt(1) !== ':'
|
||||
, isAbsolute = !!result[2] || isUnc // UNC paths are always absolute
|
||||
|
||||
return isAbsolute
|
||||
}
|
||||
|
||||
function absUnix (p) {
|
||||
return p.charAt(0) === "/" || p === ""
|
||||
}
|
||||
@@ -24,13 +24,17 @@ var build = require('./lib/build'),
|
||||
args = process.argv;
|
||||
|
||||
// Support basic help commands
|
||||
if(args[2] == '--help' || args[2] == '/?' || args[2] == '-h' ||
|
||||
args[2] == 'help' || args[2] == '-help' || args[2] == '/help') {
|
||||
if(args[2] == '--help' ||
|
||||
args[2] == '/?' ||
|
||||
args[2] == '-h' ||
|
||||
args[2] == 'help' ||
|
||||
args[2] == '-help' ||
|
||||
args[2] == '/help') {
|
||||
build.help();
|
||||
} else {
|
||||
reqs.run().then(function() {
|
||||
return build.run(args[2]);
|
||||
}).done(null, function(err) {
|
||||
reqs.run().done(function() {
|
||||
return build.run(args.slice(2));
|
||||
}, function(err) {
|
||||
console.error(err);
|
||||
process.exit(2);
|
||||
});
|
||||
|
||||
@@ -19,18 +19,26 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
var clean = require('./lib/clean'),
|
||||
var build = require('./lib/build'),
|
||||
reqs = require('./lib/check_reqs'),
|
||||
args = process.argv;
|
||||
var path = require('path');
|
||||
|
||||
// Usage support for when args are given
|
||||
if(args.length > 2) {
|
||||
clean.help();
|
||||
// Support basic help commands
|
||||
if(args[2] == '--help' ||
|
||||
args[2] == '/?' ||
|
||||
args[2] == '-h' ||
|
||||
args[2] == 'help' ||
|
||||
args[2] == '-help' ||
|
||||
args[2] == '/help') {
|
||||
console.log('Usage: ' + path.relative(process.cwd(), process.argv[1]));
|
||||
console.log('Cleans the project directory.');
|
||||
process.exit(0);
|
||||
} else {
|
||||
reqs.run().done(function() {
|
||||
return clean.run();
|
||||
return build.runClean(args.slice(2));
|
||||
}, function(err) {
|
||||
console.error('ERROR: ' + err);
|
||||
console.error(err);
|
||||
process.exit(2);
|
||||
});
|
||||
}
|
||||
|
||||
433
bin/templates/cordova/lib/build.js
vendored
433
bin/templates/cordova/lib/build.js
vendored
@@ -25,89 +25,378 @@ var shell = require('shelljs'),
|
||||
path = require('path'),
|
||||
fs = require('fs'),
|
||||
ROOT = path.join(__dirname, '..', '..');
|
||||
var check_reqs = require('./check_reqs');
|
||||
var exec = require('./exec');
|
||||
|
||||
var LOCAL_PROPERTIES_TEMPLATE =
|
||||
'# This file is automatically generated.\n' +
|
||||
'# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n';
|
||||
|
||||
function hasCustomRules() {
|
||||
return fs.existsSync(path.join(ROOT, 'custom_rules.xml'));
|
||||
}
|
||||
module.exports.getAntArgs = function(cmd) {
|
||||
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');
|
||||
}
|
||||
return args;
|
||||
};
|
||||
|
||||
/*
|
||||
* Builds the project with ant.
|
||||
* Returns a promise.
|
||||
*/
|
||||
module.exports.run = function(build_type) {
|
||||
//default build type
|
||||
build_type = typeof build_type !== 'undefined' ? build_type : "--debug";
|
||||
var args = module.exports.getAntArgs('debug');
|
||||
switch(build_type) {
|
||||
case '--debug' :
|
||||
break;
|
||||
case '--release' :
|
||||
args[0] = 'release';
|
||||
break;
|
||||
case '--nobuild' :
|
||||
console.log('Skipping build...');
|
||||
return Q();
|
||||
default :
|
||||
return Q.reject('Build option \'' + build_type + '\' not recognized.');
|
||||
}
|
||||
// Without our custom_rules.xml, we need to clean before building.
|
||||
var ret = Q();
|
||||
if (!hasCustomRules()) {
|
||||
ret = require('./clean').run();
|
||||
}
|
||||
return ret.then(function() {
|
||||
return spawn('ant', args);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets the path to the apk file, if not such file exists then
|
||||
* the script will error out. (should we error or just return undefined?)
|
||||
*/
|
||||
module.exports.get_apk = function() {
|
||||
var binDir = '';
|
||||
if(!hasCustomRules()) {
|
||||
binDir = path.join(ROOT, 'bin');
|
||||
} else {
|
||||
binDir = path.join(ROOT, 'ant-build');
|
||||
}
|
||||
if (fs.existsSync(binDir)) {
|
||||
var candidates = fs.readdirSync(binDir).filter(function(p) {
|
||||
// Need to choose between release and debug .apk.
|
||||
return path.extname(p) == '.apk';
|
||||
}).map(function(p) {
|
||||
p = path.join(binDir, p);
|
||||
function find_files(directory, predicate) {
|
||||
if (fs.existsSync(directory)) {
|
||||
var candidates = fs.readdirSync(directory).filter(predicate).map(function(p) {
|
||||
p = path.join(directory, p);
|
||||
return { p: p, t: fs.statSync(p).mtime };
|
||||
}).sort(function(a,b) {
|
||||
return a.t > b.t ? -1 :
|
||||
a.t < b.t ? 1 : 0;
|
||||
});
|
||||
if (candidates.length === 0) {
|
||||
console.error('ERROR : No .apk found in ' + binDir + ' directory');
|
||||
process.exit(2);
|
||||
}
|
||||
console.log('Using apk: ' + candidates[0].p);
|
||||
return candidates[0].p;
|
||||
var timeDiff = b.t - a.t;
|
||||
if (timeDiff === 0) {
|
||||
return a.p.length - b.p.length;
|
||||
}
|
||||
return timeDiff;
|
||||
}).map(function(p) { return p.p; });
|
||||
return candidates;
|
||||
} else {
|
||||
console.error('ERROR : unable to find project ' + binDir + ' directory, could not locate .apk');
|
||||
console.error('ERROR : unable to find project ' + directory + ' directory, could not locate .apk');
|
||||
process.exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
function hasCustomRules() {
|
||||
return fs.existsSync(path.join(ROOT, 'custom_rules.xml'));
|
||||
}
|
||||
|
||||
function extractProjectNameFromManifest(projectPath) {
|
||||
var manifestPath = path.join(projectPath, 'AndroidManifest.xml');
|
||||
var manifestData = fs.readFileSync(manifestPath, 'utf8');
|
||||
var m = /<activity[\s\S]*?android:name\s*=\s*"(.*?)"/i.exec(manifestData);
|
||||
if (!m) {
|
||||
throw new Error('Could not find activity name in ' + manifestPath);
|
||||
}
|
||||
return m[1];
|
||||
}
|
||||
|
||||
function extractSubProjectPaths() {
|
||||
var data = fs.readFileSync(path.join(ROOT, 'project.properties'), 'utf8');
|
||||
var ret = {};
|
||||
var r = /^\s*android\.library\.reference\.\d+=(.*)(?:\s|$)/mg
|
||||
var m;
|
||||
while (m = r.exec(data)) {
|
||||
ret[m[1]] = 1;
|
||||
}
|
||||
return Object.keys(ret);
|
||||
}
|
||||
|
||||
var builders = {
|
||||
ant: {
|
||||
getArgs: function(cmd) {
|
||||
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');
|
||||
}
|
||||
return args;
|
||||
},
|
||||
|
||||
prepEnv: function() {
|
||||
return check_reqs.check_ant()
|
||||
.then(function() {
|
||||
// Copy in build.xml 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.
|
||||
var sdkDir = process.env['ANDROID_HOME'];
|
||||
var buildTemplate = fs.readFileSync(path.join(sdkDir, 'tools', 'lib', 'build.template'), 'utf8');
|
||||
function writeBuildXml(projectPath) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
var subProjects = extractSubProjectPaths();
|
||||
writeBuildXml(ROOT);
|
||||
for (var i = 0; i < subProjects.length; ++i) {
|
||||
writeBuildXml(path.join(ROOT, subProjects[i]));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Builds the project with ant.
|
||||
* Returns a promise.
|
||||
*/
|
||||
build: function(build_type) {
|
||||
// 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();
|
||||
}
|
||||
|
||||
var builder = this;
|
||||
var args = this.getArgs(build_type == 'debug' ? 'debug' : 'release');
|
||||
return check_reqs.check_ant()
|
||||
.then(function() {
|
||||
return spawn('ant', args);
|
||||
}).then(function() {
|
||||
return builder.getOutputFiles();
|
||||
});
|
||||
},
|
||||
|
||||
clean: function() {
|
||||
var args = this.getArgs('clean');
|
||||
return check_reqs.check_ant()
|
||||
.then(function() {
|
||||
return spawn('ant', args);
|
||||
});
|
||||
},
|
||||
|
||||
// Find the recently-generated output APK files
|
||||
// Ant only generates one output file; return it.
|
||||
getOutputFiles: function() {
|
||||
var binDir;
|
||||
if(hasCustomRules()) {
|
||||
binDir = path.join(ROOT, 'ant-build');
|
||||
} else {
|
||||
binDir = path.join(ROOT, 'bin');
|
||||
}
|
||||
var candidates = find_files(binDir, function(candidate) { return path.extname(candidate) == '.apk'; });
|
||||
if (candidates.length === 0) {
|
||||
console.error('ERROR : No .apk found in ' + binDir + ' directory');
|
||||
process.exit(2);
|
||||
}
|
||||
var ret = candidates[0];
|
||||
return [ret];
|
||||
}
|
||||
},
|
||||
gradle: {
|
||||
getArgs: function(cmd) {
|
||||
var lintSteps;
|
||||
if (process.env['BUILD_MULTIPLE_APKS']) {
|
||||
lintSteps = [
|
||||
'lint',
|
||||
'lintVitalX86Release',
|
||||
'lintVitalArmv7Release',
|
||||
'compileLint',
|
||||
'copyReleaseLint',
|
||||
'copyDebugLint'
|
||||
];
|
||||
} else {
|
||||
lintSteps = [
|
||||
'lint',
|
||||
'lintVitalRelease',
|
||||
'compileLint',
|
||||
'copyReleaseLint',
|
||||
'copyDebugLint'
|
||||
];
|
||||
}
|
||||
if (cmd == 'debug') {
|
||||
cmd = 'assembleDebug';
|
||||
} else if (cmd == 'release') {
|
||||
cmd = 'assembleRelease';
|
||||
}
|
||||
var args = [cmd, '-b', path.join(ROOT, 'build.gradle')];
|
||||
// 10 seconds -> 6 seconds
|
||||
args.push('-Dorg.gradle.daemon=true');
|
||||
// Excluding lint: 6s-> 1.6s
|
||||
for (var i = 0; i < lintSteps.length; ++i) {
|
||||
args.push('-x', lintSteps[i]);
|
||||
}
|
||||
// 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() {
|
||||
return check_reqs.check_gradle()
|
||||
.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.
|
||||
var projectPath = ROOT;
|
||||
// check_reqs ensures that this is set.
|
||||
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);
|
||||
} else {
|
||||
shell.cp('-f', path.join(wrapperDir, 'gradlew'), projectPath);
|
||||
}
|
||||
shell.rm('-rf', path.join(projectPath, 'gradle', 'wrapper'));
|
||||
shell.mkdir('-p', path.join(projectPath, 'gradle'));
|
||||
shell.cp('-r', path.join(wrapperDir, 'gradle', 'wrapper'), path.join(projectPath, 'gradle'));
|
||||
|
||||
// If the gradle distribution URL is set, make sure it points to version 1.12.
|
||||
// If it's not set, do nothing, assuming that we're using a future version of gradle that we don't want to mess with.
|
||||
// For some reason, using ^ and $ don't work. This does the job, though.
|
||||
var distributionUrlRegex = /distributionUrl.*zip/;
|
||||
var distributionUrl = 'distributionUrl=http\\://services.gradle.org/distributions/gradle-1.12-all.zip';
|
||||
var gradleWrapperPropertiesPath = path.join(projectPath, 'gradle', 'wrapper', 'gradle-wrapper.properties');
|
||||
shell.sed('-i', distributionUrlRegex, distributionUrl, gradleWrapperPropertiesPath);
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Builds the project with gradle.
|
||||
* Returns a promise.
|
||||
*/
|
||||
build: function(build_type) {
|
||||
var builder = this;
|
||||
var wrapper = path.join(ROOT, 'gradlew');
|
||||
var args = this.getArgs(build_type == 'debug' ? 'debug' : 'release');
|
||||
return Q().then(function() {
|
||||
return spawn(wrapper, args);
|
||||
}).then(function() {
|
||||
return builder.getOutputFiles(build_type);
|
||||
});
|
||||
},
|
||||
|
||||
clean: function() {
|
||||
var builder = this;
|
||||
var wrapper = path.join(ROOT, 'gradlew');
|
||||
var args = builder.getArgs('clean');
|
||||
return Q().then(function() {
|
||||
return spawn(wrapper, args);
|
||||
});
|
||||
},
|
||||
|
||||
// Find the recently-generated output APK files
|
||||
// Gradle can generate multiple output files; return all of them.
|
||||
getOutputFiles: function(build_type) {
|
||||
var binDir = path.join(ROOT, 'build', 'outputs', 'apk');
|
||||
var candidates = find_files(binDir, function(candidate) {
|
||||
// Need to choose between release and debug .apk.
|
||||
if (build_type === 'debug') {
|
||||
return (path.extname(candidate) == '.apk' && candidate.indexOf('-debug') >= 0);
|
||||
}
|
||||
if (build_type === 'release') {
|
||||
return (path.extname(candidate) == '.apk' && candidate.indexOf('-release') >= 0);
|
||||
}
|
||||
return path.extname(candidate) == '.apk';
|
||||
});
|
||||
return candidates;
|
||||
}
|
||||
},
|
||||
|
||||
none: {
|
||||
prepEnv: function() {
|
||||
return Q();
|
||||
},
|
||||
build: function() {
|
||||
console.log('Skipping build...');
|
||||
return Q();
|
||||
},
|
||||
clean: function() {
|
||||
return Q();
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
function parseOpts(options) {
|
||||
// Backwards-compatibility: Allow a single string argument
|
||||
if (typeof options == "string") options = [options];
|
||||
|
||||
var ret = {
|
||||
buildType: 'debug',
|
||||
buildMethod: process.env['ANDROID_BUILD'] || 'ant'
|
||||
};
|
||||
|
||||
// Iterate through command line options
|
||||
for (var i=0; options && (i < options.length); ++i) {
|
||||
if (options[i].substring && options[i].substring(0,2) == "--") {
|
||||
var option = options[i].substring(2);
|
||||
switch(option) {
|
||||
case 'debug':
|
||||
case 'release':
|
||||
ret.buildType = option;
|
||||
break;
|
||||
case 'ant':
|
||||
case 'gradle':
|
||||
ret.buildMethod = option;
|
||||
break;
|
||||
case 'nobuild' :
|
||||
ret.buildMethod = 'none';
|
||||
break;
|
||||
default :
|
||||
return Q.reject('Build option \'' + options[i] + '\' not recognized.');
|
||||
}
|
||||
} else {
|
||||
return Q.reject('Build option \'' + options[i] + '\' not recognized.');
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Builds the project with the specifed options
|
||||
* Returns a promise.
|
||||
*/
|
||||
module.exports.runClean = function(options) {
|
||||
var opts = parseOpts(options);
|
||||
var builder = builders[opts.buildMethod];
|
||||
return builder.prepEnv()
|
||||
.then(function() {
|
||||
return builder.clean();
|
||||
}).then(function() {
|
||||
shell.rm('-rf', path.join(ROOT, 'out'));
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* Builds the project with the specifed options
|
||||
* Returns a promise.
|
||||
*/
|
||||
module.exports.run = function(options) {
|
||||
var opts = parseOpts(options);
|
||||
|
||||
var builder = builders[opts.buildMethod];
|
||||
return builder.prepEnv()
|
||||
.then(function() {
|
||||
return builder.build(opts.buildType);
|
||||
}).then(function(apkFiles) {
|
||||
// TODO: Rather than copy apks to out, it might be better to
|
||||
// just write out what the last .apk build was. These files
|
||||
// are used by get_apk().
|
||||
var outputDir = path.join(ROOT, 'out');
|
||||
shell.mkdir('-p', outputDir);
|
||||
var builtApks = [];
|
||||
for (var i=0; i < apkFiles.length; ++i) {
|
||||
var dst = path.join(outputDir, path.basename(apkFiles[i]));
|
||||
builtApks.push(dst);
|
||||
shell.cp('-f', apkFiles[i], dst);
|
||||
}
|
||||
console.log('Built the following APKs:\n' + builtApks.join('\n'));
|
||||
return builtApks;
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* Detects the architecture of a device/emulator
|
||||
* Returns "arm" or "x86".
|
||||
*/
|
||||
module.exports.detectArchitecture = function(target) {
|
||||
return exec('adb -s ' + target + ' shell cat /proc/cpuinfo')
|
||||
.then(function(output) {
|
||||
if (/intel/i.exec(output)) {
|
||||
return 'x86';
|
||||
}
|
||||
return 'arm';
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* Gets the path to the apk file, if not such file exists then
|
||||
* the script will error out. (should we error or just return undefined?)
|
||||
* This is called by the run script to install the apk to the device
|
||||
*/
|
||||
module.exports.get_apk = function(build_type, architecture) {
|
||||
var outputDir = path.join(ROOT, 'out');
|
||||
var candidates = find_files(outputDir, function(filename) { return (!architecture) || filename.indexOf(architecture) >= 0; });
|
||||
if (candidates.length === 0) {
|
||||
console.error('ERROR : No .apk found in ' + outputDir + ' directory');
|
||||
process.exit(2);
|
||||
}
|
||||
// TODO: Use build_type here.
|
||||
console.log('Using apk: ' + candidates[0]);
|
||||
return candidates[0];
|
||||
};
|
||||
|
||||
module.exports.help = function() {
|
||||
console.log('Usage: ' + path.relative(process.cwd(), path.join(ROOT, 'cordova', 'build')) + ' [build_type]');
|
||||
console.log('Build Types : ');
|
||||
console.log(' \'--debug\': Default build, will build project in using ant debug');
|
||||
console.log(' \'--release\': will build project using ant release');
|
||||
console.log(' \'--debug\': Default build, will build project in debug mode');
|
||||
console.log(' \'--release\': will build project for release');
|
||||
console.log(' \'--ant\': Default build, will build project with ant');
|
||||
console.log(' \'--gradle\': will build project with gradle');
|
||||
console.log(' \'--nobuild\': will skip build process (can be used with run command)');
|
||||
process.exit(0);
|
||||
}
|
||||
};
|
||||
|
||||
39
bin/templates/cordova/lib/clean.js
vendored
39
bin/templates/cordova/lib/clean.js
vendored
@@ -1,39 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
var build = require('./build'),
|
||||
spawn = require('./spawn'),
|
||||
path = require('path');
|
||||
|
||||
/*
|
||||
* Cleans the project using ant
|
||||
* Returns a promise.
|
||||
*/
|
||||
module.exports.run = function() {
|
||||
var args = build.getAntArgs('clean');
|
||||
return spawn('ant', args);
|
||||
}
|
||||
|
||||
module.exports.help = function() {
|
||||
console.log('Usage: ' + path.relative(process.cwd(), process.argv[1]));
|
||||
console.log('Cleans the project directory.');
|
||||
process.exit(0);
|
||||
}
|
||||
16
bin/templates/cordova/lib/device.js
vendored
16
bin/templates/cordova/lib/device.js
vendored
@@ -58,14 +58,18 @@ module.exports.install = function(target) {
|
||||
// default device
|
||||
target = typeof target !== 'undefined' ? target : device_list[0];
|
||||
|
||||
if (device_list.indexOf(target) < 0)
|
||||
if (device_list.indexOf(target) < 0) {
|
||||
return Q.reject('ERROR: Unable to find target \'' + target + '\'.');
|
||||
}
|
||||
|
||||
var apk_path = build.get_apk();
|
||||
launchName = appinfo.getActivityName();
|
||||
console.log('Installing app on device...');
|
||||
var cmd = 'adb -s ' + target + ' install -r "' + apk_path + '"';
|
||||
return exec(cmd);
|
||||
return build.detectArchitecture(target)
|
||||
.then(function(arch) {
|
||||
var apk_path = build.get_apk(null, arch);
|
||||
launchName = appinfo.getActivityName();
|
||||
console.log('Installing app on device...');
|
||||
var cmd = 'adb -s ' + target + ' install -r "' + apk_path + '"';
|
||||
return exec(cmd);
|
||||
});
|
||||
}).then(function(output) {
|
||||
if (output.match(/Failure/)) return Q.reject('ERROR: Failed to install apk to device: ' + output);
|
||||
|
||||
|
||||
29
bin/templates/cordova/lib/emulator.js
vendored
29
bin/templates/cordova/lib/emulator.js
vendored
@@ -28,6 +28,7 @@ var shell = require('shelljs'),
|
||||
ROOT = path.join(__dirname, '..', '..'),
|
||||
child_process = require('child_process'),
|
||||
new_emulator = 'cordova_emulator';
|
||||
var check_reqs = require('./check_reqs');
|
||||
|
||||
/**
|
||||
* Returns a Promise for a list of emulator images in the form of objects
|
||||
@@ -84,7 +85,7 @@ module.exports.list_images = function() {
|
||||
* Returns a promise.
|
||||
*/
|
||||
module.exports.best_image = function() {
|
||||
var project_target = this.get_target().replace('android-', '');
|
||||
var project_target = check_reqs.get_target().replace('android-', '');
|
||||
return this.list_images()
|
||||
.then(function(images) {
|
||||
var closest = 9999;
|
||||
@@ -120,11 +121,6 @@ module.exports.list_started = function() {
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.get_target = function() {
|
||||
var target = shell.grep(/target=android-[\d+]/, path.join(ROOT, 'project.properties'));
|
||||
return target.split('=')[1].replace('\n', '').replace('\r', '').replace(' ', '');
|
||||
}
|
||||
|
||||
// Returns a promise.
|
||||
module.exports.list_targets = function() {
|
||||
return exec('android list targets')
|
||||
@@ -156,7 +152,7 @@ module.exports.start = function(emulator_ID) {
|
||||
.then(function(list) {
|
||||
started_emulators = list;
|
||||
num_started = started_emulators.length;
|
||||
if (typeof emulator_ID === 'undefined') {
|
||||
if (!emulator_ID) {
|
||||
return self.list_images()
|
||||
.then(function(emulator_list) {
|
||||
if (emulator_list.length > 0) {
|
||||
@@ -167,11 +163,11 @@ module.exports.start = function(emulator_ID) {
|
||||
return emulator_ID;
|
||||
});
|
||||
} else {
|
||||
return Q.reject('ERROR : No emulator images (avds) found, if you would like to create an\n' +
|
||||
' avd follow the instructions provided here:\n' +
|
||||
' http://developer.android.com/tools/devices/index.html\n' +
|
||||
' Or run \'android create avd --name <name> --target <targetID>\'\n' +
|
||||
' in on the command line.');
|
||||
var androidCmd = check_reqs.getAbsoluteAndroidCmd();
|
||||
return Q.reject('ERROR : No emulator images (avds) found.\n' +
|
||||
'1. Download desired System Image by running: ' + androidCmd + ' sdk\n' +
|
||||
'2. Create an AVD by running: ' + androidCmd + ' avd\n' +
|
||||
'HINT: For a faster emulator, use an Intel System Image and install the HAXM device driver\n');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -297,9 +293,12 @@ module.exports.install = function(target) {
|
||||
return Q.reject('Unable to find target \'' + target + '\'. Failed to deploy to emulator.');
|
||||
}
|
||||
|
||||
console.log('Installing app on emulator...');
|
||||
var apk_path = build.get_apk();
|
||||
return exec('adb -s ' + target + ' install -r "' + apk_path + '"');
|
||||
return build.detectArchitecture(target)
|
||||
.then(function(arch) {
|
||||
var apk_path = build.get_apk(null, arch);
|
||||
console.log('Installing app on emulator...');
|
||||
return exec('adb -s ' + target + ' install -r "' + apk_path + '"');
|
||||
});
|
||||
}).then(function(output) {
|
||||
if (output.match(/Failure/)) {
|
||||
return Q.reject('Failed to install apk to emulator: ' + output);
|
||||
|
||||
@@ -20,6 +20,6 @@
|
||||
*/
|
||||
|
||||
// Coho updates this line:
|
||||
var VERSION = "3.6.0-dev";
|
||||
var VERSION = "4.0.0-dev";
|
||||
|
||||
console.log(VERSION);
|
||||
|
||||
@@ -29,9 +29,11 @@
|
||||
/>
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
|
||||
<application android:icon="@drawable/icon" android:label="@string/app_name"
|
||||
android:hardwareAccelerated="true">
|
||||
android:hardwareAccelerated="true" android:supportsRtl="true">
|
||||
<activity android:name="__ACTIVITY__"
|
||||
android:label="@string/activity_name"
|
||||
android:launchMode="singleTop"
|
||||
|
||||
201
bin/templates/project/build.gradle
Normal file
201
bin/templates/project/build.gradle
Normal file
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
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 java.util.regex.Pattern
|
||||
import groovy.swing.SwingBuilder
|
||||
|
||||
ext.cordova = {}
|
||||
apply from: 'cordova.gradle', to: ext.cordova
|
||||
apply plugin: 'android'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:0.12.0+'
|
||||
}
|
||||
}
|
||||
|
||||
ext.multiarch=false
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: '*.jar')
|
||||
for (subproject in getProjectList()) {
|
||||
compile project(subproject)
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
sourceSets {
|
||||
main {
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
java.srcDirs = ['src']
|
||||
resources.srcDirs = ['src']
|
||||
aidl.srcDirs = ['src']
|
||||
renderscript.srcDirs = ['src']
|
||||
res.srcDirs = ['res']
|
||||
assets.srcDirs = ['assets']
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
versionCode Integer.parseInt(System.env.ANDROID_VERSION_CODE ?: ("" + getVersionCodeFromManifest() + "0"))
|
||||
}
|
||||
|
||||
compileSdkVersion cordova.cordovaSdkVersion
|
||||
buildToolsVersion cordova.cordovaBuildToolsVersion
|
||||
|
||||
if (multiarch || System.env.BUILD_MULTIPLE_APKS) {
|
||||
productFlavors {
|
||||
armv7 {
|
||||
versionCode defaultConfig.versionCode + 2
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a", ""
|
||||
}
|
||||
}
|
||||
x86 {
|
||||
versionCode defaultConfig.versionCode + 4
|
||||
ndk {
|
||||
abiFilters "x86", ""
|
||||
}
|
||||
}
|
||||
all {
|
||||
ndk {
|
||||
abiFilters "all", ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
|
||||
if (System.env.RELEASE_SIGNING_PROPERTIES_FILE) {
|
||||
signingConfigs {
|
||||
release {
|
||||
// These must be set or Gradle will complain (even if they are overridden).
|
||||
keyAlias = ""
|
||||
keyPassword = ""
|
||||
storeFile = null
|
||||
storePassword = ""
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
addSigningProps(System.env.RELEASE_SIGNING_PROPERTIES_FILE, signingConfigs.release)
|
||||
}
|
||||
if (System.env.DEBUG_SIGNING_PROPERTIES_FILE) {
|
||||
addSigningProps(System.env.DEBUG_SIGNING_PROPERTIES_FILE, signingConfigs.debug)
|
||||
}
|
||||
}
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = '1.12'
|
||||
}
|
||||
|
||||
def promptForPassword(msg) {
|
||||
if (System.console() == null) {
|
||||
def ret = null
|
||||
new SwingBuilder().edt {
|
||||
dialog(modal: true, title: 'Enter password', alwaysOnTop: true, resizable: false, locationRelativeTo: null, pack: true, show: true) {
|
||||
vbox {
|
||||
label(text: msg)
|
||||
def input = passwordField()
|
||||
button(defaultButton: true, text: 'OK', actionPerformed: {
|
||||
ret = input.password;
|
||||
dispose();
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!ret) {
|
||||
throw new GradleException('User canceled build')
|
||||
}
|
||||
return new String(ret)
|
||||
} else {
|
||||
return System.console().readPassword('\n' + msg);
|
||||
}
|
||||
}
|
||||
|
||||
def promptForReleaseKeyPassword() {
|
||||
if (!System.env.RELEASE_SIGNING_PROPERTIES_FILE) {
|
||||
return;
|
||||
}
|
||||
if (!android.signingConfigs.release.storePassword) {
|
||||
android.signingConfigs.release.storePassword = promptForPassword('Enter key store password: ')
|
||||
println('set to:' + android.signingConfigs.release.storePassword)
|
||||
}
|
||||
if (!android.signingConfigs.release.keyPassword) {
|
||||
android.signingConfigs.release.keyPassword = promptForPassword('Enter key password: ');
|
||||
}
|
||||
}
|
||||
|
||||
gradle.taskGraph.whenReady { taskGraph ->
|
||||
taskGraph.getAllTasks().each() { task ->
|
||||
if (task.name == 'validateReleaseSigning') {
|
||||
promptForReleaseKeyPassword()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def getVersionCodeFromManifest() {
|
||||
def manifestFile = file(android.sourceSets.main.manifest.srcFile)
|
||||
def pattern = Pattern.compile("versionCode=\"(\\d+)\"")
|
||||
def matcher = pattern.matcher(manifestFile.getText())
|
||||
matcher.find()
|
||||
return Integer.parseInt(matcher.group(1))
|
||||
}
|
||||
|
||||
def getProjectList() {
|
||||
def manifestFile = file("project.properties")
|
||||
def pattern = Pattern.compile("android.library.reference.(\\d+)\\s*=\\s*(.*)")
|
||||
def matcher = pattern.matcher(manifestFile.getText())
|
||||
def projects = []
|
||||
while (matcher.find()) {
|
||||
projects.add(":" + matcher.group(2).replace("/",":"))
|
||||
}
|
||||
return projects
|
||||
}
|
||||
|
||||
def ensureValueExists(filePath, props, key) {
|
||||
if (props.get(key) == null) {
|
||||
throw new GradleException(filePath + ': Missing key required "' + key + '"')
|
||||
}
|
||||
return props.get(key)
|
||||
}
|
||||
|
||||
def addSigningProps(propsFilePath, signingConfig) {
|
||||
def propsFile = file(propsFilePath)
|
||||
propsFile.withReader { reader ->
|
||||
def props = new Properties()
|
||||
props.load(reader)
|
||||
signingConfig.keyAlias = ensureValueExists(propsFilePath, props, 'keyAlias')
|
||||
signingConfig.keyPassword = props.get('keyPassword')
|
||||
signingConfig.storeFile = RelativePath.parse(true, ensureValueExists(propsFilePath, props, 'storeFile')).getFile(propsFile.getParentFile())
|
||||
signingConfig.storePassword = props.get('storePassword')
|
||||
}
|
||||
}
|
||||
|
||||
120
bin/templates/project/cordova.gradle
Normal file
120
bin/templates/project/cordova.gradle
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
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 java.util.regex.Pattern
|
||||
|
||||
String getProjectTarget(String defaultTarget) {
|
||||
def manifestFile = file("project.properties")
|
||||
def pattern = Pattern.compile("target\\s*=\\s*(.*)")
|
||||
def matcher = pattern.matcher(manifestFile.getText())
|
||||
if (matcher.find()) {
|
||||
matcher.group(1)
|
||||
} else {
|
||||
defaultTarget
|
||||
}
|
||||
}
|
||||
|
||||
String[] getAvailableBuildTools() {
|
||||
def buildToolsDir = new File(getAndroidSdkDir(), "build-tools")
|
||||
buildToolsDir.list()
|
||||
.findAll { it ==~ /[0-9.]+/ }
|
||||
.sort { a, b -> compareVersions(b, a) }
|
||||
}
|
||||
|
||||
String latestBuildToolsAvailable(String minBuildToolsVersion) {
|
||||
def availableBuildToolsVersions
|
||||
try {
|
||||
availableBuildToolsVersions = getAvailableBuildTools()
|
||||
} catch (e) {
|
||||
println "An exception occurred while trying to find the Android build tools."
|
||||
throw e
|
||||
}
|
||||
if (availableBuildToolsVersions.length > 0) {
|
||||
def highestBuildToolsVersion = availableBuildToolsVersions[0]
|
||||
if (compareVersions(highestBuildToolsVersion, minBuildToolsVersion) < 0) {
|
||||
throw new RuntimeException(
|
||||
"No usable Android build tools found. Highest installed version is " +
|
||||
highestBuildToolsVersion + "; minimum version required is " +
|
||||
minBuildToolsVersion + ".")
|
||||
}
|
||||
highestBuildToolsVersion
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
"No installed build tools found. Please install the Android build tools version " +
|
||||
minBuildToolsVersion + " or higher.")
|
||||
}
|
||||
}
|
||||
|
||||
// Return the first non-zero result of subtracting version list elements
|
||||
// pairwise. If they are all identical, return the difference in length of
|
||||
// the two lists.
|
||||
int compareVersionList(Collection aParts, Collection bParts) {
|
||||
def pairs = ([aParts, bParts]).transpose()
|
||||
pairs.findResult(aParts.size()-bParts.size()) {it[0] - it[1] != 0 ? it[0] - it[1] : null}
|
||||
}
|
||||
|
||||
// Compare two version strings, such as "19.0.0" and "18.1.1.0". If all matched
|
||||
// elements are identical, the longer version is the largest by this method.
|
||||
// Examples:
|
||||
// "19.0.0" > "19"
|
||||
// "19.0.1" > "19.0.0"
|
||||
// "19.1.0" > "19.0.1"
|
||||
// "19" > "18.999.999"
|
||||
int compareVersions(String a, String b) {
|
||||
def aParts = a.tokenize('.').collect {it.toInteger()}
|
||||
def bParts = b.tokenize('.').collect {it.toInteger()}
|
||||
compareVersionList(aParts, bParts)
|
||||
}
|
||||
|
||||
String getAndroidSdkDir() {
|
||||
def rootDir = project.rootDir
|
||||
def androidSdkDir = null
|
||||
String envVar = System.getenv("ANDROID_HOME")
|
||||
def localProperties = new File(rootDir, 'local.properties')
|
||||
String systemProperty = System.getProperty("android.home")
|
||||
if (envVar != null) {
|
||||
androidSdkDir = envVar
|
||||
} else if (localProperties.exists()) {
|
||||
Properties properties = new Properties()
|
||||
localProperties.withInputStream { instr ->
|
||||
properties.load(instr)
|
||||
}
|
||||
def sdkDirProp = properties.getProperty('sdk.dir')
|
||||
if (sdkDirProp != null) {
|
||||
androidSdkDir = sdkDirProp
|
||||
} else {
|
||||
sdkDirProp = properties.getProperty('android.dir')
|
||||
if (sdkDirProp != null) {
|
||||
androidSdkDir = (new File(rootDir, sdkDirProp)).getAbsolutePath()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (androidSdkDir == null && systemProperty != null) {
|
||||
androidSdkDir = systemProperty
|
||||
}
|
||||
if (androidSdkDir == null) {
|
||||
throw new RuntimeException(
|
||||
"Unable to determine Android SDK directory.")
|
||||
}
|
||||
androidSdkDir
|
||||
}
|
||||
|
||||
cordovaSdkVersion = System.env.MIN_SDK_VERSION ?: getProjectTarget("android-19")
|
||||
cordovaBuildToolsVersion = latestBuildToolsAvailable("19.1.0")
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project>
|
||||
<target name="-pre-compile">
|
||||
<!-- Fix library references due to bug in build.xml: See: https://groups.google.com/forum/#!topic/android-developers/0ivH-YqCjzg -->
|
||||
<pathconvert property="fixedJarsPath" refid="project.all.jars.path">
|
||||
<filtermapper>
|
||||
<replacestring from="/bin/" to="/ant-build/"/>
|
||||
<replacestring from="\bin\" to="\ant-build\"/>
|
||||
</filtermapper>
|
||||
</pathconvert>
|
||||
<path id="project.all.jars.path">
|
||||
<pathelement path="${fixedJarsPath}"/>
|
||||
</path>
|
||||
<echo message="Set jars path to: ${toString:project.all.jars.path}"/>
|
||||
</target>
|
||||
<!-- Rename AndroidManifest.xml so that Eclipse's import wizard doesn't detect ant-build as a project -->
|
||||
<target name="-post-build">
|
||||
<move file="ant-build/AndroidManifest.xml" tofile="ant-build/AndroidManifest.cordova.xml" failonerror="false" overwrite="true" />
|
||||
<move file="CordovaLib/ant-build/AndroidManifest.xml" tofile="CordovaLib/ant-build/AndroidManifest.cordova.xml" failonerror="false" overwrite="true" />
|
||||
</target>
|
||||
</project>
|
||||
|
||||
14
bin/templates/project/gitignore
Normal file
14
bin/templates/project/gitignore
Normal file
@@ -0,0 +1,14 @@
|
||||
# Non-project-specific build files:
|
||||
build.xml
|
||||
local.properties
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/gradle
|
||||
# Ant builds
|
||||
ant-built
|
||||
ant-gen
|
||||
# Eclipse builds
|
||||
gen
|
||||
out
|
||||
# Gradle builds
|
||||
/build
|
||||
15
bin/templates/project/project.properties
Normal file
15
bin/templates/project/project.properties
Normal file
@@ -0,0 +1,15 @@
|
||||
# This file is automatically generated by Android Tools.
|
||||
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||
#
|
||||
# This file must be checked in Version Control Systems.
|
||||
#
|
||||
# To customize properties used by the Ant build system edit
|
||||
# "ant.properties", and override values to adapt the script to your
|
||||
# project structure.
|
||||
#
|
||||
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
|
||||
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
|
||||
|
||||
android.library.reference.1=CordovaLib
|
||||
# Project target.
|
||||
target=This_gets_replaced
|
||||
18
bin/templates/project/settings.gradle
Normal file
18
bin/templates/project/settings.gradle
Normal file
@@ -0,0 +1,18 @@
|
||||
import java.util.regex.Pattern
|
||||
|
||||
def getProjectList() {
|
||||
def manifestFile = file("project.properties")
|
||||
def pattern = Pattern.compile("android.library.reference.(\\d+)\\s*=\\s*(.*)")
|
||||
def matcher = pattern.matcher(manifestFile.getText())
|
||||
def projects = []
|
||||
while (matcher.find()) {
|
||||
projects.add(":" + matcher.group(2).replace("/",":"))
|
||||
}
|
||||
return projects
|
||||
}
|
||||
|
||||
for (subproject in getProjectList()) {
|
||||
include subproject
|
||||
}
|
||||
|
||||
include ':'
|
||||
34
framework/assets/www/cordova.js
vendored
34
framework/assets/www/cordova.js
vendored
@@ -1,5 +1,5 @@
|
||||
// Platform: android
|
||||
// 3.6.0-dev-70cdca3
|
||||
// 3.7.0-dev-1258511
|
||||
/*
|
||||
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 CORDOVA_JS_BUILD_LABEL = '3.6.0-dev-70cdca3';
|
||||
var CORDOVA_JS_BUILD_LABEL = '3.7.0-dev-1258511';
|
||||
// file: src/scripts/require.js
|
||||
|
||||
/*jshint -W079 */
|
||||
@@ -1497,6 +1497,17 @@ module.exports = {
|
||||
cordova.addDocumentEventHandler('menubutton');
|
||||
cordova.addDocumentEventHandler('searchbutton');
|
||||
|
||||
function bindButtonChannel(buttonName) {
|
||||
// generic button bind used for volumeup/volumedown buttons
|
||||
var volumeButtonChannel = cordova.addDocumentEventHandler(buttonName + 'button');
|
||||
volumeButtonChannel.onHasSubscribersChange = function() {
|
||||
exec(null, null, "App", "overrideButton", [buttonName, this.numHandlers == 1]);
|
||||
};
|
||||
}
|
||||
// Inject a listener for the volume buttons on the document.
|
||||
bindButtonChannel('volumeup');
|
||||
bindButtonChannel('volumedown');
|
||||
|
||||
// Let native code know we are all done on the JS side.
|
||||
// Native code will then un-hide the WebView.
|
||||
channel.onCordovaReady.subscribe(function() {
|
||||
@@ -1574,6 +1585,21 @@ module.exports = {
|
||||
exec(null, null, "App", "overrideBackbutton", [override]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Override the default behavior of the Android volume button.
|
||||
* If overridden, when the volume button is pressed, the "volume[up|down]button"
|
||||
* JavaScript event will be fired.
|
||||
*
|
||||
* Note: The user should not have to call this method. Instead, when the user
|
||||
* registers for the "volume[up|down]button" event, this is automatically done.
|
||||
*
|
||||
* @param button volumeup, volumedown
|
||||
* @param override T=override, F=cancel override
|
||||
*/
|
||||
overrideButton:function(button, override) {
|
||||
exec(null, null, "App", "overrideButton", [button, override]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Exit and terminate the application.
|
||||
*/
|
||||
@@ -1667,11 +1693,11 @@ function handlePluginsObject(path, moduleList, finishPluginLoading) {
|
||||
function findCordovaPath() {
|
||||
var path = null;
|
||||
var scripts = document.getElementsByTagName('script');
|
||||
var term = 'cordova.js';
|
||||
var term = '/cordova.js';
|
||||
for (var n = scripts.length-1; n>-1; n--) {
|
||||
var src = scripts[n].src.replace(/\?.*$/, ''); // Strip any query param (CB-6007).
|
||||
if (src.indexOf(term) == (src.length - term.length)) {
|
||||
path = src.substring(0, src.length - term.length);
|
||||
path = src.substring(0, src.length - term.length) + '/';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
61
framework/build.gradle
Normal file
61
framework/build.gradle
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// This should be updated with each cordova-android release.
|
||||
// It can affect things like where the .apk is generated.
|
||||
// It also dictates what the minimum android build-tools version
|
||||
// that you need (Set in bin/templates/project/cordova.gradle).
|
||||
// Be sure to also update the value in:
|
||||
// (1) bin/templates/project/build.gradle, and
|
||||
// (2) the distribution URL in bin/templates/cordova/lib/build.js.
|
||||
classpath 'com.android.tools.build:gradle:0.12.+'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'android-library'
|
||||
|
||||
android {
|
||||
compileSdkVersion cordova.cordovaSdkVersion
|
||||
buildToolsVersion cordova.cordovaBuildToolsVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
java.srcDirs = ['src']
|
||||
resources.srcDirs = ['src']
|
||||
aidl.srcDirs = ['src']
|
||||
renderscript.srcDirs = ['src']
|
||||
res.srcDirs = ['res']
|
||||
assets.srcDirs = ['assets']
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,7 @@
|
||||
<content src="index.html" />
|
||||
|
||||
<preference name="loglevel" value="DEBUG" />
|
||||
|
||||
<!--
|
||||
<preference name="splashscreen" value="resourceName" />
|
||||
<preference name="backgroundColor" value="0xFFF" />
|
||||
|
||||
@@ -54,35 +54,25 @@ import android.widget.RelativeLayout;
|
||||
* @see CordovaWebViewClient
|
||||
* @see CordovaWebView
|
||||
*/
|
||||
public class CordovaChromeClient extends WebChromeClient {
|
||||
public class AndroidChromeClient extends WebChromeClient {
|
||||
|
||||
public static final int FILECHOOSER_RESULTCODE = 5173;
|
||||
private String TAG = "CordovaLog";
|
||||
private static final String LOG_TAG = "AndroidChromeClient";
|
||||
private long MAX_QUOTA = 100 * 1024 * 1024;
|
||||
protected CordovaInterface cordova;
|
||||
protected CordovaWebView appView;
|
||||
protected final CordovaInterface cordova;
|
||||
protected final AndroidWebView appView;
|
||||
|
||||
// the video progress view
|
||||
private View mVideoProgressView;
|
||||
|
||||
// File Chooser
|
||||
public ValueCallback<Uri> mUploadMessage;
|
||||
protected ValueCallback<Uri> mUploadMessage;
|
||||
|
||||
@Deprecated
|
||||
public CordovaChromeClient(CordovaInterface cordova) {
|
||||
this.cordova = cordova;
|
||||
}
|
||||
|
||||
public CordovaChromeClient(CordovaInterface ctx, CordovaWebView app) {
|
||||
public AndroidChromeClient(CordovaInterface ctx, AndroidWebView app) {
|
||||
this.cordova = ctx;
|
||||
this.appView = app;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setWebView(CordovaWebView view) {
|
||||
this.appView = view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the client to display a javascript alert dialog.
|
||||
*
|
||||
@@ -228,7 +218,7 @@ public class CordovaChromeClient extends WebChromeClient {
|
||||
public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize,
|
||||
long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater)
|
||||
{
|
||||
LOG.d(TAG, "onExceededDatabaseQuota estimatedSize: %d currentQuota: %d totalUsedQuota: %d", estimatedSize, currentQuota, totalUsedQuota);
|
||||
LOG.d(LOG_TAG, "onExceededDatabaseQuota estimatedSize: %d currentQuota: %d totalUsedQuota: %d", estimatedSize, currentQuota, totalUsedQuota);
|
||||
quotaUpdater.updateQuota(MAX_QUOTA);
|
||||
}
|
||||
|
||||
@@ -241,7 +231,7 @@ public class CordovaChromeClient extends WebChromeClient {
|
||||
//This is only for Android 2.1
|
||||
if(android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.ECLAIR_MR1)
|
||||
{
|
||||
LOG.d(TAG, "%s: Line %d : %s", sourceID, lineNumber, message);
|
||||
LOG.d(LOG_TAG, "%s: Line %d : %s", sourceID, lineNumber, message);
|
||||
super.onConsoleMessage(message, lineNumber, sourceID);
|
||||
}
|
||||
}
|
||||
@@ -251,7 +241,7 @@ public class CordovaChromeClient extends WebChromeClient {
|
||||
public boolean onConsoleMessage(ConsoleMessage consoleMessage)
|
||||
{
|
||||
if (consoleMessage.message() != null)
|
||||
LOG.d(TAG, "%s: Line %d : %s" , consoleMessage.sourceId() , consoleMessage.lineNumber(), consoleMessage.message());
|
||||
LOG.d(LOG_TAG, "%s: Line %d : %s" , consoleMessage.sourceId() , consoleMessage.lineNumber(), consoleMessage.message());
|
||||
return super.onConsoleMessage(consoleMessage);
|
||||
}
|
||||
|
||||
@@ -310,7 +300,7 @@ public class CordovaChromeClient extends WebChromeClient {
|
||||
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
|
||||
this.openFileChooser(uploadMsg, "*/*");
|
||||
}
|
||||
|
||||
|
||||
public void openFileChooser( ValueCallback<Uri> uploadMsg, String acceptType ) {
|
||||
this.openFileChooser(uploadMsg, acceptType, null);
|
||||
}
|
||||
@@ -324,8 +314,4 @@ public class CordovaChromeClient extends WebChromeClient {
|
||||
this.cordova.getActivity().startActivityForResult(Intent.createChooser(i, "File Browser"),
|
||||
FILECHOOSER_RESULTCODE);
|
||||
}
|
||||
|
||||
public ValueCallback<Uri> getValueCallback() {
|
||||
return this.mUploadMessage;
|
||||
}
|
||||
}
|
||||
50
framework/src/org/apache/cordova/AndroidExposedJsApi.java
Executable file
50
framework/src/org/apache/cordova/AndroidExposedJsApi.java
Executable file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
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.webkit.JavascriptInterface;
|
||||
import org.json.JSONException;
|
||||
|
||||
/**
|
||||
* Contains APIs that the JS can call. All functions in here should also have
|
||||
* an equivalent entry in CordovaChromeClient.java, and be added to
|
||||
* cordova-js/lib/android/plugin/android/promptbasednativeapi.js
|
||||
*/
|
||||
class AndroidExposedJsApi implements ExposedJsApi {
|
||||
private final CordovaBridge bridge;
|
||||
|
||||
AndroidExposedJsApi(CordovaBridge bridge) {
|
||||
this.bridge = bridge;
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
|
||||
return bridge.jsExec(bridgeSecret, service, action, callbackId, arguments);
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException {
|
||||
bridge.jsSetNativeToJsBridgeMode(bridgeSecret, value);
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public String retrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException {
|
||||
return bridge.jsRetrieveJsMessages(bridgeSecret, fromOnlineEvent);
|
||||
}
|
||||
}
|
||||
775
framework/src/org/apache/cordova/AndroidWebView.java
Executable file
775
framework/src/org/apache/cordova/AndroidWebView.java
Executable file
@@ -0,0 +1,775 @@
|
||||
/*
|
||||
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.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;
|
||||
|
||||
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 Whitelist internalWhitelist;
|
||||
private Whitelist externalWhitelist;
|
||||
private CordovaPreferences preferences;
|
||||
// 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(CordovaInterface cordova, List<PluginEntry> pluginEntries,
|
||||
Whitelist internalWhitelist, Whitelist externalWhitelist,
|
||||
CordovaPreferences preferences) {
|
||||
if (this.cordova != null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
this.cordova = cordova;
|
||||
this.internalWhitelist = internalWhitelist;
|
||||
this.externalWhitelist = externalWhitelist;
|
||||
this.preferences = preferences;
|
||||
|
||||
pluginManager = new PluginManager(this, this.cordova, pluginEntries);
|
||||
resourceApi = new CordovaResourceApi(this.getContext(), pluginManager);
|
||||
bridge = new CordovaBridge(pluginManager, new NativeToJsMessageQueue(this, cordova));
|
||||
pluginManager.addService("App", "org.apache.cordova.CoreAndroid");
|
||||
initWebViewSettings();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@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.ICE_CREAM_SANDWICH_MR1)
|
||||
Level16Apis.enableUniversalAccess(settings);
|
||||
// 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) {
|
||||
this.loadedUrl = url;
|
||||
this.pluginManager.init();
|
||||
}
|
||||
|
||||
// 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("file://") || url.startsWith("javascript:") || internalWhitelist.isUrlWhiteListed(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 (url.startsWith("file://") || internalWhitelist.isUrlWhiteListed(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) {
|
||||
this.loadUrl("javascript:cordova.fireDocumentEvent('volumedownbutton');");
|
||||
return true;
|
||||
}
|
||||
else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
|
||||
this.loadUrl("javascript:cordova.fireDocumentEvent('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)) {
|
||||
this.loadUrl("javascript:cordova.fireDocumentEvent('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()) {
|
||||
this.loadUrl("javascript:cordova.fireDocumentEvent('menubutton');");
|
||||
}
|
||||
this.lastMenuEventTime = event.getEventTime();
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
// If search key
|
||||
else if (keyCode == KeyEvent.KEYCODE_SEARCH) {
|
||||
this.loadUrl("javascript:cordova.fireDocumentEvent('searchbutton');");
|
||||
return true;
|
||||
}
|
||||
|
||||
//Does webkit change this behavior?
|
||||
return super.onKeyUp(keyCode, 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);
|
||||
}
|
||||
|
||||
public void handlePause(boolean keepRunning)
|
||||
{
|
||||
LOG.d(TAG, "Handle the pause");
|
||||
// Send pause event to JavaScript
|
||||
this.loadUrl("javascript:try{cordova.fireDocumentEvent('pause');}catch(e){console.log('exception firing pause event from native');};");
|
||||
|
||||
// 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 (including setInterval)
|
||||
this.pauseTimers();
|
||||
}
|
||||
}
|
||||
|
||||
public void handleResume(boolean keepRunning, boolean activityResultKeepRunning)
|
||||
{
|
||||
this.loadUrl("javascript:try{cordova.fireDocumentEvent('resume');}catch(e){console.log('exception firing resume event from native');};");
|
||||
|
||||
// Forward to plugins
|
||||
if (this.pluginManager != null) {
|
||||
this.pluginManager.onResume(keepRunning);
|
||||
}
|
||||
|
||||
// Resume JavaScript timers (including setInterval)
|
||||
this.resumeTimers();
|
||||
}
|
||||
|
||||
public void handleDestroy()
|
||||
{
|
||||
// Send destroy event to JavaScript
|
||||
this.loadUrl("javascript:try{cordova.require('cordova/channel').onDestroy.fire();}catch(e){console.log('exception firing destroy event from native');};");
|
||||
|
||||
// Load blank page so that JavaScript onunload is called
|
||||
this.loadUrl("about:blank");
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
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 Whitelist getWhitelist() {
|
||||
return this.internalWhitelist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Whitelist getExternalWhitelist() {
|
||||
return this.externalWhitelist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CordovaPreferences getPreferences() {
|
||||
return preferences;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFilePickerResult(Uri uri) {
|
||||
if (null == chromeClient.mUploadMessage)
|
||||
return;
|
||||
chromeClient.mUploadMessage.onReceiveValue(uri);
|
||||
chromeClient.mUploadMessage = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postMessage(String id, Object data) {
|
||||
return pluginManager.postMessage(id, data);
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,6 @@ package org.apache.cordova;
|
||||
|
||||
import java.util.Hashtable;
|
||||
|
||||
import org.apache.cordova.CordovaInterface;
|
||||
|
||||
import org.apache.cordova.LOG;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
@@ -38,6 +35,7 @@ import android.webkit.SslErrorHandler;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
|
||||
|
||||
/**
|
||||
* This class is the WebViewClient that implements callbacks for our web view.
|
||||
* The kind of callbacks that happen here are regarding the rendering of the
|
||||
@@ -50,42 +48,20 @@ import android.webkit.WebViewClient;
|
||||
* @see CordovaChromeClient
|
||||
* @see CordovaWebView
|
||||
*/
|
||||
public class CordovaWebViewClient extends WebViewClient {
|
||||
public class AndroidWebViewClient extends WebViewClient {
|
||||
|
||||
private static final String TAG = "CordovaWebViewClient";
|
||||
CordovaInterface cordova;
|
||||
CordovaWebView appView;
|
||||
CordovaUriHelper helper;
|
||||
private static final String TAG = "AndroidWebViewClient";
|
||||
protected final CordovaInterface cordova;
|
||||
protected final AndroidWebView appView;
|
||||
protected final CordovaUriHelper helper;
|
||||
private boolean doClearHistory = false;
|
||||
boolean isCurrentlyLoading;
|
||||
|
||||
/** The authorization tokens. */
|
||||
private Hashtable<String, AuthenticationToken> authenticationTokens = new Hashtable<String, AuthenticationToken>();
|
||||
|
||||
@Deprecated
|
||||
public CordovaWebViewClient(CordovaInterface cordova) {
|
||||
public AndroidWebViewClient(CordovaInterface cordova, AndroidWebView view) {
|
||||
this.cordova = cordova;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param cordova
|
||||
* @param view
|
||||
*/
|
||||
public CordovaWebViewClient(CordovaInterface cordova, CordovaWebView view) {
|
||||
this.cordova = cordova;
|
||||
this.appView = view;
|
||||
helper = new CordovaUriHelper(cordova, view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param view
|
||||
*/
|
||||
@Deprecated
|
||||
public void setWebView(CordovaWebView view) {
|
||||
this.appView = view;
|
||||
helper = new CordovaUriHelper(cordova, view);
|
||||
}
|
||||
@@ -100,17 +76,12 @@ public class CordovaWebViewClient extends WebViewClient {
|
||||
*/
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
return helper.shouldOverrideUrlLoading(view, url);
|
||||
return helper.shouldOverrideUrlLoading(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
|
||||
*
|
||||
* @param view
|
||||
* @param handler
|
||||
* @param host
|
||||
* @param realm
|
||||
*/
|
||||
@Override
|
||||
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
|
||||
@@ -140,16 +111,11 @@ public class CordovaWebViewClient extends WebViewClient {
|
||||
super.onPageStarted(view, url, favicon);
|
||||
isCurrentlyLoading = true;
|
||||
LOG.d(TAG, "onPageStarted(" + url + ")");
|
||||
// Flush stale messages.
|
||||
this.appView.bridge.reset(url);
|
||||
// Flush stale messages & reset plugins.
|
||||
this.appView.onPageReset();
|
||||
|
||||
// Broadcast message that page has loaded
|
||||
this.appView.postMessage("onPageStarted", url);
|
||||
|
||||
// Notify all plugins of the navigation, so they can clean up if necessary.
|
||||
if (this.appView.pluginManager != null) {
|
||||
this.appView.pluginManager.onReset();
|
||||
}
|
||||
this.appView.getPluginManager().postMessage("onPageStarted", url);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,10 +148,10 @@ public class CordovaWebViewClient extends WebViewClient {
|
||||
}
|
||||
|
||||
// Clear timeout flag
|
||||
this.appView.loadUrlTimeout++;
|
||||
appView.loadUrlTimeout++;
|
||||
|
||||
// Broadcast message that page has loaded
|
||||
this.appView.postMessage("onPageFinished", url);
|
||||
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) {
|
||||
@@ -195,7 +161,7 @@ public class CordovaWebViewClient extends WebViewClient {
|
||||
Thread.sleep(2000);
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
appView.postMessage("spinner", "stop");
|
||||
appView.getPluginManager().postMessage("spinner", "stop");
|
||||
}
|
||||
});
|
||||
} catch (InterruptedException e) {
|
||||
@@ -207,7 +173,7 @@ public class CordovaWebViewClient extends WebViewClient {
|
||||
|
||||
// Shutdown if blank loaded
|
||||
if (url.equals("about:blank")) {
|
||||
appView.postMessage("exit", null);
|
||||
appView.getPluginManager().postMessage("exit", null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,7 +195,7 @@ public class CordovaWebViewClient extends WebViewClient {
|
||||
LOG.d(TAG, "CordovaWebViewClient.onReceivedError: Error code=%s Description=%s URL=%s", errorCode, description, failingUrl);
|
||||
|
||||
// Clear timeout flag
|
||||
this.appView.loadUrlTimeout++;
|
||||
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
|
||||
@@ -252,7 +218,7 @@ public class CordovaWebViewClient extends WebViewClient {
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
this.appView.postMessage("onReceivedError", data);
|
||||
this.appView.getPluginManager().postMessage("onReceivedError", data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -361,5 +327,4 @@ public class CordovaWebViewClient extends WebViewClient {
|
||||
public void clearAuthenticationTokens() {
|
||||
this.authenticationTokens.clear();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -30,7 +30,6 @@ import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.res.XmlResourceParser;
|
||||
import android.util.Log;
|
||||
|
||||
public class ConfigXmlParser {
|
||||
private static String TAG = "ConfigXmlParser";
|
||||
@@ -80,7 +79,6 @@ public class ConfigXmlParser {
|
||||
String service = "", pluginClass = "", paramType = "";
|
||||
boolean onload = false;
|
||||
boolean insideFeature = false;
|
||||
ArrayList<String> urlMap = null;
|
||||
|
||||
// Add implicitly allowed URLs
|
||||
internalWhitelist.addWhiteListEntry("file:///*", false);
|
||||
@@ -90,13 +88,7 @@ public class ConfigXmlParser {
|
||||
while (eventType != XmlResourceParser.END_DOCUMENT) {
|
||||
if (eventType == XmlResourceParser.START_TAG) {
|
||||
String strNode = xml.getName();
|
||||
if (strNode.equals("url-filter")) {
|
||||
Log.w(TAG, "Plugin " + service + " is using deprecated tag <url-filter>");
|
||||
if (urlMap == null) {
|
||||
urlMap = new ArrayList<String>(2);
|
||||
}
|
||||
urlMap.add(xml.getAttributeValue(null, "value"));
|
||||
} else if (strNode.equals("feature")) {
|
||||
if (strNode.equals("feature")) {
|
||||
//Check for supported feature sets aka. plugins (Accelerometer, Geolocation, etc)
|
||||
//Set the bit for reading params
|
||||
insideFeature = true;
|
||||
@@ -147,13 +139,12 @@ public class ConfigXmlParser {
|
||||
{
|
||||
String strNode = xml.getName();
|
||||
if (strNode.equals("feature")) {
|
||||
pluginEntries.add(new PluginEntry(service, pluginClass, onload, urlMap));
|
||||
pluginEntries.add(new PluginEntry(service, pluginClass, onload));
|
||||
|
||||
service = "";
|
||||
pluginClass = "";
|
||||
insideFeature = false;
|
||||
onload = false;
|
||||
urlMap = null;
|
||||
}
|
||||
}
|
||||
try {
|
||||
|
||||
@@ -18,8 +18,9 @@
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
@@ -29,7 +30,6 @@ import org.apache.cordova.LOG;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
@@ -44,14 +44,12 @@ import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.webkit.ValueCallback;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
@@ -91,12 +89,6 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
||||
// The webview for our app
|
||||
protected CordovaWebView appView;
|
||||
|
||||
@Deprecated // unused.
|
||||
protected CordovaWebViewClient webViewClient;
|
||||
|
||||
@Deprecated // Will be removed. Use findViewById() to retrieve views.
|
||||
protected LinearLayout root;
|
||||
|
||||
protected ProgressDialog spinnerDialog = null;
|
||||
private final ExecutorService threadPool = Executors.newCachedThreadPool();
|
||||
|
||||
@@ -116,7 +108,6 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
||||
// Draw a splash screen using an image located in the drawable resource directory.
|
||||
// This is not the same as calling super.loadSplashscreen(url)
|
||||
protected int splashscreen = 0;
|
||||
protected int splashscreenTime = 3000;
|
||||
|
||||
// LoadUrl timeout value in msec (default of 20 sec)
|
||||
protected int loadUrlTimeoutValue = 20000;
|
||||
@@ -135,64 +126,6 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
||||
protected String launchUrl;
|
||||
protected ArrayList<PluginEntry> pluginEntries;
|
||||
|
||||
/**
|
||||
* Sets the authentication token.
|
||||
*
|
||||
* @param authenticationToken
|
||||
* @param host
|
||||
* @param realm
|
||||
*/
|
||||
public void setAuthenticationToken(AuthenticationToken authenticationToken, String host, String realm) {
|
||||
if (this.appView != null && this.appView.viewClient != null) {
|
||||
this.appView.viewClient.setAuthenticationToken(authenticationToken, host, realm);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the authentication token.
|
||||
*
|
||||
* @param host
|
||||
* @param realm
|
||||
*
|
||||
* @return the authentication token or null if did not exist
|
||||
*/
|
||||
public AuthenticationToken removeAuthenticationToken(String host, String realm) {
|
||||
if (this.appView != null && this.appView.viewClient != null) {
|
||||
return this.appView.viewClient.removeAuthenticationToken(host, realm);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the authentication token.
|
||||
*
|
||||
* In order it tries:
|
||||
* 1- host + realm
|
||||
* 2- host
|
||||
* 3- realm
|
||||
* 4- no host, no realm
|
||||
*
|
||||
* @param host
|
||||
* @param realm
|
||||
*
|
||||
* @return the authentication token
|
||||
*/
|
||||
public AuthenticationToken getAuthenticationToken(String host, String realm) {
|
||||
if (this.appView != null && this.appView.viewClient != null) {
|
||||
return this.appView.viewClient.getAuthenticationToken(host, realm);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all authentication tokens.
|
||||
*/
|
||||
public void clearAuthenticationTokens() {
|
||||
if (this.appView != null && this.appView.viewClient != null) {
|
||||
this.appView.viewClient.clearAuthenticationTokens();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity is first created.
|
||||
*/
|
||||
@@ -209,6 +142,38 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
||||
|
||||
loadConfig();
|
||||
}
|
||||
|
||||
protected void init() {
|
||||
if(!preferences.getBoolean("ShowTitle", false))
|
||||
{
|
||||
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,
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
} else {
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN,
|
||||
WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
|
||||
}
|
||||
|
||||
appView = makeWebView();
|
||||
|
||||
// TODO: Have the views set this themselves.
|
||||
if (preferences.getBoolean("DisallowOverscroll", false)) {
|
||||
appView.getView().setOverScrollMode(View.OVER_SCROLL_NEVER);
|
||||
}
|
||||
createViews();
|
||||
|
||||
// TODO: Make this a preference (CB-6153)
|
||||
// Setup the hardware volume controls to handle volume control
|
||||
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
protected void loadConfig() {
|
||||
@@ -226,32 +191,29 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
protected void createViews() {
|
||||
// This builds the view. We could probably get away with NOT having a LinearLayout, but I like having a bucket!
|
||||
// This builds the view. We could probably get away with NOT having a LinearLayout, but I like having a bucket!
|
||||
Display display = getWindowManager().getDefaultDisplay();
|
||||
int width = display.getWidth();
|
||||
int height = display.getHeight();
|
||||
|
||||
root = new LinearLayoutSoftKeyboardDetect(this, width, height);
|
||||
LinearLayoutSoftKeyboardDetect root = new LinearLayoutSoftKeyboardDetect(this, width, height);
|
||||
root.setOrientation(LinearLayout.VERTICAL);
|
||||
root.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, 0.0F));
|
||||
|
||||
appView.setId(100);
|
||||
appView.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
appView.getView().setId(100);
|
||||
appView.getView().setLayoutParams(new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
1.0F));
|
||||
|
||||
// Add web view but make it invisible while loading URL
|
||||
appView.setVisibility(View.INVISIBLE);
|
||||
root.addView((View) appView);
|
||||
appView.getView().setVisibility(View.INVISIBLE);
|
||||
root.addView(appView.getView());
|
||||
setContentView(root);
|
||||
|
||||
// TODO: Setting this on the appView causes it to show when <html style="opacity:0">.
|
||||
int backgroundColor = preferences.getInteger("BackgroundColor", Color.BLACK);
|
||||
root.setBackgroundColor(backgroundColor);
|
||||
appView.setBackgroundColor(backgroundColor);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -268,93 +230,50 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
||||
* require a more specialized web view.
|
||||
*/
|
||||
protected CordovaWebView makeWebView() {
|
||||
return new CordovaWebView(CordovaActivity.this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the client for the default web view object.
|
||||
*
|
||||
* This is intended to be overridable by subclasses of CordovaIntent which
|
||||
* require a more specialized web view.
|
||||
*
|
||||
* @param webView the default constructed web view object
|
||||
*/
|
||||
protected CordovaWebViewClient makeWebViewClient(CordovaWebView webView) {
|
||||
return webView.makeWebViewClient(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the chrome client for the default web view object.
|
||||
*
|
||||
* This is intended to be overridable by subclasses of CordovaIntent which
|
||||
* require a more specialized web view.
|
||||
*
|
||||
* @param webView the default constructed web view object
|
||||
*/
|
||||
protected CordovaChromeClient makeChromeClient(CordovaWebView webView) {
|
||||
return webView.makeWebChromeClient(this);
|
||||
}
|
||||
|
||||
public void init() {
|
||||
this.init(appView, null, null);
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@Deprecated // Call init() instead and override makeWebView() to customize.
|
||||
public void init(CordovaWebView webView, CordovaWebViewClient webViewClient, CordovaChromeClient webChromeClient) {
|
||||
LOG.d(TAG, "CordovaActivity.init()");
|
||||
|
||||
if(!preferences.getBoolean("ShowTitle", false))
|
||||
{
|
||||
getWindow().requestFeature(Window.FEATURE_NO_TITLE);
|
||||
String r = preferences.getString("webView", null);
|
||||
CordovaWebView ret = null;
|
||||
if (r != null) {
|
||||
try {
|
||||
Class<?> webViewClass = Class.forName(r);
|
||||
Constructor<?> constructor = webViewClass.getConstructor(Context.class);
|
||||
ret = (CordovaWebView) constructor.newInstance((Context)this);
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (InstantiationException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
} catch (InvocationTargetException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchMethodException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
} else {
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN,
|
||||
WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
|
||||
|
||||
if (ret == null) {
|
||||
// If all else fails, return a default WebView
|
||||
ret = new AndroidWebView(this);
|
||||
}
|
||||
|
||||
appView = webView != null ? webView : makeWebView();
|
||||
if (appView.pluginManager == null) {
|
||||
appView.init(this, webViewClient != null ? webViewClient : makeWebViewClient(appView),
|
||||
webChromeClient != null ? webChromeClient : makeChromeClient(appView),
|
||||
pluginEntries, internalWhitelist, externalWhitelist, preferences);
|
||||
}
|
||||
|
||||
// TODO: Have the views set this themselves.
|
||||
if (preferences.getBoolean("DisallowOverscroll", false)) {
|
||||
appView.setOverScrollMode(View.OVER_SCROLL_NEVER);
|
||||
}
|
||||
createViews();
|
||||
|
||||
// TODO: Make this a preference (CB-6153)
|
||||
// Setup the hardware volume controls to handle volume control
|
||||
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
||||
ret.init(this, pluginEntries, internalWhitelist, externalWhitelist, preferences);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the url into the webview.
|
||||
*/
|
||||
public void loadUrl(String url) {
|
||||
public void loadUrl(String url, int splashscreenTime) {
|
||||
if (appView == null) {
|
||||
init();
|
||||
}
|
||||
this.splashscreenTime = preferences.getInteger("SplashScreenDelay", this.splashscreenTime);
|
||||
String splash = preferences.getString("SplashScreen", null);
|
||||
if(this.splashscreenTime > 0 && splash != null)
|
||||
if(splashscreenTime > 0 && splash != null)
|
||||
{
|
||||
this.splashscreen = getResources().getIdentifier(splash, "drawable", getClass().getPackage().getName());;
|
||||
if(this.splashscreen != 0)
|
||||
{
|
||||
this.showSplashScreen(this.splashscreenTime);
|
||||
this.showSplashScreen(splashscreenTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,21 +281,17 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
||||
this.keepRunning = preferences.getBoolean("KeepRunning", true);
|
||||
|
||||
//Check if the view is attached to anything
|
||||
if(appView.getParent() != null)
|
||||
if(appView.getView().getParent() != null)
|
||||
{
|
||||
// Then load the spinner
|
||||
this.loadSpinner();
|
||||
}
|
||||
//Load the correct splashscreen
|
||||
|
||||
if(this.splashscreen != 0)
|
||||
{
|
||||
this.appView.loadUrl(url, this.splashscreenTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.appView.loadUrl(url);
|
||||
appView.getPluginManager().postMessage("splashscreen", "show");
|
||||
}
|
||||
this.appView.loadUrlIntoView(url, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -386,10 +301,11 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
||||
* @param url
|
||||
* @param time The number of ms to wait before loading webview
|
||||
*/
|
||||
public void loadUrl(final String url, int time) {
|
||||
|
||||
this.splashscreenTime = time;
|
||||
this.loadUrl(url);
|
||||
public void loadUrl(final String url) {
|
||||
if (appView == null) {
|
||||
init();
|
||||
}
|
||||
this.loadUrl(url, preferences.getInteger("SplashScreenDelay", 3000));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -425,134 +341,6 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void cancelLoadUrl() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the resource cache.
|
||||
*/
|
||||
@Deprecated // Call method on appView directly.
|
||||
public void clearCache() {
|
||||
if (appView == null) {
|
||||
init();
|
||||
}
|
||||
this.appView.clearCache(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear web history in this web view.
|
||||
*/
|
||||
@Deprecated // Call method on appView directly.
|
||||
public void clearHistory() {
|
||||
this.appView.clearHistory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to previous page in history. (We manage our own history)
|
||||
*
|
||||
* @return true if we went back, false if we are already at top
|
||||
*/
|
||||
@Deprecated // Call method on appView directly.
|
||||
public boolean backHistory() {
|
||||
if (this.appView != null) {
|
||||
return appView.backHistory();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get boolean property for activity.
|
||||
*/
|
||||
@Deprecated // Call method on preferences directly.
|
||||
public boolean getBooleanProperty(String name, boolean defaultValue) {
|
||||
return preferences.getBoolean(name, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get int property for activity.
|
||||
*/
|
||||
@Deprecated // Call method on preferences directly.
|
||||
public int getIntegerProperty(String name, int defaultValue) {
|
||||
return preferences.getInteger(name, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get string property for activity.
|
||||
*/
|
||||
@Deprecated // Call method on preferences directly.
|
||||
public String getStringProperty(String name, String defaultValue) {
|
||||
return preferences.getString(name, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get double property for activity.
|
||||
*/
|
||||
@Deprecated // Call method on preferences directly.
|
||||
public double getDoubleProperty(String name, double defaultValue) {
|
||||
return preferences.getDouble(name, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set boolean property on activity.
|
||||
* This method has been deprecated in 3.0 and will be removed at a future
|
||||
* time. Please use config.xml instead.
|
||||
*
|
||||
* @param name
|
||||
* @param value
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public void setBooleanProperty(String name, boolean value) {
|
||||
Log.d(TAG, "Setting boolean properties in CordovaActivity will be deprecated in 3.0 on July 2013, please use config.xml");
|
||||
this.getIntent().putExtra(name.toLowerCase(), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set int property on activity.
|
||||
* This method has been deprecated in 3.0 and will be removed at a future
|
||||
* time. Please use config.xml instead.
|
||||
*
|
||||
* @param name
|
||||
* @param value
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public void setIntegerProperty(String name, int value) {
|
||||
Log.d(TAG, "Setting integer properties in CordovaActivity will be deprecated in 3.0 on July 2013, please use config.xml");
|
||||
this.getIntent().putExtra(name.toLowerCase(), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set string property on activity.
|
||||
* This method has been deprecated in 3.0 and will be removed at a future
|
||||
* time. Please use config.xml instead.
|
||||
*
|
||||
* @param name
|
||||
* @param value
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public void setStringProperty(String name, String value) {
|
||||
Log.d(TAG, "Setting string properties in CordovaActivity will be deprecated in 3.0 on July 2013, please use config.xml");
|
||||
this.getIntent().putExtra(name.toLowerCase(), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set double property on activity.
|
||||
* This method has been deprecated in 3.0 and will be removed at a future
|
||||
* time. Please use config.xml instead.
|
||||
*
|
||||
* @param name
|
||||
* @param value
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public void setDoubleProperty(String name, double value) {
|
||||
Log.d(TAG, "Setting double properties in CordovaActivity will be deprecated in 3.0 on July 2013, please use config.xml");
|
||||
this.getIntent().putExtra(name.toLowerCase(), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the system is about to start resuming a previous activity.
|
||||
*/
|
||||
@@ -642,41 +430,6 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to all plugins.
|
||||
*/
|
||||
public void postMessage(String id, Object data) {
|
||||
if (this.appView != null) {
|
||||
this.appView.postMessage(id, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Add services to res/xml/plugins.xml instead.
|
||||
*
|
||||
* Add a class that implements a service.
|
||||
*/
|
||||
@Deprecated
|
||||
public void addService(String serviceType, String className) {
|
||||
if (this.appView != null && this.appView.pluginManager != null) {
|
||||
this.appView.pluginManager.addService(serviceType, className);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send JavaScript statement back to JavaScript.
|
||||
* (This is a convenience method)
|
||||
*
|
||||
* @param statement
|
||||
*/
|
||||
@Deprecated // Call method on appView directly.
|
||||
public void sendJavascript(String statement) {
|
||||
if (this.appView != null) {
|
||||
this.appView.bridge.getMessageQueue().addJavaScript(statement);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the spinner. Must be called from the UI thread.
|
||||
*
|
||||
@@ -711,6 +464,11 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
||||
* End this activity by calling finish for activity
|
||||
*/
|
||||
public void endActivity() {
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
this.activityState = ACTIVITY_EXITING;
|
||||
super.finish();
|
||||
}
|
||||
@@ -751,21 +509,16 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
||||
LOG.d(TAG, "Incoming Result");
|
||||
super.onActivityResult(requestCode, resultCode, intent);
|
||||
Log.d(TAG, "Request code = " + requestCode);
|
||||
if (appView != null && requestCode == CordovaChromeClient.FILECHOOSER_RESULTCODE) {
|
||||
ValueCallback<Uri> mUploadMessage = this.appView.getWebChromeClient().getValueCallback();
|
||||
Log.d(TAG, "did we get here?");
|
||||
if (null == mUploadMessage)
|
||||
return;
|
||||
if (appView != null && requestCode == AndroidChromeClient.FILECHOOSER_RESULTCODE) {
|
||||
Uri result = intent == null || resultCode != Activity.RESULT_OK ? null : intent.getData();
|
||||
Log.d(TAG, "result = " + result);
|
||||
mUploadMessage.onReceiveValue(result);
|
||||
mUploadMessage = null;
|
||||
appView.onFilePickerResult(result);
|
||||
}
|
||||
CordovaPlugin callback = this.activityResultCallback;
|
||||
if(callback == null && initCallbackClass != null) {
|
||||
// The application was restarted, but had defined an initial callback
|
||||
// before being shut down.
|
||||
this.activityResultCallback = appView.pluginManager.getPlugin(initCallbackClass);
|
||||
//this.activityResultCallback = appView.pluginManager.getPlugin(initCallbackClass);
|
||||
this.activityResultCallback = appView.getPluginManager().getPlugin(initCallbackClass);
|
||||
callback = this.activityResultCallback;
|
||||
}
|
||||
if(callback != null) {
|
||||
@@ -792,7 +545,6 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
||||
// If errorUrl specified, then load it
|
||||
final String errorUrl = preferences.getString("errorUrl", null);
|
||||
if ((errorUrl != null) && (errorUrl.startsWith("file://") || internalWhitelist.isUrlWhiteListed(errorUrl)) && (!failingUrl.equals(errorUrl))) {
|
||||
|
||||
// Load URL on UI thread
|
||||
me.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
@@ -808,7 +560,7 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
||||
me.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
if (exit) {
|
||||
me.appView.setVisibility(View.GONE);
|
||||
me.appView.getView().setVisibility(View.GONE);
|
||||
me.displayError("Application Error", description + " (" + failingUrl + ")", "OK", exit);
|
||||
}
|
||||
}
|
||||
@@ -846,59 +598,31 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if URL is in approved list of URLs to load.
|
||||
*/
|
||||
@Deprecated // Use whitelist object directly.
|
||||
public boolean isUrlWhiteListed(String url) {
|
||||
return internalWhitelist.isUrlWhiteListed(url);
|
||||
}
|
||||
|
||||
/*
|
||||
* Hook in Cordova for menu plugins
|
||||
*/
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
this.postMessage("onCreateOptionsMenu", menu);
|
||||
if (appView != null) {
|
||||
appView.getPluginManager().postMessage("onCreateOptionsMenu", menu);
|
||||
}
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
this.postMessage("onPrepareOptionsMenu", menu);
|
||||
if (appView != null) {
|
||||
appView.getPluginManager().postMessage("onPrepareOptionsMenu", menu);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
this.postMessage("onOptionsItemSelected", item);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Activity context.
|
||||
*/
|
||||
@Deprecated
|
||||
public Context getContext() {
|
||||
LOG.d(TAG, "This will be deprecated December 2012");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@Deprecated // Call method on appView directly.
|
||||
public void showWebPage(String url, boolean openExternal, boolean clearHistory, HashMap<String, Object> params) {
|
||||
if (this.appView != null) {
|
||||
appView.showWebPage(url, openExternal, clearHistory, params);
|
||||
if (appView != null) {
|
||||
appView.getPluginManager().postMessage("onOptionsItemSelected", item);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected Dialog splashDialog;
|
||||
@@ -959,36 +683,6 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
||||
this.runOnUiThread(runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event)
|
||||
{
|
||||
if (appView != null && (appView.isCustomViewShowing() || appView.getFocusedChild() != null ) &&
|
||||
(keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU)) {
|
||||
return appView.onKeyUp(keyCode, event);
|
||||
} else {
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Android 2.x needs to be able to check where the cursor is. Android 4.x does not
|
||||
*
|
||||
* (non-Javadoc)
|
||||
* @see android.app.Activity#onKeyDown(int, android.view.KeyEvent)
|
||||
*/
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event)
|
||||
{
|
||||
//Determine if the focus is on the current view or not
|
||||
if (appView != null && appView.getFocusedChild() != null && (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU)) {
|
||||
return appView.onKeyDown(keyCode, event);
|
||||
}
|
||||
else
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when a message is sent to plugin.
|
||||
*
|
||||
@@ -1012,14 +706,14 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
||||
if (splashResource != null) {
|
||||
splashscreen = getResources().getIdentifier(splashResource, "drawable", getClass().getPackage().getName());
|
||||
}
|
||||
this.showSplashScreen(this.splashscreenTime);
|
||||
this.showSplashScreen(preferences.getInteger("SplashScreenDelay", 3000));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ("spinner".equals(id)) {
|
||||
if ("stop".equals(data.toString())) {
|
||||
this.spinnerStop();
|
||||
this.appView.setVisibility(View.VISIBLE);
|
||||
this.appView.getView().setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
else if ("onReceivedError".equals(id)) {
|
||||
|
||||
@@ -32,8 +32,6 @@ import android.net.Uri;
|
||||
* Plugins must extend this class and override one of the execute methods.
|
||||
*/
|
||||
public class CordovaPlugin {
|
||||
@Deprecated // This is never set.
|
||||
public String id;
|
||||
public CordovaWebView webView;
|
||||
public CordovaInterface cordova;
|
||||
protected CordovaPreferences preferences;
|
||||
|
||||
@@ -84,7 +84,7 @@ public class CordovaResourceApi {
|
||||
// Creating this is light-weight.
|
||||
private static OkHttpClient httpClient = new OkHttpClient();
|
||||
|
||||
static Thread jsThread;
|
||||
public static Thread jsThread;
|
||||
|
||||
private final AssetManager assetManager;
|
||||
private final ContentResolver contentResolver;
|
||||
|
||||
@@ -25,14 +25,14 @@ import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.webkit.WebView;
|
||||
|
||||
class CordovaUriHelper {
|
||||
public class CordovaUriHelper {
|
||||
|
||||
private static final String TAG = "CordovaUriHelper";
|
||||
|
||||
private CordovaWebView appView;
|
||||
private CordovaInterface cordova;
|
||||
|
||||
CordovaUriHelper(CordovaInterface cdv, CordovaWebView webView)
|
||||
public CordovaUriHelper(CordovaInterface cdv, CordovaWebView webView)
|
||||
{
|
||||
appView = webView;
|
||||
cordova = cdv;
|
||||
@@ -47,9 +47,9 @@ class CordovaUriHelper {
|
||||
* @return true to override, false for default behavior
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
|
||||
boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
public boolean shouldOverrideUrlLoading(String url) {
|
||||
// Give plugins the chance to handle the url
|
||||
if (this.appView.pluginManager.onOverrideUrlLoading(url)) {
|
||||
if (this.appView.getPluginManager().onOverrideUrlLoading(url)) {
|
||||
// Do nothing other than what the plugins wanted.
|
||||
// If any returned true, then the request was handled.
|
||||
return true;
|
||||
|
||||
916
framework/src/org/apache/cordova/CordovaWebView.java
Executable file → Normal file
916
framework/src/org/apache/cordova/CordovaWebView.java
Executable file → Normal file
@@ -1,488 +1,48 @@
|
||||
/*
|
||||
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 java.util.Locale;
|
||||
|
||||
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.widget.FrameLayout;
|
||||
import android.webkit.WebChromeClient.CustomViewCallback;
|
||||
|
||||
/*
|
||||
* 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 CordovaWebView extends WebView {
|
||||
public interface CordovaWebView {
|
||||
public static final String CORDOVA_VERSION = "4.0.0-dev";
|
||||
|
||||
public static final String TAG = "CordovaWebView";
|
||||
public static final String CORDOVA_VERSION = "3.6.0-dev";
|
||||
void init(CordovaInterface cordova, List<PluginEntry> pluginEntries,
|
||||
Whitelist internalWhitelist, Whitelist externalWhitelist,
|
||||
CordovaPreferences preferences);
|
||||
|
||||
private HashSet<Integer> boundKeyCodes = new HashSet<Integer>();
|
||||
View getView();
|
||||
|
||||
public PluginManager pluginManager;
|
||||
private boolean paused;
|
||||
void loadUrlIntoView(String url, boolean recreatePlugins);
|
||||
|
||||
private BroadcastReceiver receiver;
|
||||
void stopLoading();
|
||||
|
||||
boolean canGoBack();
|
||||
|
||||
/** Activities and other important classes **/
|
||||
private CordovaInterface cordova;
|
||||
CordovaWebViewClient viewClient;
|
||||
private CordovaChromeClient chromeClient;
|
||||
void clearCache(boolean b);
|
||||
|
||||
// Flag to track that a loadUrl timeout occurred
|
||||
int loadUrlTimeout = 0;
|
||||
void clearHistory();
|
||||
|
||||
private long lastMenuEventTime = 0;
|
||||
boolean backHistory();
|
||||
|
||||
CordovaBridge bridge;
|
||||
void handlePause(boolean keepRunning);
|
||||
|
||||
/** custom view created by the browser (a video player for example) */
|
||||
private View mCustomView;
|
||||
private WebChromeClient.CustomViewCallback mCustomViewCallback;
|
||||
void onNewIntent(Intent intent);
|
||||
|
||||
private CordovaResourceApi resourceApi;
|
||||
private Whitelist internalWhitelist;
|
||||
private Whitelist externalWhitelist;
|
||||
void handleResume(boolean keepRunning, boolean activityResultKeepRunning);
|
||||
|
||||
// The URL passed to loadUrl(), not necessarily the URL of the current page.
|
||||
String loadedUrl;
|
||||
private CordovaPreferences preferences;
|
||||
void handleDestroy();
|
||||
|
||||
class ActivityResult {
|
||||
|
||||
int request;
|
||||
int result;
|
||||
Intent incoming;
|
||||
|
||||
public ActivityResult(int req, int res, Intent intent) {
|
||||
request = req;
|
||||
result = res;
|
||||
incoming = intent;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
|
||||
new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
Gravity.CENTER);
|
||||
|
||||
public CordovaWebView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public CordovaWebView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public CordovaWebView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@TargetApi(11)
|
||||
@Deprecated
|
||||
public CordovaWebView(Context context, AttributeSet attrs, int defStyle, boolean privateBrowsing) {
|
||||
super(context, attrs, defStyle, privateBrowsing);
|
||||
}
|
||||
|
||||
// Use two-phase init so that the control will work with XML layouts.
|
||||
public void init(CordovaInterface cordova, CordovaWebViewClient webViewClient, CordovaChromeClient webChromeClient,
|
||||
List<PluginEntry> pluginEntries, Whitelist internalWhitelist, Whitelist externalWhitelist,
|
||||
CordovaPreferences preferences) {
|
||||
if (this.cordova != null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
this.cordova = cordova;
|
||||
this.viewClient = webViewClient;
|
||||
this.chromeClient = webChromeClient;
|
||||
this.internalWhitelist = internalWhitelist;
|
||||
this.externalWhitelist = externalWhitelist;
|
||||
this.preferences = preferences;
|
||||
super.setWebChromeClient(webChromeClient);
|
||||
super.setWebViewClient(webViewClient);
|
||||
|
||||
pluginManager = new PluginManager(this, this.cordova, pluginEntries);
|
||||
bridge = new CordovaBridge(pluginManager, new NativeToJsMessageQueue(this, cordova));
|
||||
resourceApi = new CordovaResourceApi(this.getContext(), pluginManager);
|
||||
|
||||
pluginManager.addService("App", "org.apache.cordova.App");
|
||||
initWebViewSettings();
|
||||
exposeJsInterface();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void initIfNecessary() {
|
||||
if (pluginManager == null) {
|
||||
Log.w(TAG, "CordovaWebView.init() was not called. This will soon be required.");
|
||||
// Before the refactor to a two-phase init, the Context needed to implement CordovaInterface.
|
||||
CordovaInterface cdv = (CordovaInterface)getContext();
|
||||
if (!Config.isInitialized()) {
|
||||
Config.init(cdv.getActivity());
|
||||
}
|
||||
init(cdv, makeWebViewClient(cdv), makeWebChromeClient(cdv), Config.getPluginEntries(), Config.getWhitelist(), Config.getExternalWhitelist(), Config.getPreferences());
|
||||
}
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
// Enable JavaScript
|
||||
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.ICE_CREAM_SANDWICH_MR1)
|
||||
Level16Apis.enableUniversalAccess(settings);
|
||||
// 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) {
|
||||
getSettings().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();
|
||||
}
|
||||
}
|
||||
|
||||
public CordovaChromeClient makeWebChromeClient(CordovaInterface cordova) {
|
||||
return new CordovaChromeClient(cordova, this);
|
||||
}
|
||||
|
||||
public CordovaWebViewClient makeWebViewClient(CordovaInterface cordova) {
|
||||
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
return new CordovaWebViewClient(cordova, this);
|
||||
}
|
||||
return new IceCreamCordovaWebViewClient(cordova, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
this.addJavascriptInterface(new ExposedJsApi(bridge), "_cordovaNative");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWebViewClient(WebViewClient client) {
|
||||
this.viewClient = (CordovaWebViewClient)client;
|
||||
super.setWebViewClient(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWebChromeClient(WebChromeClient client) {
|
||||
this.chromeClient = (CordovaChromeClient)client;
|
||||
super.setWebChromeClient(client);
|
||||
}
|
||||
|
||||
public CordovaChromeClient getWebChromeClient() {
|
||||
return this.chromeClient;
|
||||
}
|
||||
|
||||
|
||||
public Whitelist getWhitelist() {
|
||||
return this.internalWhitelist;
|
||||
}
|
||||
|
||||
public Whitelist getExternalWhitelist() {
|
||||
return this.externalWhitelist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the url into the webview.
|
||||
*
|
||||
* @param url
|
||||
*/
|
||||
@Override
|
||||
public void loadUrl(String url) {
|
||||
if (url.equals("about:blank") || url.startsWith("javascript:")) {
|
||||
this.loadUrlNow(url);
|
||||
}
|
||||
else {
|
||||
this.loadUrlIntoView(url);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the url into the webview after waiting for period of time.
|
||||
* This is used to display the splashscreen for certain amount of time.
|
||||
*
|
||||
* @param url
|
||||
* @param time The number of ms to wait before loading webview
|
||||
*/
|
||||
@Deprecated
|
||||
public void loadUrl(final String url, int time) {
|
||||
if(url == null)
|
||||
{
|
||||
this.loadUrlIntoView(Config.getStartUrl());
|
||||
}
|
||||
else
|
||||
{
|
||||
this.loadUrlIntoView(url);
|
||||
}
|
||||
}
|
||||
|
||||
public void loadUrlIntoView(final String url) {
|
||||
loadUrlIntoView(url, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the url into the webview.
|
||||
*
|
||||
* @param url
|
||||
*/
|
||||
public void loadUrlIntoView(final String url, boolean recreatePlugins) {
|
||||
LOG.d(TAG, ">>> loadUrl(" + url + ")");
|
||||
|
||||
initIfNecessary();
|
||||
|
||||
if (recreatePlugins) {
|
||||
this.loadedUrl = url;
|
||||
this.pluginManager.init();
|
||||
}
|
||||
|
||||
// Create a timeout timer for loadUrl
|
||||
final CordovaWebView me = this;
|
||||
final int currentLoadUrlTimeout = me.loadUrlTimeout;
|
||||
final int loadUrlTimeoutValue = Integer.parseInt(this.getProperty("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(me, -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.
|
||||
*
|
||||
* @param url
|
||||
*/
|
||||
void loadUrlNow(String url) {
|
||||
if (LOG.isLoggable(LOG.DEBUG) && !url.startsWith("javascript:")) {
|
||||
LOG.d(TAG, ">>> loadUrlNow()");
|
||||
}
|
||||
if (url.startsWith("file://") || url.startsWith("javascript:") || internalWhitelist.isUrlWhiteListed(url)) {
|
||||
super.loadUrl(url);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the url into the webview after waiting for period of time.
|
||||
* This is used to display the splashscreen for certain amount of time.
|
||||
*
|
||||
* @param url
|
||||
* @param time The number of ms to wait before loading webview
|
||||
*/
|
||||
public void loadUrlIntoView(final String url, final int time) {
|
||||
|
||||
// If not first page of app, then load immediately
|
||||
// Add support for browser history if we use it.
|
||||
if ((url.startsWith("javascript:")) || this.canGoBack()) {
|
||||
}
|
||||
|
||||
// If first page, then show splashscreen
|
||||
else {
|
||||
|
||||
LOG.d(TAG, "loadUrlIntoView(%s, %d)", url, time);
|
||||
|
||||
// Send message to show splashscreen now if desired
|
||||
this.postMessage("splashscreen", "show");
|
||||
}
|
||||
|
||||
// Load url
|
||||
this.loadUrlIntoView(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);
|
||||
this.postMessage("onScrollChanged", myEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@@ -502,433 +62,37 @@ public class CordovaWebView extends WebView {
|
||||
* savedCallbackContext.sendPluginResult(dataResult);
|
||||
*/
|
||||
@Deprecated
|
||||
public void sendJavascript(String statement) {
|
||||
this.bridge.getMessageQueue().addJavaScript(statement);
|
||||
}
|
||||
void sendJavascript(String statememt);
|
||||
|
||||
/**
|
||||
* Send a plugin result back to JavaScript.
|
||||
* (This is a convenience method)
|
||||
*
|
||||
* @param result
|
||||
* @param callbackId
|
||||
*/
|
||||
public void sendPluginResult(PluginResult result, String callbackId) {
|
||||
this.bridge.getMessageQueue().addPluginResult(result, callbackId);
|
||||
}
|
||||
void showWebPage(String errorUrl, boolean b, boolean c, HashMap<String, Object> params);
|
||||
|
||||
/**
|
||||
* Send a message to all plugins.
|
||||
*
|
||||
* @param id The message id
|
||||
* @param data The message data
|
||||
*/
|
||||
public void postMessage(String id, Object data) {
|
||||
if (this.pluginManager != null) {
|
||||
this.pluginManager.postMessage(id, data);
|
||||
}
|
||||
}
|
||||
boolean isCustomViewShowing();
|
||||
|
||||
void showCustomView(View view, CustomViewCallback callback);
|
||||
|
||||
/**
|
||||
* 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()) {
|
||||
super.goBack();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void hideCustomView();
|
||||
|
||||
CordovaResourceApi getResourceApi();
|
||||
|
||||
/**
|
||||
* 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);
|
||||
void setButtonPlumbedToJs(int keyCode, boolean override);
|
||||
boolean isButtonPlumbedToJs(int keyCode);
|
||||
|
||||
// If clearing history
|
||||
if (clearHistory) {
|
||||
this.clearHistory();
|
||||
}
|
||||
void sendPluginResult(PluginResult cr, String callbackId);
|
||||
|
||||
// If loading into our webview
|
||||
if (!openExternal) {
|
||||
PluginManager getPluginManager();
|
||||
|
||||
// Make sure url is in whitelist
|
||||
if (url.startsWith("file://") || internalWhitelist.isUrlWhiteListed(url)) {
|
||||
// TODO: What about params?
|
||||
// Load new URL
|
||||
this.loadUrl(url);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get string property for activity.
|
||||
*
|
||||
* @param name
|
||||
* @param defaultValue
|
||||
* @return the String value for the named property
|
||||
*/
|
||||
public String getProperty(String name, String defaultValue) {
|
||||
Bundle bundle = this.cordova.getActivity().getIntent().getExtras();
|
||||
if (bundle == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
name = name.toLowerCase(Locale.getDefault());
|
||||
Object p = bundle.get(name);
|
||||
if (p == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
return p.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event)
|
||||
{
|
||||
if(boundKeyCodes.contains(keyCode))
|
||||
{
|
||||
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
|
||||
this.loadUrl("javascript:cordova.fireDocumentEvent('volumedownbutton');");
|
||||
return true;
|
||||
}
|
||||
// If volumeup key
|
||||
else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
|
||||
this.loadUrl("javascript:cordova.fireDocumentEvent('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)) {
|
||||
this.loadUrl("javascript:cordova.fireDocumentEvent('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()) {
|
||||
this.loadUrl("javascript:cordova.fireDocumentEvent('menubutton');");
|
||||
}
|
||||
this.lastMenuEventTime = event.getEventTime();
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
// If search key
|
||||
else if (keyCode == KeyEvent.KEYCODE_SEARCH) {
|
||||
this.loadUrl("javascript:cordova.fireDocumentEvent('searchbutton');");
|
||||
return true;
|
||||
}
|
||||
|
||||
//Does webkit change this behavior?
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated // Use setButtonPlumbedToJs() instead.
|
||||
public void bindButton(boolean override)
|
||||
{
|
||||
setButtonPlumbedToJs(KeyEvent.KEYCODE_BACK, override);
|
||||
}
|
||||
|
||||
@Deprecated // Use setButtonPlumbedToJs() instead.
|
||||
public void bindButton(String button, boolean override) {
|
||||
if (button.compareTo("volumeup")==0) {
|
||||
setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_UP, override);
|
||||
}
|
||||
else if (button.compareTo("volumedown")==0) {
|
||||
setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_DOWN, override);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated // Use setButtonPlumbedToJs() instead.
|
||||
public void bindButton(int keyCode, boolean keyDown, boolean override) {
|
||||
setButtonPlumbedToJs(keyCode, override);
|
||||
}
|
||||
|
||||
@Deprecated // Use isButtonPlumbedToJs
|
||||
public boolean isBackButtonBound()
|
||||
{
|
||||
return isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK);
|
||||
}
|
||||
|
||||
public boolean isButtonPlumbedToJs(int keyCode)
|
||||
{
|
||||
return boundKeyCodes.contains(keyCode);
|
||||
}
|
||||
|
||||
public void handlePause(boolean keepRunning)
|
||||
{
|
||||
LOG.d(TAG, "Handle the pause");
|
||||
// Send pause event to JavaScript
|
||||
this.loadUrl("javascript:try{cordova.fireDocumentEvent('pause');}catch(e){console.log('exception firing pause event from native');};");
|
||||
|
||||
// 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 (including setInterval)
|
||||
this.pauseTimers();
|
||||
}
|
||||
paused = true;
|
||||
|
||||
}
|
||||
Whitelist getWhitelist();
|
||||
Whitelist getExternalWhitelist();
|
||||
CordovaPreferences getPreferences();
|
||||
|
||||
public void handleResume(boolean keepRunning, boolean activityResultKeepRunning)
|
||||
{
|
||||
void onFilePickerResult(Uri uri);
|
||||
|
||||
this.loadUrl("javascript:try{cordova.fireDocumentEvent('resume');}catch(e){console.log('exception firing resume event from native');};");
|
||||
|
||||
// Forward to plugins
|
||||
if (this.pluginManager != null) {
|
||||
this.pluginManager.onResume(keepRunning);
|
||||
}
|
||||
|
||||
// Resume JavaScript timers (including setInterval)
|
||||
this.resumeTimers();
|
||||
paused = false;
|
||||
}
|
||||
void setNetworkAvailable(boolean online);
|
||||
|
||||
public void handleDestroy()
|
||||
{
|
||||
// Send destroy event to JavaScript
|
||||
this.loadUrl("javascript:try{cordova.require('cordova/channel').onDestroy.fire();}catch(e){console.log('exception firing destroy event from native');};");
|
||||
String getUrl();
|
||||
|
||||
// Load blank page so that JavaScript onunload is called
|
||||
this.loadUrl("about:blank");
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isPaused()
|
||||
{
|
||||
return paused;
|
||||
}
|
||||
|
||||
@Deprecated // This never did anything.
|
||||
public boolean hadKeyEvent() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@Deprecated // This never did anything
|
||||
public void storeResult(int requestCode, int resultCode, Intent intent) {
|
||||
}
|
||||
|
||||
public CordovaResourceApi getResourceApi() {
|
||||
return resourceApi;
|
||||
}
|
||||
|
||||
public CordovaPreferences getPreferences() {
|
||||
return preferences;
|
||||
}
|
||||
// TODO: Work on deleting these by removing refs from plugins.
|
||||
Context getContext();
|
||||
void loadUrl(String url);
|
||||
Object postMessage(String id, Object data);
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ import java.util.HashMap;
|
||||
/**
|
||||
* This class exposes methods in Cordova that can be called from JavaScript.
|
||||
*/
|
||||
public class App extends CordovaPlugin {
|
||||
public class CoreAndroid extends CordovaPlugin {
|
||||
|
||||
protected static final String TAG = "CordovaApp";
|
||||
private BroadcastReceiver telephonyReceiver;
|
||||
@@ -75,7 +75,7 @@ public class App extends CordovaPlugin {
|
||||
// indicative of what this actually does (shows the webview).
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
webView.postMessage("spinner", "stop");
|
||||
webView.getPluginManager().postMessage("spinner", "stop");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -247,7 +247,7 @@ public class App extends CordovaPlugin {
|
||||
* Exit the Android application.
|
||||
*/
|
||||
public void exitApp() {
|
||||
this.webView.postMessage("exit", null);
|
||||
this.webView.getPluginManager().postMessage("exit", null);
|
||||
}
|
||||
|
||||
|
||||
@@ -271,15 +271,15 @@ public class App extends CordovaPlugin {
|
||||
String extraData = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
|
||||
if (extraData.equals(TelephonyManager.EXTRA_STATE_RINGING)) {
|
||||
LOG.i(TAG, "Telephone RINGING");
|
||||
webView.postMessage("telephone", "ringing");
|
||||
webView.getPluginManager().postMessage("telephone", "ringing");
|
||||
}
|
||||
else if (extraData.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)) {
|
||||
LOG.i(TAG, "Telephone OFFHOOK");
|
||||
webView.postMessage("telephone", "offhook");
|
||||
webView.getPluginManager().postMessage("telephone", "offhook");
|
||||
}
|
||||
else if (extraData.equals(TelephonyManager.EXTRA_STATE_IDLE)) {
|
||||
LOG.i(TAG, "Telephone IDLE");
|
||||
webView.postMessage("telephone", "idle");
|
||||
webView.getPluginManager().postMessage("telephone", "idle");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,162 +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.File;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Environment;
|
||||
import android.os.StatFs;
|
||||
|
||||
/**
|
||||
* This class provides file directory utilities.
|
||||
* All file operations are performed on the SD card.
|
||||
*
|
||||
* It is used by the FileUtils class.
|
||||
*/
|
||||
@Deprecated // Deprecated in 3.1. To be removed in 4.0.
|
||||
public class DirectoryManager {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String LOG_TAG = "DirectoryManager";
|
||||
|
||||
/**
|
||||
* Determine if a file or directory exists.
|
||||
* @param name The name of the file to check.
|
||||
* @return T=exists, F=not found
|
||||
*/
|
||||
public static boolean testFileExists(String name) {
|
||||
boolean status;
|
||||
|
||||
// If SD card exists
|
||||
if ((testSaveLocationExists()) && (!name.equals(""))) {
|
||||
File path = Environment.getExternalStorageDirectory();
|
||||
File newPath = constructFilePaths(path.toString(), name);
|
||||
status = newPath.exists();
|
||||
}
|
||||
// If no SD card
|
||||
else {
|
||||
status = false;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the free disk space
|
||||
*
|
||||
* @return Size in KB or -1 if not available
|
||||
*/
|
||||
public static long getFreeDiskSpace(boolean checkInternal) {
|
||||
String status = Environment.getExternalStorageState();
|
||||
long freeSpace = 0;
|
||||
|
||||
// If SD card exists
|
||||
if (status.equals(Environment.MEDIA_MOUNTED)) {
|
||||
freeSpace = freeSpaceCalculation(Environment.getExternalStorageDirectory().getPath());
|
||||
}
|
||||
else if (checkInternal) {
|
||||
freeSpace = freeSpaceCalculation("/");
|
||||
}
|
||||
// If no SD card and we haven't been asked to check the internal directory then return -1
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return freeSpace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a path return the number of free KB
|
||||
*
|
||||
* @param path to the file system
|
||||
* @return free space in KB
|
||||
*/
|
||||
private static long freeSpaceCalculation(String path) {
|
||||
StatFs stat = new StatFs(path);
|
||||
long blockSize = stat.getBlockSize();
|
||||
long availableBlocks = stat.getAvailableBlocks();
|
||||
return availableBlocks * blockSize / 1024;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if SD card exists.
|
||||
*
|
||||
* @return T=exists, F=not found
|
||||
*/
|
||||
public static boolean testSaveLocationExists() {
|
||||
String sDCardStatus = Environment.getExternalStorageState();
|
||||
boolean status;
|
||||
|
||||
// If SD card is mounted
|
||||
if (sDCardStatus.equals(Environment.MEDIA_MOUNTED)) {
|
||||
status = true;
|
||||
}
|
||||
|
||||
// If no SD card
|
||||
else {
|
||||
status = false;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new file object from two file paths.
|
||||
*
|
||||
* @param file1 Base file path
|
||||
* @param file2 Remaining file path
|
||||
* @return File object
|
||||
*/
|
||||
private static File constructFilePaths (String file1, String file2) {
|
||||
File newPath;
|
||||
if (file2.startsWith(file1)) {
|
||||
newPath = new File(file2);
|
||||
}
|
||||
else {
|
||||
newPath = new File(file1 + "/" + file2);
|
||||
}
|
||||
return newPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we can use the SD Card to store the temporary file. If not then use
|
||||
* the internal cache directory.
|
||||
*
|
||||
* @return the absolute path of where to store the file
|
||||
*/
|
||||
public static String getTempDirectoryPath(Context ctx) {
|
||||
File cache = null;
|
||||
|
||||
// SD Card Mounted
|
||||
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
||||
cache = new File(Environment.getExternalStorageDirectory().getAbsolutePath() +
|
||||
"/Android/data/" + ctx.getPackageName() + "/cache/");
|
||||
}
|
||||
// Use internal storage
|
||||
else {
|
||||
cache = ctx.getCacheDir();
|
||||
}
|
||||
|
||||
// Create the cache directory if it doesn't exist
|
||||
if (!cache.exists()) {
|
||||
cache.mkdirs();
|
||||
}
|
||||
|
||||
return cache.getAbsolutePath();
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
package org.apache.cordova;
|
||||
|
||||
/**
|
||||
* This used to be the class that should be extended by application
|
||||
* developers, but everything has been moved to CordovaActivity. So
|
||||
* you should extend CordovaActivity instead of DroidGap. This class
|
||||
* will be removed at a future time.
|
||||
*
|
||||
* @see CordovaActivity
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public class DroidGap extends CordovaActivity {
|
||||
|
||||
}
|
||||
@@ -1,186 +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.IOException;
|
||||
|
||||
import android.media.ExifInterface;
|
||||
|
||||
@Deprecated // Deprecated in 3.1. To be removed in 4.0.
|
||||
public class ExifHelper {
|
||||
private String aperture = null;
|
||||
private String datetime = null;
|
||||
private String exposureTime = null;
|
||||
private String flash = null;
|
||||
private String focalLength = null;
|
||||
private String gpsAltitude = null;
|
||||
private String gpsAltitudeRef = null;
|
||||
private String gpsDateStamp = null;
|
||||
private String gpsLatitude = null;
|
||||
private String gpsLatitudeRef = null;
|
||||
private String gpsLongitude = null;
|
||||
private String gpsLongitudeRef = null;
|
||||
private String gpsProcessingMethod = null;
|
||||
private String gpsTimestamp = null;
|
||||
private String iso = null;
|
||||
private String make = null;
|
||||
private String model = null;
|
||||
private String orientation = null;
|
||||
private String whiteBalance = null;
|
||||
|
||||
private ExifInterface inFile = null;
|
||||
private ExifInterface outFile = null;
|
||||
|
||||
/**
|
||||
* The file before it is compressed
|
||||
*
|
||||
* @param filePath
|
||||
* @throws IOException
|
||||
*/
|
||||
public void createInFile(String filePath) throws IOException {
|
||||
this.inFile = new ExifInterface(filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* The file after it has been compressed
|
||||
*
|
||||
* @param filePath
|
||||
* @throws IOException
|
||||
*/
|
||||
public void createOutFile(String filePath) throws IOException {
|
||||
this.outFile = new ExifInterface(filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all the EXIF data from the input file.
|
||||
*/
|
||||
public void readExifData() {
|
||||
this.aperture = inFile.getAttribute(ExifInterface.TAG_APERTURE);
|
||||
this.datetime = inFile.getAttribute(ExifInterface.TAG_DATETIME);
|
||||
this.exposureTime = inFile.getAttribute(ExifInterface.TAG_EXPOSURE_TIME);
|
||||
this.flash = inFile.getAttribute(ExifInterface.TAG_FLASH);
|
||||
this.focalLength = inFile.getAttribute(ExifInterface.TAG_FOCAL_LENGTH);
|
||||
this.gpsAltitude = inFile.getAttribute(ExifInterface.TAG_GPS_ALTITUDE);
|
||||
this.gpsAltitudeRef = inFile.getAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF);
|
||||
this.gpsDateStamp = inFile.getAttribute(ExifInterface.TAG_GPS_DATESTAMP);
|
||||
this.gpsLatitude = inFile.getAttribute(ExifInterface.TAG_GPS_LATITUDE);
|
||||
this.gpsLatitudeRef = inFile.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF);
|
||||
this.gpsLongitude = inFile.getAttribute(ExifInterface.TAG_GPS_LONGITUDE);
|
||||
this.gpsLongitudeRef = inFile.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF);
|
||||
this.gpsProcessingMethod = inFile.getAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD);
|
||||
this.gpsTimestamp = inFile.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP);
|
||||
this.iso = inFile.getAttribute(ExifInterface.TAG_ISO);
|
||||
this.make = inFile.getAttribute(ExifInterface.TAG_MAKE);
|
||||
this.model = inFile.getAttribute(ExifInterface.TAG_MODEL);
|
||||
this.orientation = inFile.getAttribute(ExifInterface.TAG_ORIENTATION);
|
||||
this.whiteBalance = inFile.getAttribute(ExifInterface.TAG_WHITE_BALANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the previously stored EXIF data to the output file.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void writeExifData() throws IOException {
|
||||
// Don't try to write to a null file
|
||||
if (this.outFile == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.aperture != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_APERTURE, this.aperture);
|
||||
}
|
||||
if (this.datetime != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_DATETIME, this.datetime);
|
||||
}
|
||||
if (this.exposureTime != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_EXPOSURE_TIME, this.exposureTime);
|
||||
}
|
||||
if (this.flash != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_FLASH, this.flash);
|
||||
}
|
||||
if (this.focalLength != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_FOCAL_LENGTH, this.focalLength);
|
||||
}
|
||||
if (this.gpsAltitude != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_GPS_ALTITUDE, this.gpsAltitude);
|
||||
}
|
||||
if (this.gpsAltitudeRef != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF, this.gpsAltitudeRef);
|
||||
}
|
||||
if (this.gpsDateStamp != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_GPS_DATESTAMP, this.gpsDateStamp);
|
||||
}
|
||||
if (this.gpsLatitude != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_GPS_LATITUDE, this.gpsLatitude);
|
||||
}
|
||||
if (this.gpsLatitudeRef != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, this.gpsLatitudeRef);
|
||||
}
|
||||
if (this.gpsLongitude != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, this.gpsLongitude);
|
||||
}
|
||||
if (this.gpsLongitudeRef != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, this.gpsLongitudeRef);
|
||||
}
|
||||
if (this.gpsProcessingMethod != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD, this.gpsProcessingMethod);
|
||||
}
|
||||
if (this.gpsTimestamp != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, this.gpsTimestamp);
|
||||
}
|
||||
if (this.iso != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_ISO, this.iso);
|
||||
}
|
||||
if (this.make != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_MAKE, this.make);
|
||||
}
|
||||
if (this.model != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_MODEL, this.model);
|
||||
}
|
||||
if (this.orientation != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_ORIENTATION, this.orientation);
|
||||
}
|
||||
if (this.whiteBalance != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_WHITE_BALANCE, this.whiteBalance);
|
||||
}
|
||||
|
||||
this.outFile.saveAttributes();
|
||||
}
|
||||
|
||||
public int getOrientation() {
|
||||
int o = Integer.parseInt(this.orientation);
|
||||
|
||||
if (o == ExifInterface.ORIENTATION_NORMAL) {
|
||||
return 0;
|
||||
} else if (o == ExifInterface.ORIENTATION_ROTATE_90) {
|
||||
return 90;
|
||||
} else if (o == ExifInterface.ORIENTATION_ROTATE_180) {
|
||||
return 180;
|
||||
} else if (o == ExifInterface.ORIENTATION_ROTATE_270) {
|
||||
return 270;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void resetOrientation() {
|
||||
this.orientation = "" + ExifInterface.ORIENTATION_NORMAL;
|
||||
}
|
||||
}
|
||||
52
framework/src/org/apache/cordova/ExposedJsApi.java
Executable file → Normal file
52
framework/src/org/apache/cordova/ExposedJsApi.java
Executable file → Normal file
@@ -1,52 +1,12 @@
|
||||
/*
|
||||
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.webkit.JavascriptInterface;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
/**
|
||||
* Contains APIs that the JS can call. All functions in here should also have
|
||||
* an equivalent entry in CordovaChromeClient.java, and be added to
|
||||
* cordova-js/lib/android/plugin/android/promptbasednativeapi.js
|
||||
/*
|
||||
* Any exposed Javascript API MUST implement these three things!
|
||||
*/
|
||||
/* package */ class ExposedJsApi {
|
||||
|
||||
private CordovaBridge bridge;
|
||||
|
||||
public ExposedJsApi(CordovaBridge bridge) {
|
||||
this.bridge = bridge;
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
|
||||
return bridge.jsExec(bridgeSecret, service, action, callbackId, arguments);
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException {
|
||||
bridge.jsSetNativeToJsBridgeMode(bridgeSecret, value);
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public String retrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException {
|
||||
return bridge.jsRetrieveJsMessages(bridgeSecret, fromOnlineEvent);
|
||||
}
|
||||
public interface ExposedJsApi {
|
||||
public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException;
|
||||
public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException;
|
||||
public String retrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException;
|
||||
}
|
||||
|
||||
@@ -1,163 +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.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import org.apache.cordova.CordovaInterface;
|
||||
import org.apache.cordova.LOG;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Locale;
|
||||
|
||||
@Deprecated // Deprecated in 3.1. To be removed in 4.0.
|
||||
public class FileHelper {
|
||||
private static final String LOG_TAG = "FileUtils";
|
||||
private static final String _DATA = "_data";
|
||||
|
||||
/**
|
||||
* Returns the real path of the given URI string.
|
||||
* If the given URI string represents a content:// URI, the real path is retrieved from the media store.
|
||||
*
|
||||
* @param uriString the URI string of the audio/image/video
|
||||
* @param cordova the current application context
|
||||
* @return the full path to the file
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public static String getRealPath(String uriString, CordovaInterface cordova) {
|
||||
String realPath = null;
|
||||
|
||||
if (uriString.startsWith("content://")) {
|
||||
String[] proj = { _DATA };
|
||||
Cursor cursor = cordova.getActivity().managedQuery(Uri.parse(uriString), proj, null, null, null);
|
||||
int column_index = cursor.getColumnIndexOrThrow(_DATA);
|
||||
cursor.moveToFirst();
|
||||
realPath = cursor.getString(column_index);
|
||||
if (realPath == null) {
|
||||
LOG.e(LOG_TAG, "Could get real path for URI string %s", uriString);
|
||||
}
|
||||
} else if (uriString.startsWith("file://")) {
|
||||
realPath = uriString.substring(7);
|
||||
if (realPath.startsWith("/android_asset/")) {
|
||||
LOG.e(LOG_TAG, "Cannot get real path for URI string %s because it is a file:///android_asset/ URI.", uriString);
|
||||
realPath = null;
|
||||
}
|
||||
} else {
|
||||
realPath = uriString;
|
||||
}
|
||||
|
||||
return realPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the real path of the given URI.
|
||||
* If the given URI is a content:// URI, the real path is retrieved from the media store.
|
||||
*
|
||||
* @param uri the URI of the audio/image/video
|
||||
* @param cordova the current application context
|
||||
* @return the full path to the file
|
||||
*/
|
||||
public static String getRealPath(Uri uri, CordovaInterface cordova) {
|
||||
return FileHelper.getRealPath(uri.toString(), cordova);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an input stream based on given URI string.
|
||||
*
|
||||
* @param uriString the URI string from which to obtain the input stream
|
||||
* @param cordova the current application context
|
||||
* @return an input stream into the data at the given URI or null if given an invalid URI string
|
||||
* @throws IOException
|
||||
*/
|
||||
public static InputStream getInputStreamFromUriString(String uriString, CordovaInterface cordova) throws IOException {
|
||||
if (uriString.startsWith("content")) {
|
||||
Uri uri = Uri.parse(uriString);
|
||||
return cordova.getActivity().getContentResolver().openInputStream(uri);
|
||||
} else if (uriString.startsWith("file://")) {
|
||||
int question = uriString.indexOf("?");
|
||||
if (question > -1) {
|
||||
uriString = uriString.substring(0,question);
|
||||
}
|
||||
if (uriString.startsWith("file:///android_asset/")) {
|
||||
Uri uri = Uri.parse(uriString);
|
||||
String relativePath = uri.getPath().substring(15);
|
||||
return cordova.getActivity().getAssets().open(relativePath);
|
||||
} else {
|
||||
return new FileInputStream(getRealPath(uriString, cordova));
|
||||
}
|
||||
} else {
|
||||
return new FileInputStream(getRealPath(uriString, cordova));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the "file://" prefix from the given URI string, if applicable.
|
||||
* If the given URI string doesn't have a "file://" prefix, it is returned unchanged.
|
||||
*
|
||||
* @param uriString the URI string to operate on
|
||||
* @return a path without the "file://" prefix
|
||||
*/
|
||||
public static String stripFileProtocol(String uriString) {
|
||||
if (uriString.startsWith("file://")) {
|
||||
uriString = uriString.substring(7);
|
||||
}
|
||||
return uriString;
|
||||
}
|
||||
|
||||
public static String getMimeTypeForExtension(String path) {
|
||||
String extension = path;
|
||||
int lastDot = extension.lastIndexOf('.');
|
||||
if (lastDot != -1) {
|
||||
extension = extension.substring(lastDot + 1);
|
||||
}
|
||||
// Convert the URI string to lower case to ensure compatibility with MimeTypeMap (see CB-2185).
|
||||
extension = extension.toLowerCase(Locale.getDefault());
|
||||
if (extension.equals("3ga")) {
|
||||
return "audio/3gpp";
|
||||
}
|
||||
return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mime type of the data specified by the given URI string.
|
||||
*
|
||||
* @param uriString the URI string of the data
|
||||
* @return the mime type of the specified data
|
||||
*/
|
||||
public static String getMimeType(String uriString, CordovaInterface cordova) {
|
||||
String mimeType = null;
|
||||
|
||||
Uri uri = Uri.parse(uriString);
|
||||
if (uriString.startsWith("content://")) {
|
||||
mimeType = cordova.getActivity().getContentResolver().getType(uri);
|
||||
} else {
|
||||
mimeType = getMimeTypeForExtension(uri.getPath());
|
||||
}
|
||||
|
||||
return mimeType;
|
||||
}
|
||||
}
|
||||
@@ -32,16 +32,11 @@ import android.webkit.WebResourceResponse;
|
||||
import android.webkit.WebView;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
|
||||
public class IceCreamCordovaWebViewClient extends AndroidWebViewClient {
|
||||
|
||||
private static final String TAG = "IceCreamCordovaWebViewClient";
|
||||
private CordovaUriHelper helper;
|
||||
|
||||
public IceCreamCordovaWebViewClient(CordovaInterface cordova) {
|
||||
super(cordova);
|
||||
}
|
||||
|
||||
public IceCreamCordovaWebViewClient(CordovaInterface cordova, CordovaWebView view) {
|
||||
public IceCreamCordovaWebViewClient(CordovaInterface cordova, AndroidWebView view) {
|
||||
super(cordova, view);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,43 +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.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
||||
@Deprecated // Deprecated in 3.1. To be removed in 4.0.
|
||||
public class JSONUtils {
|
||||
public static List<String> toStringList(JSONArray array) throws JSONException {
|
||||
if(array == null) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
List<String> list = new ArrayList<String>();
|
||||
|
||||
for (int i = 0; i < array.length(); i++) {
|
||||
list.add(array.get(i).toString());
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,7 +84,7 @@ public class NativeToJsMessageQueue {
|
||||
registeredListeners[3] = new PrivateApiBridgeMode();
|
||||
reset();
|
||||
}
|
||||
|
||||
|
||||
public boolean isBridgeEnabled() {
|
||||
return activeBridgeMode != null;
|
||||
}
|
||||
@@ -298,7 +298,7 @@ public class NativeToJsMessageQueue {
|
||||
public void run() {
|
||||
String js = popAndEncodeAsJs();
|
||||
if (js != null) {
|
||||
webView.loadUrlNow("javascript:" + js);
|
||||
webView.loadUrlIntoView("javascript:" + js, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -18,43 +18,38 @@
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.cordova.CordovaPlugin;
|
||||
|
||||
/**
|
||||
* This class represents a service entry object.
|
||||
*/
|
||||
public class PluginEntry {
|
||||
public final class PluginEntry {
|
||||
|
||||
/**
|
||||
* The name of the service that this plugin implements
|
||||
*/
|
||||
public String service;
|
||||
public final String service;
|
||||
|
||||
/**
|
||||
* The plugin class name that implements the service.
|
||||
*/
|
||||
public String pluginClass;
|
||||
public final String pluginClass;
|
||||
|
||||
/**
|
||||
* The pre-instantiated plugin to use for this entry.
|
||||
*/
|
||||
public CordovaPlugin plugin;
|
||||
public final CordovaPlugin plugin;
|
||||
|
||||
/**
|
||||
* Flag that indicates the plugin object should be created when PluginManager is initialized.
|
||||
*/
|
||||
public boolean onload;
|
||||
|
||||
private List<String> urlFilters;
|
||||
|
||||
public final boolean onload;
|
||||
|
||||
/**
|
||||
* Constructs with a CordovaPlugin already instantiated.
|
||||
*/
|
||||
public PluginEntry(String service, CordovaPlugin plugin) {
|
||||
this(service, plugin.getClass().getName(), true, plugin, null);
|
||||
this(service, plugin.getClass().getName(), true, plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,27 +58,13 @@ public class PluginEntry {
|
||||
* @param onload Create plugin object when HTML page is loaded
|
||||
*/
|
||||
public PluginEntry(String service, String pluginClass, boolean onload) {
|
||||
this(service, pluginClass, onload, null, null);
|
||||
}
|
||||
|
||||
@Deprecated // urlFilters are going away
|
||||
public PluginEntry(String service, String pluginClass, boolean onload, List<String> urlFilters) {
|
||||
this.service = service;
|
||||
this.pluginClass = pluginClass;
|
||||
this.onload = onload;
|
||||
this.urlFilters = urlFilters;
|
||||
plugin = null;
|
||||
this(service, pluginClass, onload, null);
|
||||
}
|
||||
|
||||
private PluginEntry(String service, String pluginClass, boolean onload, CordovaPlugin plugin, List<String> urlFilters) {
|
||||
private PluginEntry(String service, String pluginClass, boolean onload, CordovaPlugin plugin) {
|
||||
this.service = service;
|
||||
this.pluginClass = pluginClass;
|
||||
this.onload = onload;
|
||||
this.urlFilters = urlFilters;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public List<String> getUrlFilters() {
|
||||
return urlFilters;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.cordova.CordovaWebView;
|
||||
import org.apache.cordova.CallbackContext;
|
||||
@@ -51,31 +51,21 @@ public class PluginManager {
|
||||
private final CordovaInterface ctx;
|
||||
private final CordovaWebView app;
|
||||
|
||||
// Stores mapping of Plugin Name -> <url-filter> values.
|
||||
// Using <url-filter> is deprecated.
|
||||
protected HashMap<String, List<String>> urlMap = new HashMap<String, List<String>>();
|
||||
|
||||
@Deprecated
|
||||
PluginManager(CordovaWebView cordovaWebView, CordovaInterface cordova) {
|
||||
this(cordovaWebView, cordova, null);
|
||||
}
|
||||
|
||||
PluginManager(CordovaWebView cordovaWebView, CordovaInterface cordova, List<PluginEntry> pluginEntries) {
|
||||
public PluginManager(CordovaWebView cordovaWebView, CordovaInterface cordova, Collection<PluginEntry> pluginEntries) {
|
||||
this.ctx = cordova;
|
||||
this.app = cordovaWebView;
|
||||
if (pluginEntries == null) {
|
||||
ConfigXmlParser parser = new ConfigXmlParser();
|
||||
parser.parse(ctx.getActivity());
|
||||
pluginEntries = parser.getPluginEntries();
|
||||
}
|
||||
setPluginEntries(pluginEntries);
|
||||
}
|
||||
|
||||
public void setPluginEntries(List<PluginEntry> pluginEntries) {
|
||||
public Collection<PluginEntry> getPluginEntries() {
|
||||
return entryMap.values();
|
||||
}
|
||||
|
||||
public void setPluginEntries(Collection<PluginEntry> pluginEntries) {
|
||||
this.onPause(false);
|
||||
this.onDestroy();
|
||||
pluginMap.clear();
|
||||
urlMap.clear();
|
||||
entryMap.clear();
|
||||
for (PluginEntry entry : pluginEntries) {
|
||||
addService(entry);
|
||||
}
|
||||
@@ -92,23 +82,10 @@ public class PluginManager {
|
||||
this.startupPlugins();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void loadPlugins() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all plugin objects.
|
||||
*/
|
||||
@Deprecated // Should not be exposed as public.
|
||||
public void clearPluginObjects() {
|
||||
pluginMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create plugins objects that have onload set.
|
||||
*/
|
||||
@Deprecated // Should not be exposed as public.
|
||||
public void startupPlugins() {
|
||||
private void startupPlugins() {
|
||||
for (PluginEntry entry : entryMap.values()) {
|
||||
if (entry.onload) {
|
||||
getPlugin(entry.service);
|
||||
@@ -163,11 +140,6 @@ public class PluginManager {
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void exec(String service, String action, String callbackId, String jsonArgs, boolean async) {
|
||||
exec(service, action, callbackId, jsonArgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the plugin object that implements the service.
|
||||
* If the plugin object does not already exist, then create it.
|
||||
@@ -214,15 +186,10 @@ public class PluginManager {
|
||||
*/
|
||||
public void addService(PluginEntry entry) {
|
||||
this.entryMap.put(entry.service, entry);
|
||||
List<String> urlFilters = entry.getUrlFilters();
|
||||
if (urlFilters != null) {
|
||||
urlMap.put(entry.service, urlFilters);
|
||||
}
|
||||
if (entry.plugin != null) {
|
||||
entry.plugin.privateInitialize(ctx, app, app.getPreferences());
|
||||
pluginMap.put(entry.service, entry.plugin);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -298,18 +265,9 @@ public class PluginManager {
|
||||
// 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()) {
|
||||
List<String> urlFilters = urlMap.get(entry.service);
|
||||
if (urlFilters != null) {
|
||||
for (String s : urlFilters) {
|
||||
if (url.startsWith(s)) {
|
||||
return getPlugin(entry.service).onOverrideUrlLoading(url);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||
if (plugin != null && plugin.onOverrideUrlLoading(url)) {
|
||||
return true;
|
||||
}
|
||||
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||
if (plugin != null && plugin.onOverrideUrlLoading(url)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -43,7 +43,7 @@ public class ScrollEvent {
|
||||
* @param view
|
||||
*/
|
||||
|
||||
ScrollEvent(int nx, int ny, int x, int y, View view)
|
||||
public ScrollEvent(int nx, int ny, int x, int y, View view)
|
||||
{
|
||||
l = x; y = t; nl = nx; nt = ny;
|
||||
targetView = view;
|
||||
|
||||
54
package.json
54
package.json
@@ -1,28 +1,30 @@
|
||||
{
|
||||
"name": "cordova-android",
|
||||
"version": "3.4.0",
|
||||
"description": "cordova-android release",
|
||||
"main": "bin/create",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git-wip-us.apache.org/repos/asf/cordova-android.git"
|
||||
},
|
||||
"keywords": [
|
||||
"android",
|
||||
"cordova",
|
||||
"apache"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "jasmine-node --color spec"
|
||||
},
|
||||
"author": "Apache Software Foundation",
|
||||
"license": "Apache version 2.0",
|
||||
"dependencies": {
|
||||
"q": "^0.9.0",
|
||||
"shelljs": "^0.2.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jasmine-node": "~1",
|
||||
"promise-matchers": "~0"
|
||||
}
|
||||
"name": "cordova-android",
|
||||
"version": "4.0.0-dev",
|
||||
"description": "cordova-android release",
|
||||
"main": "bin/create",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git-wip-us.apache.org/repos/asf/cordova-android.git"
|
||||
},
|
||||
"keywords": [
|
||||
"android",
|
||||
"cordova",
|
||||
"apache"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "jasmine-node --color spec",
|
||||
"test-build": "rm -rf \"test create\"; ./bin/create \"test create\" com.test.app Test && \"./test create/cordova/build\" && rm -rf \"test create\""
|
||||
},
|
||||
"author": "Apache Software Foundation",
|
||||
"license": "Apache version 2.0",
|
||||
"dependencies": {
|
||||
"q": "^0.9.0",
|
||||
"shelljs": "^0.2.6",
|
||||
"which": "^1.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jasmine-node": "~1",
|
||||
"promise-matchers": "~0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,5 +255,15 @@
|
||||
<category android:name="android.intent.category.SAMPLE_CODE" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:windowSoftInputMode="adjustPan"
|
||||
android:label="@string/app_name"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:name="org.apache.cordova.test.SabotagedActivity" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.SAMPLE_CODE" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
8
test/assets/www/error.html
Normal file
8
test/assets/www/error.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>OH NOES!</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Things went terribly wrong!</h1>
|
||||
</body>
|
||||
</html>
|
||||
@@ -14,6 +14,25 @@
|
||||
"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.$
|
||||
under the License.
|
||||
-->
|
||||
This is an error page.
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=320, user-scalable=no" />
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||
<title>Expected Error</title>
|
||||
<link rel="stylesheet" href="../master.css" type="text/css" media="screen" title="no title">
|
||||
<script type="text/javascript" charset="utf-8" src="../cordova.js"></script>
|
||||
<script type="text/javascript" charset="utf-8" src="../main.js"></script>
|
||||
</head>
|
||||
<body onload="init();" id="stage" class="theme">
|
||||
<h1>Expected Error</h1>
|
||||
<div id="info">
|
||||
<h4>Cordova: <span id="cordova"> </span></h4>
|
||||
<h4>Deviceready: <span id="deviceready"> </span></h4>
|
||||
</div>
|
||||
<div id="info">
|
||||
This is an expected error page because the initial href doesn't exist.
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
</head>
|
||||
<body onload="init();" id="stage" class="theme">
|
||||
<h1>Cordova Android Tests</h1>
|
||||
<h1>Cordova Android Native Tests</h1>
|
||||
<div id="info">
|
||||
<h4>Cordova: <span id="cordova"> </span></h4>
|
||||
<h4>Deviceready: <span id="deviceready"> </span></h4>
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<h4>Deviceready: <span id="deviceready"> </span></h4>
|
||||
</div>
|
||||
<div id="info">
|
||||
<h4>The menu items should be:</h4>
|
||||
<h4>The options menu items should be:</h4>
|
||||
<li>Item1<br>
|
||||
<li>Item2<br>
|
||||
<li>Item3<br>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<org.apache.cordova.CordovaWebView
|
||||
<org.apache.cordova.AndroidWebView
|
||||
android:id="@+id/cordovaWebView"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent" />
|
||||
|
||||
@@ -18,5 +18,5 @@
|
||||
under the License.
|
||||
-->
|
||||
<resources>
|
||||
<string name="app_name">CordovaTests</string>
|
||||
<string name="app_name">CordovaNativeTests</string>
|
||||
</resources>
|
||||
|
||||
@@ -26,6 +26,10 @@
|
||||
Apache Cordova Team
|
||||
</author>
|
||||
<access origin="*.apache.org" />
|
||||
<access origin="http://*.google.com/*" />
|
||||
<access origin="https://*.google.com/*" />
|
||||
<access origin="https://*.googleapis.com/*" />
|
||||
<access origin="https://*.gstatic.com/*" />
|
||||
<content src="index.html" />
|
||||
<preference name="loglevel" value="DEBUG" />
|
||||
<preference name="useBrowserHistory" value="true" />
|
||||
|
||||
@@ -22,12 +22,12 @@ package org.apache.cordova.test;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.apache.cordova.AndroidChromeClient;
|
||||
import org.apache.cordova.AndroidWebViewClient;
|
||||
import org.apache.cordova.Config;
|
||||
import org.apache.cordova.CordovaChromeClient;
|
||||
import org.apache.cordova.CordovaWebView;
|
||||
import org.apache.cordova.CordovaInterface;
|
||||
import org.apache.cordova.CordovaPlugin;
|
||||
import org.apache.cordova.CordovaWebViewClient;
|
||||
import org.apache.cordova.test.R;
|
||||
|
||||
import android.app.Activity;
|
||||
@@ -51,8 +51,8 @@ public class CordovaWebViewTestActivity extends Activity implements CordovaInter
|
||||
Config.init(this);
|
||||
|
||||
cordovaWebView = (CordovaWebView) findViewById(R.id.cordovaWebView);
|
||||
cordovaWebView.init(this, new CordovaWebViewClient(this, cordovaWebView), new CordovaChromeClient(this, cordovaWebView),
|
||||
Config.getPluginEntries(), Config.getWhitelist(), Config.getPreferences());
|
||||
cordovaWebView.init(this, Config.getPluginEntries(), Config.getWhitelist(),
|
||||
Config.getExternalWhitelist(), Config.getPreferences());
|
||||
|
||||
cordovaWebView.loadUrl("file:///android_asset/www/index.html");
|
||||
|
||||
@@ -105,4 +105,4 @@ public class CordovaWebViewTestActivity extends Activity implements CordovaInter
|
||||
cordovaWebView.handleDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
92
test/src/org/apache/cordova/test/SabotagedActivity.java
Normal file
92
test/src/org/apache/cordova/test/SabotagedActivity.java
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
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 java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.FileOutputStream;
|
||||
|
||||
import org.apache.cordova.Config;
|
||||
import org.apache.cordova.CordovaActivity;
|
||||
|
||||
import android.content.res.AssetManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
|
||||
public class SabotagedActivity extends CordovaActivity {
|
||||
|
||||
private String BAD_ASSET = "www/error.html";
|
||||
private String LOG_TAG = "SabotagedActivity";
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// copyErrorAsset();
|
||||
super.init();
|
||||
super.loadUrl(Config.getStartUrl());
|
||||
}
|
||||
|
||||
/*
|
||||
* Sometimes we need to move code around before we can do anything. This will
|
||||
* copy the bad code out of the assets before we initalize Cordova so that when Cordova actually
|
||||
* initializes, we have something for it to navigate to.
|
||||
*/
|
||||
|
||||
private void copyErrorAsset () {
|
||||
AssetManager assetManager = getAssets();
|
||||
String[] files = null;
|
||||
try {
|
||||
files = assetManager.list(BAD_ASSET);
|
||||
} catch (IOException e) {
|
||||
Log.e(LOG_TAG, e.getMessage());
|
||||
}
|
||||
|
||||
for(String filename : files) {
|
||||
InputStream in = null;
|
||||
OutputStream out = null;
|
||||
try {
|
||||
in = assetManager.open(BAD_ASSET);
|
||||
out = new FileOutputStream(Environment.getExternalStorageDirectory().toString() +"/" + filename);
|
||||
copy(in, out);
|
||||
in.close();
|
||||
in = null;
|
||||
out.flush();
|
||||
out.close();
|
||||
out = null;
|
||||
} catch(Exception e) {
|
||||
Log.e("tag", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Quick and Dirty Copy!
|
||||
private void copy(InputStream in, OutputStream out) throws IOException {
|
||||
byte[] buffer = new byte[1024];
|
||||
int read;
|
||||
while((read = in.read(buffer)) != -1){
|
||||
out.write(buffer, 0, read);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +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.test;
|
||||
|
||||
import android.os.Bundle;
|
||||
import org.apache.cordova.*;
|
||||
|
||||
public class basicauth extends DroidGap {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
super.init();
|
||||
|
||||
// LogCat: onReceivedHttpAuthRequest(browserspy.dk:80,BrowserSpy.dk - HTTP Password Test)
|
||||
AuthenticationToken token = new AuthenticationToken();
|
||||
token.setUserName("test");
|
||||
token.setPassword("test");
|
||||
super.setAuthenticationToken(token, "browserspy.dk:80", "BrowserSpy.dk - HTTP Password Test");
|
||||
|
||||
// Add web site to whitelist
|
||||
Config.init();
|
||||
Config.addWhiteListEntry("http://browserspy.dk*", true);
|
||||
|
||||
// Load test
|
||||
super.loadUrl("file:///android_asset/www/basicauth/index.html");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import org.apache.cordova.test.backbuttonmultipage;
|
||||
import android.test.ActivityInstrumentationTestCase2;
|
||||
import android.test.UiThreadTest;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.BaseInputConnection;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
@@ -174,7 +175,7 @@ public class BackButtonMultiPageTest extends ActivityInstrumentationTestCase2<ba
|
||||
{
|
||||
String url = testView.getUrl();
|
||||
assertTrue(url.endsWith("sample3.html"));
|
||||
BaseInputConnection viewConnection = new BaseInputConnection(testView, true);
|
||||
BaseInputConnection viewConnection = new BaseInputConnection((View) testView, true);
|
||||
KeyEvent backDown = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
|
||||
KeyEvent backUp = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
|
||||
viewConnection.sendKeyEvent(backDown);
|
||||
@@ -187,7 +188,7 @@ public class BackButtonMultiPageTest extends ActivityInstrumentationTestCase2<ba
|
||||
{
|
||||
String url = testView.getUrl();
|
||||
assertTrue(url.endsWith("sample2.html"));
|
||||
BaseInputConnection viewConnection = new BaseInputConnection(testView, true);
|
||||
BaseInputConnection viewConnection = new BaseInputConnection((View) testView, true);
|
||||
KeyEvent backDown = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
|
||||
KeyEvent backUp = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
|
||||
viewConnection.sendKeyEvent(backDown);
|
||||
|
||||
@@ -59,9 +59,9 @@ public class CordovaActivityTest extends ActivityInstrumentationTestCase2<MainTe
|
||||
}
|
||||
|
||||
|
||||
public void testForCordovaView() {
|
||||
public void testForAndroidWebView() {
|
||||
String className = testView.getClass().getSimpleName();
|
||||
assertTrue(className.equals("CordovaWebView"));
|
||||
assertTrue(className.equals("AndroidWebView"));
|
||||
}
|
||||
|
||||
public void testForLinearLayout() {
|
||||
|
||||
@@ -65,7 +65,7 @@ public class CordovaResourceApiTest extends ActivityInstrumentationTestCase2<Cor
|
||||
cordovaWebView = activity.cordovaWebView;
|
||||
resourceApi = cordovaWebView.getResourceApi();
|
||||
resourceApi.setThreadCheckingEnabled(false);
|
||||
cordovaWebView.pluginManager.addService(new PluginEntry("CordovaResourceApiTestPlugin1", new CordovaPlugin() {
|
||||
cordovaWebView.getPluginManager().addService(new PluginEntry("CordovaResourceApiTestPlugin1", new CordovaPlugin() {
|
||||
@Override
|
||||
public Uri remapUri(Uri uri) {
|
||||
if (uri.getQuery() != null && uri.getQuery().contains("pluginRewrite")) {
|
||||
|
||||
@@ -49,11 +49,11 @@ public class CordovaTest extends
|
||||
assertNotNull(testView);
|
||||
}
|
||||
|
||||
public void testForCordovaView() {
|
||||
public void testForAndroidWebView() {
|
||||
//Sleep for no reason!!!!
|
||||
sleep();
|
||||
sleep();
|
||||
String className = testView.getClass().getSimpleName();
|
||||
assertTrue(className.equals("CordovaWebView"));
|
||||
assertTrue(className.equals("AndroidWebView"));
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
66
test/src/org/apache/cordova/test/junit/GapClientTest.java
Normal file
66
test/src/org/apache/cordova/test/junit/GapClientTest.java
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
package org.apache.cordova.test.junit;
|
||||
|
||||
import org.apache.cordova.CordovaWebView;
|
||||
import org.apache.cordova.PluginManager;
|
||||
import org.apache.cordova.test.CordovaWebViewTestActivity;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
import android.content.res.Resources;
|
||||
import android.test.ActivityInstrumentationTestCase2;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
public class GapClientTest extends ActivityInstrumentationTestCase2<CordovaWebViewTestActivity> {
|
||||
|
||||
private CordovaWebViewTestActivity testActivity;
|
||||
private FrameLayout containerView;
|
||||
private LinearLayout innerContainer;
|
||||
private View testView;
|
||||
private String rString;
|
||||
|
||||
public GapClientTest() {
|
||||
super("org.apache.cordova.test.activities",CordovaWebViewTestActivity.class);
|
||||
}
|
||||
|
||||
protected void setUp() throws Exception{
|
||||
super.setUp();
|
||||
testActivity = this.getActivity();
|
||||
containerView = (FrameLayout) testActivity.findViewById(android.R.id.content);
|
||||
innerContainer = (LinearLayout) containerView.getChildAt(0);
|
||||
testView = innerContainer.getChildAt(0);
|
||||
|
||||
}
|
||||
|
||||
public void testPreconditions(){
|
||||
assertNotNull(innerContainer);
|
||||
assertNotNull(testView);
|
||||
}
|
||||
|
||||
public void testForAndroidWebView() {
|
||||
String className = testView.getClass().getSimpleName();
|
||||
assertTrue(className.equals("AndroidWebView"));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
package org.apache.cordova.test.junit;
|
||||
|
||||
import org.apache.cordova.CordovaWebView;
|
||||
import org.apache.cordova.test.SabotagedActivity;
|
||||
import org.apache.cordova.test.splashscreen;
|
||||
|
||||
import android.app.Instrumentation;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.AssetManager;
|
||||
import android.test.ActivityInstrumentationTestCase2;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
|
||||
public class IntentUriOverrideTest extends ActivityInstrumentationTestCase2<SabotagedActivity> {
|
||||
|
||||
private int TIMEOUT = 1000;
|
||||
|
||||
private SabotagedActivity testActivity;
|
||||
private FrameLayout containerView;
|
||||
private LinearLayout innerContainer;
|
||||
private CordovaWebView testView;
|
||||
private Instrumentation mInstr;
|
||||
private String BAD_URL = "file:///sdcard/download/wl-exploit.htm";
|
||||
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public IntentUriOverrideTest()
|
||||
{
|
||||
super("org.apache.cordova.test",SabotagedActivity.class);
|
||||
}
|
||||
|
||||
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
mInstr = this.getInstrumentation();
|
||||
Intent badIntent = new Intent();
|
||||
badIntent.setClassName("org.apache.cordova.test", "org.apache.cordova.test.SabotagedActivity");
|
||||
badIntent.putExtra("url", BAD_URL);
|
||||
setActivityIntent(badIntent);
|
||||
testActivity = getActivity();
|
||||
containerView = (FrameLayout) testActivity.findViewById(android.R.id.content);
|
||||
innerContainer = (LinearLayout) containerView.getChildAt(0);
|
||||
testView = (CordovaWebView) innerContainer.getChildAt(0);
|
||||
}
|
||||
|
||||
|
||||
public void testPreconditions(){
|
||||
assertNotNull(innerContainer);
|
||||
assertNotNull(testView);
|
||||
}
|
||||
|
||||
public void testChangeStartUrl() throws Throwable
|
||||
{
|
||||
runTestOnUiThread(new Runnable() {
|
||||
public void run()
|
||||
{
|
||||
boolean isBadUrl = testView.getUrl().equals(BAD_URL);
|
||||
assertFalse(isBadUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void sleep() {
|
||||
try {
|
||||
Thread.sleep(TIMEOUT);
|
||||
} catch (InterruptedException e) {
|
||||
fail("Unexpected Timeout");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,32 +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.test.junit;
|
||||
|
||||
import org.apache.cordova.test.menus;
|
||||
|
||||
import android.test.ActivityInstrumentationTestCase2;
|
||||
|
||||
public class MenuTest extends ActivityInstrumentationTestCase2<menus> {
|
||||
|
||||
public MenuTest() {
|
||||
super("org.apache.cordova.test", menus.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,80 +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.test;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
|
||||
import org.apache.cordova.*;
|
||||
import org.apache.cordova.LOG;
|
||||
|
||||
public class menus extends DroidGap {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
super.init();
|
||||
super.registerForContextMenu(super.appView);
|
||||
super.loadUrl("file:///android_asset/www/menus/index.html");
|
||||
}
|
||||
|
||||
// Demonstrate how to add your own menus to app
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
int base = Menu.FIRST;
|
||||
// Group, item id, order, title
|
||||
menu.add(base, base, base, "Item1");
|
||||
menu.add(base, base + 1, base + 1, "Item2");
|
||||
menu.add(base, base + 2, base + 2, "Item3");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
LOG.d("menus", "Item " + item.getItemId() + " pressed.");
|
||||
this.appView.loadUrl("javascript:alert('Menu " + item.getItemId() + " pressed.')");
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
LOG.d("menus", "onPrepareOptionsMenu()");
|
||||
// this.appView.loadUrl("javascript:alert('onPrepareOptionsMenu()')");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo info) {
|
||||
LOG.d("menus", "onCreateContextMenu()");
|
||||
menu.setHeaderTitle("Test Context Menu");
|
||||
menu.add(200, 200, 200, "Context Item1");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
this.appView.loadUrl("javascript:alert('Context Menu " + item.getItemId() + " pressed.')");
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -32,16 +32,16 @@ public class userwebview extends MainTestActivity {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
testViewClient = new TestViewClient(this, appView);
|
||||
testChromeClient = new TestChromeClient(this, appView);
|
||||
testViewClient = new TestViewClient(this, ((AndroidWebView)appView));
|
||||
testChromeClient = new TestChromeClient(this, ((AndroidWebView)appView));
|
||||
super.init();
|
||||
appView.setWebViewClient(testViewClient);
|
||||
appView.setWebChromeClient(testChromeClient);
|
||||
((AndroidWebView)appView).setWebViewClient(testViewClient);
|
||||
((AndroidWebView)appView).setWebChromeClient(testChromeClient);
|
||||
super.loadUrl("file:///android_asset/www/userwebview/index.html");
|
||||
}
|
||||
|
||||
public class TestChromeClient extends CordovaChromeClient {
|
||||
public TestChromeClient(CordovaInterface ctx, CordovaWebView app) {
|
||||
public class TestChromeClient extends AndroidChromeClient {
|
||||
public TestChromeClient(CordovaInterface ctx, AndroidWebView app) {
|
||||
super(ctx, app);
|
||||
LOG.d("userwebview", "TestChromeClient()");
|
||||
}
|
||||
@@ -57,8 +57,8 @@ public class userwebview extends MainTestActivity {
|
||||
/**
|
||||
* This class can be used to override the GapViewClient and receive notification of webview events.
|
||||
*/
|
||||
public class TestViewClient extends CordovaWebViewClient {
|
||||
public TestViewClient(CordovaInterface ctx, CordovaWebView app) {
|
||||
public class TestViewClient extends AndroidWebViewClient {
|
||||
public TestViewClient(CordovaInterface ctx, AndroidWebView app) {
|
||||
super(ctx, app);
|
||||
LOG.d("userwebview", "TestViewClient()");
|
||||
}
|
||||
|
||||
@@ -22,23 +22,22 @@ import android.os.Bundle;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import org.apache.cordova.*;
|
||||
import org.apache.cordova.LOG;
|
||||
|
||||
public class whitelist extends MainTestActivity {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
super.init();
|
||||
appView.setWebViewClient(new TestViewClient(this, appView));
|
||||
((AndroidWebView)appView).setWebViewClient(new TestViewClient(this, ((AndroidWebView)appView)));
|
||||
super.loadUrl("file:///android_asset/www/whitelist/index.html");
|
||||
}
|
||||
|
||||
/**
|
||||
* This class can be used to override the GapViewClient and receive notification of webview events.
|
||||
*/
|
||||
public class TestViewClient extends CordovaWebViewClient {
|
||||
public class TestViewClient extends AndroidWebViewClient {
|
||||
|
||||
public TestViewClient(CordovaInterface ctx, CordovaWebView app) {
|
||||
public TestViewClient(CordovaInterface ctx, AndroidWebView app) {
|
||||
super(ctx, app);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user