Compare commits

..

35 Commits

Author SHA1 Message Date
Ian Clelland
857d841adb Fix plumbing of key events to instance webview 2014-05-20 15:16:10 -04:00
Ian Clelland
c66135d4de Make jsMessageQueue visible to out-of-package subclasses 2014-05-20 13:50:58 -04:00
Ian Clelland
4a3ed323db Promote CordovaWebView to an abstract base class.
This resolves the problem where pluginManager was not available as a
field on a generic CordovaWebView, which broke backwards-compatibility
with existing plugins (including core Cordova plugins).

This also necessitates splitting AndroidWebView into two classes, so
that it now *has* a WebView, rather than *is* a WebView, due to Java's
lack of multiple inheritance. All WebView-defined methods are now
implemented on the AndroidWebViewImpl class, and are delegated from the
AndroidWebView class as necessary.
2014-05-16 16:40:54 -04:00
Ian Clelland
97008305ff Merge branch 'master' into pluggable_webview
Conflicts:
	framework/src/org/apache/cordova/CordovaWebView.java
2014-05-15 15:59:11 -04:00
Ian Clelland
1a17083e8c Add more required methods on CordovaWebView interface 2014-05-15 15:56:10 -04:00
Joe Bowser
b6664cc859 Added two more required methods to CordovaWebView to get the Junit tests running, removed tests that make no sense 2014-05-14 11:09:21 -07:00
Ian Clelland
e595c313a1 Use correct client object in recent versions of android again 2014-05-02 10:29:53 -04:00
Ian Clelland
955da2e360 Clean up merge commit
Reinstate fix for github issue #96 (b715d20)
Re-remove extra calls to set up client objects (8e31ef7b)
Reinstate license header in CordovaChromeClient.java
2014-05-02 10:22:38 -04:00
Joe Bowser
04b3fc0268 Outsmarted by vim, needed Eclipse to clean this up 2014-04-30 15:09:54 -07:00
Joe Bowser
105ccc81a5 This is an ugly merge commit, because the rebase made even less sense.
This should add the old setProperty methods required for the tests. We
decided to not deprecate them.  I don't make a habit of doing merge
commits, due to their destructive nature, but I think I might have
merged too much stuff in.

Merge branch 'pluggable_webview' of https://git-wip-us.apache.org/repos/asf/cordova-android into pluggable_webview

Conflicts:
	framework/src/org/apache/cordova/AndroidChromeClient.java
	framework/src/org/apache/cordova/AndroidWebView.java
	framework/src/org/apache/cordova/CordovaActivity.java
	framework/src/org/apache/cordova/CordovaWebView.java
2014-04-30 14:59:40 -07:00
Joe Bowser
3571307df5 Adding setIntegerProperty, setBooleanProperty and setStringProperty back, due to possible demand, and due to the fact that I don't want to rewrite my tests 2014-04-30 11:33:26 -07:00
Ian Clelland
df05f3a3c0 Try other constructors besides first 2014-04-29 22:50:12 -04:00
Ian Clelland
8e31ef7be6 Defer construction of client objects to WebView 2014-04-29 22:50:12 -04:00
Joe Bowser
f4555f7c96 Removing the xwalk_core_library reference so we can use this with MozillaView 2014-04-29 22:50:11 -04:00
Ningxin Hu
8408da55ea Add getView() API into CordovaWebView.
This API is to get the actual View.

The concrete webview implementation can use compositing instead of extending
underlying webview.
2014-04-29 22:50:11 -04:00
Ningxin Hu
4a67dd2e28 Crosswalk runtime needs the two permissions to auto detect connection status.
See more details at: https://crosswalk-project.org/jira/browse/XWALK-1324
2014-04-29 22:50:11 -04:00
Joe Bowser
bd806a34d8 Removing XWalkWebView and making it a plugin component 2014-04-29 22:50:11 -04:00
Joe Bowser
2f7e833a79 Got the bridge to work with Crosswalk 2014-04-29 22:50:11 -04:00
Joe Bowser
c17503ab78 w00t! Managed to get XWalk to work. Next Step: Make it installable
like a Cordova Plugin!
2014-04-29 22:50:11 -04:00
Joe Bowser
19f76d34db Hurray! It runs! Now that we have the default WebView working, it's time
to make things a little more pluggable.
2014-04-29 22:50:11 -04:00
Joe Bowser
25c8b2fabb Removing the deprecated setAttribute methods to clean up the codebase 2014-04-29 22:44:05 -04:00
Joe Bowser
bfd8bf9ca4 Merge pull request #3 from huningxin/pluggable_webview
Make correct webview client and chrome client for specific webview engin...
2014-04-29 13:25:19 -07:00
Ningxin Hu
7a5405d2ab Delegate making WebViewClient and ChromeClient to webview engine.
Revert the change of webview preference name.
2014-04-24 09:42:51 +08:00
Joe Bowser
b9a24f00ad Removing the xwalk_core_library reference so we can use this with MozillaView 2014-04-23 14:31:30 -07:00
Ningxin Hu
dbfc292353 Make correct webview client and chrome client for specific webview engine.
It changes the webview preference naming from full name to prefix, since the
prefix is also used to construct the name of WebView, WebViewClient and
ChromeClient.

For example, for Crosswalk webview, config.xml contains:
<preference name="webView" value="org.apache.cordova.engine.crosswalk.XWalkCordova" />
2014-04-23 16:33:31 +08:00
Joe Bowser
a09255b2ff Merge pull request #2 from huningxin/pluggable_webview
Pluggable webview
2014-04-22 15:55:52 -07:00
Ningxin Hu
9d1c72cc07 Add getView() API into CordovaWebView.
This API is to get the actual View.

The concrete webview implementation can use compositing instead of extending
underlying webview.
2014-04-21 15:22:19 +08:00
Ningxin Hu
09ac30ef2e Crosswalk runtime needs the two permissions to auto detect connection status.
See more details at: https://crosswalk-project.org/jira/browse/XWALK-1324
2014-04-21 15:17:23 +08:00
Andrew Grieve
79e313a0c0 Catch uncaught exceptions in from plugins and turn them into error responses.
When a plugin throws an unchecked exception, we're not catching it
anywhere and so the error callback is not being called.

This change adds a try/catch to catch such exceptions.
2014-03-17 11:58:14 -07:00
Andrew Grieve
9f4c75d1c2 Add NOTICE file 2014-03-17 11:58:14 -07:00
Joe Bowser
b37492644c Removing XWalkWebView and making it a plugin component 2014-03-12 15:04:28 -07:00
Joe Bowser
04a792a8c2 Got the bridge to work with Crosswalk 2014-03-10 14:39:43 -07:00
Joe Bowser
35ec24c3f0 w00t! Managed to get XWalk to work. Next Step: Make it installable
like a Cordova Plugin!
2014-03-07 15:03:22 -08:00
Joe Bowser
61b23677d1 Hurray! It runs! Now that we have the default WebView working, it's time
to make things a little more pluggable.
2014-03-05 15:50:02 -08:00
Joe Bowser
90037dc6cd Removing the deprecated setAttribute methods to clean up the codebase 2014-03-04 15:02:30 -08:00
103 changed files with 3807 additions and 4859 deletions

View File

@@ -1,24 +1,3 @@
<!--
#
# 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.
#
-->
# Contributing to Apache Cordova
Anyone can contribute to Cordova. And we need your contributions.

87
LICENSE
View File

@@ -199,89 +199,4 @@
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.
ADDITIONAL LICENSES:
================================================================================
bin/node_modules/q
================================================================================
Copyright 20092012 Kristopher Michael Kowal. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
================================================================================
bin/node_modules/shelljs
================================================================================
Copyright (c) 2012, Artur Adib <aadib@mozilla.com>
All rights reserved.
You may use this project under the terms of the New BSD license as follows:
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Artur Adib nor the
names of the contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL ARTUR ADIB BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================================================
bin/node_modules/shelljs
================================================================================
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.
limitations under the License.

0
README.md Normal file → Executable file
View File

View File

@@ -20,181 +20,6 @@
-->
## Release Notes for Cordova (Android) ##
### 3.6.4 (Sept 30, 2014) ###
* Set VERSION to 3.6.4 (via coho)
* Update JS snapshot to version 3.6.4 (via coho)
* CB-7634 Detect JAVA_HOME properly on Ubuntu
* CB-7579 Fix run script's ability to use non-arch-specific APKs
* CB-6511 Fixes build for android when app name contains unicode characters.
* CB-7463: Adding licences. I don't know what the gradle syntax is for comments, that still needs to be done.
* CB-7463: Looked at the Apache BigTop git, gradle uses C-style comments
* CB-7460: Fixing bug with KitKat where the background colour would override the CSS colours on the application
* CB-7674 Preference activation no longer occurs in CordovaActivity.onCreate()
### 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.
* Revert accidentally removed lines from NOTICE
* CB-6552: added top level package.json
* CB-6491 add CONTRIBUTING.md
* CB-6543 Fix cordova/run failure when no custom_rules.xml available
* defaults.xml: Add AndroidLaunchMode preference
* Add JavaDoc for CordovaResourceApi
* CB-6388: Handle binary data correctly in LOAD_URL bridge
* Fix CB-6048: Set launchMode=singleTop so tapping app icon does not always restart app
* Remove incorrect usage of AlertDialog.Builder.create
* Catch uncaught exceptions in from plugins and turn them into error responses.
* Add NOTICE file
* CB-6047 Fix online sometimes getting in a bad state on page transitions.
* Add another convenience overload for CordovaResourceApi.copyResource
* Update framework's .classpath to what Eclipse wants it to be.
* README.md: `android update` to `android-19`.
* Fix NPE when POLLING bridge mode is used.
* Updating NOTICE to include Square for OkHttp
* CB-5398 Apply KitKat content URI fix to all content URIs
* CB-5398 Work-around for KitKat content: URLs not rendering in <img> tags
* CB-5908: add splascreen images to template
* CB-5395: Make scheme and host (but not path) case-insensitive in whitelist
* Ignore multiple onPageFinished() callbacks & onReceivedError due to stopLoading()
* Removing addJavascriptInterface support from all Android versions lower than 4.2 due to security vu
* CB-4984 Don't create on CordovaActivity name
* CB-5917 Add a loadUrlIntoView overload that doesn't recreate plugins.
* Use thread pool for load timeout.
* CB-5715 For CLI, hide assets/www and res/xml/config.xml by default
* CB-5793 ant builds: Rename AndroidManifest during -post-build to avoid Eclipse detecting ant-build/
* CB-5889 Make update script find project name instead of using "null" for CordovaLib
* CB-5889 Add a message in the update script about needing to import CordovaLib when using an IDE.
### 3.4.0 (Feb 2014) ###
43 commits from 10 authors. Highlights include:

View File

@@ -1 +1 @@
3.6.4
3.6.0-dev

View File

@@ -51,7 +51,7 @@ get_sdks = function() {
return Q();
}, function(stderr) {
if (stderr.match(/command\snot\sfound/) || stderr.match(/'android' is not recognized/)) {
if (stderr.match(/command\snot\sfound/)) {
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'));

View File

@@ -19,166 +19,78 @@
under the License.
*/
var shelljs = require('shelljs'),
var shell = require('shelljs'),
child_process = require('child_process'),
Q = require('q'),
path = require('path'),
fs = require('fs'),
which = require('which'),
ROOT = path.join(__dirname, '..', '..');
var isWindows = process.platform == 'win32';
function forgivingWhichSync(cmd) {
try {
return which.sync(cmd);
} catch (e) {
return '';
}
}
function tryCommand(cmd, errMsg) {
var d = Q.defer();
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() {
if(fs.existsSync(path.join(ROOT, 'framework', 'project.properties'))) {
var target = shelljs.grep(/target=android-[\d+]/, 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 = shelljs.grep(/target=android-[\d+]/, path.join(ROOT, 'project.properties')) ||
shelljs.grep(/target=Google Inc.:Google APIs:[\d+]/, path.join(ROOT, 'project.properties'));
if(target == "" || !target) {
// Try Google Glass APIs
target = shelljs.grep(/target=Google Inc.:Glass Development Kit Preview:[\d+]/, path.join(ROOT, 'project.properties'));
}
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'));
return target.split('=')[1].replace('\n', '').replace('\r', '');
}
}
// Returns a promise. Called only by build and clean commands.
// Returns a promise.
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();
};
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();
});
return d.promise;
}
// Returns a promise.
module.exports.check_java = function() {
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.
// fs.realpathSync is require on Ubuntu, which symplinks from /usr/bin -> JDK
var maybeJavaHome = path.dirname(path.dirname(fs.realpathSync(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 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];
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;
}
}
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));
}
}).then(function() {
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'];
return tryCommand('java -version', msg)
}).then(function() {
msg = 'Failed to run "javac -version", make sure you have a Java JDK (not just a JRE) installed.';
return tryCommand('javac -version', msg)
else d.resolve();
});
return d.promise;
}
// Returns a promise.
module.exports.check_android = function() {
return Q().then(function() {
var androidCmdPath = forgivingWhichSync('android');
var adbInPath = !!forgivingWhichSync('adb');
var hasAndroidHome = !!process.env['ANDROID_HOME'] && fs.existsSync(process.env['ANDROID_HOME']);
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());
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);
});
};
module.exports.check_android_target = function(valid_target) {
var msg = 'Failed to run "android". Make sure you have the latest Android SDK installed, and that the "android" command (inside the tools/ folder) is added to your PATH.';
return tryCommand('android list targets', msg)
.then(function(output) {
return d.promise.then(function(output) {
if (!output.match(valid_target)) {
throw new Error('Please install Android target "' + valid_target + '".\n' +
'Hint: Run "android" from your command-line to open the SDK manager.');
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/)) {
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'));
}
});
};
}
// Returns a promise.
module.exports.run = function() {
return Q.all([this.check_java(), this.check_android()]);
return Q.all([this.check_ant(), this.check_java(), this.check_android()]);
}

View File

@@ -71,7 +71,6 @@ 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.
@@ -83,46 +82,14 @@ function copyJsAndLibrary(projectPath, shared, projectName) {
}
}
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 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 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) {
function copyAntRules(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);
}
function copyScripts(projectPath) {
@@ -139,50 +106,6 @@ function copyScripts(projectPath) {
shell.cp(path.join(ROOT, 'bin', 'lib', 'android_sdk_version.js'), path.join(projectPath, 'cordova', 'lib', 'android_sdk_version.js'));
}
/**
* Test whether a package name is acceptable for use as an android project.
* Returns a promise, fulfilled if the package name is acceptable; rejected
* otherwise.
*/
function validatePackageName(package_name) {
//Make the package conform to Java package types
//Enforce underscore limitation
if (!/^[a-zA-Z]+(\.[a-zA-Z0-9][a-zA-Z0-9_]*)+$/.test(package_name)) {
return Q.reject('Package name must look like: com.company.Name');
}
//Class is a reserved word
if(/\b[Cc]lass\b/.test(package_name)) {
return Q.reject('class is a reserved word');
}
return Q.resolve();
}
/**
* Test whether a project name is acceptable for use as an android class.
* Returns a promise, fulfilled if the project name is acceptable; rejected
* otherwise.
*/
function validateProjectName(project_name) {
//Make sure there's something there
if (project_name === '') {
return Q.reject('Project name cannot be empty');
}
//Enforce stupid name error
if (project_name === 'CordovaActivity') {
return Q.reject('Project name cannot be CordovaActivity');
}
//Classes in Java don't begin with numbers
if (/^[0-9]/.test(project_name)) {
return Q.reject('Project name must not begin with a number');
}
return Q.resolve();
}
/**
* $ create [options]
*
@@ -210,11 +133,9 @@ exports.createProject = function(project_path, package_name, project_name, proje
project_template_dir :
path.join(ROOT, 'bin', 'templates', 'project');
var safe_activity_name = project_name.replace(/\W/g, '');
var package_as_path = package_name.replace(/\./g, path.sep);
var activity_dir = path.join(project_path, 'src', package_as_path);
// safe_activity_name is being hardcoded to avoid issues with unicode app name (https://issues.apache.org/jira/browse/CB-6511)
// TODO: provide option to specify activity name via CLI (proposal: https://issues.apache.org/jira/browse/CB-7231)
var safe_activity_name = "CordovaApp";
var activity_path = path.join(activity_dir, safe_activity_name + '.java');
var target_api = check_reqs.get_target();
var manifest_path = path.join(project_path, 'AndroidManifest.xml');
@@ -224,15 +145,17 @@ exports.createProject = function(project_path, package_name, project_name, proje
return Q.reject('Project already exists! Delete and recreate');
}
//Make the package conform to Java package types
return validatePackageName(package_name)
.then(function() {
validateProjectName(project_name);
})
if (!/[a-zA-Z0-9_]+\.[a-zA-Z0-9_](.[a-zA-Z0-9_])*/.test(package_name)) {
return Q.reject('Package name must look like: com.company.Name');
}
if (project_name === 'CordovaActivity') {
return Q.reject('Project name cannot be CordovaActivity');
}
// Check that requirements are met and proper targets are installed
return check_reqs.run()
.then(function() {
return check_reqs.run();
}).then(function() {
// Log the given values for the project
console.log('Creating Cordova project for the Android platform:');
console.log('\tPath: ' + project_path);
@@ -247,7 +170,6 @@ 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'));
@@ -279,10 +201,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);
copyBuildRules(project_path);
copyAntRules(project_path);
});
// Link it to local android install.
writeProjectProperties(project_path, target_api);
return runAndroidUpdate(project_path, target_api, use_shared_project);
}).then(function() {
console.log('Project successfully created.');
});
@@ -305,24 +227,22 @@ function extractProjectNameFromManifest(projectPath) {
}
// Returns a promise.
exports.updateProject = function(projectPath, shared) {
var newVersion = fs.readFileSync(path.join(ROOT, 'VERSION'), 'utf-8').trim();
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()
.then(function() {
var projectName = extractProjectNameFromManifest(projectPath);
var target_api = check_reqs.get_target();
copyJsAndLibrary(projectPath, shared, projectName);
copyJsAndLibrary(projectPath, false, projectName);
copyScripts(projectPath);
copyBuildRules(projectPath);
copyAntRules(projectPath);
removeDebuggableFromManifest(projectPath);
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.');
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.');
});
});
};
// For testing
exports.validatePackageName = validatePackageName;
exports.validateProjectName = validateProjectName;

23
bin/node_modules/which/LICENSE generated vendored
View File

@@ -1,23 +0,0 @@
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
View File

@@ -1,5 +0,0 @@
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
View File

@@ -1,14 +0,0 @@
#!/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
View File

@@ -1,31 +0,0 @@
{
"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
View File

@@ -1,104 +0,0 @@
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 === ""
}

View File

@@ -24,17 +24,13 @@ 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().done(function() {
return build.run(args.slice(2));
}, function(err) {
reqs.run().then(function() {
return build.run(args[2]);
}).done(null, function(err) {
console.error(err);
process.exit(2);
});

View File

@@ -19,26 +19,18 @@
under the License.
*/
var build = require('./lib/build'),
var clean = require('./lib/clean'),
reqs = require('./lib/check_reqs'),
args = process.argv;
var path = require('path');
// 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);
// Usage support for when args are given
if(args.length > 2) {
clean.help();
} else {
reqs.run().done(function() {
return build.runClean(args.slice(2));
return clean.run();
}, function(err) {
console.error(err);
console.error('ERROR: ' + err);
process.exit(2);
});
}

View File

@@ -23,4 +23,10 @@
<!-- Preferences for Android -->
<preference name="loglevel" value="DEBUG" />
<preference name="AndroidLaunchMode" value="singleTop" />
<!-- This is required for native Android hooks -->
<feature name="App">
<param name="android-package" value="org.apache.cordova.App" />
</feature>
</widget>

View File

@@ -20,366 +20,94 @@
*/
var shell = require('shelljs'),
exec = require('./exec'),
spawn = require('./spawn'),
Q = require('q'),
path = require('path'),
fs = require('fs'),
ROOT = path.join(__dirname, '..', '..');
var check_reqs = require('./check_reqs');
var LOCAL_PROPERTIES_TEMPLATE =
'# This file is automatically generated.\n' +
'# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n';
function findApks(directory) {
var ret = [];
if (fs.existsSync(directory)) {
fs.readdirSync(directory).forEach(function(p) {
if (path.extname(p) == '.apk') {
ret.push(path.join(directory, p));
}
});
}
return ret;
}
function sortFilesByDate(files) {
return files.map(function(p) {
return { p: p, t: fs.statSync(p).mtime };
}).sort(function(a, b) {
var timeDiff = b.t - a.t;
return timeDiff === 0 ? a.p.length - b.p.length : timeDiff;
}).map(function(p) { return p.p; });
}
function findOutputApksHelper(dir, build_type) {
var ret = findApks(dir).filter(function(candidate) {
// Need to choose between release and debug .apk.
if (build_type === 'debug') {
return /-debug/.exec(candidate) && !/-unaligned|-unsigned/.exec(candidate);
}
if (build_type === 'release') {
return /-release/.exec(candidate) && !/-unaligned/.exec(candidate);
}
return true;
});
ret = sortFilesByDate(ret);
if (ret.length === 0) {
return ret;
}
var archSpecific = !!/-x86|-arm/.exec(ret[0]);
return ret.filter(function(p) {
return !!/-x86|-arm/.exec(p) == archSpecific;
});
}
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);
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 m[1];
}
return args;
};
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);
});
},
clean: function() {
var args = this.getArgs('clean');
return check_reqs.check_ant()
.then(function() {
return spawn('ant', args);
});
},
findOutputApks: function(build_type) {
var binDir = path.join(ROOT, hasCustomRules() ? 'ant-build' : 'bin');
return findOutputApksHelper(binDir, build_type);
}
},
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'
];
}
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'));
});
},
/*
* Builds the project with gradle.
* Returns a promise.
*/
build: function(build_type) {
var builder = this;
var wrapper = path.join(ROOT, 'gradlew');
var args = builder.getArgs('build');
return Q().then(function() {
return spawn(wrapper, args);
});
},
clean: function() {
var builder = this;
var wrapper = path.join(ROOT, 'gradlew');
var args = builder.getArgs('clean');
return Q().then(function() {
return spawn(wrapper, args);
});
},
findOutputApks: function(build_type) {
var binDir = path.join(ROOT, 'build', 'apk');
return findOutputApksHelper(binDir, build_type);
}
},
none: {
prepEnv: function() {
return Q();
},
build: function() {
/*
* 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(null);
},
clean: function() {
return Q();
},
findOutputApks: function(build_type) {
return sortFilesByDate(builders.ant.findOutputApks(build_type).concat(builders.gradle.findOutputApks(build_type)));
}
default :
return Q.reject('Build option \'' + build_type + '\' not recognized.');
}
};
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 (/^--/.exec(options[i])) {
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.');
}
// Without our custom_rules.xml, we need to clean before building.
var ret = Q();
if (!hasCustomRules()) {
ret = require('./clean').run();
}
return ret;
return ret.then(function() {
return spawn('ant', args);
});
}
/*
* Builds the project with the specifed options
* Returns a promise.
* 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.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() {
var apkPaths = builder.findOutputApks(opts.buildType);
console.log('Built the following apk(s):');
console.log(' ' + apkPaths.join('\n '));
return {
apkPaths: apkPaths,
buildType: opts.buildType,
buildMethod: opts.buildMethod
};
});
};
/*
* 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';
});
};
module.exports.findBestApkForArchitecture = function(buildResults, arch) {
var paths = buildResults.apkPaths.filter(function(p) {
if (buildResults.buildType == 'debug') {
return /-debug/.exec(p);
}
return !/-debug/.exec(p);
});
var archPattern = new RegExp('-' + arch);
var hasArchPattern = /-x86|-arm/;
for (var i = 0; i < paths.length; ++i) {
if (hasArchPattern.exec(paths[i])) {
if (archPattern.exec(paths[i])) {
return paths[i];
}
} else {
return paths[i];
}
module.exports.get_apk = function() {
var binDir = '';
if(!hasCustomRules()) {
binDir = path.join(ROOT, 'bin');
} else {
binDir = path.join(ROOT, 'ant-build');
}
throw new Error('Could not find apk architecture: ' + arch + ' build-type: ' + buildResults.buildType);
};
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);
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;
} else {
console.error('ERROR : unable to find project ' + binDir + ' directory, could not locate .apk');
process.exit(2);
}
}
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 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(' \'--debug\': Default build, will build project in using ant debug');
console.log(' \'--release\': will build project using ant release');
console.log(' \'--nobuild\': will skip build process (can be used with run command)');
process.exit(0);
};
}

View File

@@ -1,3 +1,5 @@
#!/usr/bin/env node
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
@@ -17,16 +19,21 @@
under the License.
*/
package org.apache.cordova.test.junit;
var build = require('./build'),
spawn = require('./spawn'),
path = require('path');
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);
}
/*
* 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);
}

View File

@@ -48,7 +48,7 @@ module.exports.list = function() {
* and launches it.
* Returns a promise.
*/
module.exports.install = function(target, buildResults) {
module.exports.install = function(target) {
var launchName;
return this.list()
.then(function(device_list) {
@@ -56,20 +56,16 @@ module.exports.install = function(target, buildResults) {
return Q.reject('ERROR: Failed to deploy to device, no devices found.');
// default device
target = target || device_list[0];
target = typeof target !== 'undefined' ? target : device_list[0];
if (device_list.indexOf(target) < 0)
return Q.reject('ERROR: Unable to find target \'' + target + '\'.');
return build.detectArchitecture(target)
.then(function(arch) {
var apk_path = build.findBestApkForArchitecture(buildResults, arch);
launchName = appinfo.getActivityName();
console.log('Using apk: ' + apk_path);
console.log('Installing app on device...');
var cmd = 'adb -s ' + target + ' install -r "' + apk_path + '"';
return exec(cmd);
});
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);
}).then(function(output) {
if (output.match(/Failure/)) return Q.reject('ERROR: Failed to install apk to device: ' + output);

View File

@@ -283,7 +283,7 @@ module.exports.create_image = function(name, target) {
* If no started emulators are found, error out.
* Returns a promise.
*/
module.exports.install = function(target, buildResults) {
module.exports.install = function(target) {
var self = this;
return this.list_started()
.then(function(emulator_list) {
@@ -292,18 +292,14 @@ module.exports.install = function(target, buildResults) {
}
// default emulator
target = target || emulator_list[0];
target = typeof target !== 'undefined' ? target : emulator_list[0];
if (emulator_list.indexOf(target) < 0) {
return Q.reject('Unable to find target \'' + target + '\'. Failed to deploy to emulator.');
}
return build.detectArchitecture(target)
.then(function(arch) {
var apk_path = build.findBestApkForArchitecture(buildResults, arch);
console.log('Installing app on emulator...');
console.log('Using apk: ' + apk_path);
return exec('adb -s ' + target + ' install -r "' + apk_path + '"');
});
console.log('Installing app on emulator...');
var apk_path = build.get_apk();
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);

View File

@@ -33,16 +33,16 @@ var path = require('path'),
* Returns a promise.
*/
module.exports.run = function(args) {
var buildFlags = [];
var build_type;
var install_target;
for (var i=2; i<args.length; i++) {
if (args[i] == '--debug') {
buildFlags.push('--debug');
build_type = '--debug';
} else if (args[i] == '--release') {
buildFlags.push('--release');
build_type = '--release';
} else if (args[i] == '--nobuild') {
buildFlags.push('--nobuild');
build_type = '--nobuild';
} else if (args[i] == '--device') {
install_target = '--device';
} else if (args[i] == '--emulator') {
@@ -55,13 +55,13 @@ var path = require('path'),
}
}
return build.run(buildFlags).then(function(buildResults) {
return build.run(build_type).then(function() {
if (install_target == '--device') {
return device.install(null, buildResults);
return device.install();
} else if (install_target == '--emulator') {
return emulator.list_started().then(function(started) {
var p = started && started.length > 0 ? Q() : emulator.start();
return p.then(function() { return emulator.install(null, buildResults); });
return p.then(function() { emulator.install(); });
});
} else if (install_target) {
var devices, started_emulators, avds;
@@ -75,16 +75,16 @@ var path = require('path'),
}).then(function(res) {
avds = res;
if (devices.indexOf(install_target) > -1) {
return device.install(install_target, buildResults);
return device.install(install_target);
} else if (started_emulators.indexOf(install_target) > -1) {
return emulator.install(install_target, buildResults);
return emulator.install(install_target);
} else {
// if target emulator isn't started, then start it.
var emulator_ID;
for(avd in avds) {
if(avds[avd].name == install_target) {
return emulator.start(install_target)
.then(function() { emulator.install(emulator_ID, buildResults); });
.then(function() { emulator.install(emulator_ID); });
}
}
return Q.reject('Target \'' + install_target + '\' not found, unable to run project');
@@ -96,13 +96,13 @@ var path = require('path'),
.then(function(device_list) {
if (device_list.length > 0) {
console.log('WARNING : No target specified, deploying to device \'' + device_list[0] + '\'.');
return device.install(device_list[0], buildResults);
return device.install(device_list[0]);
} else {
return emulator.list_started()
.then(function(emulator_list) {
if (emulator_list.length > 0) {
console.log('WARNING : No target specified, deploying to emulator \'' + emulator_list[0] + '\'.');
return emulator.install(emulator_list[0], buildResults);
return emulator.install(emulator_list[0]);
} else {
console.log('WARNING : No started emulators found, starting an emulator.');
return emulator.best_image()
@@ -111,7 +111,7 @@ var path = require('path'),
return emulator.start(best_avd.name)
.then(function(emulator_ID) {
console.log('WARNING : No target specified, deploying to emulator \'' + emulator_ID + '\'.');
return emulator.install(emulator_ID, buildResults);
return emulator.install(emulator_ID);
});
} else {
return emulator.start();
@@ -125,8 +125,8 @@ var path = require('path'),
});
}
module.exports.help = function(args) {
console.log('Usage: ' + path.relative(process.cwd(), args[1]) + ' [options]');
module.exports.help = function() {
console.log('Usage: ' + path.relative(process.cwd(), args[0]) + ' [options]');
console.log('Build options :');
console.log(' --debug : Builds project in debug mode');
console.log(' --release : Builds project in release mode');

View File

@@ -26,7 +26,7 @@ var run = require('./lib/run'),
// Support basic help commands
if (args[2] == '--help' || args[2] == '/?' || args[2] == '-h' ||
args[2] == 'help' || args[2] == '-help' || args[2] == '/help') {
run.help(args);
run.help();
} else {
reqs.run().done(function() {
return run.run(args);

View File

@@ -20,6 +20,6 @@
*/
// Coho updates this line:
var VERSION = "3.6.4";
var VERSION = "3.6.0-dev";
console.log(VERSION);

View File

@@ -22,7 +22,7 @@ package __ID__;
import android.os.Bundle;
import org.apache.cordova.*;
public class __ACTIVITY__ extends CordovaActivity
public class __ACTIVITY__ extends CordovaActivity
{
@Override
public void onCreate(Bundle savedInstanceState)
@@ -30,6 +30,8 @@ public class __ACTIVITY__ extends CordovaActivity
super.onCreate(savedInstanceState);
super.init();
// Set by <content src="index.html" /> in config.xml
loadUrl(launchUrl);
super.loadUrl(Config.getStartUrl());
//super.loadUrl("file:///android_asset/www/index.html");
}
}

View File

@@ -17,8 +17,8 @@
specific language governing permissions and limitations
under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="__PACKAGE__" android:versionName="1.0" android:versionCode="1" android:hardwareAccelerated="true">
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:windowSoftInputMode="adjustPan"
package="__PACKAGE__" android:versionName="1.0" android:versionCode="1" android:hardwareAccelerated="true">
<supports-screens
android:largeScreens="true"
android:normalScreens="true"
@@ -29,16 +29,15 @@
/>
<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">
<activity android:name="__ACTIVITY__"
android:label="@string/activity_name"
android:launchMode="singleTop"
<activity android:name="__ACTIVITY__" android:label="@string/app_name" android:launchMode="singleTop"
android:theme="@android:style/Theme.Black.NoTitleBar"
android:windowSoftInputMode="adjustResize"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale">
<intent-filter android:label="@string/launcher_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
@@ -46,4 +45,4 @@
</application>
<uses-sdk android:minSdkVersion="10" android:targetSdkVersion="__APILEVEL__"/>
</manifest>
</manifest>

View File

@@ -1,94 +0,0 @@
import java.util.regex.Pattern
apply plugin: 'android'
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.10.+'
}
}
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("" + getVersionCodeFromManifest() + "0")
}
compileSdkVersion 19
buildToolsVersion "19.0.0"
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
}
}
task wrapper(type: Wrapper) {
gradleVersion = '1.12'
}
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
}

View File

@@ -13,7 +13,6 @@
</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" />

View File

@@ -1,14 +0,0 @@
# 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

View File

@@ -1,15 +0,0 @@
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system 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

View File

@@ -1,9 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- App label shown within list of installed apps, battery & network usage screens. -->
<string name="app_name">__NAME__</string>
<!-- App label shown on the launcher. -->
<string name="launcher_name">@string/app_name</string>
<!-- App label shown on the task switcher. -->
<string name="activity_name">@string/launcher_name</string>
</resources>

View File

@@ -1,18 +0,0 @@
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 ':'

View File

@@ -2,8 +2,8 @@
<classpath>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
<classpathentry kind="output" path="bin/classes"/>
</classpath>

View File

@@ -19,5 +19,5 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.apache.cordova" android:versionName="1.0" android:versionCode="1">
<uses-sdk android:minSdkVersion="10" />
<uses-sdk android:minSdkVersion="8" />
</manifest>

View File

@@ -1,5 +1,5 @@
// Platform: android
// 8ca0f3b2b87e0759c5236b91c80f18438544409c
// 3.6.0-dev-7e845f3
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
@@ -19,7 +19,7 @@
under the License.
*/
;(function() {
var PLATFORM_VERSION_BUILD_LABEL = '3.6.4';
var CORDOVA_JS_BUILD_LABEL = '3.6.0-dev-7e845f3';
// file: src/scripts/require.js
/*jshint -W079 */
@@ -175,8 +175,7 @@ function createEvent(type, data) {
var cordova = {
define:define,
require:require,
version:PLATFORM_VERSION_BUILD_LABEL,
platformVersion:PLATFORM_VERSION_BUILD_LABEL,
version:CORDOVA_JS_BUILD_LABEL,
platformId:platform.id,
/**
* Methods to add/remove your own addEventListener hijacking on document + window.
@@ -266,7 +265,7 @@ var cordova = {
try {
cordova.callbackFromNative(callbackId, true, args.status, [args.message], args.keepCallback);
} catch (e) {
console.log("Error in success callback: " + callbackId + " = "+e);
console.log("Error in error callback: " + callbackId + " = "+e);
}
},
@@ -345,18 +344,18 @@ define("cordova/android/promptbasednativeapi", function(require, exports, module
/**
* Implements the API of ExposedJsApi.java, but uses prompt() to communicate.
* This is used pre-JellyBean, where addJavascriptInterface() is disabled.
* This is used only on the 2.3 simulator, where addJavascriptInterface() is broken.
*/
module.exports = {
exec: function(bridgeSecret, service, action, callbackId, argsJson) {
return prompt(argsJson, 'gap:'+JSON.stringify([bridgeSecret, service, action, callbackId]));
exec: function(service, action, callbackId, argsJson) {
return prompt(argsJson, 'gap:'+JSON.stringify([service, action, callbackId]));
},
setNativeToJsBridgeMode: function(bridgeSecret, value) {
prompt(value, 'gap_bridge_mode:' + bridgeSecret);
setNativeToJsBridgeMode: function(value) {
prompt(value, 'gap_bridge_mode:');
},
retrieveJsMessages: function(bridgeSecret, fromOnlineEvent) {
return prompt(+fromOnlineEvent, 'gap_poll:' + bridgeSecret);
retrieveJsMessages: function(fromOnlineEvent) {
return prompt(+fromOnlineEvent, 'gap_poll:');
}
};
@@ -870,10 +869,13 @@ var cordova = require('cordova'),
nativeApiProvider = require('cordova/android/nativeapiprovider'),
utils = require('cordova/utils'),
base64 = require('cordova/base64'),
channel = require('cordova/channel'),
jsToNativeModes = {
PROMPT: 0,
JS_OBJECT: 1
JS_OBJECT: 1,
// This mode is currently for benchmarking purposes only. It must be enabled
// on the native side through the ENABLE_LOCATION_CHANGE_EXEC_MODE
// constant within CordovaWebViewClient.java before it will work.
LOCATION_CHANGE: 2
},
nativeToJsModes = {
// Polls for messages using the JS->Native bridge.
@@ -893,17 +895,9 @@ var cordova = require('cordova'),
jsToNativeBridgeMode, // Set lazily.
nativeToJsBridgeMode = nativeToJsModes.ONLINE_EVENT,
pollEnabled = false,
messagesFromNative = [],
bridgeSecret = -1;
messagesFromNative = [];
function androidExec(success, fail, service, action, args) {
if (bridgeSecret < 0) {
// If we ever catch this firing, we'll need to queue up exec()s
// and fire them once we get a secret. For now, I don't think
// it's possible for exec() to be called since plugins are parsed but
// not run until until after onNativeReady.
throw new Error('exec() called without bridgeSecret');
}
// Set default bridge modes if they have not already been set.
// By default, we use the failsafe, since addJavascriptInterface breaks too often
if (jsToNativeBridgeMode === undefined) {
@@ -924,35 +918,29 @@ function androidExec(success, fail, service, action, args) {
cordova.callbacks[callbackId] = {success:success, fail:fail};
}
var messages = nativeApiProvider.get().exec(bridgeSecret, service, action, callbackId, argsJson);
// If argsJson was received by Java as null, try again with the PROMPT bridge mode.
// This happens in rare circumstances, such as when certain Unicode characters are passed over the bridge on a Galaxy S2. See CB-2666.
if (jsToNativeBridgeMode == jsToNativeModes.JS_OBJECT && messages === "@Null arguments.") {
androidExec.setJsToNativeBridgeMode(jsToNativeModes.PROMPT);
androidExec(success, fail, service, action, args);
androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
return;
if (jsToNativeBridgeMode == jsToNativeModes.LOCATION_CHANGE) {
window.location = 'http://cdv_exec/' + service + '#' + action + '#' + callbackId + '#' + argsJson;
} else {
androidExec.processMessages(messages, true);
var messages = nativeApiProvider.get().exec(service, action, callbackId, argsJson);
// If argsJson was received by Java as null, try again with the PROMPT bridge mode.
// This happens in rare circumstances, such as when certain Unicode characters are passed over the bridge on a Galaxy S2. See CB-2666.
if (jsToNativeBridgeMode == jsToNativeModes.JS_OBJECT && messages === "@Null arguments.") {
androidExec.setJsToNativeBridgeMode(jsToNativeModes.PROMPT);
androidExec(success, fail, service, action, args);
androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
return;
} else {
androidExec.processMessages(messages, true);
}
}
}
androidExec.init = function() {
bridgeSecret = +prompt('', 'gap_init:' + nativeToJsBridgeMode);
channel.onNativeReady.fire();
};
function pollOnceFromOnlineEvent() {
pollOnce(true);
}
function pollOnce(opt_fromOnlineEvent) {
if (bridgeSecret < 0) {
// This can happen when the NativeToJsMessageQueue resets the online state on page transitions.
// We know there's nothing to retrieve, so no need to poll.
return;
}
var msg = nativeApiProvider.get().retrieveJsMessages(bridgeSecret, !!opt_fromOnlineEvent);
var msg = nativeApiProvider.get().retrieveJsMessages(!!opt_fromOnlineEvent);
androidExec.processMessages(msg);
}
@@ -1002,10 +990,7 @@ androidExec.setNativeToJsBridgeMode = function(mode) {
nativeToJsBridgeMode = mode;
// Tell the native side to switch modes.
// Otherwise, it will be set by androidExec.init()
if (bridgeSecret >= 0) {
nativeApiProvider.get().setNativeToJsBridgeMode(bridgeSecret, mode);
}
nativeApiProvider.get().setNativeToJsBridgeMode(mode);
if (mode == nativeToJsModes.POLLING) {
pollEnabled = true;
@@ -1184,16 +1169,6 @@ function replaceNavigator(origNavigator) {
for (var key in origNavigator) {
if (typeof origNavigator[key] == 'function') {
newNavigator[key] = origNavigator[key].bind(origNavigator);
} else {
(function(k) {
Object.defineProperty(newNavigator, k, {
get: function() {
return origNavigator[k];
},
configurable: true,
enumerable: true
});
})(key);
}
}
}
@@ -1313,16 +1288,6 @@ function replaceNavigator(origNavigator) {
for (var key in origNavigator) {
if (typeof origNavigator[key] == 'function') {
newNavigator[key] = origNavigator[key].bind(origNavigator);
} else {
(function(k) {
Object.defineProperty(newNavigator, k, {
get: function() {
return origNavigator[k];
},
configurable: true,
enumerable: true
});
})(key);
}
}
}
@@ -1500,8 +1465,10 @@ module.exports = {
exec = require('cordova/exec'),
modulemapper = require('cordova/modulemapper');
// Get the shared secret needed to use the bridge.
exec.init();
// Tell the native code that a page change has occurred.
exec(null, null, 'PluginManager', 'startup', []);
// Tell the JS that the native side is ready.
channel.onNativeReady.fire();
// TODO: Extract this as a proper plugin.
modulemapper.clobbers('cordova/plugin/android/app', 'navigator.app');
@@ -1518,17 +1485,6 @@ 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() {
@@ -1606,21 +1562,6 @@ 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.
*/
@@ -1714,11 +1655,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;
}
}

View File

@@ -1,54 +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.
*/
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.10.+'
}
}
apply plugin: 'android-library'
android {
compileSdkVersion 19
buildToolsVersion "19.0.0"
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']
}
}
}

View File

@@ -30,25 +30,13 @@
Apache Cordova Team
</author>
<!-- Allow access to arbitrary URLs in the Cordova WebView. This is a
development mode setting, and should be changed for production. -->
<access origin="http://*/*"/>
<access origin="https://*/*"/>
<!-- Grant certain URLs the ability to launch external applications. This
behaviour is set to match that of Cordova versions before 3.6.0, and
should be reviewed before launching an application in production. It
may be changed in the future. -->
<access origin="tel:*" launch-external="yes"/>
<access origin="geo:*" launch-external="yes"/>
<access origin="mailto:*" launch-external="yes"/>
<access origin="sms:*" launch-external="yes"/>
<access origin="market:*" launch-external="yes"/>
<access origin="*"/>
<!-- <content src="http://mysite.com/myapp.html" /> for external pages -->
<content src="index.html" />
<preference name="loglevel" value="DEBUG" />
<!--
<preference name="splashscreen" value="resourceName" />
<preference name="backgroundColor" value="0xFFF" />
@@ -56,4 +44,8 @@
<preference name="InAppBrowserStorageEnabled" value="true" />
<preference name="disallowOverscroll" value="true" />
-->
<!-- This is required for native Android hooks -->
<feature name="App">
<param name="android-package" value="org.apache.cordova.App" />
</feature>
</widget>

View File

@@ -1,19 +1,3 @@
/*
* Copyright (C) 2012 Square, Inc.
*
* Licensed 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 com.squareup.okhttp.internal.spdy;
public enum ErrorCode {

View File

@@ -1,19 +1,3 @@
/*
* Copyright (C) 2012 Square, Inc.
*
* Licensed 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 com.squareup.okhttp.internal.spdy;
import java.io.DataInputStream;

View File

@@ -1,19 +1,3 @@
/*
* Copyright (C) 2012 Square, Inc.
*
* Licensed 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 com.squareup.okhttp.internal.spdy;
import com.squareup.okhttp.internal.Util;

View File

@@ -0,0 +1,397 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.LOG;
import org.json.JSONArray;
import org.json.JSONException;
import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.webkit.ConsoleMessage;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebStorage;
import android.webkit.WebView;
import android.webkit.GeolocationPermissions.Callback;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
/**
* This class is the WebChromeClient that implements callbacks for our web view.
* The kind of callbacks that happen here are on the chrome outside the document,
* such as onCreateWindow(), onConsoleMessage(), onProgressChanged(), etc. Related
* to but different than CordovaWebViewClient.
*
* @see <a href="http://developer.android.com/reference/android/webkit/WebChromeClient.html">WebChromeClient</a>
* @see <a href="http://developer.android.com/guide/webapps/webview.html">WebView guide</a>
* @see CordovaWebViewClient
* @see CordovaWebView
*/
public class AndroidChromeClient extends WebChromeClient implements CordovaChromeClient {
public static final int FILECHOOSER_RESULTCODE = 5173;
private static final String LOG_TAG = "CordovaChromeClient";
private String TAG = "CordovaLog";
private long MAX_QUOTA = 100 * 1024 * 1024;
protected CordovaInterface cordova;
protected CordovaWebView appView;
// the video progress view
private View mVideoProgressView;
// File Chooser
public ValueCallback<Uri> mUploadMessage;
/**
* Constructor.
*
* @param cordova
*/
public AndroidChromeClient(CordovaInterface cordova) {
this.cordova = cordova;
}
/**
* Constructor.
*
* @param ctx
* @param app
*/
public AndroidChromeClient(CordovaInterface ctx, CordovaWebView app) {
this.cordova = ctx;
this.appView = app;
}
/**
* Constructor.
*
* @param view
*/
public void setWebView(CordovaWebView view) {
this.appView = view;
}
/**
* Tell the client to display a javascript alert dialog.
*
* @param view
* @param url
* @param message
* @param result
*/
@Override
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity());
dlg.setMessage(message);
dlg.setTitle("Alert");
//Don't let alerts break the back button
dlg.setCancelable(true);
dlg.setPositiveButton(android.R.string.ok,
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
result.confirm();
}
});
dlg.setOnCancelListener(
new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
result.cancel();
}
});
dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {
//DO NOTHING
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK)
{
result.confirm();
return false;
}
else
return true;
}
});
dlg.show();
return true;
}
/**
* Tell the client to display a confirm dialog to the user.
*
* @param view
* @param url
* @param message
* @param result
*/
@Override
public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity());
dlg.setMessage(message);
dlg.setTitle("Confirm");
dlg.setCancelable(true);
dlg.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
result.confirm();
}
});
dlg.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
result.cancel();
}
});
dlg.setOnCancelListener(
new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
result.cancel();
}
});
dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {
//DO NOTHING
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK)
{
result.cancel();
return false;
}
else
return true;
}
});
dlg.show();
return true;
}
/**
* Tell the client to display a prompt dialog to the user.
* If the client returns true, WebView will assume that the client will
* handle the prompt dialog and call the appropriate JsPromptResult method.
*
* Since we are hacking prompts for our own purposes, we should not be using them for
* this purpose, perhaps we should hack console.log to do this instead!
*
* @param view
* @param url
* @param message
* @param defaultValue
* @param result
*/
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
// Security check to make sure any requests are coming from the page initially
// loaded in webview and not another loaded in an iframe.
boolean reqOk = false;
if (url.startsWith("file://") || Config.isUrlWhiteListed(url)) {
reqOk = true;
}
// Calling PluginManager.exec() to call a native service using
// prompt(this.stringify(args), "gap:"+this.stringify([service, action, callbackId, true]));
if (reqOk && defaultValue != null && defaultValue.length() > 3 && defaultValue.substring(0, 4).equals("gap:")) {
JSONArray array;
try {
array = new JSONArray(defaultValue.substring(4));
String service = array.getString(0);
String action = array.getString(1);
String callbackId = array.getString(2);
//String r = this.appView.exposedJsApi.exec(service, action, callbackId, message);
String r = this.appView.exec(service, action, callbackId, message);
result.confirm(r == null ? "" : r);
} catch (JSONException e) {
e.printStackTrace();
return false;
}
}
// Sets the native->JS bridge mode.
else if (reqOk && defaultValue != null && defaultValue.equals("gap_bridge_mode:")) {
try {
//this.appView.exposedJsApi.setNativeToJsBridgeMode(Integer.parseInt(message));
this.appView.setNativeToJsBridgeMode(Integer.parseInt(message));
result.confirm("");
} catch (NumberFormatException e){
result.confirm("");
e.printStackTrace();
}
}
// Polling for JavaScript messages
else if (reqOk && defaultValue != null && defaultValue.equals("gap_poll:")) {
//String r = this.appView.exposedJsApi.retrieveJsMessages("1".equals(message));
String r = this.appView.retrieveJsMessages("1".equals(message));
result.confirm(r == null ? "" : r);
}
// Do NO-OP so older code doesn't display dialog
else if (defaultValue != null && defaultValue.equals("gap_init:")) {
result.confirm("OK");
}
// Show dialog
else {
final JsPromptResult res = result;
AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity());
dlg.setMessage(message);
final EditText input = new EditText(this.cordova.getActivity());
if (defaultValue != null) {
input.setText(defaultValue);
}
dlg.setView(input);
dlg.setCancelable(false);
dlg.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
String usertext = input.getText().toString();
res.confirm(usertext);
}
});
dlg.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
res.cancel();
}
});
dlg.show();
}
return true;
}
/**
* Handle database quota exceeded notification.
*/
@Override
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);
quotaUpdater.updateQuota(MAX_QUOTA);
}
// console.log in api level 7: http://developer.android.com/guide/developing/debug-tasks.html
// Expect this to not compile in a future Android release!
@SuppressWarnings("deprecation")
@Override
public void onConsoleMessage(String message, int lineNumber, String sourceID)
{
//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);
super.onConsoleMessage(message, lineNumber, sourceID);
}
}
@TargetApi(8)
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage)
{
if (consoleMessage.message() != null)
LOG.d(TAG, "%s: Line %d : %s" , consoleMessage.sourceId() , consoleMessage.lineNumber(), consoleMessage.message());
return super.onConsoleMessage(consoleMessage);
}
@Override
/**
* Instructs the client to show a prompt to ask the user to set the Geolocation permission state for the specified origin.
*
* @param origin
* @param callback
*/
public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
super.onGeolocationPermissionsShowPrompt(origin, callback);
callback.invoke(origin, true, false);
}
// API level 7 is required for this, see if we could lower this using something else
@Override
public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
this.appView.showCustomView(view, callback);
}
@Override
public void onHideCustomView() {
this.appView.hideCustomView();
}
@Override
/**
* Ask the host application for a custom progress view to show while
* a <video> is loading.
* @return View The progress view.
*/
public View getVideoLoadingProgressView() {
if (mVideoProgressView == null) {
// Create a new Loading view programmatically.
// create the linear layout
LinearLayout layout = new LinearLayout(this.appView.getContext());
layout.setOrientation(LinearLayout.VERTICAL);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
layout.setLayoutParams(layoutParams);
// the proress bar
ProgressBar bar = new ProgressBar(this.appView.getContext());
LinearLayout.LayoutParams barLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
barLayoutParams.gravity = Gravity.CENTER;
bar.setLayoutParams(barLayoutParams);
layout.addView(bar);
mVideoProgressView = layout;
}
return mVideoProgressView;
}
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
this.openFileChooser(uploadMsg, "*/*");
}
public void openFileChooser( ValueCallback<Uri> uploadMsg, String acceptType ) {
this.openFileChooser(uploadMsg, acceptType, null);
}
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture)
{
mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("*/*");
this.cordova.getActivity().startActivityForResult(Intent.createChooser(i, "File Browser"),
FILECHOOSER_RESULTCODE);
}
public ValueCallback<Uri> getValueCallback() {
return this.mUploadMessage;
}
}

View File

@@ -0,0 +1,837 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import java.util.HashMap;
import java.util.Locale;
import org.apache.cordova.Config;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.LOG;
import org.apache.cordova.PluginManager;
import org.apache.cordova.PluginResult;
import org.json.JSONException;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
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.WindowManager;
import android.webkit.WebChromeClient;
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 AndroidCordovaWebView extends CordovaWebView {
public static final String TAG = "CordovaWebView";
public static final String CORDOVA_VERSION = "3.6.0-dev";
private boolean paused;
private BroadcastReceiver receiver;
private AndroidWebView webview;
/** Activities and other important classes **/
private CordovaInterface cordova;
private CordovaWebViewClient viewClient;
private CordovaChromeClient chromeClient;
private String url;
// Flag to track that a loadUrl timeout occurred
int loadUrlTimeout = 0;
private boolean bound;
private boolean handleButton = false;
ExposedJsApi exposedJsApi;
/** custom view created by the browser (a video player for example) */
private View mCustomView;
private WebChromeClient.CustomViewCallback mCustomViewCallback;
private ActivityResult mResult = null;
private CordovaResourceApi resourceApi;
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);
/**
* Constructor.
*
* @param context
*/
public AndroidCordovaWebView(Context context) {
if (CordovaInterface.class.isInstance(context))
{
this.cordova = (CordovaInterface) context;
}
else
{
Log.d(TAG, "Your activity must implement CordovaInterface to work");
}
webview = new AndroidWebView(context);
webview.setCordovaWebView(this);
this.loadConfiguration();
this.setup();
}
/**
* Constructor.
*
* @param context
* @param attrs
*/
public AndroidCordovaWebView(Context context, AttributeSet attrs) {
if (CordovaInterface.class.isInstance(context))
{
this.cordova = (CordovaInterface) context;
}
else
{
Log.d(TAG, "Your activity must implement CordovaInterface to work");
}
webview = new AndroidWebView(context, attrs);
webview.setCordovaWebView(this);
this.loadConfiguration();
this.setup();
}
/**
* Constructor.
*
* @param context
* @param attrs
* @param defStyle
*
*/
public AndroidCordovaWebView(Context context, AttributeSet attrs, int defStyle) {
if (CordovaInterface.class.isInstance(context))
{
this.cordova = (CordovaInterface) context;
}
else
{
Log.d(TAG, "Your activity must implement CordovaInterface to work");
}
webview = new AndroidWebView(context, attrs, defStyle);
webview.setCordovaWebView(this);
this.loadConfiguration();
this.setup();
}
/**
* Constructor.
*
* @param context
* @param attrs
* @param defStyle
* @param privateBrowsing
*/
@TargetApi(11)
public AndroidCordovaWebView(Context context, AttributeSet attrs, int defStyle, boolean privateBrowsing) {
if (CordovaInterface.class.isInstance(context))
{
this.cordova = (CordovaInterface) context;
}
else
{
Log.d(TAG, "Your activity must implement CordovaInterface to work");
}
webview = new AndroidWebView(context, attrs, defStyle, privateBrowsing);
webview.setCordovaWebView(this);
this.loadConfiguration();
this.setup();
}
/**
* Create a default WebViewClient object for this webview. This can be overridden by the
* main application's CordovaActivity subclass.
*
* There are two possible client objects that can be returned:
* AndroidWebViewClient for android < 3.0
* IceCreamCordovaWebViewClient for Android >= 3.0 (Supports shouldInterceptRequest)
*/
@Override
public CordovaWebViewClient makeWebViewClient() {
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB)
{
return (CordovaWebViewClient) new AndroidWebViewClient(this.cordova, this);
}
else
{
return (CordovaWebViewClient) new IceCreamCordovaWebViewClient(this.cordova, this);
}
}
/**
* Create a default WebViewClient object for this webview. This can be overridden by the
* main application's CordovaActivity subclass.
*/
@Override
public CordovaChromeClient makeWebChromeClient() {
return (CordovaChromeClient) new AndroidChromeClient(this.cordova);
}
/**
* Initialize webview.
*/
private void setup() {
pluginManager = new PluginManager(this, this.cordova);
jsMessageQueue = new NativeToJsMessageQueue(this, cordova);
exposedJsApi = new AndroidExposedJsApi(pluginManager, jsMessageQueue);
resourceApi = new CordovaResourceApi(this.getContext(), pluginManager);
exposeJsInterface();
}
private void exposeJsInterface() {
int SDK_INT = Build.VERSION.SDK_INT;
if ((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;
}
webview.addJavascriptInterface(exposedJsApi, "_cordovaNative");
}
/**
* Set the WebViewClient.
*
* @param client
*/
@Override
public void setWebViewClient(CordovaWebViewClient client) {
this.viewClient = client;
webview.setWebViewClient((WebViewClient) client);
}
// @Override
public CordovaWebViewClient getWebViewClient() {
return this.viewClient;
}
/**
* Set the WebChromeClient.
*
* @param client
*/
@Override
public void setWebChromeClient(CordovaChromeClient client) {
this.chromeClient = client;
webview.setWebChromeClient((WebChromeClient) client);
}
@Override
public CordovaChromeClient getWebChromeClient() {
return this.chromeClient;
}
/**
* 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 {
String initUrl = getProperty("url", null);
// If first page of app, then set URL to load to be the one passed in
if (initUrl == null) {
this.loadUrlIntoView(url);
}
// Otherwise use the URL specified in the activity's extras bundle
else {
this.loadUrlIntoView(initUrl);
}
}
}
/**
* 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
*/
@Override
public void loadUrl(final String url, int time) {
String initUrl = getProperty("url", null);
// If first page of app, then set URL to load to be the one passed in
if (initUrl == null) {
this.loadUrlIntoView(url, time);
}
// Otherwise use the URL specified in the activity's extras bundle
else {
this.loadUrlIntoView(initUrl);
}
}
@Override
public void loadUrlIntoView(final String url) {
loadUrlIntoView(url, true);
}
/**
* Load the url into the webview.
*
* @param url
*/
@Override
public void loadUrlIntoView(final String url, boolean recreatePlugins) {
if (recreatePlugins) {
this.url = url;
this.pluginManager.init();
}
webview.loadUrlIntoView(url);
}
/**
* Load URL in webview.
*
* @param url
*/
@Override
public void loadUrlNow(String url) {
webview.loadUrlNow(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
*/
@Override
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
postMessage("splashscreen", "show");
}
// Load url
this.loadUrlIntoView(url);
}
@Override
public void stopLoading() {
//viewClient.isCurrentlyLoading = false;
webview.stopLoading();
}
/**
* Send JavaScript statement back to JavaScript.
* (This is a convenience method)
*
* @param statement
*/
@Override
public void sendJavascript(String statement) {
this.jsMessageQueue.addJavaScript(statement);
}
/**
* Send a plugin result back to JavaScript.
* (This is a convenience method)
*
* @param result
* @param callbackId
*/
@Override
public void sendPluginResult(PluginResult result, String callbackId) {
this.jsMessageQueue.addPluginResult(result, callbackId);
}
/**
* Send a message to all plugins.
*
* @param id The message id
* @param data The message data
*/
@Override
public void postMessage(String id, Object data) {
if (this.pluginManager != null) {
this.pluginManager.postMessage(id, data);
}
}
/**
* Go to previous page in history. (We manage our own history)
*
* @return true if we went back, false if we are already at top
*/
@Override
public boolean backHistory() {
return webview.backHistory();
}
/**
* 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
*/
@Override
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://") || Config.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);
}
}
/**
* Check configuration parameters from Config.
* Approved list of URLs that can be loaded into Cordova
* <access origin="http://server regexp" subdomains="true" />
* Log level: ERROR, WARN, INFO, DEBUG, VERBOSE (default=ERROR)
* <log level="DEBUG" />
*/
private void loadConfiguration() {
if ("true".equals(this.getProperty("Fullscreen", "false"))) {
this.cordova.getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
this.cordova.getActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
}
/**
* 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();
}
/*
* onKeyDown
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
return webview.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
return webview.onKeyUp(keyCode, event);
}
@Override
public void bindButton(boolean override)
{
this.bound = override;
}
@Override
public void bindButton(String button, boolean override) {
if (button.compareTo("volumeup")==0) {
webview.bindButton(KeyEvent.KEYCODE_VOLUME_UP, true);
}
else if (button.compareTo("volumedown")==0) {
webview.bindButton(KeyEvent.KEYCODE_VOLUME_DOWN, true);
}
}
@Override
public boolean isBackButtonBound()
{
return this.bound;
}
@Override
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)
webview.pauseTimers();
}
paused = true;
}
@Override
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)
webview.resumeTimers();
paused = false;
}
@Override
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 {
this.cordova.getActivity().unregisterReceiver(this.receiver);
} catch (Exception e) {
Log.e(TAG, "Error unregistering configuration receiver: " + e.getMessage(), e);
}
}
}
@Override
public void onNewIntent(Intent intent)
{
//Forward to plugins
if (this.pluginManager != null) {
this.pluginManager.onNewIntent(intent);
}
}
@Override
public boolean isPaused()
{
return paused;
}
/* CB-1146 */
public boolean hadKeyEvent() {
return handleButton;
}
@Override
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();
}
@Override
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
*/
@Override
public boolean isCustomViewShowing() {
return mCustomView != null;
}
public void storeResult(int requestCode, int resultCode, Intent intent) {
mResult = new ActivityResult(requestCode, resultCode, intent);
}
/* git:5ca233779d11177ec2bef97afa2910d383d6d4a2 */
public CordovaResourceApi getResourceApi() {
return resourceApi;
}
@Override
public void setLayoutParams(
android.widget.LinearLayout.LayoutParams layoutParams) {
webview.setLayoutParams(layoutParams);
}
@SuppressLint("NewApi")
@Override
public void setOverScrollMode(int mode) {
webview.setOverScrollMode(mode);
}
@Override
public void addJavascript(String statement) {
this.jsMessageQueue.addJavaScript(statement);
}
@Override
public CordovaPlugin getPlugin(String initCallbackClass) {
return this.pluginManager.getPlugin(initCallbackClass);
}
@Override
public String exec(String service, String action, String callbackId,
String message) throws JSONException {
return this.exposedJsApi.exec(service, action, callbackId, message);
}
@Override
public void setNativeToJsBridgeMode(int parseInt) {
this.exposedJsApi.setNativeToJsBridgeMode(parseInt);
}
@Override
public String retrieveJsMessages(boolean equals) {
return this.exposedJsApi.retrieveJsMessages(equals);
}
@Override
public boolean onOverrideUrlLoading(String url) {
return this.pluginManager.onOverrideUrlLoading(url);
}
@Override
public void resetJsMessageQueue() {
this.jsMessageQueue.reset();
}
@Override
public void onReset() {
this.pluginManager.onReset();
}
@Override
public void incUrlTimeout() {
this.loadUrlTimeout++;
}
@Override
public PluginManager getPluginManager() {
return this.pluginManager;
}
@Override
public void setLayoutParams(
android.widget.FrameLayout.LayoutParams layoutParams) {
webview.setLayoutParams(layoutParams);
}
@Override
public View getView() {
return webview;
}
@Override
public void setId(int i) {
webview.setId(i);
}
@Override
public int getVisibility() {
return webview.getVisibility();
}
@Override
public void setVisibility(int invisible) {
webview.setVisibility(invisible);
}
@Override
public Object getParent() {
return webview.getParent();
}
@Override
public boolean canGoBack() {
return webview.canGoBack();
}
@Override
public void clearCache(boolean b) {
webview.clearCache(b);
}
@Override
public void clearHistory() {
webview.clearHistory();
}
@Override
public Object getFocusedChild() {
return webview.getFocusedChild();
}
@Override
public Context getContext() {
return webview.getContext();
}
@Override
public void setNetworkAvailable(boolean online) {
webview.setNetworkAvailable(online);
}
@Override
public String getUrl() {
return webview.getUrl();
}
}

View File

@@ -0,0 +1,76 @@
/*
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.apache.cordova.PluginManager;
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
*/
public /* package */ class AndroidExposedJsApi implements ExposedJsApi {
private PluginManager pluginManager;
private NativeToJsMessageQueue jsMessageQueue;
public AndroidExposedJsApi(PluginManager pluginManager, NativeToJsMessageQueue jsMessageQueue) {
this.pluginManager = pluginManager;
this.jsMessageQueue = jsMessageQueue;
}
@JavascriptInterface
public String exec(String service, String action, String callbackId, String arguments) throws JSONException {
// If the arguments weren't received, send a message back to JS. It will switch bridge modes and try again. See CB-2666.
// We send a message meant specifically for this case. It starts with "@" so no other message can be encoded into the same string.
if (arguments == null) {
return "@Null arguments.";
}
jsMessageQueue.setPaused(true);
try {
// Tell the resourceApi what thread the JS is running on.
CordovaResourceApi.jsThread = Thread.currentThread();
pluginManager.exec(service, action, callbackId, arguments);
String ret = "";
if (!NativeToJsMessageQueue.DISABLE_EXEC_CHAINING) {
ret = jsMessageQueue.popAndEncode(false);
}
return ret;
} catch (Throwable e) {
e.printStackTrace();
return "";
} finally {
jsMessageQueue.setPaused(false);
}
}
@JavascriptInterface
public void setNativeToJsBridgeMode(int value) {
jsMessageQueue.setBridgeMode(value);
}
@JavascriptInterface
public String retrieveJsMessages(boolean fromOnlineEvent) {
return jsMessageQueue.popAndEncode(fromOnlineEvent);
}
}

View File

@@ -0,0 +1,500 @@
package org.apache.cordova;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
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.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.webkit.WebBackForwardList;
import android.webkit.WebHistoryItem;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebSettings.LayoutAlgorithm;
public class AndroidWebView extends WebView {
public static final String TAG = "CordovaWebView";
public static final String CORDOVA_VERSION = "3.6.0-dev";
private ArrayList<Integer> keyDownCodes = new ArrayList<Integer>();
private ArrayList<Integer> keyUpCodes = new ArrayList<Integer>();
private BroadcastReceiver receiver;
private AndroidCordovaWebView cordovaWebView;
/** Activities and other important classes **/
private CordovaInterface cordova;
// Flag to track that a loadUrl timeout occurred
int loadUrlTimeout = 0;
private boolean bound;
private long lastMenuEventTime = 0;
/**
* Constructor.
*
* @param context
*/
public AndroidWebView(Context context) {
super(context);
this.setup((CordovaInterface)context);
}
/**
* Constructor.
*
* @param context
* @param attrs
*/
public AndroidWebView(Context context, AttributeSet attrs) {
super(context, attrs);
this.setup((CordovaInterface)context);
}
/**
* Constructor.
*
* @param context
* @param attrs
* @param defStyle
*
*/
public AndroidWebView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.setup((CordovaInterface)context);
}
/**
* Constructor.
*
* @param context
* @param attrs
* @param defStyle
* @param privateBrowsing
*/
@SuppressWarnings("deprecation")
@TargetApi(11)
public AndroidWebView(Context context, AttributeSet attrs, int defStyle, boolean privateBrowsing) {
super(context, attrs, defStyle, privateBrowsing);
this.setup((CordovaInterface)context);
}
/**
* Initialize webview.
*/
@SuppressWarnings("deprecation")
@SuppressLint({ "NewApi", "SetJavaScriptEnabled" })
private void setup(CordovaInterface cordova) {
this.cordova = cordova;
this.setInitialScale(0);
this.setVerticalScrollBarEnabled(false);
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 = cordova.getActivity().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!
try {
final String packageName = cordova.getActivity().getPackageName();
final PackageManager pm = cordova.getActivity().getPackageManager();
ApplicationInfo appInfo;
appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
if((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0 &&
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT)
{
setWebContentsDebuggingEnabled(true);
}
} catch (IllegalArgumentException e) {
Log.d(TAG, "You have one job! To turn on Remote Web Debugging! YOU HAVE FAILED! ");
e.printStackTrace();
} catch (NameNotFoundException e) {
Log.d(TAG, "This should never happen: Your application's package can't be found.");
e.printStackTrace();
}
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);
String pathToCache = cordova.getActivity().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
settings.setAppCachePath(pathToCache);
settings.setAppCacheEnabled(true);
// Fix for CB-1405
// Google issue 4641
updateUserAgentString();
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) {
updateUserAgentString();
}
};
cordova.getActivity().registerReceiver(this.receiver, intentFilter);
}
// end CB-1405
}
/**
* 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 updateUserAgentString() {
this.getSettings().getUserAgentString();
}
public AndroidCordovaWebView getCordovaWebView() {
return cordovaWebView;
}
public void setCordovaWebView(AndroidCordovaWebView cordovaWebView) {
this.cordovaWebView = cordovaWebView;
}
/**
* Load the url into the webview.
*
* @param url
*/
@Override
public void loadUrl(String url) {
this.getCordovaWebView().loadUrl(url);
}
/**
* Load the url into the webview.
*
* @param url
*/
public void loadUrlIntoView(final String url) {
LOG.d(TAG, ">>> loadUrl(" + url + ")");
// Create a timeout timer for loadUrl
final AndroidWebView me = this;
final int currentLoadUrlTimeout = me.loadUrlTimeout;
final int loadUrlTimeoutValue = Integer.parseInt(getCordovaWebView().getProperty("LoadUrlTimeoutValue", "20000"));
// Timeout error method
final Runnable loadError = new Runnable() {
public void run() {
me.stopLoading();
LOG.e(TAG, "CordovaWebView: TIMEOUT ERROR!");
CordovaWebViewClient viewClient = me.getCordovaWebView().getWebViewClient();
if (viewClient != null) {
viewClient.onReceivedError(me.getCordovaWebView(), -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
*/
public void loadUrlNow(String url) {
if (LOG.isLoggable(LOG.DEBUG) && !url.startsWith("javascript:")) {
LOG.d(TAG, ">>> loadUrlNow()");
}
if (url.startsWith("file://") || url.startsWith("javascript:") || Config.isUrlWhiteListed(url)) {
super.loadUrl(url);
}
}
@Override
public void stopLoading() {
//viewClient.isCurrentlyLoading = false;
super.stopLoading();
}
@Override
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);
getCordovaWebView().postMessage("onScrollChanged", myEvent);
}
/**
* 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;
}
/*
* Add a key code to either the keyUp or keyDown handler lists.
*/
void bindButton(int keyCode, boolean keyDown) {
if(keyDown)
{
keyDownCodes.add(keyCode);
}
else
{
keyUpCodes.add(keyCode);
}
}
/*
* onKeyDown
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
if(keyDownCodes.contains(keyCode))
{
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
// only override default behavior is event bound
LOG.d(TAG, "Down Key Hit");
this.loadUrl("javascript:cordova.fireDocumentEvent('volumedownbutton');");
return true;
}
// If volumeup key
else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
LOG.d(TAG, "Up Key Hit");
this.loadUrl("javascript:cordova.fireDocumentEvent('volumeupbutton');");
return true;
}
else
{
return super.onKeyDown(keyCode, event);
}
}
else if(keyCode == KeyEvent.KEYCODE_BACK)
{
return !(this.startOfHistory()) || this.bound;
}
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(getCordovaWebView().isCustomViewShowing()) {
getCordovaWebView().hideCustomView();
} else {
// The webview is currently displayed
// If back key is bound, then send event to JavaScript
if (this.bound) {
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
else {
//this.activityState = ACTIVITY_EXITING;
//return false;
// If they hit back button when app is initializing, app should exit instead of hang until initialization (CB2-458)
this.cordova.getActivity().finish();
}
}
}
}
// 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;
}
else if(keyUpCodes.contains(keyCode))
{
//What the hell should this do?
return super.onKeyUp(keyCode, event);
}
//Does webkit change this behavior?
return super.onKeyUp(keyCode, event);
}
// 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;
}
@Override
public WebBackForwardList restoreState(Bundle savedInstanceState)
{
WebBackForwardList myList = super.restoreState(savedInstanceState);
Log.d(TAG, "WebView restoration crew now restoring!");
//Initialize the plugin manager once more
getCordovaWebView().pluginManager.init();
return myList;
}
}

View File

@@ -0,0 +1,496 @@
/*
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.ByteArrayInputStream;
import java.util.Hashtable;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.LOG;
import org.json.JSONException;
import org.json.JSONObject;
import android.annotation.TargetApi;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Bitmap;
import android.net.Uri;
import android.net.http.SslError;
import android.util.Log;
import android.view.View;
import android.webkit.HttpAuthHandler;
import android.webkit.SslErrorHandler;
import android.webkit.WebResourceResponse;
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
* document instead of the chrome surrounding it, such as onPageStarted(),
* shouldOverrideUrlLoading(), etc. Related to but different than
* CordovaChromeClient.
*
* @see <a href="http://developer.android.com/reference/android/webkit/WebViewClient.html">WebViewClient</a>
* @see <a href="http://developer.android.com/guide/webapps/webview.html">WebView guide</a>
* @see CordovaChromeClient
* @see CordovaWebView
*/
public class AndroidWebViewClient extends WebViewClient implements CordovaWebViewClient{
private static final String TAG = "CordovaWebViewClient";
private static final String CORDOVA_EXEC_URL_PREFIX = "http://cdv_exec/";
CordovaInterface cordova;
CordovaWebView appView;
private boolean doClearHistory = false;
boolean isCurrentlyLoading;
/** The authorization tokens. */
private Hashtable<String, AuthenticationToken> authenticationTokens = new Hashtable<String, AuthenticationToken>();
/**
* Constructor.
*
* @param cordova
*/
public AndroidWebViewClient(CordovaInterface cordova) {
this.cordova = cordova;
}
/**
* Constructor.
*
* @param cordova
* @param view
*/
public AndroidWebViewClient(CordovaInterface cordova, CordovaWebView view) {
this.cordova = cordova;
this.appView = view;
}
/**
* Constructor.
*
* @param view
*/
public void setWebView(CordovaWebView view) {
this.appView = view;
}
// Parses commands sent by setting the webView's URL to:
// cdvbrg:service/action/callbackId#jsonArgs
private void handleExecUrl(String url) {
int idx1 = CORDOVA_EXEC_URL_PREFIX.length();
int idx2 = url.indexOf('#', idx1 + 1);
int idx3 = url.indexOf('#', idx2 + 1);
int idx4 = url.indexOf('#', idx3 + 1);
if (idx1 == -1 || idx2 == -1 || idx3 == -1 || idx4 == -1) {
Log.e(TAG, "Could not decode URL command: " + url);
return;
}
String service = url.substring(idx1, idx2);
String action = url.substring(idx2 + 1, idx3);
String callbackId = url.substring(idx3 + 1, idx4);
String jsonArgs = url.substring(idx4 + 1);
try {
appView.exec(service, action, callbackId, jsonArgs);
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* Give the host application a chance to take over the control when a new url
* is about to be loaded in the current WebView.
*
* @param view The WebView that is initiating the callback.
* @param url The url to be loaded.
* @return true to override, false for default behavior
*/
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// Check if it's an exec() bridge command message.
if (NativeToJsMessageQueue.ENABLE_LOCATION_CHANGE_EXEC_MODE && url.startsWith(CORDOVA_EXEC_URL_PREFIX)) {
handleExecUrl(url);
}
// Give plugins the chance to handle the url
else if (this.appView.onOverrideUrlLoading(url)) {
}
// If dialing phone (tel:5551212)
else if (url.startsWith(WebView.SCHEME_TEL)) {
try {
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse(url));
this.cordova.getActivity().startActivity(intent);
} catch (android.content.ActivityNotFoundException e) {
LOG.e(TAG, "Error dialing " + url + ": " + e.toString());
}
}
// If displaying map (geo:0,0?q=address)
else if (url.startsWith("geo:")) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
this.cordova.getActivity().startActivity(intent);
} catch (android.content.ActivityNotFoundException e) {
LOG.e(TAG, "Error showing map " + url + ": " + e.toString());
}
}
// If sending email (mailto:abc@corp.com)
else if (url.startsWith(WebView.SCHEME_MAILTO)) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
this.cordova.getActivity().startActivity(intent);
} catch (android.content.ActivityNotFoundException e) {
LOG.e(TAG, "Error sending email " + url + ": " + e.toString());
}
}
// If sms:5551212?body=This is the message
else if (url.startsWith("sms:")) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
// Get address
String address = null;
int parmIndex = url.indexOf('?');
if (parmIndex == -1) {
address = url.substring(4);
}
else {
address = url.substring(4, parmIndex);
// If body, then set sms body
Uri uri = Uri.parse(url);
String query = uri.getQuery();
if (query != null) {
if (query.startsWith("body=")) {
intent.putExtra("sms_body", query.substring(5));
}
}
}
intent.setData(Uri.parse("sms:" + address));
intent.putExtra("address", address);
intent.setType("vnd.android-dir/mms-sms");
this.cordova.getActivity().startActivity(intent);
} catch (android.content.ActivityNotFoundException e) {
LOG.e(TAG, "Error sending sms " + url + ":" + e.toString());
}
}
//Android Market
else if(url.startsWith("market:")) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
this.cordova.getActivity().startActivity(intent);
} catch (android.content.ActivityNotFoundException e) {
LOG.e(TAG, "Error loading Google Play Store: " + url, e);
}
}
// All else
else {
// If our app or file:, then load into a new Cordova webview container by starting a new instance of our activity.
// Our app continues to run. When BACK is pressed, our app is redisplayed.
if (url.startsWith("file://") || url.startsWith("data:") || Config.isUrlWhiteListed(url)) {
return false;
}
// If not our application, let default viewer handle
else {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
this.cordova.getActivity().startActivity(intent);
} catch (android.content.ActivityNotFoundException e) {
LOG.e(TAG, "Error loading url " + url, e);
}
}
}
return true;
}
/**
* 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) {
// Get the authentication token
AuthenticationToken token = this.getAuthenticationToken(host, realm);
if (token != null) {
handler.proceed(token.getUserName(), token.getPassword());
}
else {
// Handle 401 like we'd normally do!
super.onReceivedHttpAuthRequest(view, handler, host, realm);
}
}
/**
* Notify the host application that a page has started loading.
* This method is called once for each main frame load so a page with iframes or framesets will call onPageStarted
* one time for the main frame. This also means that onPageStarted will not be called when the contents of an
* embedded frame changes, i.e. clicking a link whose target is an iframe.
*
* @param view The webview initiating the callback.
* @param url The url of the page.
*/
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
isCurrentlyLoading = true;
LOG.d(TAG, "onPageStarted(" + url + ")");
// Flush stale messages.
this.appView.resetJsMessageQueue();
// Broadcast message that page has loaded
this.appView.postMessage("onPageStarted", url);
// Notify all plugins of the navigation, so they can clean up if necessary.
this.appView.onReset();
}
/**
* Notify the host application that a page has finished loading.
* This method is called only for main frame. When onPageFinished() is called, the rendering picture may not be updated yet.
*
*
* @param view The webview initiating the callback.
* @param url The url of the page.
*/
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
// Ignore excessive calls.
if (!isCurrentlyLoading) {
return;
}
isCurrentlyLoading = false;
LOG.d(TAG, "onPageFinished(" + url + ")");
/**
* Because of a timing issue we need to clear this history in onPageFinished as well as
* onPageStarted. However we only want to do this if the doClearHistory boolean is set to
* true. You see when you load a url with a # in it which is common in jQuery applications
* onPageStared is not called. Clearing the history at that point would break jQuery apps.
*/
if (this.doClearHistory) {
view.clearHistory();
this.doClearHistory = false;
}
// Clear timeout flag
this.appView.incUrlTimeout();
// Broadcast message that page has loaded
this.appView.postMessage("onPageFinished", url);
// Make app visible after 2 sec in case there was a JS error and Cordova JS never initialized correctly
if (this.appView.getVisibility() == View.INVISIBLE) {
Thread t = new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(2000);
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
appView.postMessage("spinner", "stop");
}
});
} catch (InterruptedException e) {
}
}
});
t.start();
}
// Shutdown if blank loaded
if (url.equals("about:blank")) {
appView.postMessage("exit", null);
}
}
/**
* Report an error to the host application. These errors are unrecoverable (i.e. the main resource is unavailable).
* The errorCode parameter corresponds to one of the ERROR_* constants.
*
* @param view The WebView that is initiating the callback.
* @param errorCode The error code corresponding to an ERROR_* value.
* @param description A String describing the error.
* @param failingUrl The url that failed to load.
*/
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
// Ignore error due to stopLoading().
if (!isCurrentlyLoading) {
return;
}
LOG.d(TAG, "CordovaWebViewClient.onReceivedError: Error code=%s Description=%s URL=%s", errorCode, description, failingUrl);
// Clear timeout flag
this.appView.incUrlTimeout();
// Handle error
JSONObject data = new JSONObject();
try {
data.put("errorCode", errorCode);
data.put("description", description);
data.put("url", failingUrl);
} catch (JSONException e) {
e.printStackTrace();
}
this.appView.postMessage("onReceivedError", data);
}
/**
* Notify the host application that an SSL error occurred while loading a resource.
* The host application must call either handler.cancel() or handler.proceed().
* Note that the decision may be retained for use in response to future SSL errors.
* The default behavior is to cancel the load.
*
* @param view The WebView that is initiating the callback.
* @param handler An SslErrorHandler object that will handle the user's response.
* @param error The SSL error object.
*/
@TargetApi(8)
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
final String packageName = this.cordova.getActivity().getPackageName();
final PackageManager pm = this.cordova.getActivity().getPackageManager();
ApplicationInfo appInfo;
try {
appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
// debug = true
handler.proceed();
return;
} else {
// debug = false
super.onReceivedSslError(view, handler, error);
}
} catch (NameNotFoundException e) {
// When it doubt, lock it out!
super.onReceivedSslError(view, handler, error);
}
}
/**
* Sets the authentication token.
*
* @param authenticationToken
* @param host
* @param realm
*/
public void setAuthenticationToken(AuthenticationToken authenticationToken, String host, String realm) {
if (host == null) {
host = "";
}
if (realm == null) {
realm = "";
}
this.authenticationTokens.put(host.concat(realm), authenticationToken);
}
/**
* 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) {
return this.authenticationTokens.remove(host.concat(realm));
}
/**
* 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) {
AuthenticationToken token = null;
token = this.authenticationTokens.get(host.concat(realm));
if (token == null) {
// try with just the host
token = this.authenticationTokens.get(host);
// Try the realm
if (token == null) {
token = this.authenticationTokens.get(realm);
}
// if no host found, just query for default
if (token == null) {
token = this.authenticationTokens.get("");
}
}
return token;
}
/**
* Clear all authentication tokens.
*/
public void clearAuthenticationTokens() {
this.authenticationTokens.clear();
}
@Override
public void onReceivedError(CordovaWebView me, int i, String string,
String url) {
// Only deal with this if we're dealing with a proper classic webview.
if(WebView.class.isInstance(me))
{
this.onReceivedError(me, i, string, url);
}
}
}

View File

@@ -32,7 +32,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.telephony.TelephonyManager;
import android.view.KeyEvent;
import java.util.HashMap;
@@ -47,12 +46,16 @@ public class App extends CordovaPlugin {
/**
* Sets the context of the Command. This can then be used to do things like
* get file paths associated with the Activity.
*
* @param cordova The context of the main Activity.
* @param webView The CordovaWebView Cordova is running in.
*/
@Override
public void pluginInitialize() {
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
super.initialize(cordova, webView);
this.initTelephonyReceiver();
}
/**
* Executes the request and returns PluginResult.
*
@@ -187,11 +190,7 @@ public class App extends CordovaPlugin {
* Clear page history for the app.
*/
public void clearHistory() {
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
webView.clearHistory();
}
});
this.webView.clearHistory();
}
/**
@@ -214,7 +213,7 @@ public class App extends CordovaPlugin {
*/
public void overrideBackbutton(boolean override) {
LOG.i("App", "WARNING: Back Button Default Behavior will be overridden. The backbutton event will be fired!");
webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_BACK, override);
webView.bindButton(override);
}
/**
@@ -226,12 +225,7 @@ public class App extends CordovaPlugin {
*/
public void overrideButton(String button, boolean override) {
LOG.i("App", "WARNING: Volume Button Default Behavior will be overridden. The volume event will be fired!");
if (button.equals("volumeup")) {
webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_UP, override);
}
else if (button.equals("volumedown")) {
webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_DOWN, override);
}
webView.bindButton(button, override);
}
/**
@@ -240,7 +234,7 @@ public class App extends CordovaPlugin {
* @return boolean
*/
public boolean isBackbuttonOverridden() {
return webView.isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK);
return webView.isBackButtonBound();
}
/**

View File

@@ -19,34 +19,183 @@
package org.apache.cordova;
import java.util.List;
import java.io.IOException;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.cordova.LOG;
import org.xmlpull.v1.XmlPullParserException;
import android.app.Activity;
import android.content.res.XmlResourceParser;
import android.graphics.Color;
import android.util.Log;
@Deprecated // Use Whitelist, CordovaPrefences, etc. directly.
public class Config {
private static final String TAG = "Config";
static ConfigXmlParser parser;
public static final String TAG = "Config";
private Config() {
}
private Whitelist whitelist = new Whitelist();
private String startUrl;
private static Config self = null;
public static void init(Activity action) {
parser = new ConfigXmlParser();
parser.parse(action);
parser.getPreferences().setPreferencesBundle(action.getIntent().getExtras());
parser.getPreferences().copyIntoIntentExtras(action);
//Just re-initialize this! Seriously, we lose this all the time
self = new Config(action);
}
// Intended to be used for testing only; creates an empty configuration.
public static void init() {
if (parser == null) {
parser = new ConfigXmlParser();
if (self == null) {
self = new Config();
}
}
// Intended to be used for testing only; creates an empty configuration.
private Config() {
}
private Config(Activity action) {
if (action == null) {
LOG.i("CordovaLog", "There is no activity. Is this on the lock screen?");
return;
}
// First checking the class namespace for config.xml
int id = action.getResources().getIdentifier("config", "xml", action.getClass().getPackage().getName());
if (id == 0) {
// If we couldn't find config.xml there, we'll look in the namespace from AndroidManifest.xml
id = action.getResources().getIdentifier("config", "xml", action.getPackageName());
if (id == 0) {
LOG.i("CordovaLog", "config.xml missing. Ignoring...");
return;
}
}
// Add implicitly allowed URLs
whitelist.addWhiteListEntry("file:///*", false);
whitelist.addWhiteListEntry("content:///*", false);
whitelist.addWhiteListEntry("data:*", false);
XmlResourceParser xml = action.getResources().getXml(id);
int eventType = -1;
while (eventType != XmlResourceParser.END_DOCUMENT) {
if (eventType == XmlResourceParser.START_TAG) {
String strNode = xml.getName();
if (strNode.equals("access")) {
String origin = xml.getAttributeValue(null, "origin");
String subdomains = xml.getAttributeValue(null, "subdomains");
if (origin != null) {
whitelist.addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0));
}
}
else if (strNode.equals("log")) {
String level = xml.getAttributeValue(null, "level");
Log.d(TAG, "The <log> tag is deprecated. Use <preference name=\"loglevel\" value=\"" + level + "\"/> instead.");
if (level != null) {
LOG.setLogLevel(level);
}
}
else if (strNode.equals("preference")) {
String name = xml.getAttributeValue(null, "name").toLowerCase(Locale.getDefault());
/* Java 1.6 does not support switch-based strings
Java 7 does, but we're using Dalvik, which is apparently not Java.
Since we're reading XML, this has to be an ugly if/else.
Also, due to cast issues, each of them has to call their separate putExtra!
Wheee!!! Isn't Java FUN!?!?!?
Note: We should probably pass in the classname for the variable splash on splashscreen!
*/
if (name.equalsIgnoreCase("LogLevel")) {
String level = xml.getAttributeValue(null, "value");
LOG.setLogLevel(level);
} else if (name.equalsIgnoreCase("SplashScreen")) {
String value = xml.getAttributeValue(null, "value");
int resource = 0;
if (value == null)
{
value = "splash";
}
resource = action.getResources().getIdentifier(value, "drawable", action.getClass().getPackage().getName());
action.getIntent().putExtra(name, resource);
}
else if(name.equalsIgnoreCase("BackgroundColor")) {
int value = xml.getAttributeIntValue(null, "value", Color.BLACK);
action.getIntent().putExtra(name, value);
}
else if(name.equalsIgnoreCase("LoadUrlTimeoutValue")) {
int value = xml.getAttributeIntValue(null, "value", 20000);
action.getIntent().putExtra(name, value);
}
else if(name.equalsIgnoreCase("SplashScreenDelay")) {
int value = xml.getAttributeIntValue(null, "value", 3000);
action.getIntent().putExtra(name, value);
}
else if(name.equalsIgnoreCase("KeepRunning"))
{
boolean value = xml.getAttributeValue(null, "value").equals("true");
action.getIntent().putExtra(name, value);
}
else if(name.equalsIgnoreCase("InAppBrowserStorageEnabled"))
{
boolean value = xml.getAttributeValue(null, "value").equals("true");
action.getIntent().putExtra(name, value);
}
else if(name.equalsIgnoreCase("DisallowOverscroll"))
{
boolean value = xml.getAttributeValue(null, "value").equals("true");
action.getIntent().putExtra(name, value);
}
else
{
String value = xml.getAttributeValue(null, "value");
action.getIntent().putExtra(name, value);
}
/*
LOG.i("CordovaLog", "Found preference for %s=%s", name, value);
*/
}
else if (strNode.equals("content")) {
String src = xml.getAttributeValue(null, "src");
LOG.i("CordovaLog", "Found start page location: %s", src);
if (src != null) {
Pattern schemeRegex = Pattern.compile("^[a-z-]+://");
Matcher matcher = schemeRegex.matcher(src);
if (matcher.find()) {
startUrl = src;
} else {
if (src.charAt(0) == '/') {
src = src.substring(1);
}
startUrl = "file:///android_asset/www/" + src;
}
}
}
}
try {
eventType = xml.next();
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* Add entry to approved list of URLs (whitelist)
*
@@ -54,11 +203,11 @@ public class Config {
* @param subdomains T=include all subdomains under origin
*/
public static void addWhiteListEntry(String origin, boolean subdomains) {
if (parser == null) {
if (self == null) {
Log.e(TAG, "Config was not initialised. Did you forget to Config.init(this)?");
return;
}
parser.getInternalWhitelist().addWhiteListEntry(origin, subdomains);
self.whitelist.addWhiteListEntry(origin, subdomains);
}
/**
@@ -68,55 +217,17 @@ public class Config {
* @return true if whitelisted
*/
public static boolean isUrlWhiteListed(String url) {
if (parser == null) {
if (self == null) {
Log.e(TAG, "Config was not initialised. Did you forget to Config.init(this)?");
return false;
}
return parser.getInternalWhitelist().isUrlWhiteListed(url);
}
/**
* Determine if URL is in approved list of URLs to launch external applications.
*
* @param url
* @return true if whitelisted
*/
public static boolean isUrlExternallyWhiteListed(String url) {
if (parser == null) {
Log.e(TAG, "Config was not initialised. Did you forget to Config.init(this)?");
return false;
}
return parser.getExternalWhitelist().isUrlWhiteListed(url);
return self.whitelist.isUrlWhiteListed(url);
}
public static String getStartUrl() {
if (parser == null) {
if (self == null || self.startUrl == null) {
return "file:///android_asset/www/index.html";
}
return parser.getLaunchUrl();
}
public static String getErrorUrl() {
return parser.getPreferences().getString("errorurl", null);
}
public static Whitelist getWhitelist() {
return parser.getInternalWhitelist();
}
public static Whitelist getExternalWhitelist() {
return parser.getExternalWhitelist();
}
public static List<PluginEntry> getPluginEntries() {
return parser.getPluginEntries();
}
public static CordovaPreferences getPreferences() {
return parser.getPreferences();
}
public static boolean isInitialized() {
return parser != null;
return self.startUrl;
}
}

View File

@@ -1,181 +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 java.util.ArrayList;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.cordova.LOG;
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";
private String launchUrl = "file:///android_asset/www/index.html";
private CordovaPreferences prefs = new CordovaPreferences();
private Whitelist internalWhitelist = new Whitelist();
private Whitelist externalWhitelist = new Whitelist();
private ArrayList<PluginEntry> pluginEntries = new ArrayList<PluginEntry>(20);
public Whitelist getInternalWhitelist() {
return internalWhitelist;
}
public Whitelist getExternalWhitelist() {
return externalWhitelist;
}
public CordovaPreferences getPreferences() {
return prefs;
}
public ArrayList<PluginEntry> getPluginEntries() {
return pluginEntries;
}
public String getLaunchUrl() {
return launchUrl;
}
public void parse(Activity action) {
// First checking the class namespace for config.xml
int id = action.getResources().getIdentifier("config", "xml", action.getClass().getPackage().getName());
if (id == 0) {
// If we couldn't find config.xml there, we'll look in the namespace from AndroidManifest.xml
id = action.getResources().getIdentifier("config", "xml", action.getPackageName());
if (id == 0) {
LOG.e(TAG, "res/xml/config.xml is missing!");
return;
}
}
parse(action.getResources().getXml(id));
}
public void parse(XmlResourceParser xml) {
int eventType = -1;
String service = "", pluginClass = "", paramType = "";
boolean onload = false;
boolean insideFeature = false;
ArrayList<String> urlMap = null;
// Add implicitly allowed URLs
internalWhitelist.addWhiteListEntry("file:///*", false);
internalWhitelist.addWhiteListEntry("content:///*", false);
internalWhitelist.addWhiteListEntry("data:*", false);
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")) {
//Check for supported feature sets aka. plugins (Accelerometer, Geolocation, etc)
//Set the bit for reading params
insideFeature = true;
service = xml.getAttributeValue(null, "name");
}
else if (insideFeature && strNode.equals("param")) {
paramType = xml.getAttributeValue(null, "name");
if (paramType.equals("service")) // check if it is using the older service param
service = xml.getAttributeValue(null, "value");
else if (paramType.equals("package") || paramType.equals("android-package"))
pluginClass = xml.getAttributeValue(null,"value");
else if (paramType.equals("onload"))
onload = "true".equals(xml.getAttributeValue(null, "value"));
}
else if (strNode.equals("access")) {
String origin = xml.getAttributeValue(null, "origin");
String subdomains = xml.getAttributeValue(null, "subdomains");
boolean external = (xml.getAttributeValue(null, "launch-external") != null);
if (origin != null) {
if (external) {
externalWhitelist.addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0));
} else {
if ("*".equals(origin)) {
// Special-case * origin to mean http and https when used for internal
// whitelist. This prevents external urls like sms: and geo: from being
// handled internally.
internalWhitelist.addWhiteListEntry("http://*/*", false);
internalWhitelist.addWhiteListEntry("https://*/*", false);
} else {
internalWhitelist.addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0));
}
}
}
}
else if (strNode.equals("preference")) {
String name = xml.getAttributeValue(null, "name").toLowerCase(Locale.ENGLISH);
String value = xml.getAttributeValue(null, "value");
prefs.set(name, value);
}
else if (strNode.equals("content")) {
String src = xml.getAttributeValue(null, "src");
if (src != null) {
setStartUrl(src);
}
}
}
else if (eventType == XmlResourceParser.END_TAG)
{
String strNode = xml.getName();
if (strNode.equals("feature")) {
pluginEntries.add(new PluginEntry(service, pluginClass, onload, urlMap));
service = "";
pluginClass = "";
insideFeature = false;
onload = false;
urlMap = null;
}
}
try {
eventType = xml.next();
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void setStartUrl(String src) {
Pattern schemeRegex = Pattern.compile("^[a-z-]+://");
Matcher matcher = schemeRegex.matcher(src);
if (matcher.find()) {
launchUrl = src;
} else {
if (src.charAt(0) == '/') {
src = src.substring(1);
}
launchUrl = "file:///android_asset/www/" + src;
}
}
}

View File

@@ -18,8 +18,10 @@
*/
package org.apache.cordova;
import java.util.ArrayList;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -37,6 +39,7 @@ import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Color;
import android.media.AudioManager;
import android.net.Uri;
@@ -49,16 +52,16 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import android.webkit.ValueCallback;
import android.webkit.WebViewClient;
import android.widget.LinearLayout;
/**
* This class is the main Android activity that represents the Cordova
* application. It should be extended by the user to load the specific
* application. It should be extended by the user to load the specific
* html file that contains the application.
*
* As an example:
@@ -75,7 +78,7 @@ import android.widget.LinearLayout;
* super.onCreate(savedInstanceState);
* super.init();
* // Load your application
* loadUrl(launchUrl);
* super.loadUrl(Config.getStartUrl());
* }
* }
* </pre>
@@ -92,16 +95,18 @@ 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 boolean cancelLoadUrl = false;
protected ProgressDialog spinnerDialog = null;
private final ExecutorService threadPool = Executors.newCachedThreadPool();
// The initial URL for our app
// ie http://server/path/index.html#abc?query
//private String url = null;
private static int ACTIVITY_STARTING = 0;
private static int ACTIVITY_RUNNING = 1;
private static int ACTIVITY_EXITING = 2;
@@ -111,6 +116,10 @@ public class CordovaActivity extends Activity implements CordovaInterface {
protected CordovaPlugin activityResultCallback = null;
protected boolean activityResultKeepRunning;
// Default background color for activity
// (this is not the color for the webview, which is set in HTML)
private int backgroundColor = Color.BLACK;
/*
* The variables below are used to cache some of the activity properties.
*/
@@ -128,165 +137,73 @@ public class CordovaActivity extends Activity implements CordovaInterface {
// when another application (activity) is started.
protected boolean keepRunning = true;
private int lastRequestCode;
private Object responseCode;
private Intent lastIntent;
private Object lastResponseCode;
private String initCallbackClass;
// Read from config.xml:
protected CordovaPreferences preferences;
protected Whitelist internalWhitelist;
protected Whitelist externalWhitelist;
protected String launchUrl;
protected ArrayList<PluginEntry> pluginEntries;
private Object LOG_TAG;
/**
* 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.
*
* @param savedInstanceState
*/
@SuppressWarnings("deprecation")
@Override
public void onCreate(Bundle savedInstanceState) {
LOG.i(TAG, "Apache Cordova native platform version " + CordovaWebView.CORDOVA_VERSION + " is starting");
Config.init(this);
LOG.d(TAG, "CordovaActivity.onCreate()");
// need to activate preferences before super.onCreate to avoid "requestFeature() must be called before adding content" exception
loadConfig();
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);
}
super.onCreate(savedInstanceState);
if(savedInstanceState != null)
{
initCallbackClass = savedInstanceState.getString("callbackClass");
}
}
if(!this.getBooleanProperty("ShowTitle", false))
{
getWindow().requestFeature(Window.FEATURE_NO_TITLE);
}
@SuppressWarnings("deprecation")
protected void loadConfig() {
ConfigXmlParser parser = new ConfigXmlParser();
parser.parse(this);
preferences = parser.getPreferences();
preferences.setPreferencesBundle(getIntent().getExtras());
preferences.copyIntoIntentExtras(this);
internalWhitelist = parser.getInternalWhitelist();
externalWhitelist = parser.getExternalWhitelist();
launchUrl = parser.getLaunchUrl();
pluginEntries = parser.getPluginEntries();
Config.parser = parser;
}
@SuppressWarnings("deprecation")
protected void createViews() {
if(this.getBooleanProperty("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
{
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
}
// This builds the view. We could probably get away with NOT having a LinearLayout, but I like having a bucket!
LOG.d(TAG, "CordovaActivity.createViews()");
Display display = getWindowManager().getDefaultDisplay();
int width = display.getWidth();
int height = display.getHeight();
root = new LinearLayoutSoftKeyboardDetect(this, width, height);
root.setOrientation(LinearLayout.VERTICAL);
root.setBackgroundColor(this.backgroundColor);
root.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT, 0.0F));
appView.setId(100);
appView.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);
// need to remove appView from any existing parent before invoking root.addView(appView)
ViewParent parent = appView.getParent();
if ((parent != null) && (parent != root)) {
LOG.d(TAG, "removing appView from existing parent");
ViewGroup parentGroup = (ViewGroup) parent;
parentGroup.removeView(appView);
}
root.addView((View) appView);
setContentView(root);
int backgroundColor = preferences.getInteger("BackgroundColor", Color.BLACK);
root.setBackgroundColor(backgroundColor);
// Setup the hardware volume controls to handle volume control
setVolumeControlStream(AudioManager.STREAM_MUSIC);
}
/**
* Get the Android activity.
*
* @return the Activity
*/
@Override public Activity getActivity() {
public Activity getActivity() {
return this;
}
@@ -297,80 +214,143 @@ public class CordovaActivity extends Activity implements CordovaInterface {
* require a more specialized web view.
*/
protected CordovaWebView makeWebView() {
return new CordovaWebView(CordovaActivity.this);
String r = this.getStringProperty("webView", "org.apache.cordova.AndroidCordovaWebView");
try {
Class webViewClass = Class.forName(r);
Constructor<CordovaWebView> [] webViewConstructors = webViewClass.getConstructors();
if(CordovaWebView.class.isAssignableFrom(webViewClass)) {
for (Constructor<CordovaWebView> constructor : webViewConstructors) {
try {
CordovaWebView webView = (CordovaWebView) constructor.newInstance(this);
return webView;
} catch (IllegalArgumentException e) {
LOG.d(TAG, "Illegal arguments; trying next constructor.");
}
}
}
LOG.e(TAG, "The WebView Engine is NOT a proper WebView, defaulting to system WebView");
} catch (ClassNotFoundException e) {
LOG.e(TAG, "The WebView Engine was not found, defaulting to system WebView");
} catch (InstantiationException e) {
LOG.e(TAG, "Unable to instantiate the WebView, defaulting to system WebView");
} catch (IllegalAccessException e) {
LOG.e(TAG, "Illegal Access to Constructor. This should never happen, defaulting to system WebView");
} catch (IllegalArgumentException e) {
LOG.e(TAG, "The WebView does not implement the default constructor, defaulting to system WebView");
} catch (InvocationTargetException e) {
LOG.e(TAG, "Invocation Target Exception! Reflection is hard, defaulting to system WebView");
}
// If all else fails, return a default WebView
return (CordovaWebView) new AndroidCordovaWebView(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.
* This is intended to be overridable by subclasses of CordovaActivity which
* require a more specialized web view. By default, it allows the webView
* to create its own client objects.
*
* @param webView the default constructed web view object
*/
protected CordovaWebViewClient makeWebViewClient(CordovaWebView webView) {
return webView.makeWebViewClient(this);
return webView.makeWebViewClient();
}
/**
* 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.
* This is intended to be overridable by subclasses of CordovaActivity which
* require a more specialized web view. By default, it allows the webView
* to create its own client objects.
*
* @param webView the default constructed web view object
*/
protected CordovaChromeClient makeChromeClient(CordovaWebView webView) {
return webView.makeWebChromeClient(this);
return webView.makeWebChromeClient();
}
/**
* Create and initialize web container with default web view objects.
*/
public void init() {
this.init(appView, null, null);
CordovaWebView webView = makeWebView();
this.init(webView, makeWebViewClient(webView), makeChromeClient(webView));
}
/**
* Initialize web container with web view objects.
*
* @param webView
* @param webViewClient
* @param webChromeClient
*/
@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()");
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);
// Set up web container
this.appView = webView;
this.appView.setId(100);
this.appView.setWebViewClient(webViewClient);
this.appView.setWebChromeClient(webChromeClient);
webViewClient.setWebView(this.appView);
webChromeClient.setWebView(this.appView);
this.appView.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
1.0F));
if (this.getBooleanProperty("DisallowOverscroll", false)) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD) {
//Note: We're using the parent class, because all we know is that this will be a view
this.appView.setOverScrollMode(View.OVER_SCROLL_NEVER);
}
}
// TODO: Have the views set this themselves.
if (preferences.getBoolean("DisallowOverscroll", false)) {
appView.setOverScrollMode(View.OVER_SCROLL_NEVER);
}
createViews();
// Add web view but make it invisible while loading URL
this.appView.setVisibility(View.INVISIBLE);
this.root.addView((View) this.appView.getView());
setContentView(this.root);
// TODO: Make this a preference (CB-6153)
// Setup the hardware volume controls to handle volume control
setVolumeControlStream(AudioManager.STREAM_MUSIC);
// Clear cancel flag
this.cancelLoadUrl = false;
}
/**
* Load the url into the webview.
*
* @param url
*/
public void loadUrl(String url) {
if (appView == null) {
init();
// Init web view if not already done
if (this.appView == null) {
this.init();
}
this.splashscreenTime = preferences.getInteger("SplashScreenDelay", this.splashscreenTime);
String splash = preferences.getString("SplashScreen", null);
if(this.splashscreenTime > 0 && splash != null)
this.splashscreenTime = this.getIntegerProperty("SplashScreenDelay", this.splashscreenTime);
if(this.splashscreenTime > 0)
{
this.splashscreen = getResources().getIdentifier(splash, "drawable", getClass().getPackage().getName());;
this.splashscreen = this.getIntegerProperty("SplashScreen", 0);
if(this.splashscreen != 0)
{
this.showSplashScreen(this.splashscreenTime);
}
}
// Set backgroundColor
this.backgroundColor = this.getIntegerProperty("BackgroundColor", Color.BLACK);
this.root.setBackgroundColor(this.backgroundColor);
// If keepRunning
this.keepRunning = preferences.getBoolean("KeepRunning", true);
this.keepRunning = this.getBooleanProperty("KeepRunning", true);
//Check if the view is attached to anything
if(appView.getParent() != null)
@@ -401,6 +381,7 @@ public class CordovaActivity extends Activity implements CordovaInterface {
this.splashscreenTime = time;
this.loadUrl(url);
}
/*
@@ -411,10 +392,10 @@ public class CordovaActivity extends Activity implements CordovaInterface {
// If loadingDialog property, then show the App loading dialog for first page of app
String loading = null;
if ((this.appView == null) || !this.appView.canGoBack()) {
loading = preferences.getString("LoadingDialog", null);
loading = this.getStringProperty("LoadingDialog", null);
}
else {
loading = preferences.getString("LoadingPageDialog", null);
loading = this.getStringProperty("LoadingPageDialog", null);
}
if (loading != null) {
@@ -436,17 +417,13 @@ 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();
if (this.appView == null) {
this.init();
}
this.appView.clearCache(true);
}
@@ -454,7 +431,6 @@ public class CordovaActivity extends Activity implements CordovaInterface {
/**
* Clear web history in this web view.
*/
@Deprecated // Call method on appView directly.
public void clearHistory() {
this.appView.clearHistory();
}
@@ -464,7 +440,6 @@ public class CordovaActivity extends Activity implements CordovaInterface {
*
* @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();
@@ -472,102 +447,122 @@ public class CordovaActivity extends Activity implements CordovaInterface {
return false;
}
@Override
/**
* Called by the system when the device configuration changes while your activity is running.
*
* @param Configuration newConfig
*/
public void onConfigurationChanged(Configuration newConfig) {
//don't reload the current page when the orientation is changed
super.onConfigurationChanged(newConfig);
}
/**
* Get boolean property for activity.
*
* @param name
* @param defaultValue
* @return the boolean value of the named property
*/
@Deprecated // Call method on preferences directly.
public boolean getBooleanProperty(String name, boolean defaultValue) {
return preferences.getBoolean(name, defaultValue);
Bundle bundle = this.getIntent().getExtras();
if (bundle == null) {
return defaultValue;
}
name = name.toLowerCase(Locale.getDefault());
Boolean p;
try {
p = (Boolean) bundle.get(name);
} catch (ClassCastException e) {
String s = bundle.get(name).toString();
if ("true".equals(s)) {
p = true;
}
else {
p = false;
}
}
if (p == null) {
return defaultValue;
}
return p.booleanValue();
}
/**
* Get int property for activity.
*
* @param name
* @param defaultValue
* @return the int value for the named property
*/
@Deprecated // Call method on preferences directly.
public int getIntegerProperty(String name, int defaultValue) {
return preferences.getInteger(name, defaultValue);
Bundle bundle = this.getIntent().getExtras();
if (bundle == null) {
return defaultValue;
}
name = name.toLowerCase(Locale.getDefault());
Integer p;
try {
p = (Integer) bundle.get(name);
} catch (ClassCastException e) {
p = Integer.parseInt(bundle.get(name).toString());
}
if (p == null) {
return defaultValue;
}
return p.intValue();
}
/**
* Get string property for activity.
*
* @param name
* @param defaultValue
* @return the String value for the named property
*/
@Deprecated // Call method on preferences directly.
public String getStringProperty(String name, String defaultValue) {
return preferences.getString(name, defaultValue);
Bundle bundle = this.getIntent().getExtras();
if (bundle == null) {
return defaultValue;
}
name = name.toLowerCase(Locale.getDefault());
String p = bundle.getString(name);
if (p == null) {
return defaultValue;
}
return p;
}
/**
* Get double property for activity.
*
* @param name
* @param defaultValue
* @return the double value for the named property
*/
@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);
Bundle bundle = this.getIntent().getExtras();
if (bundle == null) {
return defaultValue;
}
name = name.toLowerCase(Locale.getDefault());
Double p;
try {
p = (Double) bundle.get(name);
} catch (ClassCastException e) {
p = Double.parseDouble(bundle.get(name).toString());
}
if (p == null) {
return defaultValue;
}
return p.doubleValue();
}
@Override
/**
* Called when the system is about to start resuming a previous activity.
*/
@Override
protected void onPause() {
super.onPause();
@@ -590,10 +585,10 @@ public class CordovaActivity extends Activity implements CordovaInterface {
this.removeSplashScreen();
}
@Override
/**
* Called when the activity receives a new intent
**/
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
//Forward to plugins
@@ -601,14 +596,22 @@ public class CordovaActivity extends Activity implements CordovaInterface {
this.appView.onNewIntent(intent);
}
@Override
/**
* Called when the activity will start interacting with the user.
*/
@Override
protected void onResume() {
super.onResume();
//Reload the configuration
Config.init(this);
LOG.d(TAG, "Resuming the App");
//Code to test CB-3064
String errorUrl = this.getStringProperty("ErrorUrl", null);
LOG.d(TAG, "CB-3064: The errorUrl is " + errorUrl);
if (this.activityState == ACTIVITY_STARTING) {
this.activityState = ACTIVITY_RUNNING;
return;
@@ -617,9 +620,6 @@ public class CordovaActivity extends Activity implements CordovaInterface {
if (this.appView == null) {
return;
}
// Force window to have focus, so application always
// receive user input. Workaround for some devices (Samsung Galaxy Note 3 at least)
this.getWindow().getDecorView().requestFocus();
this.appView.handleResume(this.keepRunning, this.activityResultKeepRunning);
@@ -634,10 +634,10 @@ public class CordovaActivity extends Activity implements CordovaInterface {
}
}
@Override
/**
* The final call you receive before your activity is destroyed.
*/
@Override
public void onDestroy() {
LOG.d(TAG, "CordovaActivity.onDestroy()");
super.onDestroy();
@@ -655,6 +655,9 @@ public class CordovaActivity extends Activity implements CordovaInterface {
/**
* 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.appView != null) {
@@ -662,29 +665,18 @@ public class CordovaActivity extends Activity implements CordovaInterface {
}
}
/**
* @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);
this.appView.addJavascript(statement);
//this.appView.jsMessageQueue.addJavaScript(statement);
}
}
@@ -748,6 +740,7 @@ public class CordovaActivity extends Activity implements CordovaInterface {
super.startActivityForResult(intent, requestCode);
}
@Override
/**
* Called when an activity you launched exits, giving you the requestCode you started it with,
* the resultCode it returned, and any additional data from it.
@@ -757,18 +750,19 @@ public class CordovaActivity extends Activity implements CordovaInterface {
* @param resultCode The integer result code returned by the child activity through its setResult().
* @param data An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
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();
ValueCallback<Uri> mUploadMessage = ((CordovaChromeClient) this.appView.getWebChromeClient()).getValueCallback();
Log.d(TAG, "did we get here?");
if (null == mUploadMessage)
return;
Uri result = intent == null || resultCode != Activity.RESULT_OK ? null : intent.getData();
Log.d(TAG, "result = " + result);
// Uri filepath = Uri.parse("file://" + FileUtils.getRealPathFromURI(result, this));
// Log.d(TAG, "result = " + filepath);
mUploadMessage.onReceiveValue(result);
mUploadMessage = null;
}
@@ -776,7 +770,8 @@ public class CordovaActivity extends Activity implements CordovaInterface {
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.getPlugin(initCallbackClass);
callback = this.activityResultCallback;
}
if(callback != null) {
@@ -801,8 +796,8 @@ public class CordovaActivity extends Activity implements CordovaInterface {
final CordovaActivity me = this;
// 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))) {
final String errorUrl = me.getStringProperty("errorUrl", null);
if ((errorUrl != null) && (errorUrl.startsWith("file://") || Config.isUrlWhiteListed(errorUrl)) && (!failingUrl.equals(errorUrl))) {
// Load URL on UI thread
me.runOnUiThread(new Runnable() {
@@ -829,6 +824,11 @@ public class CordovaActivity extends Activity implements CordovaInterface {
/**
* Display an error dialog and optionally exit application.
*
* @param title
* @param message
* @param button
* @param exit
*/
public void displayError(final String title, final String message, final String button, final boolean exit) {
final CordovaActivity me = this;
@@ -859,14 +859,17 @@ public class CordovaActivity extends Activity implements CordovaInterface {
/**
* Determine if URL is in approved list of URLs to load.
*
* @param url
* @return true if the url is whitelisted
*/
@Deprecated // Use whitelist object directly.
public boolean isUrlWhiteListed(String url) {
return internalWhitelist.isUrlWhiteListed(url);
return Config.isUrlWhiteListed(url);
}
/*
* Hook in Cordova for menu plugins
*
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
@@ -888,6 +891,9 @@ public class CordovaActivity extends Activity implements CordovaInterface {
/**
* Get Activity context.
*
* @return self
* @deprecated
*/
@Deprecated
public Context getContext() {
@@ -905,7 +911,6 @@ public class CordovaActivity extends Activity implements CordovaInterface {
* @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);
@@ -941,7 +946,7 @@ public class CordovaActivity extends Activity implements CordovaInterface {
root.setMinimumHeight(display.getHeight());
root.setMinimumWidth(display.getWidth());
root.setOrientation(LinearLayout.VERTICAL);
root.setBackgroundColor(preferences.getInteger("backgroundColor", Color.BLACK));
root.setBackgroundColor(that.getIntegerProperty("backgroundColor", Color.BLACK));
root.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT, 0.0F));
root.setBackgroundResource(that.splashscreen);
@@ -1019,10 +1024,7 @@ public class CordovaActivity extends Activity implements CordovaInterface {
else {
// If the splash dialog is showing don't try to show it again
if (this.splashDialog == null || !this.splashDialog.isShowing()) {
String splashResource = preferences.getString("SplashScreen", null);
if (splashResource != null) {
splashscreen = getResources().getIdentifier(splashResource, "drawable", getClass().getPackage().getName());
}
this.splashscreen = this.getIntegerProperty("SplashScreen", 0);
this.showSplashScreen(this.splashscreenTime);
}
}
@@ -1060,4 +1062,66 @@ public class CordovaActivity extends Activity implements CordovaInterface {
outState.putString("callbackClass", cClass);
}
}
/**
* 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);
}
}

View File

@@ -1,183 +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 org.apache.cordova.PluginManager;
import org.json.JSONArray;
import org.json.JSONException;
import android.util.Log;
/**
* 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
*/
public class CordovaBridge {
private static final String LOG_TAG = "CordovaBridge";
private PluginManager pluginManager;
private NativeToJsMessageQueue jsMessageQueue;
private volatile int expectedBridgeSecret = -1; // written by UI thread, read by JS thread.
private String loadedUrl;
public CordovaBridge(PluginManager pluginManager, NativeToJsMessageQueue jsMessageQueue) {
this.pluginManager = pluginManager;
this.jsMessageQueue = jsMessageQueue;
}
public String jsExec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
if (!verifySecret("exec()", bridgeSecret)) {
return null;
}
// If the arguments weren't received, send a message back to JS. It will switch bridge modes and try again. See CB-2666.
// We send a message meant specifically for this case. It starts with "@" so no other message can be encoded into the same string.
if (arguments == null) {
return "@Null arguments.";
}
jsMessageQueue.setPaused(true);
try {
// Tell the resourceApi what thread the JS is running on.
CordovaResourceApi.jsThread = Thread.currentThread();
pluginManager.exec(service, action, callbackId, arguments);
String ret = null;
if (!NativeToJsMessageQueue.DISABLE_EXEC_CHAINING) {
ret = jsMessageQueue.popAndEncode(false);
}
return ret;
} catch (Throwable e) {
e.printStackTrace();
return "";
} finally {
jsMessageQueue.setPaused(false);
}
}
public void jsSetNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException {
if (!verifySecret("setNativeToJsBridgeMode()", bridgeSecret)) {
return;
}
jsMessageQueue.setBridgeMode(value);
}
public String jsRetrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException {
if (!verifySecret("retrieveJsMessages()", bridgeSecret)) {
return null;
}
return jsMessageQueue.popAndEncode(fromOnlineEvent);
}
private boolean verifySecret(String action, int bridgeSecret) throws IllegalAccessException {
if (!jsMessageQueue.isBridgeEnabled()) {
if (bridgeSecret == -1) {
Log.d(LOG_TAG, action + " call made before bridge was enabled.");
} else {
Log.d(LOG_TAG, "Ignoring " + action + " from previous page load.");
}
return false;
}
// Bridge secret wrong and bridge not due to it being from the previous page.
if (expectedBridgeSecret < 0 || bridgeSecret != expectedBridgeSecret) {
throw new IllegalAccessException();
}
return true;
}
/** Called on page transitions */
void clearBridgeSecret() {
expectedBridgeSecret = -1;
}
/** Called by cordova.js to initialize the bridge. */
int generateBridgeSecret() {
expectedBridgeSecret = (int)(Math.random() * Integer.MAX_VALUE);
return expectedBridgeSecret;
}
public void reset(String loadedUrl) {
jsMessageQueue.reset();
clearBridgeSecret();
this.loadedUrl = loadedUrl;
}
public String promptOnJsPrompt(String origin, String message, String defaultValue) {
if (defaultValue != null && defaultValue.length() > 3 && defaultValue.startsWith("gap:")) {
JSONArray array;
try {
array = new JSONArray(defaultValue.substring(4));
int bridgeSecret = array.getInt(0);
String service = array.getString(1);
String action = array.getString(2);
String callbackId = array.getString(3);
String r = jsExec(bridgeSecret, service, action, callbackId, message);
return r == null ? "" : r;
} catch (JSONException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return "";
}
// Sets the native->JS bridge mode.
else if (defaultValue != null && defaultValue.startsWith("gap_bridge_mode:")) {
try {
int bridgeSecret = Integer.parseInt(defaultValue.substring(16));
jsSetNativeToJsBridgeMode(bridgeSecret, Integer.parseInt(message));
} catch (NumberFormatException e){
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return "";
}
// Polling for JavaScript messages
else if (defaultValue != null && defaultValue.startsWith("gap_poll:")) {
int bridgeSecret = Integer.parseInt(defaultValue.substring(9));
try {
String r = jsRetrieveJsMessages(bridgeSecret, "1".equals(message));
return r == null ? "" : r;
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return "";
}
else if (defaultValue != null && defaultValue.startsWith("gap_init:")) {
// Protect against random iframes being able to talk through the bridge.
// Trust only file URLs and the start URL's domain.
// The extra origin.startsWith("http") is to protect against iframes with data: having "" as origin.
if (origin.startsWith("file:") || (origin.startsWith("http") && loadedUrl.startsWith(origin))) {
// Enable the bridge
int bridgeMode = Integer.parseInt(defaultValue.substring(9));
jsMessageQueue.setBridgeMode(bridgeMode);
// Tell JS the bridge secret.
int secret = generateBridgeSecret();
return ""+secret;
} else {
Log.e(LOG_TAG, "gap_init called from restricted origin: " + origin);
}
return "";
}
return null;
}
public NativeToJsMessageQueue getMessageQueue() {
return jsMessageQueue;
}
}

307
framework/src/org/apache/cordova/CordovaChromeClient.java Executable file → Normal file
View File

@@ -18,314 +18,15 @@
*/
package org.apache.cordova;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.LOG;
import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.webkit.ConsoleMessage;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebStorage;
import android.webkit.WebView;
import android.webkit.GeolocationPermissions.Callback;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
/**
* This class is the WebChromeClient that implements callbacks for our web view.
* The kind of callbacks that happen here are on the chrome outside the document,
* such as onCreateWindow(), onConsoleMessage(), onProgressChanged(), etc. Related
* to but different than CordovaWebViewClient.
*
* @see <a href="http://developer.android.com/reference/android/webkit/WebChromeClient.html">WebChromeClient</a>
* @see <a href="http://developer.android.com/guide/webapps/webview.html">WebView guide</a>
* @see CordovaWebViewClient
* @see CordovaWebView
*/
public class CordovaChromeClient extends WebChromeClient {
public interface CordovaChromeClient {
public static final int FILECHOOSER_RESULTCODE = 5173;
private String TAG = "CordovaLog";
private long MAX_QUOTA = 100 * 1024 * 1024;
protected CordovaInterface cordova;
protected CordovaWebView appView;
int FILECHOOSER_RESULTCODE = 0;
// the video progress view
private View mVideoProgressView;
// File Chooser
public ValueCallback<Uri> mUploadMessage;
@Deprecated
public CordovaChromeClient(CordovaInterface cordova) {
this.cordova = cordova;
}
void setWebView(CordovaWebView appView);
public CordovaChromeClient(CordovaInterface ctx, CordovaWebView app) {
this.cordova = ctx;
this.appView = app;
}
ValueCallback<Uri> getValueCallback();
@Deprecated
public void setWebView(CordovaWebView view) {
this.appView = view;
}
/**
* Tell the client to display a javascript alert dialog.
*
* @param view
* @param url
* @param message
* @param result
* @see Other implementation in the Dialogs plugin.
*/
@Override
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity());
dlg.setMessage(message);
dlg.setTitle("Alert");
//Don't let alerts break the back button
dlg.setCancelable(true);
dlg.setPositiveButton(android.R.string.ok,
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
result.confirm();
}
});
dlg.setOnCancelListener(
new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
result.cancel();
}
});
dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {
//DO NOTHING
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK)
{
result.confirm();
return false;
}
else
return true;
}
});
dlg.show();
return true;
}
/**
* Tell the client to display a confirm dialog to the user.
*
* @param view
* @param url
* @param message
* @param result
* @see Other implementation in the Dialogs plugin.
*/
@Override
public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity());
dlg.setMessage(message);
dlg.setTitle("Confirm");
dlg.setCancelable(true);
dlg.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
result.confirm();
}
});
dlg.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
result.cancel();
}
});
dlg.setOnCancelListener(
new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
result.cancel();
}
});
dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {
//DO NOTHING
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK)
{
result.cancel();
return false;
}
else
return true;
}
});
dlg.show();
return true;
}
/**
* Tell the client to display a prompt dialog to the user.
* If the client returns true, WebView will assume that the client will
* handle the prompt dialog and call the appropriate JsPromptResult method.
*
* Since we are hacking prompts for our own purposes, we should not be using them for
* this purpose, perhaps we should hack console.log to do this instead!
*
* @see Other implementation in the Dialogs plugin.
*/
@Override
public boolean onJsPrompt(WebView view, String origin, String message, String defaultValue, JsPromptResult result) {
// Unlike the @JavascriptInterface bridge, this method is always called on the UI thread.
String handledRet = appView.bridge.promptOnJsPrompt(origin, message, defaultValue);
if (handledRet != null) {
result.confirm(handledRet);
} else {
// Returning false would also show a dialog, but the default one shows the origin (ugly).
final JsPromptResult res = result;
AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity());
dlg.setMessage(message);
final EditText input = new EditText(this.cordova.getActivity());
if (defaultValue != null) {
input.setText(defaultValue);
}
dlg.setView(input);
dlg.setCancelable(false);
dlg.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
String usertext = input.getText().toString();
res.confirm(usertext);
}
});
dlg.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
res.cancel();
}
});
dlg.show();
}
return true;
}
/**
* Handle database quota exceeded notification.
*/
@Override
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);
quotaUpdater.updateQuota(MAX_QUOTA);
}
// console.log in api level 7: http://developer.android.com/guide/developing/debug-tasks.html
// Expect this to not compile in a future Android release!
@SuppressWarnings("deprecation")
@Override
public void onConsoleMessage(String message, int lineNumber, String sourceID)
{
//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);
super.onConsoleMessage(message, lineNumber, sourceID);
}
}
@TargetApi(8)
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage)
{
if (consoleMessage.message() != null)
LOG.d(TAG, "%s: Line %d : %s" , consoleMessage.sourceId() , consoleMessage.lineNumber(), consoleMessage.message());
return super.onConsoleMessage(consoleMessage);
}
@Override
/**
* Instructs the client to show a prompt to ask the user to set the Geolocation permission state for the specified origin.
*
* @param origin
* @param callback
*/
public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
super.onGeolocationPermissionsShowPrompt(origin, callback);
callback.invoke(origin, true, false);
}
// API level 7 is required for this, see if we could lower this using something else
@Override
public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
this.appView.showCustomView(view, callback);
}
@Override
public void onHideCustomView() {
this.appView.hideCustomView();
}
@Override
/**
* Ask the host application for a custom progress view to show while
* a <video> is loading.
* @return View The progress view.
*/
public View getVideoLoadingProgressView() {
if (mVideoProgressView == null) {
// Create a new Loading view programmatically.
// create the linear layout
LinearLayout layout = new LinearLayout(this.appView.getContext());
layout.setOrientation(LinearLayout.VERTICAL);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
layout.setLayoutParams(layoutParams);
// the proress bar
ProgressBar bar = new ProgressBar(this.appView.getContext());
LinearLayout.LayoutParams barLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
barLayoutParams.gravity = Gravity.CENTER;
bar.setLayoutParams(barLayoutParams);
layout.addView(bar);
mVideoProgressView = layout;
}
return mVideoProgressView;
}
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
this.openFileChooser(uploadMsg, "*/*");
}
public void openFileChooser( ValueCallback<Uri> uploadMsg, String acceptType ) {
this.openFileChooser(uploadMsg, acceptType, null);
}
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture)
{
mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("*/*");
this.cordova.getActivity().startActivityForResult(Intent.createChooser(i, "File Browser"),
FILECHOOSER_RESULTCODE);
}
public ValueCallback<Uri> getValueCallback() {
return this.mUploadMessage;
}
}

View File

@@ -32,39 +32,20 @@ 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 CordovaWebView webView; // WebView object
public CordovaInterface cordova;
protected CordovaPreferences preferences;
/**
* Call this after constructing to initialize the plugin.
* Final because we want to be able to change args without breaking plugins.
* @param cordova The context of the main Activity.
* @param webView The associated CordovaWebView.
*/
public final void privateInitialize(CordovaInterface cordova, CordovaWebView webView, CordovaPreferences preferences) {
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
assert this.cordova == null;
this.cordova = cordova;
this.webView = webView;
this.preferences = preferences;
initialize(cordova, webView);
pluginInitialize();
}
/**
* Called after plugin construction and fields have been initialized.
* Prefer to use pluginInitialize instead since there is no value in
* having parameters on the initialize() function.
*/
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
}
/**
* Called after plugin construction and fields have been initialized.
*/
protected void pluginInitialize() {
}
/**
* Executes the request.
*

View File

@@ -1,175 +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.HashMap;
import java.util.Locale;
import java.util.Map;
import org.apache.cordova.LOG;
import android.app.Activity;
import android.os.Bundle;
public class CordovaPreferences {
private HashMap<String, String> prefs = new HashMap<String, String>(20);
private Bundle preferencesBundleExtras;
public void setPreferencesBundle(Bundle extras) {
preferencesBundleExtras = extras;
}
public void set(String name, String value) {
prefs.put(name.toLowerCase(Locale.ENGLISH), value);
}
public void set(String name, boolean value) {
set(name, "" + value);
}
public void set(String name, int value) {
set(name, "" + value);
}
public void set(String name, double value) {
set(name, "" + value);
}
public Map<String, String> getAll() {
return prefs;
}
public boolean getBoolean(String name, boolean defaultValue) {
name = name.toLowerCase(Locale.ENGLISH);
String value = prefs.get(name);
if (value != null) {
return Boolean.parseBoolean(value);
} else if (preferencesBundleExtras != null) {
Object bundleValue = preferencesBundleExtras.get(name);
if (bundleValue instanceof String) {
return "true".equals(bundleValue);
}
// Gives a nice warning if type is wrong.
return preferencesBundleExtras.getBoolean(name, defaultValue);
}
return defaultValue;
}
public int getInteger(String name, int defaultValue) {
name = name.toLowerCase(Locale.ENGLISH);
String value = prefs.get(name);
if (value != null) {
// Use Integer.decode() can't handle it if the highest bit is set.
return (int)(long)Long.decode(value);
} else if (preferencesBundleExtras != null) {
Object bundleValue = preferencesBundleExtras.get(name);
if (bundleValue instanceof String) {
return Integer.valueOf((String)bundleValue);
}
// Gives a nice warning if type is wrong.
return preferencesBundleExtras.getInt(name, defaultValue);
}
return defaultValue;
}
public double getDouble(String name, double defaultValue) {
name = name.toLowerCase(Locale.ENGLISH);
String value = prefs.get(name);
if (value != null) {
return Double.valueOf(value);
} else if (preferencesBundleExtras != null) {
Object bundleValue = preferencesBundleExtras.get(name);
if (bundleValue instanceof String) {
return Double.valueOf((String)bundleValue);
}
// Gives a nice warning if type is wrong.
return preferencesBundleExtras.getDouble(name, defaultValue);
}
return defaultValue;
}
public String getString(String name, String defaultValue) {
name = name.toLowerCase(Locale.ENGLISH);
String value = prefs.get(name);
if (value != null) {
return value;
} else if (preferencesBundleExtras != null && !"errorurl".equals(name)) {
Object bundleValue = preferencesBundleExtras.get(name);
if (bundleValue != null) {
return bundleValue.toString();
}
}
return defaultValue;
}
// Plugins should not rely on values within the intent since this does not work
// for apps with multiple webviews. Instead, they should retrieve prefs from the
// Config object associated with their webview.
public void copyIntoIntentExtras(Activity action) {
for (String name : prefs.keySet()) {
String value = prefs.get(name);
if (value == null) {
continue;
}
if (name.equals("loglevel")) {
LOG.setLogLevel(value);
} else if (name.equals("splashscreen")) {
// Note: We should probably pass in the classname for the variable splash on splashscreen!
int resource = action.getResources().getIdentifier(value, "drawable", action.getClass().getPackage().getName());
action.getIntent().putExtra(name, resource);
}
else if(name.equals("backgroundcolor")) {
int asInt = (int)(long)Long.decode(value);
action.getIntent().putExtra(name, asInt);
}
else if(name.equals("loadurltimeoutvalue")) {
int asInt = Integer.decode(value);
action.getIntent().putExtra(name, asInt);
}
else if(name.equals("splashscreendelay")) {
int asInt = Integer.decode(value);
action.getIntent().putExtra(name, asInt);
}
else if(name.equals("keeprunning"))
{
boolean asBool = Boolean.parseBoolean(value);
action.getIntent().putExtra(name, asBool);
}
else if(name.equals("inappbrowserstorageenabled"))
{
boolean asBool = Boolean.parseBoolean(value);
action.getIntent().putExtra(name, asBool);
}
else if(name.equals("disallowoverscroll"))
{
boolean asBool = Boolean.parseBoolean(value);
action.getIntent().putExtra(name, asBool);
}
else
{
action.getIntent().putExtra(name, value);
}
}
// In the normal case, the intent extras are null until the first call to putExtra().
if (preferencesBundleExtras == null) {
preferencesBundleExtras = action.getIntent().getExtras();
}
}
}

View File

@@ -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;
@@ -106,7 +106,6 @@ public class CordovaResourceApi {
return threadCheckingEnabled;
}
public static int getUriType(Uri uri) {
assertNonRelative(uri);
String scheme = uri.getScheme();
@@ -200,8 +199,6 @@ public class CordovaResourceApi {
return null;
}
//This already exists
private String getMimeTypeFromPath(String path) {
String extension = path;
int lastDot = extension.lastIndexOf('.');
@@ -220,7 +217,7 @@ public class CordovaResourceApi {
}
/**
* Opens a stream to the given URI, also providing the MIME type & length.
* Opens a stream to the givne URI, also providing the MIME type & length.
* @return Never returns null.
* @throws Throws an InvalidArgumentException for relative URIs. Relative URIs should be
* resolved before being passed into this function.
@@ -232,7 +229,7 @@ public class CordovaResourceApi {
}
/**
* Opens a stream to the given URI, also providing the MIME type & length.
* Opens a stream to the givne URI, also providing the MIME type & length.
* @return Never returns null.
* @throws Throws an InvalidArgumentException for relative URIs. Relative URIs should be
* resolved before being passed into this function.

View File

@@ -1,86 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import android.annotation.TargetApi;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.webkit.WebView;
class CordovaUriHelper {
private static final String TAG = "CordovaUriHelper";
private CordovaWebView appView;
private CordovaInterface cordova;
CordovaUriHelper(CordovaInterface cdv, CordovaWebView webView)
{
appView = webView;
cordova = cdv;
}
/**
* Give the host application a chance to take over the control when a new url
* is about to be loaded in the current WebView.
*
* @param view The WebView that is initiating the callback.
* @param url The url to be loaded.
* @return true to override, false for default behavior
*/
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
boolean shouldOverrideUrlLoading(WebView view, String url) {
// Give plugins the chance to handle the url
if (this.appView.pluginManager.onOverrideUrlLoading(url)) {
// Do nothing other than what the plugins wanted.
// If any returned true, then the request was handled.
return true;
}
else if(url.startsWith("file://") | url.startsWith("data:"))
{
//This directory on WebKit/Blink based webviews contains SQLite databases!
//DON'T CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING!
return url.contains("app_webview");
}
else if (appView.getWhitelist().isUrlWhiteListed(url)) {
// Allow internal navigation
return false;
}
else if (appView.getExternalWhitelist().isUrlWhiteListed(url))
{
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
intent.addCategory(Intent.CATEGORY_BROWSABLE);
intent.setComponent(null);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
intent.setSelector(null);
}
this.cordova.getActivity().startActivity(intent);
return true;
} catch (android.content.ActivityNotFoundException e) {
LOG.e(TAG, "Error loading url " + url, e);
}
}
// Intercept the request and do nothing with it -- block it
return true;
}
}

1013
framework/src/org/apache/cordova/CordovaWebView.java Executable file → Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,365 +1,9 @@
/*
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.Hashtable;
public interface CordovaWebViewClient {
import org.apache.cordova.CordovaInterface;
void setWebView(CordovaWebView appView);
import org.apache.cordova.LOG;
import org.json.JSONException;
import org.json.JSONObject;
import android.annotation.TargetApi;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Bitmap;
import android.net.http.SslError;
import android.view.View;
import android.webkit.HttpAuthHandler;
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
* document instead of the chrome surrounding it, such as onPageStarted(),
* shouldOverrideUrlLoading(), etc. Related to but different than
* CordovaChromeClient.
*
* @see <a href="http://developer.android.com/reference/android/webkit/WebViewClient.html">WebViewClient</a>
* @see <a href="http://developer.android.com/guide/webapps/webview.html">WebView guide</a>
* @see CordovaChromeClient
* @see CordovaWebView
*/
public class CordovaWebViewClient extends WebViewClient {
private static final String TAG = "CordovaWebViewClient";
CordovaInterface cordova;
CordovaWebView appView;
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) {
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);
}
/**
* Give the host application a chance to take over the control when a new url
* is about to be loaded in the current WebView.
*
* @param view The WebView that is initiating the callback.
* @param url The url to be loaded.
* @return true to override, false for default behavior
*/
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return helper.shouldOverrideUrlLoading(view, 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) {
// Get the authentication token
AuthenticationToken token = this.getAuthenticationToken(host, realm);
if (token != null) {
handler.proceed(token.getUserName(), token.getPassword());
}
else {
// Handle 401 like we'd normally do!
super.onReceivedHttpAuthRequest(view, handler, host, realm);
}
}
/**
* Notify the host application that a page has started loading.
* This method is called once for each main frame load so a page with iframes or framesets will call onPageStarted
* one time for the main frame. This also means that onPageStarted will not be called when the contents of an
* embedded frame changes, i.e. clicking a link whose target is an iframe.
*
* @param view The webview initiating the callback.
* @param url The url of the page.
*/
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
isCurrentlyLoading = true;
LOG.d(TAG, "onPageStarted(" + url + ")");
// Flush stale messages.
this.appView.bridge.reset(url);
// 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();
}
}
/**
* Notify the host application that a page has finished loading.
* This method is called only for main frame. When onPageFinished() is called, the rendering picture may not be updated yet.
*
*
* @param view The webview initiating the callback.
* @param url The url of the page.
*/
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
// Ignore excessive calls.
if (!isCurrentlyLoading) {
return;
}
isCurrentlyLoading = false;
LOG.d(TAG, "onPageFinished(" + url + ")");
/**
* Because of a timing issue we need to clear this history in onPageFinished as well as
* onPageStarted. However we only want to do this if the doClearHistory boolean is set to
* true. You see when you load a url with a # in it which is common in jQuery applications
* onPageStared is not called. Clearing the history at that point would break jQuery apps.
*/
if (this.doClearHistory) {
view.clearHistory();
this.doClearHistory = false;
}
// Clear timeout flag
this.appView.loadUrlTimeout++;
// Broadcast message that page has loaded
this.appView.postMessage("onPageFinished", url);
// Make app visible after 2 sec in case there was a JS error and Cordova JS never initialized correctly
if (this.appView.getVisibility() == View.INVISIBLE) {
Thread t = new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(2000);
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
appView.postMessage("spinner", "stop");
}
});
} catch (InterruptedException e) {
}
}
});
t.start();
}
// Shutdown if blank loaded
if (url.equals("about:blank")) {
appView.postMessage("exit", null);
}
}
/**
* Report an error to the host application. These errors are unrecoverable (i.e. the main resource is unavailable).
* The errorCode parameter corresponds to one of the ERROR_* constants.
*
* @param view The WebView that is initiating the callback.
* @param errorCode The error code corresponding to an ERROR_* value.
* @param description A String describing the error.
* @param failingUrl The url that failed to load.
*/
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
// Ignore error due to stopLoading().
if (!isCurrentlyLoading) {
return;
}
LOG.d(TAG, "CordovaWebViewClient.onReceivedError: Error code=%s Description=%s URL=%s", errorCode, description, failingUrl);
// Clear timeout flag
this.appView.loadUrlTimeout++;
// If this is a "Protocol Not Supported" error, then revert to the previous
// page. If there was no previous page, then punt. The application's config
// is likely incorrect (start page set to sms: or something like that)
if (errorCode == WebViewClient.ERROR_UNSUPPORTED_SCHEME) {
if (view.canGoBack()) {
view.goBack();
return;
} else {
super.onReceivedError(view, errorCode, description, failingUrl);
}
}
// Handle other errors by passing them to the webview in JS
JSONObject data = new JSONObject();
try {
data.put("errorCode", errorCode);
data.put("description", description);
data.put("url", failingUrl);
} catch (JSONException e) {
e.printStackTrace();
}
this.appView.postMessage("onReceivedError", data);
}
/**
* Notify the host application that an SSL error occurred while loading a resource.
* The host application must call either handler.cancel() or handler.proceed().
* Note that the decision may be retained for use in response to future SSL errors.
* The default behavior is to cancel the load.
*
* @param view The WebView that is initiating the callback.
* @param handler An SslErrorHandler object that will handle the user's response.
* @param error The SSL error object.
*/
@TargetApi(8)
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
final String packageName = this.cordova.getActivity().getPackageName();
final PackageManager pm = this.cordova.getActivity().getPackageManager();
ApplicationInfo appInfo;
try {
appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
// debug = true
handler.proceed();
return;
} else {
// debug = false
super.onReceivedSslError(view, handler, error);
}
} catch (NameNotFoundException e) {
// When it doubt, lock it out!
super.onReceivedSslError(view, handler, error);
}
}
/**
* Sets the authentication token.
*
* @param authenticationToken
* @param host
* @param realm
*/
public void setAuthenticationToken(AuthenticationToken authenticationToken, String host, String realm) {
if (host == null) {
host = "";
}
if (realm == null) {
realm = "";
}
this.authenticationTokens.put(host.concat(realm), authenticationToken);
}
/**
* 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) {
return this.authenticationTokens.remove(host.concat(realm));
}
/**
* 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) {
AuthenticationToken token = null;
token = this.authenticationTokens.get(host.concat(realm));
if (token == null) {
// try with just the host
token = this.authenticationTokens.get(host);
// Try the realm
if (token == null) {
token = this.authenticationTokens.get(realm);
}
// if no host found, just query for default
if (token == null) {
token = this.authenticationTokens.get("");
}
}
return token;
}
/**
* Clear all authentication tokens.
*/
public void clearAuthenticationTokens() {
this.authenticationTokens.clear();
}
void onReceivedError(CordovaWebView me, int i, String string, String url);
}

53
framework/src/org/apache/cordova/ExposedJsApi.java Executable file → Normal file
View File

@@ -1,52 +1,17 @@
/*
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
import android.webkit.JavascriptInterface;
/*
* Any exposed Javascript API MUST implement these three things!
*/
/* package */ class ExposedJsApi {
private CordovaBridge bridge;
public ExposedJsApi(CordovaBridge bridge) {
this.bridge = bridge;
}
public interface ExposedJsApi {
@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 String exec(String service, String action, String callbackId, String arguments) throws JSONException;
public void setNativeToJsBridgeMode(int value);
public String retrieveJsMessages(boolean fromOnlineEvent);
}

View File

@@ -32,10 +32,9 @@ import android.webkit.WebResourceResponse;
import android.webkit.WebView;
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
public class IceCreamCordovaWebViewClient extends AndroidWebViewClient implements CordovaWebViewClient{
private static final String TAG = "IceCreamCordovaWebViewClient";
private CordovaUriHelper helper;
public IceCreamCordovaWebViewClient(CordovaInterface cordova) {
super(cordova);
@@ -48,9 +47,8 @@ public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
try {
// Check the against the whitelist and lock out access to the WebView directory
// Changing this will cause problems for your application
if (isUrlHarmful(url)) {
// Check the against the white-list.
if ((url.startsWith("http:") || url.startsWith("https:")) && !Config.isUrlWhiteListed(url)) {
LOG.w(TAG, "URL blocked by whitelist: " + url);
// Results in a 404.
return new WebResourceResponse("text/plain", "UTF-8", null);
@@ -76,11 +74,6 @@ public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
}
}
private boolean isUrlHarmful(String url) {
return ((url.startsWith("http:") || url.startsWith("https:")) && !appView.getWhitelist().isUrlWhiteListed(url))
|| url.contains("app_webview");
}
private static boolean needsKitKatContentUrlFix(Uri uri) {
return android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && "content".equals(uri.getScheme());
}
@@ -104,4 +97,10 @@ public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
}
return false;
}
@Override
public void onReceivedError(CordovaWebView me, int i, String string,
String url) {
super.onReceivedError(me, i, string, url);
}
}

View File

@@ -88,13 +88,13 @@ public class LinearLayoutSoftKeyboardDetect extends LinearLayout {
// gone away.
else if (height > oldHeight) {
if (app != null)
app.appView.sendJavascript("cordova.fireDocumentEvent('hidekeyboard');");
app.sendJavascript("cordova.fireDocumentEvent('hidekeyboard');");
}
// If the height as gotten smaller then we will assume the soft keyboard has
// been displayed.
else if (height < oldHeight) {
if (app != null)
app.appView.sendJavascript("cordova.fireDocumentEvent('showkeyboard');");
app.sendJavascript("cordova.fireDocumentEvent('showkeyboard');");
}
// Update the old height for the next event

View File

@@ -35,19 +35,31 @@ import android.webkit.WebView;
public class NativeToJsMessageQueue {
private static final String LOG_TAG = "JsMessageQueue";
// This must match the default value in cordova-js/lib/android/exec.js
private static final int DEFAULT_BRIDGE_MODE = 2;
// Set this to true to force plugin results to be encoding as
// JS instead of the custom format (useful for benchmarking).
private static final boolean FORCE_ENCODE_USING_EVAL = false;
// Disable URL-based exec() bridge by default since it's a bit of a
// security concern.
public static final boolean ENABLE_LOCATION_CHANGE_EXEC_MODE = false;
// Disable sending back native->JS messages during an exec() when the active
// exec() is asynchronous. Set this to true when running bridge benchmarks.
static final boolean DISABLE_EXEC_CHAINING = false;
public static final boolean DISABLE_EXEC_CHAINING = false;
// Arbitrarily chosen upper limit for how much data to send to JS in one shot.
// This currently only chops up on message boundaries. It may be useful
// to allow it to break up messages.
private static int MAX_PAYLOAD_SIZE = 50 * 1024 * 10240;
/**
* The index into registeredListeners to treat as active.
*/
private int activeListenerIndex;
/**
* When true, the active listener is not fired upon enqueue. When set to false,
* the active listener will be fired if the queue is non-empty.
@@ -64,13 +76,6 @@ public class NativeToJsMessageQueue {
*/
private final BridgeMode[] registeredListeners;
/**
* When null, the bridge is disabled. This occurs during page transitions.
* When disabled, all callbacks are dropped since they are assumed to be
* relevant to the previous page.
*/
private BridgeMode activeBridgeMode;
private final CordovaInterface cordova;
private final CordovaWebView webView;
@@ -84,28 +89,22 @@ public class NativeToJsMessageQueue {
registeredListeners[3] = new PrivateApiBridgeMode();
reset();
}
public boolean isBridgeEnabled() {
return activeBridgeMode != null;
}
/**
* Changes the bridge mode.
*/
public void setBridgeMode(int value) {
if (value < -1 || value >= registeredListeners.length) {
if (value < 0 || value >= registeredListeners.length) {
Log.d(LOG_TAG, "Invalid NativeToJsBridgeMode: " + value);
} else {
BridgeMode newMode = value < 0 ? null : registeredListeners[value];
if (newMode != activeBridgeMode) {
Log.d(LOG_TAG, "Set native->JS mode to " + (newMode == null ? "null" : newMode.getClass().getSimpleName()));
if (value != activeListenerIndex) {
Log.d(LOG_TAG, "Set native->JS mode to " + value);
synchronized (this) {
activeBridgeMode = newMode;
if (newMode != null) {
newMode.reset();
if (!paused && !queue.isEmpty()) {
newMode.onNativeToJsMessageAvailable();
}
activeListenerIndex = value;
BridgeMode activeListener = registeredListeners[value];
activeListener.reset();
if (!paused && !queue.isEmpty()) {
activeListener.onNativeToJsMessageAvailable();
}
}
}
@@ -118,7 +117,8 @@ public class NativeToJsMessageQueue {
public void reset() {
synchronized (this) {
queue.clear();
setBridgeMode(-1);
setBridgeMode(DEFAULT_BRIDGE_MODE);
registeredListeners[activeListenerIndex].reset();
}
}
@@ -142,10 +142,7 @@ public class NativeToJsMessageQueue {
*/
public String popAndEncode(boolean fromOnlineEvent) {
synchronized (this) {
if (activeBridgeMode == null) {
return null;
}
activeBridgeMode.notifyOfFlush(fromOnlineEvent);
registeredListeners[activeListenerIndex].notifyOfFlush(fromOnlineEvent);
if (queue.isEmpty()) {
return null;
}
@@ -250,20 +247,16 @@ public class NativeToJsMessageQueue {
enqueueMessage(message);
}
private void enqueueMessage(JsMessage message) {
synchronized (this) {
if (activeBridgeMode == null) {
Log.d(LOG_TAG, "Dropping Native->JS message due to disabled bridge");
return;
}
queue.add(message);
if (!paused) {
activeBridgeMode.onNativeToJsMessageAvailable();
registeredListeners[activeListenerIndex].onNativeToJsMessageAvailable();
}
}
}
}
public void setPaused(boolean value) {
if (paused && value) {
// This should never happen. If a use-case for it comes up, we should
@@ -273,12 +266,16 @@ public class NativeToJsMessageQueue {
paused = value;
if (!value) {
synchronized (this) {
if (!queue.isEmpty() && activeBridgeMode != null) {
activeBridgeMode.onNativeToJsMessageAvailable();
if (!queue.isEmpty()) {
registeredListeners[activeListenerIndex].onNativeToJsMessageAvailable();
}
}
}
}
public boolean getPaused() {
return paused;
}
private abstract class BridgeMode {
abstract void onNativeToJsMessageAvailable();
@@ -311,33 +308,23 @@ public class NativeToJsMessageQueue {
/** Uses online/offline events to tell the JS when to poll for messages. */
private class OnlineEventsBridgeMode extends BridgeMode {
private boolean online;
private boolean ignoreNextFlush;
final Runnable toggleNetworkRunnable = new Runnable() {
final Runnable runnable = new Runnable() {
public void run() {
if (!queue.isEmpty()) {
ignoreNextFlush = false;
webView.setNetworkAvailable(online);
}
}
};
final Runnable resetNetworkRunnable = new Runnable() {
public void run() {
online = false;
// If the following call triggers a notifyOfFlush, then ignore it.
ignoreNextFlush = true;
webView.setNetworkAvailable(true);
}
}
};
@Override void reset() {
cordova.getActivity().runOnUiThread(resetNetworkRunnable);
online = false;
webView.setNetworkAvailable(true);
}
@Override void onNativeToJsMessageAvailable() {
cordova.getActivity().runOnUiThread(toggleNetworkRunnable);
cordova.getActivity().runOnUiThread(runnable);
}
// Track when online/offline events are fired so that we don't fire excess events.
@Override void notifyOfFlush(boolean fromOnlineEvent) {
if (fromOnlineEvent && !ignoreNextFlush) {
if (fromOnlineEvent) {
online = !online;
}
}

View File

@@ -18,10 +18,13 @@
*/
package org.apache.cordova;
import java.util.List;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
//import android.content.Context;
//import android.webkit.WebView;
/**
* This class represents a service entry object.
*/
@@ -30,60 +33,100 @@ public class PluginEntry {
/**
* The name of the service that this plugin implements
*/
public String service;
public String service = "";
/**
* The plugin class name that implements the service.
*/
public String pluginClass;
public String pluginClass = "";
/**
* The pre-instantiated plugin to use for this entry.
* The plugin object.
* Plugin objects are only created when they are called from JavaScript. (see PluginManager.exec)
* The exception is if the onload flag is set, then they are created when PluginManager is initialized.
*/
public CordovaPlugin plugin;
public CordovaPlugin plugin = null;
/**
* Flag that indicates the plugin object should be created when PluginManager is initialized.
*/
public boolean onload;
private List<String> urlFilters;
/**
* Constructs with a CordovaPlugin already instantiated.
*/
public PluginEntry(String service, CordovaPlugin plugin) {
this(service, plugin.getClass().getName(), true, plugin, null);
}
public boolean onload = false;
/**
* Constructor
*
* @param service The name of the service
* @param pluginClass The plugin class name
* @param onload Create plugin object when HTML page is loaded
*/
public PluginEntry(String service, String pluginClass, boolean onload) {
this(service, pluginClass, onload, null, 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;
}
private PluginEntry(String service, String pluginClass, boolean onload, CordovaPlugin plugin, List<String> urlFilters) {
/**
* Alternate constructor
*
* @param service The name of the service
* @param plugin The plugin associated with this entry
*/
public PluginEntry(String service, CordovaPlugin plugin) {
this.service = service;
this.pluginClass = pluginClass;
this.onload = onload;
this.urlFilters = urlFilters;
this.plugin = plugin;
this.pluginClass = plugin.getClass().getName();
this.onload = false;
}
public List<String> getUrlFilters() {
return urlFilters;
/**
* Create plugin object.
* If plugin is already created, then just return it.
*
* @return The plugin object
*/
public CordovaPlugin createPlugin(CordovaWebView webView, CordovaInterface ctx) {
if (this.plugin != null) {
return this.plugin;
}
try {
@SuppressWarnings("rawtypes")
Class c = getClassByName(this.pluginClass);
if (isCordovaPlugin(c)) {
this.plugin = (CordovaPlugin) c.newInstance();
this.plugin.initialize(ctx, webView);
return plugin;
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("Error adding plugin " + this.pluginClass + ".");
}
return null;
}
/**
* Get the class.
*
* @param clazz
* @return a reference to the named class
* @throws ClassNotFoundException
*/
@SuppressWarnings("rawtypes")
private Class getClassByName(final String clazz) throws ClassNotFoundException {
Class c = null;
if ((clazz != null) && !("".equals(clazz))) {
c = Class.forName(clazz);
}
return c;
}
/**
* Returns whether the given class extends CordovaPlugin.
*/
@SuppressWarnings("rawtypes")
private boolean isCordovaPlugin(Class c) {
if (c != null) {
return org.apache.cordova.CordovaPlugin.class.isAssignableFrom(c);
}
return false;
}
}

View File

@@ -18,9 +18,14 @@
*/
package org.apache.cordova;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.cordova.CordovaArgs;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
@@ -28,8 +33,11 @@ import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.PluginEntry;
import org.apache.cordova.PluginResult;
import org.json.JSONException;
import org.xmlpull.v1.XmlPullParserException;
import android.content.Intent;
import android.content.res.XmlResourceParser;
import android.net.Uri;
import android.os.Debug;
import android.util.Log;
@@ -45,40 +53,31 @@ public class PluginManager {
private static final int SLOW_EXEC_WARNING_THRESHOLD = Debug.isDebuggerConnected() ? 60 : 16;
// List of service entries
private final HashMap<String, CordovaPlugin> pluginMap = new HashMap<String, CordovaPlugin>();
private final HashMap<String, PluginEntry> entryMap = new HashMap<String, PluginEntry>();
private final HashMap<String, PluginEntry> entries = new HashMap<String, PluginEntry>();
private final CordovaInterface ctx;
private final CordovaWebView app;
// Flag to track first time through
private boolean firstRun;
// 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);
}
private AtomicInteger numPendingUiExecs;
PluginManager(CordovaWebView cordovaWebView, CordovaInterface cordova, List<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) {
this.onPause(false);
this.onDestroy();
pluginMap.clear();
urlMap.clear();
for (PluginEntry entry : pluginEntries) {
addService(entry);
}
/**
* Constructor.
*
* @param app
* @param ctx
*/
public PluginManager(CordovaWebView app, CordovaInterface ctx) {
this.ctx = ctx;
this.app = app;
this.firstRun = true;
this.numPendingUiExecs = new AtomicInteger(0);
}
/**
@@ -86,32 +85,114 @@ public class PluginManager {
*/
public void init() {
LOG.d(TAG, "init()");
this.onPause(false);
this.onDestroy();
pluginMap.clear();
// If first time, then load plugins from config.xml file
if (this.firstRun) {
this.loadPlugins();
this.firstRun = false;
}
// Stop plugins on current HTML page and discard plugin objects
else {
this.onPause(false);
this.onDestroy();
this.clearPluginObjects();
}
// Insert PluginManager service
this.addService(new PluginEntry("PluginManager", new PluginManagerService()));
// Start up all plugins that have onload specified
this.startupPlugins();
}
@Deprecated
/**
* Load plugins from res/xml/config.xml
*/
public void loadPlugins() {
// First checking the class namespace for config.xml
int id = this.ctx.getActivity().getResources().getIdentifier("config", "xml", this.ctx.getActivity().getClass().getPackage().getName());
if (id == 0) {
// If we couldn't find config.xml there, we'll look in the namespace from AndroidManifest.xml
id = this.ctx.getActivity().getResources().getIdentifier("config", "xml", this.ctx.getActivity().getPackageName());
if (id == 0) {
this.pluginConfigurationMissing();
//We have the error, we need to exit without crashing!
return;
}
}
XmlResourceParser xml = this.ctx.getActivity().getResources().getXml(id);
int eventType = -1;
String service = "", pluginClass = "", paramType = "";
boolean onload = false;
boolean insideFeature = false;
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.get(service) == null) {
urlMap.put(service, new ArrayList<String>(2));
}
List<String> filters = urlMap.get(service);
filters.add(xml.getAttributeValue(null, "value"));
}
else if (strNode.equals("feature")) {
//Check for supported feature sets aka. plugins (Accelerometer, Geolocation, etc)
//Set the bit for reading params
insideFeature = true;
service = xml.getAttributeValue(null, "name");
}
else if (insideFeature && strNode.equals("param")) {
paramType = xml.getAttributeValue(null, "name");
if (paramType.equals("service")) // check if it is using the older service param
service = xml.getAttributeValue(null, "value");
else if (paramType.equals("package") || paramType.equals("android-package"))
pluginClass = xml.getAttributeValue(null,"value");
else if (paramType.equals("onload"))
onload = "true".equals(xml.getAttributeValue(null, "value"));
}
}
else if (eventType == XmlResourceParser.END_TAG)
{
String strNode = xml.getName();
if (strNode.equals("feature") || strNode.equals("plugin"))
{
PluginEntry entry = new PluginEntry(service, pluginClass, onload);
this.addService(entry);
//Empty the strings to prevent plugin loading bugs
service = "";
pluginClass = "";
insideFeature = false;
}
}
try {
eventType = xml.next();
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* Delete all plugin objects.
*/
@Deprecated // Should not be exposed as public.
public void clearPluginObjects() {
pluginMap.clear();
for (PluginEntry entry : this.entries.values()) {
entry.plugin = null;
}
}
/**
* Create plugins objects that have onload set.
*/
@Deprecated // Should not be exposed as public.
public void startupPlugins() {
for (PluginEntry entry : entryMap.values()) {
for (PluginEntry entry : this.entries.values()) {
if (entry.onload) {
getPlugin(entry.service);
entry.createPlugin(this.app, this.ctx);
}
}
}
@@ -134,6 +215,20 @@ public class PluginManager {
* plugin execute method.
*/
public void exec(final String service, final String action, final String callbackId, final String rawArgs) {
if (numPendingUiExecs.get() > 0) {
numPendingUiExecs.getAndIncrement();
this.ctx.getActivity().runOnUiThread(new Runnable() {
public void run() {
execHelper(service, action, callbackId, rawArgs);
numPendingUiExecs.getAndDecrement();
}
});
} else {
execHelper(service, action, callbackId, rawArgs);
}
}
private void execHelper(final String service, final String action, final String callbackId, final String rawArgs) {
CordovaPlugin plugin = getPlugin(service);
if (plugin == null) {
Log.d(TAG, "exec() call to unknown plugin: " + service);
@@ -177,21 +272,15 @@ public class PluginManager {
* @return CordovaPlugin or null
*/
public CordovaPlugin getPlugin(String service) {
CordovaPlugin ret = pluginMap.get(service);
if (ret == null) {
PluginEntry pe = entryMap.get(service);
if (pe == null) {
return null;
}
if (pe.plugin != null) {
ret = pe.plugin;
} else {
ret = instantiatePlugin(pe.pluginClass);
}
ret.privateInitialize(ctx, app, app.getPreferences());
pluginMap.put(service, ret);
PluginEntry entry = this.entries.get(service);
if (entry == null) {
return null;
}
return ret;
CordovaPlugin plugin = entry.plugin;
if (plugin == null) {
plugin = entry.createPlugin(this.app, this.ctx);
}
return plugin;
}
/**
@@ -213,16 +302,7 @@ public class PluginManager {
* @param entry The plugin entry
*/
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);
}
this.entries.put(entry.service, entry);
}
/**
@@ -231,8 +311,10 @@ public class PluginManager {
* @param multitasking Flag indicating if multitasking is turned on for app
*/
public void onPause(boolean multitasking) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
plugin.onPause(multitasking);
for (PluginEntry entry : this.entries.values()) {
if (entry.plugin != null) {
entry.plugin.onPause(multitasking);
}
}
}
@@ -242,8 +324,10 @@ public class PluginManager {
* @param multitasking Flag indicating if multitasking is turned on for app
*/
public void onResume(boolean multitasking) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
plugin.onResume(multitasking);
for (PluginEntry entry : this.entries.values()) {
if (entry.plugin != null) {
entry.plugin.onResume(multitasking);
}
}
}
@@ -251,8 +335,10 @@ public class PluginManager {
* The final call you receive before your activity is destroyed.
*/
public void onDestroy() {
for (CordovaPlugin plugin : this.pluginMap.values()) {
plugin.onDestroy();
for (PluginEntry entry : this.entries.values()) {
if (entry.plugin != null) {
entry.plugin.onDestroy();
}
}
}
@@ -268,10 +354,12 @@ public class PluginManager {
if (obj != null) {
return obj;
}
for (CordovaPlugin plugin : this.pluginMap.values()) {
obj = plugin.onMessage(id, data);
if (obj != null) {
return obj;
for (PluginEntry entry : this.entries.values()) {
if (entry.plugin != null) {
obj = entry.plugin.onMessage(id, data);
if (obj != null) {
return obj;
}
}
}
return null;
@@ -281,8 +369,10 @@ public class PluginManager {
* Called when the activity receives a new intent.
*/
public void onNewIntent(Intent intent) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
plugin.onNewIntent(intent);
for (PluginEntry entry : this.entries.values()) {
if (entry.plugin != null) {
entry.plugin.onNewIntent(intent);
}
}
}
@@ -297,7 +387,7 @@ public class PluginManager {
// Instead, plugins should not include <url-filter> and instead ensure
// that they are loaded before this function is called (either by setting
// the onload <param> or by making an exec() call to them)
for (PluginEntry entry : this.entryMap.values()) {
for (PluginEntry entry : this.entries.values()) {
List<String> urlFilters = urlMap.get(entry.service);
if (urlFilters != null) {
for (String s : urlFilters) {
@@ -305,9 +395,8 @@ public class PluginManager {
return getPlugin(entry.service).onOverrideUrlLoading(url);
}
}
} else {
CordovaPlugin plugin = pluginMap.get(entry.service);
if (plugin != null && plugin.onOverrideUrlLoading(url)) {
} else if (entry.plugin != null) {
if (entry.plugin.onOverrideUrlLoading(url)) {
return true;
}
}
@@ -319,38 +408,54 @@ public class PluginManager {
* Called when the app navigates or refreshes.
*/
public void onReset() {
for (CordovaPlugin plugin : this.pluginMap.values()) {
plugin.onReset();
Iterator<PluginEntry> it = this.entries.values().iterator();
while (it.hasNext()) {
CordovaPlugin plugin = it.next().plugin;
if (plugin != null) {
plugin.onReset();
}
}
}
private void pluginConfigurationMissing() {
LOG.e(TAG, "=====================================================================================");
LOG.e(TAG, "ERROR: config.xml is missing. Add res/xml/config.xml to your project.");
LOG.e(TAG, "https://git-wip-us.apache.org/repos/asf?p=cordova-android.git;a=blob;f=framework/res/xml/config.xml");
LOG.e(TAG, "=====================================================================================");
}
Uri remapUri(Uri uri) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
Uri ret = plugin.remapUri(uri);
if (ret != null) {
return ret;
for (PluginEntry entry : this.entries.values()) {
if (entry.plugin != null) {
Uri ret = entry.plugin.remapUri(uri);
if (ret != null) {
return ret;
}
}
}
return null;
}
/**
* Create a plugin based on class name.
*/
private CordovaPlugin instantiatePlugin(String className) {
CordovaPlugin ret = null;
try {
Class<?> c = null;
if ((className != null) && !("".equals(className))) {
c = Class.forName(className);
private class PluginManagerService extends CordovaPlugin {
@Override
public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext) throws JSONException {
if ("startup".equals(action)) {
// The onPageStarted event of CordovaWebViewClient resets the queue of messages to be returned to javascript in response
// to exec calls. Since this event occurs on the UI thread and exec calls happen on the WebCore thread it is possible
// that onPageStarted occurs after exec calls have started happening on a new page, which can cause the message queue
// to be reset between the queuing of a new message and its retrieval by javascript. To avoid this from happening,
// javascript always sends a "startup" exec upon loading a new page which causes all future exec calls to happen on the UI
// thread (and hence after onPageStarted) until there are no more pending exec calls remaining.
numPendingUiExecs.getAndIncrement();
ctx.getActivity().runOnUiThread(new Runnable() {
public void run() {
numPendingUiExecs.getAndDecrement();
}
});
return true;
}
if (c != null & CordovaPlugin.class.isAssignableFrom(c)) {
ret = (CordovaPlugin) c.newInstance();
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("Error adding plugin " + className + ".");
return false;
}
return ret;
}
}

View File

@@ -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;

View File

@@ -120,15 +120,15 @@ public class Whitelist {
whiteList = null;
}
else { // specific access
Pattern parts = Pattern.compile("^((\\*|[A-Za-z-]+):(//)?)?(\\*|((\\*\\.)?[^*/:]+))?(:(\\d+))?(/.*)?");
Pattern parts = Pattern.compile("^((\\*|[A-Za-z-]+)://)?(\\*|((\\*\\.)?[^*/:]+))?(:(\\d+))?(/.*)?");
Matcher m = parts.matcher(origin);
if (m.matches()) {
String scheme = m.group(2);
String host = m.group(4);
String host = m.group(3);
// Special case for two urls which are allowed to have empty hosts
if (("file".equals(scheme) || "content".equals(scheme)) && host == null) host = "*";
String port = m.group(8);
String path = m.group(9);
String port = m.group(7);
String path = m.group(8);
if (scheme == null) {
// XXX making it stupid friendly for people who forget to include protocol/SSL
whiteList.add(new URLPattern("http", host, port, path));

View File

@@ -1,28 +1,21 @@
{
"name": "cordova-android",
"version": "3.6.4",
"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": "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"
],
"author": "Apache Software Foundation",
"license": "Apache version 2.0",
"dependencies": {
"q": "^0.9.0",
"shelljs": "^0.2.6"
}
}

View File

@@ -1,82 +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.
*/
/* jshint laxcomma:true */
require("promise-matchers");
var create = require("../bin/lib/create");
describe("create", function () {
describe("validatePackageName", function() {
var valid = [
"org.apache.mobilespec"
, "com.example"
, "com.42floors.package"
];
var invalid = [
""
, "com.class.is.bad"
, "0com.example.mobilespec"
, "c-m.e@a!p%e.mobilespec"
, "notenoughdots"
, ".starts.with.a.dot"
, "ends.with.a.dot."
, "_underscore.anything"
, "underscore._something"
, "_underscore._all._the._things"
];
valid.forEach(function(package_name) {
it("should accept " + package_name, function(done) {
expect(create.validatePackageName(package_name)).toHaveBeenResolved(done);
});
});
invalid.forEach(function(package_name) {
it("should reject " + package_name, function(done) {
expect(create.validatePackageName(package_name)).toHaveBeenRejected(done);
});
});
});
describe("validateProjectName", function() {
var valid = [
"mobilespec"
, "package_name"
, "PackageName"
, "CordovaLib"
];
var invalid = [
""
, "0startswithdigit"
, "CordovaActivity"
];
valid.forEach(function(project_name) {
it("should accept " + project_name, function(done) {
expect(create.validateProjectName(project_name)).toHaveBeenResolved(done);
});
});
invalid.forEach(function(project_name) {
it("should reject " + project_name, function(done) {
expect(create.validateProjectName(project_name)).toHaveBeenRejected(done);
});
});
});
});

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
<classpathentry kind="output" path="bin/classes"/>
</classpath>

View File

@@ -45,7 +45,7 @@
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-sdk android:minSdkVersion="10" android:targetSdkVersion="19"/>
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="19"/>
<instrumentation
android:name="android.test.InstrumentationTestRunner"
@@ -99,7 +99,7 @@
android:windowSoftInputMode="adjustPan"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden"
android:name="org.apache.cordova.test.MainTestActivity" >
android:name="org.apache.cordova.test.CordovaActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.SAMPLE_CODE" />
@@ -255,15 +255,5 @@
<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>

View File

@@ -26,10 +26,8 @@ These tests are designed to verify Android native features and other Android spe
There really isn't any manual setup to do. The ant script takes care of that.
You don't even need to compile cordova-x.y.z.jar or copy it, because
project.properties has a library reference to ../framework. However, Robotium
has to be installed for the onScrollChanged tests to work correctly. It can be
found at https://code.google.com/p/robotium/ and the jar should be put in the
'libs' directory'.
project.properties has a library reference to ../framework. However, Robotium has to be
installed for the onScrollChanged tests to work correctly. It can be found at https://code.google.com/p/robotium/
To run manually from command line:

View File

@@ -1,22 +1,3 @@
/*
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.
*/
cordova.define('cordova/plugin_list', function(require, exports, module) {
module.exports = [];
});
module.exports = []
});

View File

@@ -14,25 +14,6 @@
"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.$
-->
<!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"> &nbsp;</span></h4>
<h4>Deviceready: <span id="deviceready"> &nbsp;</span></h4>
</div>
<div id="info">
This is an expected error page because the initial href doesn't exist.
</body>
</html>
This is an error page.

View File

@@ -36,7 +36,7 @@
</head>
<body onload="init();" id="stage" class="theme">
<h1>Cordova Android Native Tests</h1>
<h1>Cordova Android Tests</h1>
<div id="info">
<h4>Cordova: <span id="cordova"> &nbsp;</span></h4>
<h4>Deviceready: <span id="deviceready"> &nbsp;</span></h4>

View File

@@ -33,7 +33,7 @@
<h4>Deviceready: <span id="deviceready"> &nbsp;</span></h4>
</div>
<div id="info">
<h4>The options menu items should be:</h4>
<h4>The menu items should be:</h4>
<li>Item1<br>
<li>Item2<br>
<li>Item3<br>

View File

@@ -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" />

View File

@@ -18,5 +18,5 @@
under the License.
-->
<resources>
<string name="app_name">CordovaNativeTests</string>
<string name="app_name">CordovaTests</string>
</resources>

View File

@@ -1,22 +1,4 @@
<?xml version='1.0' encoding='utf-8'?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<widget id="io.cordova.helloCordova" version="2.0.0" xmlns="http://www.w3.org/ns/widgets">
<name>Hello Cordova</name>
<description>
@@ -26,19 +8,17 @@
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" />
<preference name="exit-on-suspend" value="false" />
<preference name="showTitle" value="true" />
<feature name="Activity">
<param name="android-package" value="org.apache.cordova.test.ActivityPlugin" />
</feature>
<feature name="PluginStub">
<param name="android-package" value="org.apache.cordova.pluginApi.pluginStub" />
</feature>
<feature name="App">
<param name="android-package" value="org.apache.cordova.App" />
</feature>
</widget>

View File

@@ -18,11 +18,12 @@
*/
package org.apache.cordova.test;
import org.apache.cordova.CordovaActivity;
import org.apache.cordova.DroidGap;
import android.app.Activity;
import android.os.Bundle;
public class MainTestActivity extends CordovaActivity {
public class CordovaActivity extends DroidGap {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {

View File

@@ -22,13 +22,13 @@ package org.apache.cordova.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
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.LOG;
import org.apache.cordova.test.R;
import org.apache.cordova.test.R.id;
import org.apache.cordova.test.R.layout;
import android.app.Activity;
import android.content.Context;
@@ -47,12 +47,7 @@ public class CordovaWebViewTestActivity extends Activity implements CordovaInter
setContentView(R.layout.main);
//CB-7238: This has to be added now, because it got removed from somewhere else
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.getExternalWhitelist(), Config.getPreferences());
cordovaWebView.loadUrl("file:///android_asset/www/index.html");
@@ -105,4 +100,4 @@ public class CordovaWebViewTestActivity extends Activity implements CordovaInter
cordovaWebView.handleDestroy();
}
}
}
}

View File

@@ -1,92 +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 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);
}
}
}

View File

@@ -21,7 +21,7 @@ package org.apache.cordova.test;
import android.os.Bundle;
import org.apache.cordova.*;
public class backbuttonmultipage extends CordovaActivity {
public class backbuttonmultipage extends DroidGap {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

View File

@@ -22,12 +22,12 @@ import android.os.Bundle;
import org.apache.cordova.*;
public class background extends CordovaActivity {
public class background extends DroidGap {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//super.init(new FixWebView(this), new CordovaWebViewClient(this), new CordovaChromeClient(this));
preferences.set("keepRunning", false);
super.setBooleanProperty("keepRunning", false);
super.loadUrl("file:///android_asset/www/background/index.html");
}
}

View File

@@ -22,15 +22,18 @@ import android.graphics.Color;
import android.os.Bundle;
import org.apache.cordova.*;
public class backgroundcolor extends CordovaActivity {
public class backgroundcolor extends DroidGap {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Properties must be set before init() is called, since some are processed during init().
// backgroundColor can also be set in cordova.xml, but you must use the number equivalent of the color. For example, Color.RED is
// <preference name="backgroundColor" value="-65536" />
preferences.set("backgroundColor", Color.GREEN);
super.setIntegerProperty("backgroundColor", Color.GREEN);
super.init();
super.loadUrl("file:///android_asset/www/backgroundcolor/index.html");
}

View File

@@ -1,46 +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 CordovaActivity {
@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");
// classic webview includes port in hostname, Chromium webview does not. Handle both here.
// BTW, the realm is optional.
setAuthenticationToken(token, "browserspy.dk:80", "BrowserSpy.dk - HTTP Password Test");
setAuthenticationToken(token, "browserspy.dk", "BrowserSpy.dk - HTTP Password Test");
// Add web site to whitelist
Config.getWhitelist().addWhiteListEntry("http://browserspy.dk/*", true);
// Load test
super.loadUrl("file:///android_asset/www/basicauth/index.html");
}
}

View File

@@ -21,11 +21,12 @@ package org.apache.cordova.test;
import android.os.Bundle;
import org.apache.cordova.*;
public class errorurl extends CordovaActivity {
public class errorurl extends DroidGap {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
preferences.set("errorUrl", "file:///android_asset/www/htmlnotfound/error.html");
super.init();
this.setStringProperty("errorUrl", "file:///android_asset/www/htmlnotfound/error.html");
super.loadUrl("file:///android_asset/www/htmlnotfound/index.html");
}

View File

@@ -21,7 +21,7 @@ package org.apache.cordova.test;
import android.os.Bundle;
import org.apache.cordova.*;
public class fullscreen extends CordovaActivity {
public class fullscreen extends DroidGap {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -30,7 +30,7 @@ public class fullscreen extends CordovaActivity {
// fullscreen can also be set in cordova.xml. For example,
// <preference name="fullscreen" value="true" />
preferences.set("fullscreen", true);
super.setBooleanProperty("fullscreen", true);
super.init();
super.loadUrl("file:///android_asset/www/fullscreen/index.html");

View File

@@ -21,7 +21,7 @@ package org.apache.cordova.test;
import android.os.Bundle;
import org.apache.cordova.*;
public class htmlnotfound extends CordovaActivity {
public class htmlnotfound extends DroidGap {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

View File

@@ -21,7 +21,7 @@ package org.apache.cordova.test;
import android.os.Bundle;
import org.apache.cordova.*;
public class iframe extends CordovaActivity {
public class iframe extends DroidGap {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

View File

@@ -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;
@@ -75,7 +76,7 @@ public class BackButtonMultiPageTest extends ActivityInstrumentationTestCase2<ba
public void run()
{
String url = testView.getUrl();
assertEquals("file:///android_asset/www/backbuttonmultipage/sample2.html", url);
assertTrue(url.endsWith("sample2.html"));
testView.sendJavascript("window.location = 'sample3.html';"); }
});
@@ -84,8 +85,8 @@ public class BackButtonMultiPageTest extends ActivityInstrumentationTestCase2<ba
public void run()
{
String url = testView.getUrl();
assertEquals("file:///android_asset/www/backbuttonmultipage/sample3.html", url);
assertTrue(testView.backHistory());
assertTrue(url.endsWith("sample3.html"));
testView.backHistory();
}
});
sleep();
@@ -93,8 +94,8 @@ public class BackButtonMultiPageTest extends ActivityInstrumentationTestCase2<ba
public void run()
{
String url = testView.getUrl();
assertEquals("file:///android_asset/www/backbuttonmultipage/sample2.html", url);
assertTrue(testView.backHistory());
assertTrue(url.endsWith("sample2.html"));
testView.backHistory();
}
});
sleep();
@@ -102,7 +103,7 @@ public class BackButtonMultiPageTest extends ActivityInstrumentationTestCase2<ba
public void run()
{
String url = testView.getUrl();
assertEquals("file:///android_asset/www/backbuttonmultipage/index.html", url);
assertTrue(url.endsWith("index.html"));
}
});
}
@@ -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);

View File

@@ -21,16 +21,16 @@ package org.apache.cordova.test.junit;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.PluginManager;
import org.apache.cordova.test.MainTestActivity;
import org.apache.cordova.test.CordovaActivity;
import android.app.Instrumentation;
import android.test.ActivityInstrumentationTestCase2;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
public class CordovaActivityTest extends ActivityInstrumentationTestCase2<MainTestActivity> {
public class CordovaActivityTest extends ActivityInstrumentationTestCase2<CordovaActivity> {
private MainTestActivity testActivity;
private CordovaActivity testActivity;
private FrameLayout containerView;
private LinearLayout innerContainer;
private CordovaWebView testView;
@@ -40,7 +40,7 @@ public class CordovaActivityTest extends ActivityInstrumentationTestCase2<MainTe
@SuppressWarnings("deprecation")
public CordovaActivityTest()
{
super("org.apache.cordova.test",MainTestActivity.class);
super("org.apache.cordova.test",CordovaActivity.class);
}
protected void setUp() throws Exception {
@@ -68,7 +68,33 @@ public class CordovaActivityTest extends ActivityInstrumentationTestCase2<MainTe
String className = innerContainer.getClass().getSimpleName();
assertTrue(className.equals("LinearLayoutSoftKeyboardDetect"));
}
public void testPauseAndResume() throws Throwable
{
runTestOnUiThread(new Runnable() {
public void run()
{
mInstr.callActivityOnPause(testActivity);
}
});
sleep();
runTestOnUiThread(new Runnable() {
public void run()
{
assertTrue(testView.isPaused());
mInstr.callActivityOnResume(testActivity);
}
});
sleep();
runTestOnUiThread(new Runnable() {
public void run()
{
assertFalse(testView.isPaused());
}
});
}
private void sleep() {
try {
Thread.sleep(TIMEOUT);

View File

@@ -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")) {

View File

@@ -63,7 +63,7 @@ public class ErrorUrlTest extends ActivityInstrumentationTestCase2<errorurl> {
String good_url = "file:///android_asset/www/htmlnotfound/error.html";
String url = testView.getUrl();
assertNotNull(url);
assertEquals(good_url, url);
assertTrue(url.equals(good_url));
}
});

View File

@@ -0,0 +1,68 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova.test.junit;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CordovaChromeClient;
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;
private CordovaChromeClient appCode;
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 testForCordovaView() {
String className = testView.getClass().getSimpleName();
assertTrue(className.equals("CordovaWebView"));
}
}

View File

@@ -1,94 +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.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()
{
sleep();
boolean isBadUrl = testView.getUrl().equals(BAD_URL);
assertFalse(isBadUrl);
}
});
}
private void sleep() {
try {
Thread.sleep(TIMEOUT);
} catch (InterruptedException e) {
fail("Unexpected Timeout");
}
}
}

View File

@@ -0,0 +1,76 @@
package org.apache.cordova.test.junit;
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CordovaWebViewClient;
import org.apache.cordova.CordovaChromeClient;
import org.apache.cordova.test.userwebview;
import android.test.ActivityInstrumentationTestCase2;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
public class UserWebViewTest extends ActivityInstrumentationTestCase2<userwebview> {
public UserWebViewTest ()
{
super(userwebview.class);
}
private int TIMEOUT = 1000;
userwebview testActivity;
private FrameLayout containerView;
private LinearLayout innerContainer;
private CordovaWebView testView;
protected void setUp() throws Exception {
super.setUp();
testActivity = this.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 testCustom()
{
assertTrue(CordovaWebView.class.isInstance(testView));
assertTrue(CordovaWebViewClient.class.isInstance(testActivity.testViewClient));
assertTrue(CordovaChromeClient.class.isInstance(testActivity.testChromeClient));
}
private void sleep() {
try {
Thread.sleep(TIMEOUT);
} catch (InterruptedException e) {
fail("Unexpected Timeout");
}
}
}

View File

@@ -21,7 +21,7 @@ package org.apache.cordova.test;
import android.os.Bundle;
import org.apache.cordova.*;
public class lifecycle extends CordovaActivity {
public class lifecycle extends DroidGap {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

View File

@@ -21,11 +21,11 @@ package org.apache.cordova.test;
import android.os.Bundle;
import org.apache.cordova.*;
public class loading extends CordovaActivity {
public class loading extends DroidGap {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
preferences.set("loadingDialog", "Testing,Loading...");
super.setStringProperty("loadingDialog", "Testing,Loading...");
super.loadUrl("http://www.google.com");
}
}

View File

@@ -1,81 +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 CordovaActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// need the title to be shown (config.xml) for the options menu to be visible
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;
}
}

View File

@@ -20,15 +20,17 @@ package org.apache.cordova.test;
import android.os.Bundle;
import org.apache.cordova.*;
import org.apache.cordova.test.R;
import org.apache.cordova.test.R.drawable;
public class splashscreen extends CordovaActivity {
public class splashscreen extends DroidGap {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.init();
// Show splashscreen
preferences.set("splashscreen", "sandy");
this.setIntegerProperty("splashscreen", R.drawable.sandy);
super.loadUrl("file:///android_asset/www/splashscreen/index.html", 2000);
}

View File

@@ -21,7 +21,7 @@ package org.apache.cordova.test;
import android.os.Bundle;
import org.apache.cordova.*;
public class tests extends CordovaActivity {
public class tests extends DroidGap {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

View File

@@ -21,14 +21,14 @@ package org.apache.cordova.test;
import android.os.Bundle;
import org.apache.cordova.*;
public class timeout extends CordovaActivity {
public class timeout extends DroidGap {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.init();
// Short timeout to cause error
preferences.set("loadUrlTimeoutValue", 10);
this.setIntegerProperty("loadUrlTimeoutValue", 10);
super.loadUrl("http://www.google.com");
}
}

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