Compare commits

..

27 Commits

Author SHA1 Message Date
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
113 changed files with 4299 additions and 6863 deletions

3
.gitignore vendored
View File

@@ -37,6 +37,3 @@ Desktop.ini
# IntelliJ IDEA files # IntelliJ IDEA files
*.iml *.iml
.idea .idea
npm-debug.log
/node_modules
/framework/build

View File

@@ -1,5 +0,0 @@
language: android
install: npm install
script:
- npm test
- npm run test-build

View File

@@ -1,38 +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.
#
-->
# Contributing to Apache Cordova
Anyone can contribute to Cordova. And we need your contributions.
There are multiple ways to contribute: report bugs, improve the docs, and
contribute code.
For instructions on this, start with the
[contribution overview](http://cordova.apache.org/#contribute).
The details are explained there, but the important items are:
- Sign and submit an Apache ICLA (Contributor License Agreement).
- Have a Jira issue open that corresponds to your contribution.
- Run the tests so your patch doesn't break existing functionality.
We look forward to your contributions!

87
LICENSE
View File

@@ -199,89 +199,4 @@
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. 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.

16
NOTICE
View File

@@ -1,17 +1,5 @@
Apache Cordova Apache Cordova
Copyright 2014 The Apache Software Foundation Copyright 2012 The Apache Software Foundation
This product includes software developed at This product includes software developed at
The Apache Software Foundation (http://www.apache.org) The Apache Software Foundation (http://www.apache.org/).
=========================================================================
== NOTICE file corresponding to the section 4 d of ==
== the Apache License, Version 2.0, ==
== in this case for the Android-specific code. ==
=========================================================================
This product includes software developed as part of
The Android Open Source Project (http://source.android.com).
This software includes software developed at Square, Inc.
Copyright (C) 2013 Square, Inc.

2
README.md Normal file → Executable file
View File

@@ -84,7 +84,7 @@ Running Tests
Please see details under test/README.md. Please see details under test/README.md.
Further Reading Further Reading
---- ---
- [http://developer.android.com](http://developer.android.com) - [http://developer.android.com](http://developer.android.com)
- [http://cordova.apache.org/](http://cordova.apache.org) - [http://cordova.apache.org/](http://cordova.apache.org)

View File

@@ -20,214 +20,6 @@
--> -->
## Release Notes for Cordova (Android) ## ## Release Notes for Cordova (Android) ##
### Release 3.7.2 (May 2015) ###
* Removed Intent Functionality from Preferences - Preferences can no longer be set by intents
### Release 3.7.1 (January 2015) ###
* CB-8411 Initialize plugins only after `createViews()` is called (regression in 3.7.0)
### Release 3.7.0 (January 2015) ###
* CB-8328 Allow plugins to handle certificate challenges (close #150)
* CB-8201 Add support for auth dialogs into Cordova Android
* CB-8017 Add support for `<input type=file>` for Lollipop
* CB-8143 Loads of gradle improvements (try it with cordova/build --gradle)
* CB-8329 Cancel outstanding ActivityResult requests when a new startActivityForResult occurs
* CB-8026 Bumping up Android Version and setting it up to allow third-party cookies. This might change later.
* CB-8210 Use PluginResult for various events from native so that content-security-policy <meta> can be used
* CB-8168 Add support for `cordova/run --list` (closes #139)
* CB-8176 Vastly better auto-detection of SDK & JDK locations
* CB-8079 Use activity class package name, but fallback to application package name when looking for splash screen drawable
* CB-8147 Have corodva/build warn about unrecognized flags rather than fail
* CB-7881 Android tooling shouldn't lock application directory
* CB-8112 Turn off mediaPlaybackRequiresUserGesture
* CB-6153 Add a preference for controlling hardware button audio stream (DefaultVolumeStream)
* CB-8031 Fix race condition that shows as ConcurrentModificationException
* CB-7974 Cancel timeout timer if view is destroyed
* CB-7940 Disable exec bridge if bridgeSecret is wrong
* CB-7758 Allow content-url-hosted pages to access the bridge
* CB-6511 Fixes build for android when app name contains unicode characters.
* CB-7707 Added multipart PluginResult
* CB-6837 Fix leaked window when hitting back button while alert being rendered
* CB-7674 Move preference activation back into onCreate()
* CB-7499 Support RTL text direction
* CB-7330 Don't run check_reqs for bin/create.
### 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
### 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) ### ### 3.4.0 (Feb 2014) ###
43 commits from 10 authors. Highlights include: 43 commits from 10 authors. Highlights include:

View File

@@ -1 +1 @@
3.7.2 3.5.0-dev

View File

@@ -51,7 +51,7 @@ get_sdks = function() {
return Q(); return Q();
}, function(stderr) { }, 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.')); 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 { } else {
return Q.reject(new Error('An error occurred while listing Android targets')); return Q.reject(new Error('An error occurred while listing Android targets'));

View File

@@ -19,224 +19,78 @@
under the License. under the License.
*/ */
var shelljs = require('shelljs'), var shell = require('shelljs'),
child_process = require('child_process'), child_process = require('child_process'),
Q = require('q'), Q = require('q'),
path = require('path'), path = require('path'),
fs = require('fs'), fs = require('fs'),
which = require('which'),
ROOT = path.join(__dirname, '..', '..'); ROOT = path.join(__dirname, '..', '..');
var isWindows = process.platform == 'win32'; // Get valid target from framework/project.properties
module.exports.get_target = function() {
function forgivingWhichSync(cmd) { if(fs.existsSync(path.join(ROOT, 'framework', 'project.properties'))) {
try { var target = shell.grep(/target=android-[\d+]/, path.join(ROOT, 'framework', 'project.properties'));
return fs.realpathSync(which.sync(cmd)); return target.split('=')[1].replace('\n', '').replace('\r', '').replace(' ', '');
} catch (e) { } else if (fs.existsSync(path.join(ROOT, 'project.properties'))) {
return ''; // if no target found, we're probably in a project and project.properties is in ROOT.
// this is called on the project itself, and can support Google APIs AND Vanilla Android
var target = shell.grep(/target=android-[\d+]/, path.join(ROOT, 'project.properties')) ||
shell.grep(/target=Google Inc.:Google APIs:[\d+]/, path.join(ROOT, 'project.properties'));
return target.split('=')[1].replace('\n', '').replace('\r', '');
} }
} }
function tryCommand(cmd, errMsg) { // Returns a promise.
module.exports.check_ant = function() {
var d = Q.defer(); var d = Q.defer();
child_process.exec(cmd, function(err, stdout, stderr) { child_process.exec('ant -version', function(err, stdout, stderr) {
if (err) d.reject(new Error(errMsg)); if (err) d.reject(new Error('ERROR : executing command \'ant\', make sure you have ant installed and added to your path.'));
else d.resolve(stdout); else d.resolve();
}); });
return d.promise; return d.promise;
} }
// Get valid target from framework/project.properties
module.exports.get_target = function() {
function extractFromFile(filePath) {
var target = shelljs.grep(/\btarget=/, filePath);
if (!target) {
throw new Error('Could not find android target within: ' + filePath);
}
return target.split('=')[1].trim();
}
if (fs.existsSync(path.join(ROOT, 'framework', 'project.properties'))) {
return extractFromFile(path.join(ROOT, 'framework', 'project.properties'));
}
if (fs.existsSync(path.join(ROOT, 'project.properties'))) {
// if no target found, we're probably in a project and project.properties is in ROOT.
return extractFromFile(path.join(ROOT, 'project.properties'));
}
throw new Error('Could not find android target. File missing: ' + path.join(ROOT, 'project.properties'));
}
// Returns a promise. Called only by build and clean commands.
module.exports.check_ant = function() {
return tryCommand('ant -version', 'Failed to run "ant -version", make sure you have ant installed and added to your PATH.');
};
// Returns a promise. Called only by build and clean commands.
module.exports.check_gradle = function() {
var sdkDir = process.env['ANDROID_HOME'];
var wrapperDir = path.join(sdkDir, 'tools', 'templates', 'gradle', 'wrapper');
if (!fs.existsSync(wrapperDir)) {
return Q.reject(new Error('Could not find gradle wrapper within android sdk. Might need to update your Android SDK.\n' +
'Looked here: ' + wrapperDir));
}
return Q.when();
};
// Returns a promise. // Returns a promise.
module.exports.check_java = function() { module.exports.check_java = function() {
var javacPath = forgivingWhichSync('javac'); var d = Q.defer();
var hasJavaHome = !!process.env['JAVA_HOME']; child_process.exec('java -version', function(err, stdout, stderr) {
return Q().then(function() { if(err) {
if (hasJavaHome) { var msg =
// Windows java installer doesn't add javac to PATH, nor set JAVA_HOME (ugh). 'Failed to run \'java -version\', make sure your java environment is set up\n' +
if (!javacPath) { 'including JDK and JRE.\n' +
process.env['PATH'] += path.delimiter + path.join(process.env['JAVA_HOME'], 'bin'); 'Your JAVA_HOME variable is ' + process.env.JAVA_HOME + '\n';
} d.reject(new Error(msg + err));
} 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(javacPath));
if (fs.existsSync(path.join(maybeJavaHome, 'lib', 'tools.jar'))) {
process.env['JAVA_HOME'] = maybeJavaHome;
} else {
throw new Error('Could not find JAVA_HOME. Try setting the environment variable manually');
}
}
} else if (isWindows) {
// Try to auto-detect java in the default install paths.
var oldSilent = shelljs.config.silent;
shelljs.config.silent = true;
var firstJdkDir =
shelljs.ls(process.env['ProgramFiles'] + '\\java\\jdk*')[0] ||
shelljs.ls('C:\\Program Files\\java\\jdk*')[0] ||
shelljs.ls('C:\\Program Files (x86)\\java\\jdk*')[0];
shelljs.config.silent = oldSilent;
if (firstJdkDir) {
// shelljs always uses / in paths.
firstJdkDir = firstJdkDir.replace(/\//g, path.sep);
if (!javacPath) {
process.env['PATH'] += path.delimiter + path.join(firstJdkDir, 'bin');
}
process.env['JAVA_HOME'] = firstJdkDir;
}
}
} }
}).then(function() { else d.resolve();
var msg =
'Failed to run "java -version", make sure that you have a JDK installed.\n' +
'You can get it from: http://www.oracle.com/technetwork/java/javase/downloads.\n';
if (process.env['JAVA_HOME']) {
msg += 'Your JAVA_HOME is invalid: ' + process.env['JAVA_HOME'] + '\n';
}
return tryCommand('java -version', msg)
.then(function() {
return tryCommand('javac -version', msg);
});
}); });
return d.promise;
} }
// Returns a promise. // Returns a promise.
module.exports.check_android = function() { module.exports.check_android = function() {
return Q().then(function() { var valid_target = this.get_target();
var androidCmdPath = forgivingWhichSync('android'); var d = Q.defer();
var adbInPath = !!forgivingWhichSync('adb'); child_process.exec('android list targets', function(err, stdout, stderr) {
var hasAndroidHome = !!process.env['ANDROID_HOME'] && fs.existsSync(process.env['ANDROID_HOME']); if (err) d.reject(stderr);
function maybeSetAndroidHome(value) { else d.resolve(stdout);
if (!hasAndroidHome && fs.existsSync(value)) {
hasAndroidHome = true;
process.env['ANDROID_HOME'] = value;
}
}
if (!hasAndroidHome && !androidCmdPath) {
if (isWindows) {
// Android Studio 1.0 installer
maybeSetAndroidHome(path.join(process.env['LOCALAPPDATA'], 'Android', 'sdk'));
maybeSetAndroidHome(path.join(process.env['ProgramFiles'], 'Android', 'sdk'));
// Android Studio pre-1.0 installer
maybeSetAndroidHome(path.join(process.env['LOCALAPPDATA'], 'Android', 'android-studio', 'sdk'));
maybeSetAndroidHome(path.join(process.env['ProgramFiles'], 'Android', 'android-studio', 'sdk'));
// Stand-alone installer
maybeSetAndroidHome(path.join(process.env['LOCALAPPDATA'], 'Android', 'android-sdk'));
maybeSetAndroidHome(path.join(process.env['ProgramFiles'], 'Android', 'android-sdk'));
} else if (process.platform == 'darwin') {
// Android Studio 1.0 installer
maybeSetAndroidHome(path.join(process.env['HOME'], 'Library', 'Android', 'sdk'));
// Android Studio pre-1.0 installer
maybeSetAndroidHome('/Applications/Android Studio.app/sdk');
// Stand-alone zip file that user might think to put under /Applications
maybeSetAndroidHome('/Applications/android-sdk-macosx');
maybeSetAndroidHome('/Applications/android-sdk');
}
if (process.env['HOME']) {
// Stand-alone zip file that user might think to put under their home directory
maybeSetAndroidHome(path.join(process.env['HOME'], 'android-sdk-macosx'));
maybeSetAndroidHome(path.join(process.env['HOME'], 'android-sdk'));
}
}
if (hasAndroidHome && !androidCmdPath) {
process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'tools');
}
if (androidCmdPath && !hasAndroidHome) {
var parentDir = path.dirname(androidCmdPath);
var grandParentDir = path.dirname(parentDir);
if (path.basename(parentDir) == 'tools') {
process.env['ANDROID_HOME'] = path.dirname(parentDir);
hasAndroidHome = true;
} else if (fs.existsSync(path.join(grandParentDir, 'tools', 'android'))) {
process.env['ANDROID_HOME'] = grandParentDir;
hasAndroidHome = true;
} else {
throw new Error('ANDROID_HOME is not set and no "tools" directory found at ' + parentDir);
}
}
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());
}); });
};
module.exports.getAbsoluteAndroidCmd = function() { return d.promise.then(function(output) {
return forgivingWhichSync('android').replace(/(\s)/g, '\\$1'); if (!output.match(valid_target)) {
}; return Q.reject(new Error('Please install Android target ' + valid_target.split('-')[1] + ' (the Android newest SDK). Make sure you have the latest Android tools installed as well. Run \"android\" from your command-line to install/update any missing SDKs or tools.'));
}
module.exports.check_android_target = function(valid_target) { return Q();
// valid_target can look like: }, function(stderr) {
// android-19 if (stderr.match(/command\snot\sfound/)) {
// android-L 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.'));
// Google Inc.:Google APIs:20 } else {
// Google Inc.:Glass Development Kit Preview:20 return Q.reject(new Error('An error occurred while listing Android targets'));
var msg = 'Android SDK not found. Make sure that it is installed. If it is not at the default location, set the ANDROID_HOME environment variable.';
return tryCommand('android list targets --compact', msg)
.then(function(output) {
if (output.split('\n').indexOf(valid_target) == -1) {
var androidCmd = module.exports.getAbsoluteAndroidCmd();
throw new Error('Please install Android target: "' + valid_target + '".\n\n' +
'Hint: Open the SDK manager by running: ' + androidCmd + '\n' +
'You will require:\n' +
'1. "SDK Platform" for ' + valid_target + '\n' +
'2. "Android SDK Platform-tools (latest)\n' +
'3. "Android SDK Build-tools" (latest)');
} }
}); });
}; }
// Returns a promise. // Returns a promise.
module.exports.run = function() { 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,8 +71,6 @@ function copyJsAndLibrary(projectPath, shared, projectName) {
shell.mkdir('-p', nestedCordovaLibPath); shell.mkdir('-p', nestedCordovaLibPath);
shell.cp('-f', path.join(ROOT, 'framework', 'AndroidManifest.xml'), 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', 'project.properties'), nestedCordovaLibPath);
shell.cp('-f', path.join(ROOT, 'framework', 'build.gradle'), nestedCordovaLibPath);
shell.cp('-f', path.join(ROOT, 'framework', 'cordova.gradle'), nestedCordovaLibPath);
shell.cp('-r', path.join(ROOT, 'framework', 'src'), nestedCordovaLibPath); shell.cp('-r', path.join(ROOT, 'framework', 'src'), nestedCordovaLibPath);
// Create an eclipse project file and set the name of it to something unique. // 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. // Without this, you can't import multiple CordovaLib projects into the same workspace.
@@ -84,45 +82,14 @@ function copyJsAndLibrary(projectPath, shared, projectName) {
} }
} }
function extractSubProjectPaths(data) { function runAndroidUpdate(projectPath, target_api, shared) {
var ret = {}; var targetFrameworkDir = getFrameworkDir(projectPath, shared);
var r = /^\s*android\.library\.reference\.\d+=(.*)(?:\s|$)/mg return exec('android update project --subprojects --path "' + projectPath + '" --target ' + target_api + ' --library "' + path.relative(projectPath, targetFrameworkDir) + '"');
var m;
while (m = r.exec(data)) {
ret[m[1]] = 1;
}
return Object.keys(ret);
} }
function writeProjectProperties(projectPath, target_api, shared) { function copyAntRules(projectPath) {
var dstPath = path.join(projectPath, 'project.properties');
var templatePath = path.join(ROOT, 'bin', 'templates', 'project', 'project.properties');
var srcPath = fs.existsSync(dstPath) ? dstPath : templatePath;
var data = fs.readFileSync(srcPath, 'utf8');
data = data.replace(/^target=.*/m, 'target=' + target_api);
var subProjects = extractSubProjectPaths(data);
subProjects = subProjects.filter(function(p) {
return !(/^CordovaLib$/m.exec(p) ||
/[\\\/]cordova-android[\\\/]framework$/m.exec(p) ||
/^(\.\.[\\\/])+framework$/m.exec(p)
);
});
subProjects.unshift(shared ? path.relative(projectPath, path.join(ROOT, 'framework')) : 'CordovaLib');
data = data.replace(/^\s*android\.library\.reference\.\d+=.*\n/mg, '');
if (!/\n$/.exec(data)) {
data += '\n';
}
for (var i = 0; i < subProjects.length; ++i) {
data += 'android.library.reference.' + (i+1) + '=' + subProjects[i] + '\n';
}
fs.writeFileSync(dstPath, data);
}
function copyBuildRules(projectPath) {
var srcDir = path.join(ROOT, 'bin', 'templates', 'project'); var srcDir = path.join(ROOT, 'bin', 'templates', 'project');
shell.cp('-f', path.join(srcDir, 'custom_rules.xml'), projectPath); shell.cp('-f', path.join(srcDir, 'custom_rules.xml'), projectPath);
shell.cp('-f', path.join(srcDir, 'build.gradle'), projectPath);
} }
function copyScripts(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')); 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] * $ create [options]
* *
@@ -210,11 +133,9 @@ exports.createProject = function(project_path, package_name, project_name, proje
project_template_dir : project_template_dir :
path.join(ROOT, 'bin', 'templates', 'project'); 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 package_as_path = package_name.replace(/\./g, path.sep);
var activity_dir = path.join(project_path, 'src', package_as_path); var activity_dir = path.join(project_path, 'src', package_as_path);
// safe_activity_name is being hardcoded to avoid issues with unicode app name (https://issues.apache.org/jira/browse/CB-6511)
// TODO: provide option to specify activity name via CLI (proposal: https://issues.apache.org/jira/browse/CB-7231)
var safe_activity_name = 'MainActivity';
var activity_path = path.join(activity_dir, safe_activity_name + '.java'); var activity_path = path.join(activity_dir, safe_activity_name + '.java');
var target_api = check_reqs.get_target(); var target_api = check_reqs.get_target();
var manifest_path = path.join(project_path, 'AndroidManifest.xml'); var manifest_path = path.join(project_path, 'AndroidManifest.xml');
@@ -224,11 +145,17 @@ exports.createProject = function(project_path, package_name, project_name, proje
return Q.reject('Project already exists! Delete and recreate'); return Q.reject('Project already exists! Delete and recreate');
} }
//Make the package conform to Java package types if (!/[a-zA-Z0-9_]+\.[a-zA-Z0-9_](.[a-zA-Z0-9_])*/.test(package_name)) {
return validatePackageName(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() { .then(function() {
validateProjectName(project_name);
}).then(function() {
// Log the given values for the project // Log the given values for the project
console.log('Creating Cordova project for the Android platform:'); console.log('Creating Cordova project for the Android platform:');
console.log('\tPath: ' + project_path); console.log('\tPath: ' + project_path);
@@ -243,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, 'assets'), project_path);
shell.cp('-r', path.join(project_template_dir, 'res'), 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('-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). // Manually create directories that would be empty within the template (since git doesn't track directories).
shell.mkdir(path.join(project_path, 'libs')); shell.mkdir(path.join(project_path, 'libs'));
@@ -275,10 +201,10 @@ exports.createProject = function(project_path, package_name, project_name, proje
shell.sed('-i', /__PACKAGE__/, package_name, manifest_path); shell.sed('-i', /__PACKAGE__/, package_name, manifest_path);
shell.sed('-i', /__APILEVEL__/, target_api.split('-')[1], manifest_path); shell.sed('-i', /__APILEVEL__/, target_api.split('-')[1], manifest_path);
copyScripts(project_path); copyScripts(project_path);
copyBuildRules(project_path); copyAntRules(project_path);
}); });
// Link it to local android install. // Link it to local android install.
writeProjectProperties(project_path, target_api, use_shared_project); return runAndroidUpdate(project_path, target_api, use_shared_project);
}).then(function() { }).then(function() {
console.log('Project successfully created.'); console.log('Project successfully created.');
}); });
@@ -301,23 +227,22 @@ function extractProjectNameFromManifest(projectPath) {
} }
// Returns a promise. // Returns a promise.
exports.updateProject = function(projectPath, shared) { exports.updateProject = function(projectPath) {
var newVersion = fs.readFileSync(path.join(ROOT, 'VERSION'), 'utf-8').trim(); var version = fs.readFileSync(path.join(ROOT, 'VERSION'), 'utf-8').trim();
return Q() // Check that requirements are met and proper targets are installed
return check_reqs.run()
.then(function() { .then(function() {
var projectName = extractProjectNameFromManifest(projectPath); var projectName = extractProjectNameFromManifest(projectPath);
var target_api = check_reqs.get_target(); var target_api = check_reqs.get_target();
copyJsAndLibrary(projectPath, shared, projectName); copyJsAndLibrary(projectPath, false, projectName);
copyScripts(projectPath); copyScripts(projectPath);
copyBuildRules(projectPath); copyAntRules(projectPath);
removeDebuggableFromManifest(projectPath); removeDebuggableFromManifest(projectPath);
writeProjectProperties(projectPath, target_api, shared); return runAndroidUpdate(projectPath, target_api, false)
console.log('Android project is now at version ' + newVersion); .then(function() {
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.'); 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; args = process.argv;
// Support basic help commands // Support basic help commands
if(args[2] == '--help' || if(args[2] == '--help' || args[2] == '/?' || args[2] == '-h' ||
args[2] == '/?' || args[2] == 'help' || args[2] == '-help' || args[2] == '/help') {
args[2] == '-h' ||
args[2] == 'help' ||
args[2] == '-help' ||
args[2] == '/help') {
build.help(); build.help();
} else { } else {
reqs.run().done(function() { reqs.run().then(function() {
return build.run(args.slice(2)); return build.run(args[2]);
}, function(err) { }).done(null, function(err) {
console.error(err); console.error(err);
process.exit(2); process.exit(2);
}); });

View File

@@ -19,26 +19,18 @@
under the License. under the License.
*/ */
var build = require('./lib/build'), var clean = require('./lib/clean'),
reqs = require('./lib/check_reqs'), reqs = require('./lib/check_reqs'),
args = process.argv; args = process.argv;
var path = require('path');
// Support basic help commands // Usage support for when args are given
if(args[2] == '--help' || if(args.length > 2) {
args[2] == '/?' || clean.help();
args[2] == '-h' ||
args[2] == 'help' ||
args[2] == '-help' ||
args[2] == '/help') {
console.log('Usage: ' + path.relative(process.cwd(), process.argv[1]));
console.log('Cleans the project directory.');
process.exit(0);
} else { } else {
reqs.run().done(function() { reqs.run().done(function() {
return build.runClean(args.slice(2)); return clean.run();
}, function(err) { }, function(err) {
console.error(err); console.error('ERROR: ' + err);
process.exit(2); process.exit(2);
}); });
} }

View File

@@ -23,4 +23,10 @@
<!-- Preferences for Android --> <!-- Preferences for Android -->
<preference name="loglevel" value="DEBUG" /> <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> </widget>

View File

@@ -24,458 +24,90 @@ var shell = require('shelljs'),
Q = require('q'), Q = require('q'),
path = require('path'), path = require('path'),
fs = require('fs'), fs = require('fs'),
os = require('os'),
ROOT = path.join(__dirname, '..', '..'); ROOT = path.join(__dirname, '..', '..');
var check_reqs = require('./check_reqs');
var exec = require('./exec');
var LOCAL_PROPERTIES_TEMPLATE =
'# This file is automatically generated.\n' +
'# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n';
function 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, arch) {
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;
}
// Assume arch-specific build if newest api has -x86 or -arm.
var archSpecific = !!/-x86|-arm/.exec(ret[0]);
// And show only arch-specific ones (or non-arch-specific)
ret = ret.filter(function(p) {
return !!/-x86|-arm/.exec(p) == archSpecific;
});
if (arch && ret.length > 1) {
ret = ret.filter(function(p) {
return p.indexOf('-' + arch) != -1;
});
}
return ret;
}
function hasCustomRules() { function hasCustomRules() {
return fs.existsSync(path.join(ROOT, 'custom_rules.xml')); return fs.existsSync(path.join(ROOT, 'custom_rules.xml'));
} }
module.exports.getAntArgs = function(cmd) {
function extractProjectNameFromManifest(projectPath) { var args = [cmd, '-f', path.join(ROOT, 'build.xml')];
var manifestPath = path.join(projectPath, 'AndroidManifest.xml'); // custom_rules.xml is required for incremental builds.
var manifestData = fs.readFileSync(manifestPath, 'utf8'); if (hasCustomRules()) {
var m = /<activity[\s\S]*?android:name\s*=\s*"(.*?)"/i.exec(manifestData); args.push('-Dout.dir=ant-build', '-Dgen.absolute.dir=ant-gen');
if (!m) {
throw new Error('Could not find activity name in ' + manifestPath);
} }
return m[1]; return args;
} };
function extractSubProjectPaths() { /*
var data = fs.readFileSync(path.join(ROOT, 'project.properties'), 'utf8'); * Builds the project with ant.
var ret = {}; * Returns a promise.
var r = /^\s*android\.library\.reference\.\d+=(.*)(?:\s|$)/mg */
var m; module.exports.run = function(build_type) {
while (m = r.exec(data)) { //default build type
ret[m[1]] = 1; build_type = typeof build_type !== 'undefined' ? build_type : "--debug";
} var args = module.exports.getAntArgs('debug');
return Object.keys(ret); switch(build_type) {
} case '--debug' :
break;
var builders = { case '--release' :
ant: { args[0] = 'release';
getArgs: function(cmd) { break;
var args = [cmd, '-f', path.join(ROOT, 'build.xml')]; case '--nobuild' :
// 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, null);
}
},
gradle: {
getArgs: function(cmd, arch, extraArgs) {
if (cmd == 'release') {
cmd = 'cdvBuildRelease';
} else if (cmd == 'debug') {
cmd = 'cdvBuildDebug';
}
var args = [cmd, '-b', path.join(ROOT, 'build.gradle')];
if (arch) {
args.push('-PcdvBuildArch=' + arch);
}
// 10 seconds -> 6 seconds
args.push('-Dorg.gradle.daemon=true');
args.push.apply(args, extraArgs);
// Shaves another 100ms, but produces a "try at own risk" warning. Not worth it (yet):
// args.push('-Dorg.gradle.parallel=true');
return args;
},
prepEnv: function() {
return check_reqs.check_gradle()
.then(function() {
// Copy the gradle wrapper on each build so that:
// A) we don't require the Android SDK at project creation time, and
// B) we always use the SDK's latest version of it.
var projectPath = ROOT;
// check_reqs ensures that this is set.
var sdkDir = process.env['ANDROID_HOME'];
var wrapperDir = path.join(sdkDir, 'tools', 'templates', 'gradle', 'wrapper');
if (process.platform == 'win32') {
shell.cp('-f', path.join(wrapperDir, 'gradlew.bat'), projectPath);
} else {
shell.cp('-f', path.join(wrapperDir, 'gradlew'), projectPath);
}
shell.rm('-rf', path.join(projectPath, 'gradle', 'wrapper'));
shell.mkdir('-p', path.join(projectPath, 'gradle'));
shell.cp('-r', path.join(wrapperDir, 'gradle', 'wrapper'), path.join(projectPath, 'gradle'));
// If the gradle distribution URL is set, make sure it points to version we want.
// If it's not set, do nothing, assuming that we're using a future version of gradle that we don't want to mess with.
// For some reason, using ^ and $ don't work. This does the job, though.
var distributionUrlRegex = /distributionUrl.*zip/;
var distributionUrl = 'distributionUrl=http\\://services.gradle.org/distributions/gradle-2.2.1-all.zip';
var gradleWrapperPropertiesPath = path.join(projectPath, 'gradle', 'wrapper', 'gradle-wrapper.properties');
shell.sed('-i', distributionUrlRegex, distributionUrl, gradleWrapperPropertiesPath);
// Update the version of build.gradle in each dependent library.
var pluginBuildGradle = path.join(projectPath, 'cordova', 'lib', 'plugin-build.gradle');
var subProjects = extractSubProjectPaths();
for (var i = 0; i < subProjects.length; ++i) {
if (subProjects[i] !== 'CordovaLib') {
shell.cp('-f', pluginBuildGradle, path.join(ROOT, subProjects[i], 'build.gradle'));
}
}
var subProjectsAsGradlePaths = subProjects.map(function(p) { return ':' + p.replace(/[/\\]/g, ':') });
// Write the settings.gradle file.
fs.writeFileSync(path.join(projectPath, 'settings.gradle'),
'// GENERATED FILE - DO NOT EDIT\n' +
'include ":"\n' +
'include "' + subProjectsAsGradlePaths.join('"\ninclude "') + '"\n');
// Update dependencies within build.gradle.
var buildGradle = fs.readFileSync(path.join(projectPath, 'build.gradle'), 'utf8');
var depsList = '';
subProjectsAsGradlePaths.forEach(function(p) {
depsList += ' debugCompile project(path: "' + p + '", configuration: "debug")\n';
depsList += ' releaseCompile project(path: "' + p + '", configuration: "release")\n';
});
buildGradle = buildGradle.replace(/(SUB-PROJECT DEPENDENCIES START)[\s\S]*(\/\/ SUB-PROJECT DEPENDENCIES END)/, '$1\n' + depsList + ' $2');
fs.writeFileSync(path.join(projectPath, 'build.gradle'), buildGradle);
});
},
/*
* Builds the project with gradle.
* Returns a promise.
*/
build: function(build_type, arch, extraArgs) {
var builder = this;
var wrapper = path.join(ROOT, 'gradlew');
var args = this.getArgs(build_type == 'debug' ? 'debug' : 'release', arch, extraArgs);
return Q().then(function() {
console.log('Running: ' + wrapper + ' ' + args.concat(extraArgs).join(' '));
return spawn(wrapper, args);
});
},
clean: function(extraArgs) {
var builder = this;
var wrapper = path.join(ROOT, 'gradlew');
var args = builder.getArgs('clean', null, extraArgs);
return Q().then(function() {
console.log('Running: ' + wrapper + ' ' + args.concat(extraArgs).join(' '));
return spawn(wrapper, args);
});
},
findOutputApks: function(build_type, arch) {
var binDir = path.join(ROOT, 'build', 'outputs', 'apk');
return findOutputApksHelper(binDir, build_type, arch);
}
},
none: {
prepEnv: function() {
return Q();
},
build: function() {
console.log('Skipping build...'); console.log('Skipping build...');
return Q(null);
},
clean: function() {
return Q(); return Q();
}, default :
findOutputApks: function(build_type, arch) { return Q.reject('Build option \'' + build_type + '\' not recognized.');
return sortFilesByDate(builders.ant.findOutputApks(build_type, arch).concat(builders.gradle.findOutputApks(build_type, arch)));
}
} }
}; // Without our custom_rules.xml, we need to clean before building.
var ret = Q();
function parseOpts(options, resolvedTarget) { if (!hasCustomRules()) {
// Backwards-compatibility: Allow a single string argument ret = require('./clean').run();
if (typeof options == "string") options = [options];
var ret = {
buildType: 'debug',
buildMethod: process.env['ANDROID_BUILD'] || 'ant',
arch: null,
extraArgs: []
};
var multiValueArgs = {
'versionCode': true,
'minSdkVersion': true,
'gradleArg': true
};
// Iterate through command line options
for (var i=0; options && (i < options.length); ++i) {
if (/^--/.exec(options[i])) {
var keyValue = options[i].substring(2).split('=');
var flagName = keyValue.shift();
var flagValue = keyValue.join('=');
if (multiValueArgs[flagName] && !flagValue) {
flagValue = options[i + 1];
++i;
}
switch(flagName) {
case 'debug':
case 'release':
ret.buildType = flagName;
break;
case 'ant':
case 'gradle':
ret.buildMethod = flagName;
break;
case 'device':
case 'emulator':
// Don't need to do anything special to when building for device vs emulator.
// iOS uses this flag to switch on architecture.
break;
case 'nobuild' :
ret.buildMethod = 'none';
break;
case 'versionCode':
ret.extraArgs.push('-PcdvVersionCode=' + flagValue);
break;
case 'minSdkVersion':
ret.extraArgs.push('-PcdvMinSdkVersion=' + flagValue);
break;
case 'gradleArg':
ret.extraArgs.push(flagValue);
break;
default :
console.warn('Build option --\'' + flagName + '\' not recognized (ignoring).');
}
} else {
console.warn('Build option \'' + options[i] + '\' not recognized (ignoring).');
}
} }
return ret.then(function() {
ret.arch = resolvedTarget && resolvedTarget.arch; return spawn('ant', args);
});
return ret;
} }
/* /*
* Builds the project with the specifed options * Gets the path to the apk file, if not such file exists then
* Returns a promise. * the script will error out. (should we error or just return undefined?)
*/ */
module.exports.runClean = function(options) { module.exports.get_apk = function() {
var opts = parseOpts(options); var binDir = '';
var builder = builders[opts.buildMethod]; if(!hasCustomRules()) {
return builder.prepEnv() binDir = path.join(ROOT, 'bin');
.then(function() { } else {
return builder.clean(opts.extraArgs); binDir = path.join(ROOT, 'ant-build');
}).then(function() { }
shell.rm('-rf', path.join(ROOT, 'out')); 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) {
* Builds the project with the specifed options p = path.join(binDir, p);
* Returns a promise. return { p: p, t: fs.statSync(p).mtime };
*/ }).sort(function(a,b) {
module.exports.run = function(options, optResolvedTarget) { return a.t > b.t ? -1 :
var opts = parseOpts(options, optResolvedTarget); a.t < b.t ? 1 : 0;
var builder = builders[opts.buildMethod];
return builder.prepEnv()
.then(function() {
return builder.build(opts.buildType, opts.arch, opts.extraArgs);
}).then(function() {
var apkPaths = builder.findOutputApks(opts.buildType, opts.arch);
console.log('Built the following apk(s):');
console.log(' ' + apkPaths.join('\n '));
return {
apkPaths: apkPaths,
buildType: opts.buildType,
buildMethod: opts.buildMethod
};
});
};
/*
* Detects the architecture of a device/emulator
* Returns "arm" or "x86".
*/
module.exports.detectArchitecture = function(target) {
function helper() {
return exec('adb -s ' + target + ' shell cat /proc/cpuinfo', os.tmpdir())
.then(function(output) {
if (/intel/i.exec(output)) {
return 'x86';
}
return 'arm';
}); });
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);
} }
// It sometimes happens (at least on OS X), that this command will hang forever. }
// To fix it, either unplug & replug device, or restart adb server.
return helper().timeout(1000, 'Device communication timed out. Try unplugging & replugging the device.')
.then(null, function(err) {
if (/timed out/.exec('' + err)) {
// adb kill-server doesn't seem to do the trick.
// Could probably find a x-platform version of killall, but I'm not actually
// sure that this scenario even happens on non-OSX machines.
return exec('killall adb')
.then(function() {
console.log('adb seems hung. retrying.');
return helper()
.then(null, function() {
// The double kill is sadly often necessary, at least on mac.
console.log('Now device not found... restarting adb again.');
return exec('killall adb')
.then(function() {
return helper()
.then(null, function() {
return Q.reject('USB is flakey. Try unplugging & replugging the device.');
});
});
});
}, function() {
// For non-killall OS's.
return Q.reject(err);
})
}
throw err;
});
};
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];
}
}
throw new Error('Could not find apk architecture: ' + arch + ' build-type: ' + buildResults.buildType);
};
module.exports.help = function() { module.exports.help = function() {
console.log('Usage: ' + path.relative(process.cwd(), path.join(ROOT, 'cordova', 'build')) + ' [flags]'); console.log('Usage: ' + path.relative(process.cwd(), path.join(ROOT, 'cordova', 'build')) + ' [build_type]');
console.log('Flags:'); console.log('Build Types : ');
console.log(' \'--debug\': will build project in debug mode (default)'); console.log(' \'--debug\': Default build, will build project in using ant debug');
console.log(' \'--release\': will build project for release'); console.log(' \'--release\': will build project using ant release');
console.log(' \'--ant\': will build project with ant (default)'); console.log(' \'--nobuild\': will skip build process (can be used with run command)');
console.log(' \'--gradle\': will build project with gradle');
console.log(' \'--nobuild\': will skip build process (useful when using run command)');
console.log(' \'--versionCode=#\': Override versionCode for this build. Useful for uploading multiple APKs. Requires --gradle.');
console.log(' \'--minSdkVersion=#\': Override minSdkVersion for this build. Useful for uploading multiple APKs. Requires --gradle.');
console.log(' \'--gradleArg=<gradle command line arg>\': Extra args to pass to the gradle command. Use one flag per arg. Ex. --gradleArg=-PcdvBuildMultipleApks=true');
process.exit(0); process.exit(0);
}; }

View File

@@ -1,3 +1,5 @@
#!/usr/bin/env node
/* /*
Licensed to the Apache Software Foundation (ASF) under one Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file or more contributor license agreements. See the NOTICE file
@@ -16,23 +18,22 @@
specific language governing permissions and limitations specific language governing permissions and limitations
under the License. under the License.
*/ */
package org.apache.cordova;
/** var build = require('./build'),
* Specifies interface for HTTP auth handler object which is used to handle auth requests and spawn = require('./spawn'),
* specifying user credentials. path = require('path');
/*
* Cleans the project using ant
* Returns a promise.
*/ */
public interface ICordovaHttpAuthHandler { module.exports.run = function() {
/** var args = build.getAntArgs('clean');
* Instructs the WebView to cancel the authentication request. return spawn('ant', args);
*/ }
public void cancel ();
module.exports.help = function() {
/** console.log('Usage: ' + path.relative(process.cwd(), process.argv[1]));
* Instructs the WebView to proceed with the authentication with the given credentials. console.log('Cleans the project directory.');
* process.exit(0);
* @param username The user name }
* @param password The password
*/
public void proceed (String username, String password);
}

View File

@@ -22,102 +22,65 @@
var exec = require('./exec'), var exec = require('./exec'),
Q = require('q'), Q = require('q'),
path = require('path'), path = require('path'),
os = require('os'),
build = require('./build'), build = require('./build'),
appinfo = require('./appinfo'), appinfo = require('./appinfo'),
ROOT = path.join(__dirname, '..', '..'); ROOT = path.join(__dirname, '..', '..');
/** /**
* Returns a promise for the list of the device ID's found * Returns a promise for the list of the device ID's found
* @param lookHarder When true, try restarting adb if no devices are found.
*/ */
module.exports.list = function(lookHarder) { module.exports.list = function() {
function helper() { return exec('adb devices')
return exec('adb devices', os.tmpdir()) .then(function(output) {
.then(function(output) { var response = output.split('\n');
var response = output.split('\n'); var device_list = [];
var device_list = []; for (var i = 1; i < response.length; i++) {
for (var i = 1; i < response.length; i++) { if (response[i].match(/\w+\tdevice/) && !response[i].match(/emulator/)) {
if (response[i].match(/\w+\tdevice/) && !response[i].match(/emulator/)) { device_list.push(response[i].replace(/\tdevice/, '').replace('\r', ''));
device_list.push(response[i].replace(/\tdevice/, '').replace('\r', ''));
}
} }
return device_list;
});
}
return helper()
.then(function(list) {
if (list.length === 0 && lookHarder) {
// adb kill-server doesn't seem to do the trick.
// Could probably find a x-platform version of killall, but I'm not actually
// sure that this scenario even happens on non-OSX machines.
return exec('killall adb')
.then(function() {
console.log('Restarting adb to see if more devices are detected.');
return helper();
}, function() {
// For non-killall OS's.
return list;
});
} }
return list; return device_list;
}); });
} }
module.exports.resolveTarget = function(target) {
return this.list(true)
.then(function(device_list) {
if (!device_list || !device_list.length) {
return Q.reject('ERROR: Failed to deploy to device, no devices found.');
}
// default device
target = 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) {
return { target: target, arch: arch, isEmulator: false };
});
});
};
/* /*
* Installs a previously built application on the device * Installs a previously built application on the device
* and launches it. * and launches it.
* Returns a promise. * Returns a promise.
*/ */
module.exports.install = function(target, buildResults) { module.exports.install = function(target) {
return Q().then(function() { var launchName;
if (target && typeof target == 'object') { return this.list()
return target; .then(function(device_list) {
} if (!device_list || !device_list.length)
return module.exports.resolveTarget(target); return Q.reject('ERROR: Failed to deploy to device, no devices found.');
}).then(function(resolvedTarget) {
var apk_path = build.findBestApkForArchitecture(buildResults, resolvedTarget.arch);
var launchName = appinfo.getActivityName();
console.log('Using apk: ' + apk_path);
console.log('Installing app on device...');
var cmd = 'adb -s ' + resolvedTarget.target + ' install -r "' + apk_path + '"';
return exec(cmd, os.tmpdir())
.then(function(output) {
if (output.match(/Failure/)) return Q.reject('ERROR: Failed to install apk to device: ' + output);
//unlock screen // default device
var cmd = 'adb -s ' + resolvedTarget.target + ' shell input keyevent 82'; target = typeof target !== 'undefined' ? target : device_list[0];
return exec(cmd, os.tmpdir());
}, function(err) { return Q.reject('ERROR: Failed to install apk to device: ' + err); }) if (device_list.indexOf(target) < 0)
.then(function() { return Q.reject('ERROR: Unable to find target \'' + target + '\'.');
// launch the application
console.log('Launching application...'); var apk_path = build.get_apk();
var cmd = 'adb -s ' + resolvedTarget.target + ' shell am start -W -a android.intent.action.MAIN -n ' + launchName; launchName = appinfo.getActivityName();
return exec(cmd, os.tmpdir()); console.log('Installing app on device...');
}).then(function() { var cmd = 'adb -s ' + target + ' install -r "' + apk_path + '"';
console.log('LAUNCH SUCCESS'); return exec(cmd);
}, function(err) { }).then(function(output) {
return Q.reject('ERROR: Failed to launch application on device: ' + err); if (output.match(/Failure/)) return Q.reject('ERROR: Failed to install apk to device: ' + output);
});
//unlock screen
var cmd = 'adb -s ' + target + ' shell input keyevent 82';
return exec(cmd);
}, function(err) { return Q.reject('ERROR: Failed to install apk to device: ' + err); })
.then(function() {
// launch the application
console.log('Launching application...');
var cmd = 'adb -s ' + target + ' shell am start -W -a android.intent.action.MAIN -n ' + launchName;
return exec(cmd);
}).then(function() {
console.log('LAUNCH SUCCESS');
}, function(err) {
return Q.reject('ERROR: Failed to launch application on device: ' + err);
}); });
} }

View File

@@ -23,13 +23,11 @@ var shell = require('shelljs'),
exec = require('./exec'), exec = require('./exec'),
Q = require('q'), Q = require('q'),
path = require('path'), path = require('path'),
os = require('os'),
appinfo = require('./appinfo'), appinfo = require('./appinfo'),
build = require('./build'), build = require('./build'),
ROOT = path.join(__dirname, '..', '..'), ROOT = path.join(__dirname, '..', '..'),
child_process = require('child_process'), child_process = require('child_process'),
new_emulator = 'cordova_emulator'; new_emulator = 'cordova_emulator';
var check_reqs = require('./check_reqs');
/** /**
* Returns a Promise for a list of emulator images in the form of objects * Returns a Promise for a list of emulator images in the form of objects
@@ -86,7 +84,7 @@ module.exports.list_images = function() {
* Returns a promise. * Returns a promise.
*/ */
module.exports.best_image = function() { module.exports.best_image = function() {
var project_target = check_reqs.get_target().replace('android-', ''); var project_target = this.get_target().replace('android-', '');
return this.list_images() return this.list_images()
.then(function(images) { .then(function(images) {
var closest = 9999; var closest = 9999;
@@ -109,7 +107,7 @@ module.exports.best_image = function() {
// Returns a promise. // Returns a promise.
module.exports.list_started = function() { module.exports.list_started = function() {
return exec('adb devices', os.tmpdir()) return exec('adb devices')
.then(function(output) { .then(function(output) {
var response = output.split('\n'); var response = output.split('\n');
var started_emulator_list = []; var started_emulator_list = [];
@@ -122,9 +120,14 @@ module.exports.list_started = function() {
}); });
} }
module.exports.get_target = function() {
var target = shell.grep(/target=android-[\d+]/, path.join(ROOT, 'project.properties'));
return target.split('=')[1].replace('\n', '').replace('\r', '').replace(' ', '');
}
// Returns a promise. // Returns a promise.
module.exports.list_targets = function() { module.exports.list_targets = function() {
return exec('android list targets', os.tmpdir()) return exec('android list targets')
.then(function(output) { .then(function(output) {
var target_out = output.split('\n'); var target_out = output.split('\n');
var targets = []; var targets = [];
@@ -153,7 +156,7 @@ module.exports.start = function(emulator_ID) {
.then(function(list) { .then(function(list) {
started_emulators = list; started_emulators = list;
num_started = started_emulators.length; num_started = started_emulators.length;
if (!emulator_ID) { if (typeof emulator_ID === 'undefined') {
return self.list_images() return self.list_images()
.then(function(emulator_list) { .then(function(emulator_list) {
if (emulator_list.length > 0) { if (emulator_list.length > 0) {
@@ -164,11 +167,11 @@ module.exports.start = function(emulator_ID) {
return emulator_ID; return emulator_ID;
}); });
} else { } else {
var androidCmd = check_reqs.getAbsoluteAndroidCmd(); return Q.reject('ERROR : No emulator images (avds) found, if you would like to create an\n' +
return Q.reject('ERROR : No emulator images (avds) found.\n' + ' avd follow the instructions provided here:\n' +
'1. Download desired System Image by running: ' + androidCmd + ' sdk\n' + ' http://developer.android.com/tools/devices/index.html\n' +
'2. Create an AVD by running: ' + androidCmd + ' avd\n' + ' Or run \'android create avd --name <name> --target <targetID>\'\n' +
'HINT: For a faster emulator, use an Intel System Image and install the HAXM device driver\n'); ' in on the command line.');
} }
}); });
} else { } else {
@@ -202,7 +205,7 @@ module.exports.start = function(emulator_ID) {
console.log('BOOT COMPLETE'); console.log('BOOT COMPLETE');
//unlock screen //unlock screen
return exec('adb -s ' + emulator_id + ' shell input keyevent 82', os.tmpdir()); return exec('adb -s ' + emulator_id + ' shell input keyevent 82');
}).then(function() { }).then(function() {
//return the new emulator id for the started emulators //return the new emulator id for the started emulators
return emulator_id; return emulator_id;
@@ -232,7 +235,7 @@ module.exports.wait_for_emulator = function(num_running) {
*/ */
module.exports.wait_for_boot = function(emulator_id) { module.exports.wait_for_boot = function(emulator_id) {
var self = this; var self = this;
return exec('adb -s ' + emulator_id + ' shell getprop init.svc.bootanim', os.tmpdir()) return exec('adb -s ' + emulator_id + ' shell getprop init.svc.bootanim')
.then(function(output) { .then(function(output) {
if (output.match(/stopped/)) { if (output.match(/stopped/)) {
return; return;
@@ -274,7 +277,14 @@ module.exports.create_image = function(name, target) {
} }
} }
module.exports.resolveTarget = function(target) { /*
* Installs a previously built application on the emulator and launches it.
* If no target is specified, then it picks one.
* If no started emulators are found, error out.
* Returns a promise.
*/
module.exports.install = function(target) {
var self = this;
return this.list_started() return this.list_started()
.then(function(emulator_list) { .then(function(emulator_list) {
if (emulator_list.length < 1) { if (emulator_list.length < 1) {
@@ -282,55 +292,33 @@ module.exports.resolveTarget = function(target) {
} }
// default emulator // default emulator
target = target || emulator_list[0]; target = typeof target !== 'undefined' ? target : emulator_list[0];
if (emulator_list.indexOf(target) < 0) { if (emulator_list.indexOf(target) < 0) {
return Q.reject('Unable to find target \'' + target + '\'. Failed to deploy to emulator.'); return Q.reject('Unable to find target \'' + target + '\'. Failed to deploy to emulator.');
} }
return build.detectArchitecture(target)
.then(function(arch) {
return {target:target, arch:arch, isEmulator:true};
});
});
};
/*
* Installs a previously built application on the emulator and launches it.
* If no target is specified, then it picks one.
* If no started emulators are found, error out.
* Returns a promise.
*/
module.exports.install = function(target, buildResults) {
return Q().then(function() {
if (target && typeof target == 'object') {
return target;
}
return module.exports.resolveTarget(target);
}).then(function(resolvedTarget) {
var apk_path = build.findBestApkForArchitecture(buildResults, resolvedTarget.arch);
console.log('Installing app on emulator...'); console.log('Installing app on emulator...');
console.log('Using apk: ' + apk_path); var apk_path = build.get_apk();
return exec('adb -s ' + resolvedTarget.target + ' install -r "' + apk_path + '"', os.tmpdir()) return exec('adb -s ' + target + ' install -r "' + apk_path + '"');
.then(function(output) { }).then(function(output) {
if (output.match(/Failure/)) { if (output.match(/Failure/)) {
return Q.reject('Failed to install apk to emulator: ' + output); return Q.reject('Failed to install apk to emulator: ' + output);
} }
return Q(); return Q();
}, function(err) { }, function(err) {
return Q.reject('Failed to install apk to emulator: ' + err); return Q.reject('Failed to install apk to emulator: ' + err);
}).then(function() { }).then(function() {
//unlock screen //unlock screen
return exec('adb -s ' + resolvedTarget.target + ' shell input keyevent 82', os.tmpdir()); return exec('adb -s ' + target + ' shell input keyevent 82');
}).then(function() { }).then(function() {
// launch the application // launch the application
console.log('Launching application...'); console.log('Launching application...');
var launchName = appinfo.getActivityName(); var launchName = appinfo.getActivityName();
cmd = 'adb -s ' + resolvedTarget.target + ' shell am start -W -a android.intent.action.MAIN -n ' + launchName; cmd = 'adb -s ' + target + ' shell am start -W -a android.intent.action.MAIN -n ' + launchName;
return exec(cmd, os.tmpdir()); return exec(cmd);
}).then(function(output) { }).then(function(output) {
console.log('LAUNCH SUCCESS'); console.log('LAUNCH SUCCESS');
}, function(err) { }, function(err) {
return Q.reject('Failed to launch app on emulator: ' + err); return Q.reject('Failed to launch app on emulator: ' + err);
});
}); });
} }

View File

@@ -21,7 +21,6 @@
var shell = require('shelljs'), var shell = require('shelljs'),
path = require('path'), path = require('path'),
os = require('os'),
Q = require('q'), Q = require('q'),
child_process = require('child_process'), child_process = require('child_process'),
ROOT = path.join(__dirname, '..', '..'); ROOT = path.join(__dirname, '..', '..');
@@ -33,7 +32,7 @@ var shell = require('shelljs'),
module.exports.run = function() { module.exports.run = function() {
var cmd = 'adb logcat | grep -v nativeGetEnabledTags'; var cmd = 'adb logcat | grep -v nativeGetEnabledTags';
var d = Q.defer(); var d = Q.defer();
var adb = child_process.spawn('adb', ['logcat'], {cwd: os.tmpdir()}); var adb = child_process.spawn('adb', ['logcat']);
adb.stdout.on('data', function(data) { adb.stdout.on('data', function(data) {
var lines = data ? data.toString().split('\n') : []; var lines = data ? data.toString().split('\n') : [];

View File

@@ -1,79 +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.
*/
// GENERATED FILE! DO NOT EDIT!
buildscript {
repositories {
mavenCentral()
}
// Switch the Android Gradle plugin version requirement depending on the
// installed version of Gradle. This dependency is documented at
// http://tools.android.com/tech-docs/new-build-system/version-compatibility
// and https://issues.apache.org/jira/browse/CB-8143
if (gradle.gradleVersion >= "2.2") {
dependencies {
classpath 'com.android.tools.build:gradle:1.0.0+'
}
} else if (gradle.gradleVersion >= "2.1") {
dependencies {
classpath 'com.android.tools.build:gradle:0.14.0+'
}
} else {
dependencies {
classpath 'com.android.tools.build:gradle:0.12.0+'
}
}
}
apply plugin: 'android-library'
dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
debugCompile project(path: ":CordovaLib", configuration: "debug")
releaseCompile project(path: ":CordovaLib", configuration: "release")
}
android {
compileSdkVersion cdvCompileSdkVersion
buildToolsVersion cdvBuildToolsVersion
publishNonDefault true
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_6
targetCompatibility JavaVersion.VERSION_1_6
}
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
}
}
}
if (file('build-extras.gradle').exists()) {
apply from: 'build-extras.gradle'
}

View File

@@ -23,129 +23,110 @@ var path = require('path'),
build = require('./build'), build = require('./build'),
emulator = require('./emulator'), emulator = require('./emulator'),
device = require('./device'), device = require('./device'),
shell = require('shelljs'),
Q = require('q'); Q = require('q');
/* /*
* Runs the application on a device if available. * Runs the application on a device if available.
* If no device is found, it will use a started emulator. * If not device is found, it will use a started emulator.
* If no started emulators are found it will attempt to start an avd. * If no started emulators are found it will attempt to start an avd.
* If no avds are found it will error out. * If no avds are found it will error out.
* Returns a promise. * Returns a promise.
*/ */
module.exports.run = function(args) { module.exports.run = function(args) {
var buildFlags = []; var build_type;
var install_target; var install_target;
var list = false;
for (var i=2; i<args.length; i++) { for (var i=2; i<args.length; i++) {
if (/^--(debug|release|ant|gradle|nobuild|versionCode=|minSdkVersion=|gradleArg=)/.exec(args[i])) { if (args[i] == '--debug') {
buildFlags.push(args[i]); build_type = '--debug';
} else if (args[i] == '--release') {
build_type = '--release';
} else if (args[i] == '--nobuild') {
build_type = '--nobuild';
} else if (args[i] == '--device') { } else if (args[i] == '--device') {
install_target = '--device'; install_target = '--device';
} else if (args[i] == '--emulator') { } else if (args[i] == '--emulator') {
install_target = '--emulator'; install_target = '--emulator';
} else if (/^--target=/.exec(args[i])) { } else if (args[i].substring(0, 9) == '--target=') {
install_target = args[i].substring(9, args[i].length); install_target = args[i].substring(9, args[i].length);
} else if (args[i] == '--list') {
list = true;
} else { } else {
console.warn('Option \'' + args[i] + '\' not recognized (ignoring).'); console.error('ERROR : Run option \'' + args[i] + '\' not recognized.');
process.exit(2);
} }
} }
if (list) { return build.run(build_type).then(function() {
var output = ''; if (install_target == '--device') {
var temp = ''; return device.install();
if (!install_target) {
output += 'Available Android Devices:\n';
temp = shell.exec(path.join(__dirname, 'list-devices'), {silent:true}).output;
temp = temp.replace(/^(?=[^\s])/gm, '\t');
output += temp;
output += 'Available Android Virtual Devices:\n';
temp = shell.exec(path.join(__dirname, 'list-emulator-images'), {silent:true}).output;
temp = temp.replace(/^(?=[^\s])/gm, '\t');
output += temp;
} else if (install_target == '--emulator') { } else if (install_target == '--emulator') {
output += 'Available Android Virtual Devices:\n'; return emulator.list_started().then(function(started) {
temp = shell.exec(path.join(__dirname, 'list-emulator-images'), {silent:true}).output; var p = started && started.length > 0 ? Q() : emulator.start();
temp = temp.replace(/^(?=[^\s])/gm, '\t'); return p.then(function() { emulator.install(); });
output += temp; });
} else if (install_target == '--device') { } else if (install_target) {
output += 'Available Android Devices:\n'; var devices, started_emulators, avds;
temp = shell.exec(path.join(__dirname, 'list-devices'), {silent:true}).output; return device.list()
temp = temp.replace(/^(?=[^\s])/gm, '\t'); .then(function(res) {
output += temp; devices = res;
} return emulator.list_started();
console.log(output); }).then(function(res) {
return; started_emulators = res;
} return emulator.list_images();
}).then(function(res) {
return Q() avds = res;
.then(function() { if (devices.indexOf(install_target) > -1) {
if (!install_target) { return device.install(install_target);
} else if (started_emulators.indexOf(install_target) > -1) {
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); });
}
}
return Q.reject('Target \'' + install_target + '\' not found, unable to run project');
}
});
} else {
// no target given, deploy to device if available, otherwise use the emulator. // no target given, deploy to device if available, otherwise use the emulator.
return device.list() return device.list()
.then(function(device_list) { .then(function(device_list) {
if (device_list.length > 0) { if (device_list.length > 0) {
console.log('WARNING : No target specified, deploying to device \'' + device_list[0] + '\'.'); console.log('WARNING : No target specified, deploying to device \'' + device_list[0] + '\'.');
install_target = device_list[0]; return device.install(device_list[0]);
} else { } else {
console.log('WARNING : No target specified, deploying to emulator'); return emulator.list_started()
install_target = '--emulator'; .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]);
}).then(function() { } else {
if (install_target == '--device') { console.log('WARNING : No started emulators found, starting an emulator.');
return device.resolveTarget(null); return emulator.best_image()
} else if (install_target == '--emulator') { .then(function(best_avd) {
// Give preference to any already started emulators. Else, start one. if(best_avd) {
return emulator.list_started() return emulator.start(best_avd.name)
.then(function(started) { .then(function(emulator_ID) {
return started && started.length > 0 ? started[0] : emulator.start(); console.log('WARNING : No target specified, deploying to emulator \'' + emulator_ID + '\'.');
}).then(function(emulatorId) { return emulator.install(emulator_ID);
return emulator.resolveTarget(emulatorId); });
}); } else {
} return emulator.start();
// They specified a specific device/emulator ID. }
return device.list()
.then(function(devices) {
if (devices.indexOf(install_target) > -1) {
return device.resolveTarget(install_target);
}
return emulator.list_started()
.then(function(started_emulators) {
if (started_emulators.indexOf(install_target) > -1) {
return emulator.resolveTarget(install_target);
}
return emulator.list_images()
.then(function(avds) {
// if target emulator isn't started, then start it.
for (avd in avds) {
if (avds[avd].name == install_target) {
return emulator.start(install_target)
.then(function(emulatorId) {
return emulator.resolveTarget(emulatorId);
}); });
} }
} });
return Q.reject('Target \'' + install_target + '\' not found, unable to run project'); }
});
}); });
}); }
}).then(function(resolvedTarget) {
return build.run(buildFlags, resolvedTarget).then(function(buildResults) {
if (resolvedTarget.isEmulator) {
return emulator.install(resolvedTarget, buildResults);
}
return device.install(resolvedTarget, buildResults);
});
}); });
} }
module.exports.help = function(args) { module.exports.help = function() {
console.log('Usage: ' + path.relative(process.cwd(), args[1]) + ' [options]'); console.log('Usage: ' + path.relative(process.cwd(), args[0]) + ' [options]');
console.log('Build options :'); console.log('Build options :');
console.log(' --debug : Builds project in debug mode'); console.log(' --debug : Builds project in debug mode');
console.log(' --release : Builds project in release mode'); console.log(' --release : Builds project in release mode');

View File

@@ -26,15 +26,13 @@ var isWindows = process.platform.slice(0, 3) == 'win';
// Takes a command and optional current working directory. // Takes a command and optional current working directory.
module.exports = function(cmd, args, opt_cwd) { module.exports = function(cmd, args, opt_cwd) {
var d = Q.defer(); var d = Q.defer();
var opts = { cwd: opt_cwd, stdio: 'inherit' };
try { try {
// Work around spawn not being able to find .bat files. // Work around spawn not being able to find .bat files.
if (isWindows) { if (isWindows) {
args = [['/s', '/c', '"' + [cmd].concat(args).map(function(a){if (/^[^"].* .*[^"]/.test(a)) return '"' + a + '"'; return a;}).join(' ')+'"'].join(' ')]; args.unshift('/s', '/c', cmd);
cmd = 'cmd'; cmd = 'cmd';
opts.windowsVerbatimArguments = true;
} }
var child = child_process.spawn(cmd, args, opts); var child = child_process.spawn(cmd, args, {cwd: opt_cwd, stdio: 'inherit'});
child.on('exit', function(code) { child.on('exit', function(code) {
if (code) { if (code) {
d.reject('Error code ' + code + ' for command: ' + cmd + ' with args: ' + args); d.reject('Error code ' + code + ' for command: ' + cmd + ' with args: ' + args);
@@ -47,4 +45,5 @@ module.exports = function(cmd, args, opt_cwd) {
d.reject(e); d.reject(e);
} }
return d.promise; return d.promise;
}; }

View File

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

View File

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

View File

@@ -22,7 +22,7 @@ package __ID__;
import android.os.Bundle; import android.os.Bundle;
import org.apache.cordova.*; import org.apache.cordova.*;
public class __ACTIVITY__ extends CordovaActivity public class __ACTIVITY__ extends CordovaActivity
{ {
@Override @Override
public void onCreate(Bundle savedInstanceState) public void onCreate(Bundle savedInstanceState)
@@ -30,6 +30,8 @@ public class __ACTIVITY__ extends CordovaActivity
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
super.init(); super.init();
// Set by <content src="index.html" /> in config.xml // 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 specific language governing permissions and limitations
under the License. under the License.
--> -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <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"> package="__PACKAGE__" android:versionName="1.0" android:versionCode="1" android:hardwareAccelerated="true">
<supports-screens <supports-screens
android:largeScreens="true" android:largeScreens="true"
android:normalScreens="true" android:normalScreens="true"
@@ -29,16 +29,15 @@
/> />
<uses-permission android:name="android.permission.INTERNET" /> <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" <application android:icon="@drawable/icon" android:label="@string/app_name"
android:hardwareAccelerated="true" android:supportsRtl="true"> android:hardwareAccelerated="true">
<activity android:name="__ACTIVITY__" <activity android:name="__ACTIVITY__" android:label="@string/app_name" android:launchMode="singleTop"
android:label="@string/activity_name"
android:launchMode="singleTop"
android:theme="@android:style/Theme.Black.NoTitleBar" android:theme="@android:style/Theme.Black.NoTitleBar"
android:windowSoftInputMode="adjustResize"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale"> android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale">
<intent-filter android:label="@string/launcher_name"> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
@@ -46,4 +45,4 @@
</application> </application>
<uses-sdk android:minSdkVersion="10" android:targetSdkVersion="__APILEVEL__"/> <uses-sdk android:minSdkVersion="10" android:targetSdkVersion="__APILEVEL__"/>
</manifest> </manifest>

View File

@@ -1,285 +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.
*/
// GENERATED FILE! DO NOT EDIT!
apply plugin: 'android'
buildscript {
repositories {
mavenCentral()
}
// Switch the Android Gradle plugin version requirement depending on the
// installed version of Gradle. This dependency is documented at
// http://tools.android.com/tech-docs/new-build-system/version-compatibility
// and https://issues.apache.org/jira/browse/CB-8143
if (gradle.gradleVersion >= "2.2") {
dependencies {
classpath 'com.android.tools.build:gradle:1.0.0+'
}
} else if (gradle.gradleVersion >= "2.1") {
dependencies {
classpath 'com.android.tools.build:gradle:0.14.0+'
}
} else {
dependencies {
classpath 'com.android.tools.build:gradle:0.12.0+'
}
}
}
// Allow plugins to declare Maven dependencies via build-extras.gradle.
repositories {
mavenCentral()
}
task wrapper(type: Wrapper) {
gradleVersion = '2.2.1'
}
// Configuration properties. Set these via environment variables, build-extras.gradle, or gradle.properties.
// Refer to: http://www.gradle.org/docs/current/userguide/tutorial_this_and_that.html
ext {
apply from: 'CordovaLib/cordova.gradle'
// The value for android.compileSdkVersion.
if (!project.hasProperty('cdvCompileSdkVersion')) {
cdvCompileSdkVersion = privateHelpers.getProjectTarget()
}
// The value for android.buildToolsVersion.
if (!project.hasProperty('cdvBuildToolsVersion')) {
cdvBuildToolsVersion = privateHelpers.findLatestInstalledBuildTools()
}
// Sets the versionCode to the given value.
if (!project.hasProperty('cdvVersionCode')) {
cdvVersionCode = null
}
// Sets the minSdkVersion to the given value.
if (!project.hasProperty('cdvMinSdkVersion')) {
cdvMinSdkVersion = null
}
// Whether to build architecture-specific APKs.
if (!project.hasProperty('cdvBuildMultipleApks')) {
cdvBuildMultipleApks = false
}
// .properties files to use for release signing.
if (!project.hasProperty('cdvReleaseSigningPropertiesFile')) {
cdvReleaseSigningPropertiesFile = null
}
// .properties files to use for debug signing.
if (!project.hasProperty('cdvDebugSigningPropertiesFile')) {
cdvDebugSigningPropertiesFile = null
}
// Set by build.js script.
if (!project.hasProperty('cdvBuildArch')) {
cdvBuildArch = null
}
}
def hasBuildExtras = file('build-extras.gradle').exists()
if (hasBuildExtras) {
apply from: 'build-extras.gradle'
}
def computeBuildTargetName(debugBuild) {
def ret = 'assemble'
if (cdvBuildMultipleApks && cdvBuildArch) {
def arch = cdvBuildArch == 'arm' ? 'armv7' : cdvBuildArch
ret += '' + arch.toUpperCase().charAt(0) + arch.substring(1);
}
return ret + (debugBuild ? 'Debug' : 'Release')
}
// Make cdvBuild a task that depends on the debug/arch-sepecific task.
task cdvBuildDebug
cdvBuildDebug.dependsOn {
return computeBuildTargetName(true)
}
task cdvBuildRelease
cdvBuildRelease.dependsOn {
return computeBuildTargetName(false)
}
task cdvPrintProps << {
println('cdvCompileSdkVersion=' + cdvCompileSdkVersion)
println('cdvBuildToolsVersion=' + cdvBuildToolsVersion)
println('cdvVersionCode=' + cdvVersionCode)
println('cdvMinSdkVersion=' + cdvMinSdkVersion)
println('cdvBuildMultipleApks=' + cdvBuildMultipleApks)
println('cdvReleaseSigningPropertiesFile=' + cdvReleaseSigningPropertiesFile)
println('cdvDebugSigningPropertiesFile=' + cdvDebugSigningPropertiesFile)
println('cdvBuildArch=' + cdvBuildArch)
println('computedVersionCode=' + android.defaultConfig.versionCode)
if (android.productFlavors.has('armv7')) {
println('computedArmv7VersionCode=' + android.productFlavors.armv7.versionCode)
}
if (android.productFlavors.has('x86')) {
println('computedx86VersionCode=' + android.productFlavors.x86.versionCode)
}
}
// PLUGIN GRADLE EXTENSIONS START
// PLUGIN GRADLE EXTENSIONS END
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']
}
}
def versionCodeOverride = cdvVersionCode ? Integer.parseInt(cdvVersionCode) : null
def minSdkVersionOverride = cdvMinSdkVersion ? Integer.parseInt(cdvMinSdkVersion) : null
defaultConfig {
versionCode versionCodeOverride ?: Integer.parseInt("" + privateHelpers.extractIntFromManifest("versionCode") + "0")
if (minSdkVersionOverride != null) {
minSdkVersion minSdkVersionOverride
}
}
compileSdkVersion cdvCompileSdkVersion
buildToolsVersion cdvBuildToolsVersion
if (Boolean.valueOf(cdvBuildMultipleApks)) {
productFlavors {
armv7 {
versionCode versionCodeOverride ?: defaultConfig.versionCode + 2
ndk {
abiFilters "armeabi-v7a", ""
}
}
x86 {
versionCode versionCodeOverride ?: defaultConfig.versionCode + 4
ndk {
abiFilters "x86", ""
}
}
all {
ndk {
abiFilters "all", ""
}
}
}
} else if (!versionCodeOverride) {
def minSdkVersion = minSdkVersionOverride ?: privateHelpers.extractIntFromManifest("minSdkVersion")
// Vary versionCode by the two most common API levels:
// 14 is ICS, which is the lowest API level for many apps.
// 20 is Lollipop, which is the lowest API level for the updatable system webview.
if (minSdkVersion >= 20) {
defaultConfig.versionCode += 9
} else if (minSdkVersion >= 14) {
defaultConfig.versionCode += 8
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_6
targetCompatibility JavaVersion.VERSION_1_6
}
if (cdvReleaseSigningPropertiesFile) {
signingConfigs {
release {
// These must be set or Gradle will complain (even if they are overridden).
keyAlias = ""
keyPassword = "__unset" // And these must be set to non-empty in order to have the signing step added to the task graph.
storeFile = null
storePassword = "__unset"
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
addSigningProps(cdvReleaseSigningPropertiesFile, signingConfigs.release)
}
if (cdvDebugSigningPropertiesFile) {
addSigningProps(cdvDebugSigningPropertiesFile, signingConfigs.debug)
}
}
dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
// SUB-PROJECT DEPENDENCIES START
// SUB-PROJECT DEPENDENCIES END
}
def promptForReleaseKeyPassword() {
if (!cdvReleaseSigningPropertiesFile) {
return;
}
if ('__unset'.equals(android.signingConfigs.release.storePassword)) {
android.signingConfigs.release.storePassword = privateHelpers.promptForPassword('Enter key store password: ')
}
if ('__unset'.equals(android.signingConfigs.release.keyPassword)) {
android.signingConfigs.release.keyPassword = privateHelpers.promptForPassword('Enter key password: ');
}
}
gradle.taskGraph.whenReady { taskGraph ->
taskGraph.getAllTasks().each() { task ->
if (task.name == 'validateReleaseSigning') {
promptForReleaseKeyPassword()
}
}
}
def addSigningProps(propsFilePath, signingConfig) {
def propsFile = file(propsFilePath)
def props = new Properties()
propsFile.withReader { reader ->
props.load(reader)
}
def storeFile = new File(privateHelpers.ensureValueExists(propsFilePath, props, 'storeFile'))
if (!storeFile.isAbsolute()) {
storeFile = RelativePath.parse(true, storeFile.toString()).getFile(propsFile.getParentFile())
}
if (!storeFile.exists()) {
throw new FileNotFoundException('Keystore file does not exist: ' + storeFile.getAbsolutePath())
}
signingConfig.keyAlias = privateHelpers.ensureValueExists(propsFilePath, props, 'keyAlias')
signingConfig.keyPassword = props.get('keyPassword', signingConfig.keyPassword)
signingConfig.storeFile = storeFile
signingConfig.storePassword = props.get('storePassword', signingConfig.storePassword)
def storeType = props.get('storeType')
if (!storeType) {
def filename = storeFile.getName().toLowerCase();
if (filename.endsWith('.p12') || filename.endsWith('.pfx')) {
storeType = 'pkcs12'
}
}
if (storeType) {
signingConfig.storeType = storeType
}
}
// This can be defined within build-extras.gradle as:
// ext.postBuildExtras = { ... code here ... }
if (hasProperty('postBuildExtras')) {
postBuildExtras()
}

View File

@@ -13,7 +13,6 @@
</path> </path>
<echo message="Set jars path to: ${toString:project.all.jars.path}"/> <echo message="Set jars path to: ${toString:project.all.jars.path}"/>
</target> </target>
<!-- Rename AndroidManifest.xml so that Eclipse's import wizard doesn't detect ant-build as a project -->
<target name="-post-build"> <target name="-post-build">
<move file="ant-build/AndroidManifest.xml" tofile="ant-build/AndroidManifest.cordova.xml" failonerror="false" overwrite="true" /> <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" /> <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-build
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"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<!-- App label shown within list of installed apps, battery & network usage screens. -->
<string name="app_name">__NAME__</string> <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> </resources>

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
// Platform: android // Platform: android
// 24ab6855470f2dc0662624b597c98585e56a1666 // 3.5.0-dev-81f9a00
/* /*
Licensed to the Apache Software Foundation (ASF) under one Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file or more contributor license agreements. See the NOTICE file
@@ -19,7 +19,7 @@
under the License. under the License.
*/ */
;(function() { ;(function() {
var PLATFORM_VERSION_BUILD_LABEL = '3.7.2'; var CORDOVA_JS_BUILD_LABEL = '3.5.0-dev-81f9a00';
// file: src/scripts/require.js // file: src/scripts/require.js
/*jshint -W079 */ /*jshint -W079 */
@@ -175,8 +175,7 @@ function createEvent(type, data) {
var cordova = { var cordova = {
define:define, define:define,
require:require, require:require,
version:PLATFORM_VERSION_BUILD_LABEL, version:CORDOVA_JS_BUILD_LABEL,
platformVersion:PLATFORM_VERSION_BUILD_LABEL,
platformId:platform.id, platformId:platform.id,
/** /**
* Methods to add/remove your own addEventListener hijacking on document + window. * Methods to add/remove your own addEventListener hijacking on document + window.
@@ -263,7 +262,11 @@ var cordova = {
* Called by native code when returning successful result from an action. * Called by native code when returning successful result from an action.
*/ */
callbackSuccess: function(callbackId, args) { callbackSuccess: function(callbackId, args) {
cordova.callbackFromNative(callbackId, true, args.status, [args.message], args.keepCallback); try {
cordova.callbackFromNative(callbackId, true, args.status, [args.message], args.keepCallback);
} catch (e) {
console.log("Error in error callback: " + callbackId + " = "+e);
}
}, },
/** /**
@@ -272,39 +275,29 @@ var cordova = {
callbackError: function(callbackId, args) { callbackError: function(callbackId, args) {
// TODO: Deprecate callbackSuccess and callbackError in favour of callbackFromNative. // TODO: Deprecate callbackSuccess and callbackError in favour of callbackFromNative.
// Derive success from status. // Derive success from status.
cordova.callbackFromNative(callbackId, false, args.status, [args.message], args.keepCallback); try {
cordova.callbackFromNative(callbackId, false, args.status, [args.message], args.keepCallback);
} catch (e) {
console.log("Error in error callback: " + callbackId + " = "+e);
}
}, },
/** /**
* Called by native code when returning the result from an action. * Called by native code when returning the result from an action.
*/ */
callbackFromNative: function(callbackId, isSuccess, status, args, keepCallback) { callbackFromNative: function(callbackId, success, status, args, keepCallback) {
try { var callback = cordova.callbacks[callbackId];
var callback = cordova.callbacks[callbackId]; if (callback) {
if (callback) { if (success && status == cordova.callbackStatus.OK) {
if (isSuccess && status == cordova.callbackStatus.OK) { callback.success && callback.success.apply(null, args);
callback.success && callback.success.apply(null, args); } else if (!success) {
} else if (!isSuccess) { callback.fail && callback.fail.apply(null, args);
callback.fail && callback.fail.apply(null, args); }
}
/* // Clear callback if not expecting any more results
else if (!keepCallback) {
Note, this case is intentionally not caught. delete cordova.callbacks[callbackId];
this can happen if isSuccess is true, but callbackStatus is NO_RESULT
which is used to remove a callback from the list without calling the callbacks
typically keepCallback is false in this case
*/
// Clear callback if not expecting any more results
if (!keepCallback) {
delete cordova.callbacks[callbackId];
}
} }
}
catch (err) {
var msg = "Error in " + (isSuccess ? "Success" : "Error") + " callbackId: " + callbackId + " : " + err;
console && console.log && console.log(msg);
cordova.fireWindowEvent("cordovacallbackerror", { 'message': msg });
throw err;
} }
}, },
addConstructor: function(func) { addConstructor: function(func) {
@@ -351,18 +344,18 @@ define("cordova/android/promptbasednativeapi", function(require, exports, module
/** /**
* Implements the API of ExposedJsApi.java, but uses prompt() to communicate. * 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 = { module.exports = {
exec: function(bridgeSecret, service, action, callbackId, argsJson) { exec: function(service, action, callbackId, argsJson) {
return prompt(argsJson, 'gap:'+JSON.stringify([bridgeSecret, service, action, callbackId])); return prompt(argsJson, 'gap:'+JSON.stringify([service, action, callbackId]));
}, },
setNativeToJsBridgeMode: function(bridgeSecret, value) { setNativeToJsBridgeMode: function(value) {
prompt(value, 'gap_bridge_mode:' + bridgeSecret); prompt(value, 'gap_bridge_mode:');
}, },
retrieveJsMessages: function(bridgeSecret, fromOnlineEvent) { retrieveJsMessages: function(fromOnlineEvent) {
return prompt(+fromOnlineEvent, 'gap_poll:' + bridgeSecret); return prompt(+fromOnlineEvent, 'gap_poll:');
} }
}; };
@@ -515,14 +508,9 @@ function each(objects, func, context) {
function clobber(obj, key, value) { function clobber(obj, key, value) {
exports.replaceHookForTesting(obj, key); exports.replaceHookForTesting(obj, key);
var needsProperty = false; obj[key] = value;
try {
obj[key] = value;
} catch (e) {
needsProperty = true;
}
// Getters can only be overridden by getters. // Getters can only be overridden by getters.
if (needsProperty || obj[key] !== value) { if (obj[key] !== value) {
utils.defineGetter(obj, key, function() { utils.defineGetter(obj, key, function() {
return value; return value;
}); });
@@ -637,6 +625,7 @@ var utils = require('cordova/utils'),
* onDeviceReady* User event fired to indicate that Cordova is ready * onDeviceReady* User event fired to indicate that Cordova is ready
* onResume User event fired to indicate a start/resume lifecycle event * onResume User event fired to indicate a start/resume lifecycle event
* onPause User event fired to indicate a pause lifecycle event * onPause User event fired to indicate a pause lifecycle event
* onDestroy* Internal event fired when app is being destroyed (User should use window.onunload event, not this one).
* *
* The events marked with an * are sticky. Once they have fired, they will stay in the fired state. * The events marked with an * are sticky. Once they have fired, they will stay in the fired state.
* All listeners that subscribe after the event is fired will be executed right away. * All listeners that subscribe after the event is fired will be executed right away.
@@ -836,7 +825,6 @@ channel.createSticky('onNativeReady');
channel.createSticky('onCordovaReady'); channel.createSticky('onCordovaReady');
// Event to indicate that all automatically loaded JS plugins are loaded and ready. // Event to indicate that all automatically loaded JS plugins are loaded and ready.
// FIXME remove this
channel.createSticky('onPluginsReady'); channel.createSticky('onPluginsReady');
// Event to indicate that Cordova is ready // Event to indicate that Cordova is ready
@@ -848,6 +836,9 @@ channel.create('onResume');
// Event to indicate a pause lifecycle event // Event to indicate a pause lifecycle event
channel.create('onPause'); channel.create('onPause');
// Event to indicate a destroy lifecycle event
channel.createSticky('onDestroy');
// Channels that must fire before "deviceready" is fired. // Channels that must fire before "deviceready" is fired.
channel.waitForInitialization('onCordovaReady'); channel.waitForInitialization('onCordovaReady');
channel.waitForInitialization('onDOMContentLoaded'); channel.waitForInitialization('onDOMContentLoaded');
@@ -877,10 +868,13 @@ var cordova = require('cordova'),
nativeApiProvider = require('cordova/android/nativeapiprovider'), nativeApiProvider = require('cordova/android/nativeapiprovider'),
utils = require('cordova/utils'), utils = require('cordova/utils'),
base64 = require('cordova/base64'), base64 = require('cordova/base64'),
channel = require('cordova/channel'),
jsToNativeModes = { jsToNativeModes = {
PROMPT: 0, 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 = { nativeToJsModes = {
// Polls for messages using the JS->Native bridge. // Polls for messages using the JS->Native bridge.
@@ -900,17 +894,9 @@ var cordova = require('cordova'),
jsToNativeBridgeMode, // Set lazily. jsToNativeBridgeMode, // Set lazily.
nativeToJsBridgeMode = nativeToJsModes.ONLINE_EVENT, nativeToJsBridgeMode = nativeToJsModes.ONLINE_EVENT,
pollEnabled = false, pollEnabled = false,
messagesFromNative = [], messagesFromNative = [];
bridgeSecret = -1;
function androidExec(success, fail, service, action, args) { 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. // Set default bridge modes if they have not already been set.
// By default, we use the failsafe, since addJavascriptInterface breaks too often // By default, we use the failsafe, since addJavascriptInterface breaks too often
if (jsToNativeBridgeMode === undefined) { if (jsToNativeBridgeMode === undefined) {
@@ -931,35 +917,29 @@ function androidExec(success, fail, service, action, args) {
cordova.callbacks[callbackId] = {success:success, fail:fail}; cordova.callbacks[callbackId] = {success:success, fail:fail};
} }
var messages = nativeApiProvider.get().exec(bridgeSecret, service, action, callbackId, argsJson); if (jsToNativeBridgeMode == jsToNativeModes.LOCATION_CHANGE) {
// If argsJson was received by Java as null, try again with the PROMPT bridge mode. window.location = 'http://cdv_exec/' + service + '#' + action + '#' + callbackId + '#' + argsJson;
// 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 { } 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() { function pollOnceFromOnlineEvent() {
pollOnce(true); pollOnce(true);
} }
function pollOnce(opt_fromOnlineEvent) { function pollOnce(opt_fromOnlineEvent) {
if (bridgeSecret < 0) { var msg = nativeApiProvider.get().retrieveJsMessages(!!opt_fromOnlineEvent);
// 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);
androidExec.processMessages(msg); androidExec.processMessages(msg);
} }
@@ -1009,10 +989,7 @@ androidExec.setNativeToJsBridgeMode = function(mode) {
nativeToJsBridgeMode = mode; nativeToJsBridgeMode = mode;
// Tell the native side to switch modes. // Tell the native side to switch modes.
// Otherwise, it will be set by androidExec.init() nativeApiProvider.get().setNativeToJsBridgeMode(mode);
if (bridgeSecret >= 0) {
nativeApiProvider.get().setNativeToJsBridgeMode(bridgeSecret, mode);
}
if (mode == nativeToJsModes.POLLING) { if (mode == nativeToJsModes.POLLING) {
pollEnabled = true; pollEnabled = true;
@@ -1020,37 +997,6 @@ androidExec.setNativeToJsBridgeMode = function(mode) {
} }
}; };
function buildPayload(payload, message) {
var payloadKind = message.charAt(0);
if (payloadKind == 's') {
payload.push(message.slice(1));
} else if (payloadKind == 't') {
payload.push(true);
} else if (payloadKind == 'f') {
payload.push(false);
} else if (payloadKind == 'N') {
payload.push(null);
} else if (payloadKind == 'n') {
payload.push(+message.slice(1));
} else if (payloadKind == 'A') {
var data = message.slice(1);
payload.push(base64.toArrayBuffer(data));
} else if (payloadKind == 'S') {
payload.push(window.atob(message.slice(1)));
} else if (payloadKind == 'M') {
var multipartMessages = message.slice(1);
while (multipartMessages !== "") {
var spaceIdx = multipartMessages.indexOf(' ');
var msgLen = +multipartMessages.slice(0, spaceIdx);
var multipartMessage = multipartMessages.substr(spaceIdx + 1, msgLen);
multipartMessages = multipartMessages.slice(spaceIdx + msgLen + 1);
buildPayload(payload, multipartMessage);
}
} else {
payload.push(JSON.parse(message));
}
}
// Processes a single message, as encoded by NativeToJsMessageQueue.java. // Processes a single message, as encoded by NativeToJsMessageQueue.java.
function processMessage(message) { function processMessage(message) {
try { try {
@@ -1064,10 +1010,32 @@ function processMessage(message) {
var status = +message.slice(2, spaceIdx); var status = +message.slice(2, spaceIdx);
var nextSpaceIdx = message.indexOf(' ', spaceIdx + 1); var nextSpaceIdx = message.indexOf(' ', spaceIdx + 1);
var callbackId = message.slice(spaceIdx + 1, nextSpaceIdx); var callbackId = message.slice(spaceIdx + 1, nextSpaceIdx);
var payloadMessage = message.slice(nextSpaceIdx + 1); var payloadKind = message.charAt(nextSpaceIdx + 1);
var payload = []; var payload;
buildPayload(payload, payloadMessage); if (payloadKind == 's') {
cordova.callbackFromNative(callbackId, success, status, payload, keepCallback); payload = message.slice(nextSpaceIdx + 2);
} else if (payloadKind == 't') {
payload = true;
} else if (payloadKind == 'f') {
payload = false;
} else if (payloadKind == 'N') {
payload = null;
} else if (payloadKind == 'n') {
payload = +message.slice(nextSpaceIdx + 2);
} else if (payloadKind == 'A') {
var data = message.slice(nextSpaceIdx + 2);
var bytes = window.atob(data);
var arraybuffer = new Uint8Array(bytes.length);
for (var i = 0; i < bytes.length; i++) {
arraybuffer[i] = bytes.charCodeAt(i);
}
payload = arraybuffer.buffer;
} else if (payloadKind == 'S') {
payload = window.atob(message.slice(nextSpaceIdx + 2));
} else {
payload = JSON.parse(message.slice(nextSpaceIdx + 1));
}
cordova.callbackFromNative(callbackId, success, status, [payload], keepCallback);
} else { } else {
console.log("processMessage failed: invalid message: " + JSON.stringify(message)); console.log("processMessage failed: invalid message: " + JSON.stringify(message));
} }
@@ -1169,7 +1137,6 @@ var cordova = require('cordova');
var modulemapper = require('cordova/modulemapper'); var modulemapper = require('cordova/modulemapper');
var platform = require('cordova/platform'); var platform = require('cordova/platform');
var pluginloader = require('cordova/pluginloader'); var pluginloader = require('cordova/pluginloader');
var utils = require('cordova/utils');
var platformInitChannelsArray = [channel.onNativeReady, channel.onPluginsReady]; var platformInitChannelsArray = [channel.onNativeReady, channel.onPluginsReady];
@@ -1202,18 +1169,10 @@ function replaceNavigator(origNavigator) {
if (typeof origNavigator[key] == 'function') { if (typeof origNavigator[key] == 'function') {
newNavigator[key] = origNavigator[key].bind(origNavigator); newNavigator[key] = origNavigator[key].bind(origNavigator);
} }
else {
(function(k) {
utils.defineGetterSetter(newNavigator,key,function() {
return origNavigator[k];
});
})(key);
}
} }
} }
return newNavigator; return newNavigator;
} }
if (window.navigator) { if (window.navigator) {
window.navigator = replaceNavigator(window.navigator); window.navigator = replaceNavigator(window.navigator);
} }
@@ -1286,119 +1245,6 @@ channel.join(function() {
}, platformInitChannelsArray); }, platformInitChannelsArray);
});
// file: src/common/init_b.js
define("cordova/init_b", function(require, exports, module) {
var channel = require('cordova/channel');
var cordova = require('cordova');
var platform = require('cordova/platform');
var utils = require('cordova/utils');
var platformInitChannelsArray = [channel.onDOMContentLoaded, channel.onNativeReady];
// setting exec
cordova.exec = require('cordova/exec');
function logUnfiredChannels(arr) {
for (var i = 0; i < arr.length; ++i) {
if (arr[i].state != 2) {
console.log('Channel not fired: ' + arr[i].type);
}
}
}
window.setTimeout(function() {
if (channel.onDeviceReady.state != 2) {
console.log('deviceready has not fired after 5 seconds.');
logUnfiredChannels(platformInitChannelsArray);
logUnfiredChannels(channel.deviceReadyChannelsArray);
}
}, 5000);
// Replace navigator before any modules are required(), to ensure it happens as soon as possible.
// We replace it so that properties that can't be clobbered can instead be overridden.
function replaceNavigator(origNavigator) {
var CordovaNavigator = function() {};
CordovaNavigator.prototype = origNavigator;
var newNavigator = new CordovaNavigator();
// This work-around really only applies to new APIs that are newer than Function.bind.
// Without it, APIs such as getGamepads() break.
if (CordovaNavigator.bind) {
for (var key in origNavigator) {
if (typeof origNavigator[key] == 'function') {
newNavigator[key] = origNavigator[key].bind(origNavigator);
}
else {
(function(k) {
utils.defineGetterSetter(newNavigator,key,function() {
return origNavigator[k];
});
})(key);
}
}
}
return newNavigator;
}
if (window.navigator) {
window.navigator = replaceNavigator(window.navigator);
}
if (!window.console) {
window.console = {
log: function(){}
};
}
if (!window.console.warn) {
window.console.warn = function(msg) {
this.log("warn: " + msg);
};
}
// Register pause, resume and deviceready channels as events on document.
channel.onPause = cordova.addDocumentEventHandler('pause');
channel.onResume = cordova.addDocumentEventHandler('resume');
channel.onDeviceReady = cordova.addStickyDocumentEventHandler('deviceready');
// Listen for DOMContentLoaded and notify our channel subscribers.
if (document.readyState == 'complete' || document.readyState == 'interactive') {
channel.onDOMContentLoaded.fire();
} else {
document.addEventListener('DOMContentLoaded', function() {
channel.onDOMContentLoaded.fire();
}, false);
}
// _nativeReady is global variable that the native side can set
// to signify that the native code is ready. It is a global since
// it may be called before any cordova JS is ready.
if (window._nativeReady) {
channel.onNativeReady.fire();
}
// Call the platform-specific initialization.
platform.bootstrap && platform.bootstrap();
/**
* Create all cordova objects once native side is ready.
*/
channel.join(function() {
platform.initialize && platform.initialize();
// Fire event to notify that all objects are created
channel.onCordovaReady.fire();
// Fire onDeviceReady event once page has fully loaded, all
// constructors have run and cordova info has been received from native
// side.
channel.join(function() {
require('cordova').fireDocumentEvent('deviceready');
}, channel.deviceReadyChannelsArray);
}, platformInitChannelsArray);
}); });
// file: src/common/modulemapper.js // file: src/common/modulemapper.js
@@ -1513,72 +1359,34 @@ module.exports = {
exec = require('cordova/exec'), exec = require('cordova/exec'),
modulemapper = require('cordova/modulemapper'); modulemapper = require('cordova/modulemapper');
// Get the shared secret needed to use the bridge. // Tell the native code that a page change has occurred.
exec.init(); exec(null, null, 'PluginManager', 'startup', []);
// Tell the JS that the native side is ready.
channel.onNativeReady.fire();
// TODO: Extract this as a proper plugin. // TODO: Extract this as a proper plugin.
modulemapper.clobbers('cordova/plugin/android/app', 'navigator.app'); modulemapper.clobbers('cordova/plugin/android/app', 'navigator.app');
var APP_PLUGIN_NAME = Number(cordova.platformVersion.split('.')[0]) >= 4 ? 'CoreAndroid' : 'App';
// Inject a listener for the backbutton on the document. // Inject a listener for the backbutton on the document.
var backButtonChannel = cordova.addDocumentEventHandler('backbutton'); var backButtonChannel = cordova.addDocumentEventHandler('backbutton');
backButtonChannel.onHasSubscribersChange = function() { backButtonChannel.onHasSubscribersChange = function() {
// If we just attached the first handler or detached the last handler, // If we just attached the first handler or detached the last handler,
// let native know we need to override the back button. // let native know we need to override the back button.
exec(null, null, APP_PLUGIN_NAME, "overrideBackbutton", [this.numHandlers == 1]); exec(null, null, "App", "overrideBackbutton", [this.numHandlers == 1]);
}; };
// Add hardware MENU and SEARCH button handlers // Add hardware MENU and SEARCH button handlers
cordova.addDocumentEventHandler('menubutton'); cordova.addDocumentEventHandler('menubutton');
cordova.addDocumentEventHandler('searchbutton'); 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_PLUGIN_NAME, "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. // Let native code know we are all done on the JS side.
// Native code will then un-hide the WebView. // Native code will then un-hide the WebView.
channel.onCordovaReady.subscribe(function() { channel.onCordovaReady.subscribe(function() {
exec(onMessageFromNative, null, APP_PLUGIN_NAME, 'messageChannel', []); exec(null, null, "App", "show", []);
exec(null, null, APP_PLUGIN_NAME, "show", []);
}); });
} }
}; };
function onMessageFromNative(msg) {
var cordova = require('cordova');
var action = msg.action;
switch (action)
{
// Button events
case 'backbutton':
case 'menubutton':
case 'searchbutton':
// App life cycle events
case 'pause':
case 'resume':
// Keyboard events
case 'hidekeyboard':
case 'showkeyboard':
// Volume events
case 'volumedownbutton':
case 'volumeupbutton':
cordova.fireDocumentEvent(action);
break;
default:
throw new Error('Unknown event action ' + action);
}
}
}); });
// file: src/android/plugin/android/app.js // file: src/android/plugin/android/app.js
@@ -1648,21 +1456,6 @@ module.exports = {
exec(null, null, "App", "overrideBackbutton", [override]); 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. * Exit and terminate the application.
*/ */
@@ -1756,11 +1549,11 @@ function handlePluginsObject(path, moduleList, finishPluginLoading) {
function findCordovaPath() { function findCordovaPath() {
var path = null; var path = null;
var scripts = document.getElementsByTagName('script'); var scripts = document.getElementsByTagName('script');
var term = '/cordova.js'; var term = 'cordova.js';
for (var n = scripts.length-1; n>-1; n--) { for (var n = scripts.length-1; n>-1; n--) {
var src = scripts[n].src.replace(/\?.*$/, ''); // Strip any query param (CB-6007). var src = scripts[n].src.replace(/\?.*$/, ''); // Strip any query param (CB-6007).
if (src.indexOf(term) == (src.length - term.length)) { 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; break;
} }
} }
@@ -1977,4 +1770,4 @@ window.cordova = require('cordova');
require('cordova/init'); require('cordova/init');
})(); })();

View File

@@ -1,68 +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()
}
// Switch the Android Gradle plugin version requirement depending on the
// installed version of Gradle. This dependency is documented at
// http://tools.android.com/tech-docs/new-build-system/version-compatibility
// and https://issues.apache.org/jira/browse/CB-8143
if (gradle.gradleVersion >= "2.2") {
dependencies {
classpath 'com.android.tools.build:gradle:1.0.0+'
}
} else if (gradle.gradleVersion >= "2.1") {
dependencies {
classpath 'com.android.tools.build:gradle:0.14.0+'
}
} else {
dependencies {
classpath 'com.android.tools.build:gradle:0.12.0+'
}
}
}
apply plugin: 'android-library'
android {
compileSdkVersion cdvCompileSdkVersion
buildToolsVersion cdvBuildToolsVersion
publishNonDefault true
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_6
targetCompatibility JavaVersion.VERSION_1_6
}
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

@@ -1,165 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
import java.util.regex.Pattern
import groovy.swing.SwingBuilder
String doEnsureValueExists(filePath, props, key) {
if (props.get(key) == null) {
throw new GradleException(filePath + ': Missing key required "' + key + '"')
}
return props.get(key)
}
String doGetProjectTarget() {
def props = new Properties()
file('project.properties').withReader { reader ->
props.load(reader)
}
return doEnsureValueExists('project.properties', props, 'target')
}
String[] getAvailableBuildTools() {
def buildToolsDir = new File(getAndroidSdkDir(), "build-tools")
buildToolsDir.list()
.findAll { it ==~ /[0-9.]+/ }
.sort { a, b -> compareVersions(b, a) }
}
String doFindLatestInstalledBuildTools(String minBuildToolsVersion) {
def availableBuildToolsVersions
try {
availableBuildToolsVersions = getAvailableBuildTools()
} catch (e) {
println "An exception occurred while trying to find the Android build tools."
throw e
}
if (availableBuildToolsVersions.length > 0) {
def highestBuildToolsVersion = availableBuildToolsVersions[0]
if (compareVersions(highestBuildToolsVersion, minBuildToolsVersion) < 0) {
throw new RuntimeException(
"No usable Android build tools found. Highest installed version is " +
highestBuildToolsVersion + "; minimum version required is " +
minBuildToolsVersion + ".")
}
highestBuildToolsVersion
} else {
throw new RuntimeException(
"No installed build tools found. Please install the Android build tools version " +
minBuildToolsVersion + " or higher.")
}
}
// Return the first non-zero result of subtracting version list elements
// pairwise. If they are all identical, return the difference in length of
// the two lists.
int compareVersionList(Collection aParts, Collection bParts) {
def pairs = ([aParts, bParts]).transpose()
pairs.findResult(aParts.size()-bParts.size()) {it[0] - it[1] != 0 ? it[0] - it[1] : null}
}
// Compare two version strings, such as "19.0.0" and "18.1.1.0". If all matched
// elements are identical, the longer version is the largest by this method.
// Examples:
// "19.0.0" > "19"
// "19.0.1" > "19.0.0"
// "19.1.0" > "19.0.1"
// "19" > "18.999.999"
int compareVersions(String a, String b) {
def aParts = a.tokenize('.').collect {it.toInteger()}
def bParts = b.tokenize('.').collect {it.toInteger()}
compareVersionList(aParts, bParts)
}
String getAndroidSdkDir() {
def rootDir = project.rootDir
def androidSdkDir = null
String envVar = System.getenv("ANDROID_HOME")
def localProperties = new File(rootDir, 'local.properties')
String systemProperty = System.getProperty("android.home")
if (envVar != null) {
androidSdkDir = envVar
} else if (localProperties.exists()) {
Properties properties = new Properties()
localProperties.withInputStream { instr ->
properties.load(instr)
}
def sdkDirProp = properties.getProperty('sdk.dir')
if (sdkDirProp != null) {
androidSdkDir = sdkDirProp
} else {
sdkDirProp = properties.getProperty('android.dir')
if (sdkDirProp != null) {
androidSdkDir = (new File(rootDir, sdkDirProp)).getAbsolutePath()
}
}
}
if (androidSdkDir == null && systemProperty != null) {
androidSdkDir = systemProperty
}
if (androidSdkDir == null) {
throw new RuntimeException(
"Unable to determine Android SDK directory.")
}
androidSdkDir
}
def doExtractIntFromManifest(name) {
def manifestFile = file(android.sourceSets.main.manifest.srcFile)
def pattern = Pattern.compile(name + "=\"(\\d+)\"")
def matcher = pattern.matcher(manifestFile.getText())
matcher.find()
return Integer.parseInt(matcher.group(1))
}
def doPromptForPassword(msg) {
if (System.console() == null) {
def ret = null
new SwingBuilder().edt {
dialog(modal: true, title: 'Enter password', alwaysOnTop: true, resizable: false, locationRelativeTo: null, pack: true, show: true) {
vbox {
label(text: msg)
def input = passwordField()
button(defaultButton: true, text: 'OK', actionPerformed: {
ret = input.password;
dispose();
})
}
}
}
if (!ret) {
throw new GradleException('User canceled build')
}
return new String(ret)
} else {
return System.console().readPassword('\n' + msg);
}
}
// Properties exported here are visible to all plugins.
ext {
// These helpers are shared, but are not guaranteed to be stable / unchanged.
privateHelpers = {}
privateHelpers.getProjectTarget = { doGetProjectTarget() }
privateHelpers.findLatestInstalledBuildTools = { doFindLatestInstalledBuildTools('19.1.0') }
privateHelpers.extractIntFromManifest = { name -> doExtractIntFromManifest(name) }
privateHelpers.promptForPassword = { msg -> doPromptForPassword(msg) }
privateHelpers.ensureValueExists = { filePath, props, key -> doEnsureValueExists(filePath, props, key) }
}

View File

@@ -10,7 +10,7 @@
# Indicates whether an apk should be generated for each density. # Indicates whether an apk should be generated for each density.
split.density=false split.density=false
# Project target. # Project target.
target=android-21 target=android-19
apk-configurations= apk-configurations=
renderscript.opt.level=O0 renderscript.opt.level=O0
android.library=true android.library=true

View File

@@ -30,25 +30,13 @@
Apache Cordova Team Apache Cordova Team
</author> </author>
<!-- Allow access to arbitrary URLs in the Cordova WebView. This is a <access origin="*"/>
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"/>
<!-- <content src="http://mysite.com/myapp.html" /> for external pages --> <!-- <content src="http://mysite.com/myapp.html" /> for external pages -->
<content src="index.html" /> <content src="index.html" />
<preference name="loglevel" value="DEBUG" /> <preference name="loglevel" value="DEBUG" />
<!-- <!--
<preference name="splashscreen" value="resourceName" /> <preference name="splashscreen" value="resourceName" />
<preference name="backgroundColor" value="0xFFF" /> <preference name="backgroundColor" value="0xFFF" />
@@ -56,4 +44,8 @@
<preference name="InAppBrowserStorageEnabled" value="true" /> <preference name="InAppBrowserStorageEnabled" value="true" />
<preference name="disallowOverscroll" 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> </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; package com.squareup.okhttp.internal.spdy;
public enum ErrorCode { 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; package com.squareup.okhttp.internal.spdy;
import java.io.DataInputStream; 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; package com.squareup.okhttp.internal.spdy;
import com.squareup.okhttp.internal.Util; import com.squareup.okhttp.internal.Util;

View File

@@ -0,0 +1,400 @@
/*
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.create();
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.create();
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.create();
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,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);
}
}

File diff suppressed because it is too large Load Diff

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.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.telephony.TelephonyManager; import android.telephony.TelephonyManager;
import android.view.KeyEvent;
import java.util.HashMap; import java.util.HashMap;
@@ -41,29 +40,22 @@ import java.util.HashMap;
*/ */
public class App extends CordovaPlugin { public class App extends CordovaPlugin {
public static final String PLUGIN_NAME = "App";
protected static final String TAG = "CordovaApp"; protected static final String TAG = "CordovaApp";
private BroadcastReceiver telephonyReceiver; private BroadcastReceiver telephonyReceiver;
private CallbackContext messageChannel;
/**
* Send an event to be fired on the Javascript side.
*
* @param action The name of the event to be fired
*/
public void fireJavascriptEvent(String action) {
sendEventMessage(action);
}
/** /**
* Sets the context of the Command. This can then be used to do things like * Sets the context of the Command. This can then be used to do things like
* get file paths associated with the Activity. * 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 initialize(CordovaInterface cordova, CordovaWebView webView) {
public void pluginInitialize() { super.initialize(cordova, webView);
this.initTelephonyReceiver(); this.initTelephonyReceiver();
} }
/** /**
* Executes the request and returns PluginResult. * Executes the request and returns PluginResult.
* *
@@ -111,11 +103,6 @@ public class App extends CordovaPlugin {
else if (action.equals("exitApp")) { else if (action.equals("exitApp")) {
this.exitApp(); this.exitApp();
} }
else if (action.equals("messageChannel")) {
messageChannel = callbackContext;
return true;
}
callbackContext.sendPluginResult(new PluginResult(status, result)); callbackContext.sendPluginResult(new PluginResult(status, result));
return true; return true;
} catch (JSONException e) { } catch (JSONException e) {
@@ -203,11 +190,7 @@ public class App extends CordovaPlugin {
* Clear page history for the app. * Clear page history for the app.
*/ */
public void clearHistory() { public void clearHistory() {
cordova.getActivity().runOnUiThread(new Runnable() { this.webView.clearHistory();
public void run() {
webView.clearHistory();
}
});
} }
/** /**
@@ -230,7 +213,7 @@ public class App extends CordovaPlugin {
*/ */
public void overrideBackbutton(boolean override) { public void overrideBackbutton(boolean override) {
LOG.i("App", "WARNING: Back Button Default Behavior will be overridden. The backbutton event will be fired!"); 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);
} }
/** /**
@@ -242,12 +225,7 @@ public class App extends CordovaPlugin {
*/ */
public void overrideButton(String button, boolean override) { public void overrideButton(String button, boolean override) {
LOG.i("App", "WARNING: Volume Button Default Behavior will be overridden. The volume event will be fired!"); LOG.i("App", "WARNING: Volume Button Default Behavior will be overridden. The volume event will be fired!");
if (button.equals("volumeup")) { webView.bindButton(button, override);
webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_UP, override);
}
else if (button.equals("volumedown")) {
webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_DOWN, override);
}
} }
/** /**
@@ -256,7 +234,7 @@ public class App extends CordovaPlugin {
* @return boolean * @return boolean
*/ */
public boolean isBackbuttonOverridden() { public boolean isBackbuttonOverridden() {
return webView.isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK); return webView.isBackButtonBound();
} }
/** /**
@@ -265,7 +243,7 @@ public class App extends CordovaPlugin {
public void exitApp() { public void exitApp() {
this.webView.postMessage("exit", null); this.webView.postMessage("exit", null);
} }
/** /**
* Listen for telephony events: RINGING, OFFHOOK and IDLE * Listen for telephony events: RINGING, OFFHOOK and IDLE
@@ -303,21 +281,7 @@ public class App extends CordovaPlugin {
}; };
// Register the receiver // Register the receiver
webView.getContext().registerReceiver(this.telephonyReceiver, intentFilter); this.cordova.getActivity().registerReceiver(this.telephonyReceiver, intentFilter);
}
private void sendEventMessage(String action) {
JSONObject obj = new JSONObject();
try {
obj.put("action", action);
} catch (JSONException e) {
LOG.e(TAG, "Failed to create event message", e);
}
PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, obj);
pluginResult.setKeepCallback(true);
if (messageChannel != null) {
messageChannel.sendPluginResult(pluginResult);
}
} }
/* /*
@@ -326,6 +290,6 @@ public class App extends CordovaPlugin {
*/ */
public void onDestroy() public void onDestroy()
{ {
webView.getContext().unregisterReceiver(this.telephonyReceiver); this.cordova.getActivity().unregisterReceiver(this.telephonyReceiver);
} }
} }

View File

@@ -19,33 +19,183 @@
package org.apache.cordova; 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.app.Activity;
import android.content.res.XmlResourceParser;
import android.graphics.Color;
import android.util.Log; import android.util.Log;
@Deprecated // Use Whitelist, CordovaPrefences, etc. directly.
public class Config { 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) { public static void init(Activity action) {
parser = new ConfigXmlParser(); //Just re-initialize this! Seriously, we lose this all the time
parser.parse(action); self = new Config(action);
parser.getPreferences().setPreferencesBundle(action.getIntent().getExtras());
} }
// Intended to be used for testing only; creates an empty configuration. // Intended to be used for testing only; creates an empty configuration.
public static void init() { public static void init() {
if (parser == null) { if (self == null) {
parser = new ConfigXmlParser(); 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) * Add entry to approved list of URLs (whitelist)
* *
@@ -53,11 +203,11 @@ public class Config {
* @param subdomains T=include all subdomains under origin * @param subdomains T=include all subdomains under origin
*/ */
public static void addWhiteListEntry(String origin, boolean subdomains) { 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)?"); Log.e(TAG, "Config was not initialised. Did you forget to Config.init(this)?");
return; return;
} }
parser.getInternalWhitelist().addWhiteListEntry(origin, subdomains); self.whitelist.addWhiteListEntry(origin, subdomains);
} }
/** /**
@@ -67,55 +217,17 @@ public class Config {
* @return true if whitelisted * @return true if whitelisted
*/ */
public static boolean isUrlWhiteListed(String url) { 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)?"); Log.e(TAG, "Config was not initialised. Did you forget to Config.init(this)?");
return false; return false;
} }
return parser.getInternalWhitelist().isUrlWhiteListed(url); return self.whitelist.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);
} }
public static String getStartUrl() { public static String getStartUrl() {
if (parser == null) { if (self == null || self.startUrl == null) {
return "file:///android_asset/www/index.html"; return "file:///android_asset/www/index.html";
} }
return parser.getLaunchUrl(); return self.startUrl;
}
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;
} }
} }

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,192 +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.security.SecureRandom;
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;
private String appContentUrlPrefix;
public CordovaBridge(PluginManager pluginManager, NativeToJsMessageQueue jsMessageQueue, String packageName) {
this.pluginManager = pluginManager;
this.jsMessageQueue = jsMessageQueue;
this.appContentUrlPrefix = "content://" + packageName + ".";
}
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) {
Log.e(LOG_TAG, "Bridge access attempt with wrong secret token, possibly from malicious code. Disabling exec() bridge!");
clearBridgeSecret();
throw new IllegalAccessException();
}
return true;
}
/** Called on page transitions */
void clearBridgeSecret() {
expectedBridgeSecret = -1;
}
/** Called by cordova.js to initialize the bridge. */
int generateBridgeSecret() {
SecureRandom randGen = new SecureRandom();
expectedBridgeSecret = randGen.nextInt(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(this.appContentUrlPrefix) ||
(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;
}
}

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

@@ -1,366 +1,14 @@
/*
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; package org.apache.cordova;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.LOG;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.webkit.ConsoleMessage;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.ValueCallback; 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;
/** public interface CordovaChromeClient {
* 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 static final int FILECHOOSER_RESULTCODE = 5173; int FILECHOOSER_RESULTCODE = 0;
private String TAG = "CordovaLog";
private long MAX_QUOTA = 100 * 1024 * 1024;
protected CordovaInterface cordova;
protected CordovaWebView appView;
// the video progress view void setWebView(CordovaWebView appView);
private View mVideoProgressView;
//Keep track of last AlertDialog showed
private AlertDialog lastHandledDialog;
@Deprecated ValueCallback<Uri> getValueCallback();
public CordovaChromeClient(CordovaInterface cordova) {
this.cordova = cordova;
}
public CordovaChromeClient(CordovaInterface ctx, CordovaWebView app) {
this.cordova = ctx;
this.appView = app;
}
@Deprecated
public void setWebView(CordovaWebView view) {
this.appView = view;
}
/**
* Tell the client to display a javascript alert dialog.
*
* @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;
}
});
lastHandledDialog = dlg.show();
return true;
}
/**
* Tell the client to display a confirm dialog to the user.
*
* @param view
* @param url
* @param message
* @param result
* @see Other implementation in the Dialogs plugin.
*/
@Override
public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity());
dlg.setMessage(message);
dlg.setTitle("Confirm");
dlg.setCancelable(true);
dlg.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
result.confirm();
}
});
dlg.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
result.cancel();
}
});
dlg.setOnCancelListener(
new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
result.cancel();
}
});
dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {
//DO NOTHING
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK)
{
result.cancel();
return false;
}
else
return true;
}
});
lastHandledDialog = 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();
}
});
lastHandledDialog = 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;
}
// <input type=file> support:
// openFileChooser() is for pre KitKat and in KitKat mr1 (it's known broken in KitKat).
// For Lollipop, we use onShowFileChooser().
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(final ValueCallback<Uri> uploadMsg, String acceptType, String capture)
{
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
cordova.startActivityForResult(new CordovaPlugin() {
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
Uri result = intent == null || resultCode != Activity.RESULT_OK ? null : intent.getData();
Log.d(TAG, "Receive file chooser URL: " + result);
uploadMsg.onReceiveValue(result);
}
}, intent, FILECHOOSER_RESULTCODE);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean onShowFileChooser(WebView webView, final ValueCallback<Uri[]> filePathsCallback, final WebChromeClient.FileChooserParams fileChooserParams) {
Intent intent = fileChooserParams.createIntent();
try {
cordova.startActivityForResult(new CordovaPlugin() {
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
Uri[] result = WebChromeClient.FileChooserParams.parseResult(resultCode, intent);
Log.d(TAG, "Receive file chooser URL: " + result);
filePathsCallback.onReceiveValue(result);
}
}, intent, FILECHOOSER_RESULTCODE);
} catch (ActivityNotFoundException e) {
Log.w("No activity found to handle file chooser intent.", e);
filePathsCallback.onReceiveValue(null);
}
return true;
}
public void destroyLastDialog(){
if(lastHandledDialog != null){
lastHandledDialog.cancel();
}
}
} }

View File

@@ -1,96 +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.security.Principal;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import android.webkit.ClientCertRequest;
/**
* Implementation of the ICordovaClientCertRequest for Android WebView.
*/
public class CordovaClientCertRequest implements ICordovaClientCertRequest {
private final ClientCertRequest request;
public CordovaClientCertRequest(ClientCertRequest request) {
this.request = request;
}
/**
* Cancel this request
*/
public void cancel()
{
request.cancel();
}
/*
* Returns the host name of the server requesting the certificate.
*/
public String getHost()
{
return request.getHost();
}
/*
* Returns the acceptable types of asymmetric keys (can be null).
*/
public String[] getKeyTypes()
{
return request.getKeyTypes();
}
/*
* Returns the port number of the server requesting the certificate.
*/
public int getPort()
{
return request.getPort();
}
/*
* Returns the acceptable certificate issuers for the certificate matching the private key (can be null).
*/
public Principal[] getPrincipals()
{
return request.getPrincipals();
}
/*
* Ignore the request for now. Do not remember user's choice.
*/
public void ignore()
{
request.ignore();
}
/*
* Proceed with the specified private key and client certificate chain. Remember the user's positive choice and use it for future requests.
*
* @param privateKey The privateKey
* @param chain The certificate chain
*/
public void proceed(PrivateKey privateKey, X509Certificate[] chain)
{
request.proceed(privateKey, chain);
}
}

View File

@@ -1,51 +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.webkit.HttpAuthHandler;
/**
* Specifies interface for HTTP auth handler object which is used to handle auth requests and
* specifying user credentials.
*/
public class CordovaHttpAuthHandler implements ICordovaHttpAuthHandler {
private final HttpAuthHandler handler;
public CordovaHttpAuthHandler(HttpAuthHandler handler) {
this.handler = handler;
}
/**
* Instructs the WebView to cancel the authentication request.
*/
public void cancel () {
this.handler.cancel();
}
/**
* Instructs the WebView to proceed with the authentication with the given credentials.
*
* @param username
* @param password
*/
public void proceed (String username, String password) {
this.handler.proceed(username, password);
}
}

View File

@@ -32,39 +32,20 @@ import android.net.Uri;
* Plugins must extend this class and override one of the execute methods. * Plugins must extend this class and override one of the execute methods.
*/ */
public class CordovaPlugin { public class CordovaPlugin {
@Deprecated // This is never set.
public String id; public String id;
public CordovaWebView webView; public CordovaWebView webView; // WebView object
public CordovaInterface cordova; public CordovaInterface cordova;
protected CordovaPreferences preferences;
/** /**
* Call this after constructing to initialize the plugin. * @param cordova The context of the main Activity.
* Final because we want to be able to change args without breaking plugins. * @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; assert this.cordova == null;
this.cordova = cordova; this.cordova = cordova;
this.webView = webView; 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. * Executes the request.
* *
@@ -198,34 +179,4 @@ public class CordovaPlugin {
*/ */
public void onReset() { public void onReset() {
} }
/**
* Called when the system received an HTTP authentication request. Plugin can use
* the supplied HttpAuthHandler to process this auth challenge.
*
* @param view The WebView that is initiating the callback
* @param handler The HttpAuthHandler used to set the WebView's response
* @param host The host requiring authentication
* @param realm The realm for which authentication is required
*
* @return Returns True if plugin will resolve this auth challenge, otherwise False
*
*/
public boolean onReceivedHttpAuthRequest(CordovaWebView view, ICordovaHttpAuthHandler handler, String host, String realm) {
return false;
}
/**
* Called when he system received an SSL client certificate request. Plugin can use
* the supplied ClientCertRequest to process this certificate challenge.
*
* @param view The WebView that is initiating the callback
* @param request The client certificate request
*
* @return Returns True if plugin will resolve this auth challenge, otherwise False
*
*/
public boolean onReceivedClientCertRequest(CordovaWebView view, ICordovaClientCertRequest request) {
return false;
}
} }

View File

@@ -1,96 +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);
}
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);
}
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);
}
return defaultValue;
}
public String getString(String name, String defaultValue) {
name = name.toLowerCase(Locale.ENGLISH);
String value = prefs.get(name);
if (value != null) {
return value;
}
return defaultValue;
}
}

View File

@@ -84,7 +84,7 @@ public class CordovaResourceApi {
// Creating this is light-weight. // Creating this is light-weight.
private static OkHttpClient httpClient = new OkHttpClient(); private static OkHttpClient httpClient = new OkHttpClient();
static Thread jsThread; public static Thread jsThread;
private final AssetManager assetManager; private final AssetManager assetManager;
private final ContentResolver contentResolver; private final ContentResolver contentResolver;
@@ -106,7 +106,6 @@ public class CordovaResourceApi {
return threadCheckingEnabled; return threadCheckingEnabled;
} }
public static int getUriType(Uri uri) { public static int getUriType(Uri uri) {
assertNonRelative(uri); assertNonRelative(uri);
String scheme = uri.getScheme(); String scheme = uri.getScheme();
@@ -200,8 +199,6 @@ public class CordovaResourceApi {
return null; return null;
} }
//This already exists
private String getMimeTypeFromPath(String path) { private String getMimeTypeFromPath(String path) {
String extension = path; String extension = path;
int lastDot = extension.lastIndexOf('.'); 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. * @return Never returns null.
* @throws Throws an InvalidArgumentException for relative URIs. Relative URIs should be * @throws Throws an InvalidArgumentException for relative URIs. Relative URIs should be
* resolved before being passed into this function. * 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. * @return Never returns null.
* @throws Throws an InvalidArgumentException for relative URIs. Relative URIs should be * @throws Throws an InvalidArgumentException for relative URIs. Relative URIs should be
* resolved before being passed into this function. * 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;
}
}

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,395 +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; 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; void onReceivedError(CordovaWebView me, int i, String string, String url);
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.ClientCertRequest;
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 (if specified)
AuthenticationToken token = this.getAuthenticationToken(host, realm);
if (token != null) {
handler.proceed(token.getUserName(), token.getPassword());
return;
}
// Check if there is some plugin which can resolve this auth challenge
PluginManager pluginManager = this.appView.pluginManager;
if (pluginManager != null && pluginManager.onReceivedHttpAuthRequest(this.appView, new CordovaHttpAuthHandler(handler), host, realm)) {
this.appView.loadUrlTimeout++;
return;
}
// By default handle 401 like we'd normally do!
super.onReceivedHttpAuthRequest(view, handler, host, realm);
}
/**
* On received client cert request.
* The method forwards the request to any running plugins before using the default implementation.
*
* @param view
* @param request
*/
@Override
@TargetApi(21)
public void onReceivedClientCertRequest (WebView view, ClientCertRequest request)
{
// Check if there is some plugin which can resolve this certificate request
PluginManager pluginManager = this.appView.pluginManager;
if (pluginManager != null && pluginManager.onReceivedClientCertRequest(this.appView, new CordovaClientCertRequest(request))) {
this.appView.loadUrlTimeout++;
return;
}
// By default pass to WebViewClient
super.onReceivedClientCertRequest(view, request);
}
/**
* 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 url is not about:blank (CB-8317).
if (!isCurrentlyLoading && !url.startsWith("about:")) {
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();
}
} }

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; package org.apache.cordova;
import android.webkit.JavascriptInterface;
import org.json.JSONException; import org.json.JSONException;
/** import android.webkit.JavascriptInterface;
* Contains APIs that the JS can call. All functions in here should also have
* an equivalent entry in CordovaChromeClient.java, and be added to /*
* cordova-js/lib/android/plugin/android/promptbasednativeapi.js * Any exposed Javascript API MUST implement these three things!
*/ */
/* package */ class ExposedJsApi {
public interface ExposedJsApi {
private CordovaBridge bridge;
public ExposedJsApi(CordovaBridge bridge) {
this.bridge = bridge;
}
@JavascriptInterface @JavascriptInterface
public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException { public String exec(String service, String action, String callbackId, String arguments) throws JSONException;
return bridge.jsExec(bridgeSecret, service, action, callbackId, arguments); public void setNativeToJsBridgeMode(int value);
} public String retrieveJsMessages(boolean fromOnlineEvent);
@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);
}
} }

View File

@@ -1,66 +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.security.Principal;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
/**
* Specifies interface for handling certificate requests.
*/
public interface ICordovaClientCertRequest {
/**
* Cancel this request
*/
public void cancel();
/*
* Returns the host name of the server requesting the certificate.
*/
public String getHost();
/*
* Returns the acceptable types of asymmetric keys (can be null).
*/
public String[] getKeyTypes();
/*
* Returns the port number of the server requesting the certificate.
*/
public int getPort();
/*
* Returns the acceptable certificate issuers for the certificate matching the private key (can be null).
*/
public Principal[] getPrincipals();
/*
* Ignore the request for now. Do not remember user's choice.
*/
public void ignore();
/*
* Proceed with the specified private key and client certificate chain. Remember the user's positive choice and use it for future requests.
*
* @param privateKey The privateKey
* @param chain The certificate chain
*/
public void proceed(PrivateKey privateKey, X509Certificate[] chain);
}

View File

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

View File

@@ -18,7 +18,10 @@
*/ */
package org.apache.cordova; package org.apache.cordova;
import org.apache.cordova.LOG;
import android.content.Context; import android.content.Context;
//import android.view.View.MeasureSpec;
import android.widget.LinearLayout; import android.widget.LinearLayout;
/** /**
@@ -33,7 +36,6 @@ public class LinearLayoutSoftKeyboardDetect extends LinearLayout {
private int screenWidth = 0; private int screenWidth = 0;
private int screenHeight = 0; private int screenHeight = 0;
private CordovaActivity app = null; private CordovaActivity app = null;
private App appPlugin = null;
public LinearLayoutSoftKeyboardDetect(Context context, int width, int height) { public LinearLayoutSoftKeyboardDetect(Context context, int width, int height) {
super(context); super(context);
@@ -48,7 +50,7 @@ public class LinearLayoutSoftKeyboardDetect extends LinearLayout {
* gets smaller fire a show keyboard event and when height gets bigger fire * gets smaller fire a show keyboard event and when height gets bigger fire
* a hide keyboard event. * a hide keyboard event.
* *
* Note: We are using the core App plugin to send events over the bridge to Javascript * Note: We are using app.postMessage so that this is more compatible with the API
* *
* @param widthMeasureSpec * @param widthMeasureSpec
* @param heightMeasureSpec * @param heightMeasureSpec
@@ -85,12 +87,14 @@ public class LinearLayoutSoftKeyboardDetect extends LinearLayout {
// If the height as gotten bigger then we will assume the soft keyboard has // If the height as gotten bigger then we will assume the soft keyboard has
// gone away. // gone away.
else if (height > oldHeight) { else if (height > oldHeight) {
sendEvent("hidekeyboard"); if (app != null)
app.sendJavascript("cordova.fireDocumentEvent('hidekeyboard');");
} }
// If the height as gotten smaller then we will assume the soft keyboard has // If the height as gotten smaller then we will assume the soft keyboard has
// been displayed. // been displayed.
else if (height < oldHeight) { else if (height < oldHeight) {
sendEvent("showkeyboard"); if (app != null)
app.sendJavascript("cordova.fireDocumentEvent('showkeyboard');");
} }
// Update the old height for the next event // Update the old height for the next event
@@ -98,15 +102,4 @@ public class LinearLayoutSoftKeyboardDetect extends LinearLayout {
oldWidth = width; oldWidth = width;
} }
private void sendEvent(String event) {
if (appPlugin == null) {
appPlugin = (App)app.appView.pluginManager.getPlugin(App.PLUGIN_NAME);
}
if (appPlugin == null) {
LOG.w(TAG, "Unable to fire event without existing plugin");
return;
}
appPlugin.fireJavascriptEvent(event);
}
} }

View File

@@ -35,20 +35,31 @@ import android.webkit.WebView;
public class NativeToJsMessageQueue { public class NativeToJsMessageQueue {
private static final String LOG_TAG = "JsMessageQueue"; 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 // Set this to true to force plugin results to be encoding as
// JS instead of the custom format (useful for benchmarking). // JS instead of the custom format (useful for benchmarking).
// Doesn't work for multipart messages.
private static final boolean FORCE_ENCODE_USING_EVAL = false; 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 // Disable sending back native->JS messages during an exec() when the active
// exec() is asynchronous. Set this to true when running bridge benchmarks. // 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. // 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 // This currently only chops up on message boundaries. It may be useful
// to allow it to break up messages. // to allow it to break up messages.
private static int MAX_PAYLOAD_SIZE = 50 * 1024 * 10240; 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, * 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. * the active listener will be fired if the queue is non-empty.
@@ -65,13 +76,6 @@ public class NativeToJsMessageQueue {
*/ */
private final BridgeMode[] registeredListeners; 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 CordovaInterface cordova;
private final CordovaWebView webView; private final CordovaWebView webView;
@@ -85,28 +89,22 @@ public class NativeToJsMessageQueue {
registeredListeners[3] = new PrivateApiBridgeMode(); registeredListeners[3] = new PrivateApiBridgeMode();
reset(); reset();
} }
public boolean isBridgeEnabled() {
return activeBridgeMode != null;
}
/** /**
* Changes the bridge mode. * Changes the bridge mode.
*/ */
public void setBridgeMode(int value) { public void setBridgeMode(int value) {
if (value < -1 || value >= registeredListeners.length) { if (value < 0 || value >= registeredListeners.length) {
Log.d(LOG_TAG, "Invalid NativeToJsBridgeMode: " + value); Log.d(LOG_TAG, "Invalid NativeToJsBridgeMode: " + value);
} else { } else {
BridgeMode newMode = value < 0 ? null : registeredListeners[value]; if (value != activeListenerIndex) {
if (newMode != activeBridgeMode) { Log.d(LOG_TAG, "Set native->JS mode to " + value);
Log.d(LOG_TAG, "Set native->JS mode to " + (newMode == null ? "null" : newMode.getClass().getSimpleName()));
synchronized (this) { synchronized (this) {
activeBridgeMode = newMode; activeListenerIndex = value;
if (newMode != null) { BridgeMode activeListener = registeredListeners[value];
newMode.reset(); activeListener.reset();
if (!paused && !queue.isEmpty()) { if (!paused && !queue.isEmpty()) {
newMode.onNativeToJsMessageAvailable(); activeListener.onNativeToJsMessageAvailable();
}
} }
} }
} }
@@ -119,7 +117,8 @@ public class NativeToJsMessageQueue {
public void reset() { public void reset() {
synchronized (this) { synchronized (this) {
queue.clear(); queue.clear();
setBridgeMode(-1); setBridgeMode(DEFAULT_BRIDGE_MODE);
registeredListeners[activeListenerIndex].reset();
} }
} }
@@ -143,10 +142,7 @@ public class NativeToJsMessageQueue {
*/ */
public String popAndEncode(boolean fromOnlineEvent) { public String popAndEncode(boolean fromOnlineEvent) {
synchronized (this) { synchronized (this) {
if (activeBridgeMode == null) { registeredListeners[activeListenerIndex].notifyOfFlush(fromOnlineEvent);
return null;
}
activeBridgeMode.notifyOfFlush(fromOnlineEvent);
if (queue.isEmpty()) { if (queue.isEmpty()) {
return null; return null;
} }
@@ -251,20 +247,16 @@ public class NativeToJsMessageQueue {
enqueueMessage(message); enqueueMessage(message);
} }
private void enqueueMessage(JsMessage message) { private void enqueueMessage(JsMessage message) {
synchronized (this) { synchronized (this) {
if (activeBridgeMode == null) {
Log.d(LOG_TAG, "Dropping Native->JS message due to disabled bridge");
return;
}
queue.add(message); queue.add(message);
if (!paused) { if (!paused) {
activeBridgeMode.onNativeToJsMessageAvailable(); registeredListeners[activeListenerIndex].onNativeToJsMessageAvailable();
} }
} }
} }
public void setPaused(boolean value) { public void setPaused(boolean value) {
if (paused && value) { if (paused && value) {
// This should never happen. If a use-case for it comes up, we should // This should never happen. If a use-case for it comes up, we should
@@ -274,12 +266,16 @@ public class NativeToJsMessageQueue {
paused = value; paused = value;
if (!value) { if (!value) {
synchronized (this) { synchronized (this) {
if (!queue.isEmpty() && activeBridgeMode != null) { if (!queue.isEmpty()) {
activeBridgeMode.onNativeToJsMessageAvailable(); registeredListeners[activeListenerIndex].onNativeToJsMessageAvailable();
} }
} }
} }
} }
public boolean getPaused() {
return paused;
}
private abstract class BridgeMode { private abstract class BridgeMode {
abstract void onNativeToJsMessageAvailable(); abstract void onNativeToJsMessageAvailable();
@@ -312,33 +308,23 @@ public class NativeToJsMessageQueue {
/** Uses online/offline events to tell the JS when to poll for messages. */ /** Uses online/offline events to tell the JS when to poll for messages. */
private class OnlineEventsBridgeMode extends BridgeMode { private class OnlineEventsBridgeMode extends BridgeMode {
private boolean online; private boolean online;
private boolean ignoreNextFlush; final Runnable runnable = new Runnable() {
final Runnable toggleNetworkRunnable = new Runnable() {
public void run() { public void run() {
if (!queue.isEmpty()) { if (!queue.isEmpty()) {
ignoreNextFlush = false;
webView.setNetworkAvailable(online); 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() { @Override void reset() {
cordova.getActivity().runOnUiThread(resetNetworkRunnable); online = false;
webView.setNetworkAvailable(true);
} }
@Override void onNativeToJsMessageAvailable() { @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. // Track when online/offline events are fired so that we don't fire excess events.
@Override void notifyOfFlush(boolean fromOnlineEvent) { @Override void notifyOfFlush(boolean fromOnlineEvent) {
if (fromOnlineEvent && !ignoreNextFlush) { if (fromOnlineEvent) {
online = !online; online = !online;
} }
} }
@@ -420,43 +406,53 @@ public class NativeToJsMessageQueue {
this.pluginResult = pluginResult; this.pluginResult = pluginResult;
} }
static int calculateEncodedLengthHelper(PluginResult pluginResult) {
switch (pluginResult.getMessageType()) {
case PluginResult.MESSAGE_TYPE_BOOLEAN: // f or t
case PluginResult.MESSAGE_TYPE_NULL: // N
return 1;
case PluginResult.MESSAGE_TYPE_NUMBER: // n
return 1 + pluginResult.getMessage().length();
case PluginResult.MESSAGE_TYPE_STRING: // s
return 1 + pluginResult.getStrMessage().length();
case PluginResult.MESSAGE_TYPE_BINARYSTRING:
return 1 + pluginResult.getMessage().length();
case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
return 1 + pluginResult.getMessage().length();
case PluginResult.MESSAGE_TYPE_MULTIPART:
int ret = 1;
for (int i = 0; i < pluginResult.getMultipartMessagesSize(); i++) {
int length = calculateEncodedLengthHelper(pluginResult.getMultipartMessage(i));
int argLength = String.valueOf(length).length();
ret += argLength + 1 + length;
}
return ret;
case PluginResult.MESSAGE_TYPE_JSON:
default:
return pluginResult.getMessage().length();
}
}
int calculateEncodedLength() { int calculateEncodedLength() {
if (pluginResult == null) { if (pluginResult == null) {
return jsPayloadOrCallbackId.length() + 1; return jsPayloadOrCallbackId.length() + 1;
} }
int statusLen = String.valueOf(pluginResult.getStatus()).length(); int statusLen = String.valueOf(pluginResult.getStatus()).length();
int ret = 2 + statusLen + 1 + jsPayloadOrCallbackId.length() + 1; int ret = 2 + statusLen + 1 + jsPayloadOrCallbackId.length() + 1;
return ret + calculateEncodedLengthHelper(pluginResult); switch (pluginResult.getMessageType()) {
case PluginResult.MESSAGE_TYPE_BOOLEAN: // f or t
case PluginResult.MESSAGE_TYPE_NULL: // N
ret += 1;
break;
case PluginResult.MESSAGE_TYPE_NUMBER: // n
ret += 1 + pluginResult.getMessage().length();
break;
case PluginResult.MESSAGE_TYPE_STRING: // s
ret += 1 + pluginResult.getStrMessage().length();
break;
case PluginResult.MESSAGE_TYPE_BINARYSTRING:
ret += 1 + pluginResult.getMessage().length();
break;
case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
ret += 1 + pluginResult.getMessage().length();
break;
case PluginResult.MESSAGE_TYPE_JSON:
default:
ret += pluginResult.getMessage().length();
} }
return ret;
}
void encodeAsMessage(StringBuilder sb) {
if (pluginResult == null) {
sb.append('J')
.append(jsPayloadOrCallbackId);
return;
}
int status = pluginResult.getStatus();
boolean noResult = status == PluginResult.Status.NO_RESULT.ordinal();
boolean resultOk = status == PluginResult.Status.OK.ordinal();
boolean keepCallback = pluginResult.getKeepCallback();
static void encodeAsMessageHelper(StringBuilder sb, PluginResult pluginResult) { sb.append((noResult || resultOk) ? 'S' : 'F')
.append(keepCallback ? '1' : '0')
.append(status)
.append(' ')
.append(jsPayloadOrCallbackId)
.append(' ');
switch (pluginResult.getMessageType()) { switch (pluginResult.getMessageType()) {
case PluginResult.MESSAGE_TYPE_BOOLEAN: case PluginResult.MESSAGE_TYPE_BOOLEAN:
sb.append(pluginResult.getMessage().charAt(0)); // t or f. sb.append(pluginResult.getMessage().charAt(0)); // t or f.
@@ -480,42 +476,12 @@ public class NativeToJsMessageQueue {
sb.append('A'); sb.append('A');
sb.append(pluginResult.getMessage()); sb.append(pluginResult.getMessage());
break; break;
case PluginResult.MESSAGE_TYPE_MULTIPART:
sb.append('M');
for (int i = 0; i < pluginResult.getMultipartMessagesSize(); i++) {
PluginResult multipartMessage = pluginResult.getMultipartMessage(i);
sb.append(String.valueOf(calculateEncodedLengthHelper(multipartMessage)));
sb.append(' ');
encodeAsMessageHelper(sb, multipartMessage);
}
break;
case PluginResult.MESSAGE_TYPE_JSON: case PluginResult.MESSAGE_TYPE_JSON:
default: default:
sb.append(pluginResult.getMessage()); // [ or { sb.append(pluginResult.getMessage()); // [ or {
} }
} }
void encodeAsMessage(StringBuilder sb) {
if (pluginResult == null) {
sb.append('J')
.append(jsPayloadOrCallbackId);
return;
}
int status = pluginResult.getStatus();
boolean noResult = status == PluginResult.Status.NO_RESULT.ordinal();
boolean resultOk = status == PluginResult.Status.OK.ordinal();
boolean keepCallback = pluginResult.getKeepCallback();
sb.append((noResult || resultOk) ? 'S' : 'F')
.append(keepCallback ? '1' : '0')
.append(status)
.append(' ')
.append(jsPayloadOrCallbackId)
.append(' ');
encodeAsMessageHelper(sb, pluginResult);
}
void encodeAsJsMessage(StringBuilder sb) { void encodeAsJsMessage(StringBuilder sb) {
if (pluginResult == null) { if (pluginResult == null) {
sb.append(jsPayloadOrCallbackId); sb.append(jsPayloadOrCallbackId);

View File

@@ -18,10 +18,13 @@
*/ */
package org.apache.cordova; package org.apache.cordova;
import java.util.List; import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin; import org.apache.cordova.CordovaPlugin;
//import android.content.Context;
//import android.webkit.WebView;
/** /**
* This class represents a service entry object. * This class represents a service entry object.
*/ */
@@ -30,60 +33,100 @@ public class PluginEntry {
/** /**
* The name of the service that this plugin implements * The name of the service that this plugin implements
*/ */
public String service; public String service = "";
/** /**
* The plugin class name that implements the 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. * Flag that indicates the plugin object should be created when PluginManager is initialized.
*/ */
public boolean onload; public boolean onload = false;
private List<String> urlFilters;
/**
* Constructs with a CordovaPlugin already instantiated.
*/
public PluginEntry(String service, CordovaPlugin plugin) {
this(service, plugin.getClass().getName(), true, plugin, null);
}
/** /**
* Constructor
*
* @param service The name of the service * @param service The name of the service
* @param pluginClass The plugin class name * @param pluginClass The plugin class name
* @param onload Create plugin object when HTML page is loaded * @param onload Create plugin object when HTML page is loaded
*/ */
public PluginEntry(String service, String pluginClass, boolean onload) { public PluginEntry(String service, String pluginClass, boolean onload) {
this(service, pluginClass, onload, null, null);
}
@Deprecated // urlFilters are going away
public PluginEntry(String service, String pluginClass, boolean onload, List<String> urlFilters) {
this.service = service; this.service = service;
this.pluginClass = pluginClass; this.pluginClass = pluginClass;
this.onload = onload; 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.service = service;
this.pluginClass = pluginClass;
this.onload = onload;
this.urlFilters = urlFilters;
this.plugin = plugin; 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; package org.apache.cordova;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.cordova.CordovaArgs;
import org.apache.cordova.CordovaWebView; import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CallbackContext; import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface; import org.apache.cordova.CordovaInterface;
@@ -28,8 +33,11 @@ import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.PluginEntry; import org.apache.cordova.PluginEntry;
import org.apache.cordova.PluginResult; import org.apache.cordova.PluginResult;
import org.json.JSONException; import org.json.JSONException;
import org.xmlpull.v1.XmlPullParserException;
import android.content.Intent; import android.content.Intent;
import android.content.res.XmlResourceParser;
import android.net.Uri; import android.net.Uri;
import android.os.Debug; import android.os.Debug;
import android.util.Log; import android.util.Log;
@@ -45,40 +53,31 @@ public class PluginManager {
private static final int SLOW_EXEC_WARNING_THRESHOLD = Debug.isDebuggerConnected() ? 60 : 16; private static final int SLOW_EXEC_WARNING_THRESHOLD = Debug.isDebuggerConnected() ? 60 : 16;
// List of service entries // List of service entries
private final HashMap<String, CordovaPlugin> pluginMap = new HashMap<String, CordovaPlugin>(); private final HashMap<String, PluginEntry> entries = new HashMap<String, PluginEntry>();
private final HashMap<String, PluginEntry> entryMap = new HashMap<String, PluginEntry>();
private final CordovaInterface ctx; private final CordovaInterface ctx;
private final CordovaWebView app; private final CordovaWebView app;
// Flag to track first time through
private boolean firstRun;
// Stores mapping of Plugin Name -> <url-filter> values. // Stores mapping of Plugin Name -> <url-filter> values.
// Using <url-filter> is deprecated. // Using <url-filter> is deprecated.
protected HashMap<String, List<String>> urlMap = new HashMap<String, List<String>>(); protected HashMap<String, List<String>> urlMap = new HashMap<String, List<String>>();
@Deprecated private AtomicInteger numPendingUiExecs;
PluginManager(CordovaWebView cordovaWebView, CordovaInterface cordova) {
this(cordovaWebView, cordova, null);
}
PluginManager(CordovaWebView cordovaWebView, CordovaInterface cordova, List<PluginEntry> pluginEntries) { /**
this.ctx = cordova; * Constructor.
this.app = cordovaWebView; *
if (pluginEntries == null) { * @param app
ConfigXmlParser parser = new ConfigXmlParser(); * @param ctx
parser.parse(ctx.getActivity()); */
pluginEntries = parser.getPluginEntries(); public PluginManager(CordovaWebView app, CordovaInterface ctx) {
} this.ctx = ctx;
setPluginEntries(pluginEntries); this.app = app;
} this.firstRun = true;
this.numPendingUiExecs = new AtomicInteger(0);
public void setPluginEntries(List<PluginEntry> pluginEntries) {
this.onPause(false);
this.onDestroy();
pluginMap.clear();
urlMap.clear();
for (PluginEntry entry : pluginEntries) {
addService(entry);
}
} }
/** /**
@@ -86,36 +85,114 @@ public class PluginManager {
*/ */
public void init() { public void init() {
LOG.d(TAG, "init()"); LOG.d(TAG, "init()");
this.onPause(false);
this.onDestroy(); // If first time, then load plugins from config.xml file
pluginMap.clear(); 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(); this.startupPlugins();
} }
@Deprecated /**
* Load plugins from res/xml/config.xml
*/
public void loadPlugins() { 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. * Delete all plugin objects.
*/ */
@Deprecated // Should not be exposed as public.
public void clearPluginObjects() { public void clearPluginObjects() {
pluginMap.clear(); for (PluginEntry entry : this.entries.values()) {
entry.plugin = null;
}
} }
/** /**
* Create plugins objects that have onload set. * Create plugins objects that have onload set.
*/ */
@Deprecated // Should not be exposed as public.
public void startupPlugins() { public void startupPlugins() {
for (PluginEntry entry : entryMap.values()) { for (PluginEntry entry : this.entries.values()) {
// Add a null entry to for each non-startup plugin to avoid ConcurrentModificationException
// When iterating plugins.
if (entry.onload) { if (entry.onload) {
getPlugin(entry.service); entry.createPlugin(this.app, this.ctx);
} else {
pluginMap.put(entry.service, null);
} }
} }
} }
@@ -138,6 +215,20 @@ public class PluginManager {
* plugin execute method. * plugin execute method.
*/ */
public void exec(final String service, final String action, final String callbackId, final String rawArgs) { 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); CordovaPlugin plugin = getPlugin(service);
if (plugin == null) { if (plugin == null) {
Log.d(TAG, "exec() call to unknown plugin: " + service); Log.d(TAG, "exec() call to unknown plugin: " + service);
@@ -181,21 +272,15 @@ public class PluginManager {
* @return CordovaPlugin or null * @return CordovaPlugin or null
*/ */
public CordovaPlugin getPlugin(String service) { public CordovaPlugin getPlugin(String service) {
CordovaPlugin ret = pluginMap.get(service); PluginEntry entry = this.entries.get(service);
if (ret == null) { if (entry == null) {
PluginEntry pe = entryMap.get(service); return null;
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);
} }
return ret; CordovaPlugin plugin = entry.plugin;
if (plugin == null) {
plugin = entry.createPlugin(this.app, this.ctx);
}
return plugin;
} }
/** /**
@@ -217,16 +302,7 @@ public class PluginManager {
* @param entry The plugin entry * @param entry The plugin entry
*/ */
public void addService(PluginEntry entry) { public void addService(PluginEntry entry) {
this.entryMap.put(entry.service, entry); this.entries.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);
}
} }
/** /**
@@ -235,62 +311,22 @@ public class PluginManager {
* @param multitasking Flag indicating if multitasking is turned on for app * @param multitasking Flag indicating if multitasking is turned on for app
*/ */
public void onPause(boolean multitasking) { public void onPause(boolean multitasking) {
for (CordovaPlugin plugin : this.pluginMap.values()) { for (PluginEntry entry : this.entries.values()) {
if (plugin != null) { if (entry.plugin != null) {
plugin.onPause(multitasking); entry.plugin.onPause(multitasking);
} }
} }
} }
/**
* Called when the system received an HTTP authentication request. Plugins can use
* the supplied HttpAuthHandler to process this auth challenge.
*
* @param view The WebView that is initiating the callback
* @param handler The HttpAuthHandler used to set the WebView's response
* @param host The host requiring authentication
* @param realm The realm for which authentication is required
*
* @return Returns True if there is a plugin which will resolve this auth challenge, otherwise False
*
*/
public boolean onReceivedHttpAuthRequest(CordovaWebView view, ICordovaHttpAuthHandler handler, String host, String realm) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null && plugin.onReceivedHttpAuthRequest(view, handler, host, realm)) {
return true;
}
}
return false;
}
/**
* Called when he system received an SSL client certificate request. Plugin can use
* the supplied ClientCertRequest to process this certificate challenge.
*
* @param view The WebView that is initiating the callback
* @param request The client certificate request
*
* @return Returns True if plugin will resolve this auth challenge, otherwise False
*
*/
public boolean onReceivedClientCertRequest(CordovaWebView view, ICordovaClientCertRequest request) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null && plugin.onReceivedClientCertRequest(view, request)) {
return true;
}
}
return false;
}
/** /**
* Called when the activity will start interacting with the user. * Called when the activity will start interacting with the user.
* *
* @param multitasking Flag indicating if multitasking is turned on for app * @param multitasking Flag indicating if multitasking is turned on for app
*/ */
public void onResume(boolean multitasking) { public void onResume(boolean multitasking) {
for (CordovaPlugin plugin : this.pluginMap.values()) { for (PluginEntry entry : this.entries.values()) {
if (plugin != null) { if (entry.plugin != null) {
plugin.onResume(multitasking); entry.plugin.onResume(multitasking);
} }
} }
} }
@@ -299,9 +335,9 @@ public class PluginManager {
* The final call you receive before your activity is destroyed. * The final call you receive before your activity is destroyed.
*/ */
public void onDestroy() { public void onDestroy() {
for (CordovaPlugin plugin : this.pluginMap.values()) { for (PluginEntry entry : this.entries.values()) {
if (plugin != null) { if (entry.plugin != null) {
plugin.onDestroy(); entry.plugin.onDestroy();
} }
} }
} }
@@ -318,9 +354,9 @@ public class PluginManager {
if (obj != null) { if (obj != null) {
return obj; return obj;
} }
for (CordovaPlugin plugin : this.pluginMap.values()) { for (PluginEntry entry : this.entries.values()) {
if (plugin != null) { if (entry.plugin != null) {
obj = plugin.onMessage(id, data); obj = entry.plugin.onMessage(id, data);
if (obj != null) { if (obj != null) {
return obj; return obj;
} }
@@ -333,9 +369,9 @@ public class PluginManager {
* Called when the activity receives a new intent. * Called when the activity receives a new intent.
*/ */
public void onNewIntent(Intent intent) { public void onNewIntent(Intent intent) {
for (CordovaPlugin plugin : this.pluginMap.values()) { for (PluginEntry entry : this.entries.values()) {
if (plugin != null) { if (entry.plugin != null) {
plugin.onNewIntent(intent); entry.plugin.onNewIntent(intent);
} }
} }
} }
@@ -351,7 +387,7 @@ public class PluginManager {
// Instead, plugins should not include <url-filter> and instead ensure // Instead, plugins should not include <url-filter> and instead ensure
// that they are loaded before this function is called (either by setting // that they are loaded before this function is called (either by setting
// the onload <param> or by making an exec() call to them) // 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); List<String> urlFilters = urlMap.get(entry.service);
if (urlFilters != null) { if (urlFilters != null) {
for (String s : urlFilters) { for (String s : urlFilters) {
@@ -359,9 +395,8 @@ public class PluginManager {
return getPlugin(entry.service).onOverrideUrlLoading(url); return getPlugin(entry.service).onOverrideUrlLoading(url);
} }
} }
} else { } else if (entry.plugin != null) {
CordovaPlugin plugin = pluginMap.get(entry.service); if (entry.plugin.onOverrideUrlLoading(url)) {
if (plugin != null && plugin.onOverrideUrlLoading(url)) {
return true; return true;
} }
} }
@@ -373,17 +408,27 @@ public class PluginManager {
* Called when the app navigates or refreshes. * Called when the app navigates or refreshes.
*/ */
public void onReset() { public void onReset() {
for (CordovaPlugin plugin : this.pluginMap.values()) { Iterator<PluginEntry> it = this.entries.values().iterator();
while (it.hasNext()) {
CordovaPlugin plugin = it.next().plugin;
if (plugin != null) { if (plugin != null) {
plugin.onReset(); 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) { Uri remapUri(Uri uri) {
for (CordovaPlugin plugin : this.pluginMap.values()) { for (PluginEntry entry : this.entries.values()) {
if (plugin != null) { if (entry.plugin != null) {
Uri ret = plugin.remapUri(uri); Uri ret = entry.plugin.remapUri(uri);
if (ret != null) { if (ret != null) {
return ret; return ret;
} }
@@ -392,23 +437,25 @@ public class PluginManager {
return null; return null;
} }
/** private class PluginManagerService extends CordovaPlugin {
* Create a plugin based on class name. @Override
*/ public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext) throws JSONException {
private CordovaPlugin instantiatePlugin(String className) { if ("startup".equals(action)) {
CordovaPlugin ret = null; // The onPageStarted event of CordovaWebViewClient resets the queue of messages to be returned to javascript in response
try { // to exec calls. Since this event occurs on the UI thread and exec calls happen on the WebCore thread it is possible
Class<?> c = null; // that onPageStarted occurs after exec calls have started happening on a new page, which can cause the message queue
if ((className != null) && !("".equals(className))) { // to be reset between the queuing of a new message and its retrieval by javascript. To avoid this from happening,
c = Class.forName(className); // 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)) { return false;
ret = (CordovaPlugin) c.newInstance();
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("Error adding plugin " + className + ".");
} }
return ret;
} }
} }

19
framework/src/org/apache/cordova/PluginResult.java Normal file → Executable file
View File

@@ -18,8 +18,6 @@
*/ */
package org.apache.cordova; package org.apache.cordova;
import java.util.List;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
@@ -31,7 +29,6 @@ public class PluginResult {
private boolean keepCallback = false; private boolean keepCallback = false;
private String strMessage; private String strMessage;
private String encodedMessage; private String encodedMessage;
private List<PluginResult> multipartMessages;
public PluginResult(Status status) { public PluginResult(Status status) {
this(status, PluginResult.StatusMessages[status.ordinal()]); this(status, PluginResult.StatusMessages[status.ordinal()]);
@@ -83,13 +80,6 @@ public class PluginResult {
this.encodedMessage = Base64.encodeToString(data, Base64.NO_WRAP); this.encodedMessage = Base64.encodeToString(data, Base64.NO_WRAP);
} }
// The keepCallback and status of multipartMessages are ignored.
public PluginResult(Status status, List<PluginResult> multipartMessages) {
this.status = status.ordinal();
this.messageType = MESSAGE_TYPE_MULTIPART;
this.multipartMessages = multipartMessages;
}
public void setKeepCallback(boolean b) { public void setKeepCallback(boolean b) {
this.keepCallback = b; this.keepCallback = b;
} }
@@ -109,14 +99,6 @@ public class PluginResult {
return encodedMessage; return encodedMessage;
} }
public int getMultipartMessagesSize() {
return multipartMessages.size();
}
public PluginResult getMultipartMessage(int index) {
return multipartMessages.get(index);
}
/** /**
* If messageType == MESSAGE_TYPE_STRING, then returns the message string. * If messageType == MESSAGE_TYPE_STRING, then returns the message string.
* Otherwise, returns null. * Otherwise, returns null.
@@ -168,7 +150,6 @@ public class PluginResult {
// Use BINARYSTRING when your string may contain null characters. // Use BINARYSTRING when your string may contain null characters.
// This is required to work around a bug in the platform :(. // This is required to work around a bug in the platform :(.
public static final int MESSAGE_TYPE_BINARYSTRING = 7; public static final int MESSAGE_TYPE_BINARYSTRING = 7;
public static final int MESSAGE_TYPE_MULTIPART = 8;
public static String[] StatusMessages = new String[] { public static String[] StatusMessages = new String[] {
"No result", "No result",

View File

@@ -43,7 +43,7 @@ public class ScrollEvent {
* @param view * @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; l = x; y = t; nl = nx; nt = ny;
targetView = view; targetView = view;

View File

@@ -1,257 +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.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Color;
import android.os.Handler;
import android.view.Display;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.LinearLayout;
import org.json.JSONArray;
import org.json.JSONException;
// This file is a copy of SplashScreen.java from cordova-plugin-splashscreen, and is required only
// for pre-4.0 Cordova as a transition path to it being extracted into the plugin.
public class SplashScreenInternal extends CordovaPlugin {
private static final String LOG_TAG = "SplashScreenInternal";
private static Dialog splashDialog;
private static ProgressDialog spinnerDialog;
private static boolean firstShow = true;
@Override
protected void pluginInitialize() {
if (!firstShow) {
return;
}
// Make WebView invisible while loading URL
webView.setVisibility(View.INVISIBLE);
int drawableId = preferences.getInteger("SplashDrawableId", 0);
if (drawableId == 0) {
String splashResource = preferences.getString("SplashScreen", null);
if (splashResource != null) {
drawableId = cordova.getActivity().getResources().getIdentifier(splashResource, "drawable", cordova.getActivity().getClass().getPackage().getName());
if (drawableId == 0) {
drawableId = cordova.getActivity().getResources().getIdentifier(splashResource, "drawable", cordova.getActivity().getPackageName());
}
preferences.set("SplashDrawableId", drawableId);
}
}
firstShow = false;
loadSpinner();
showSplashScreen(true);
}
@Override
public void onPause(boolean multitasking) {
// hide the splash screen to avoid leaking a window
this.removeSplashScreen();
}
@Override
public void onDestroy() {
// hide the splash screen to avoid leaking a window
this.removeSplashScreen();
firstShow = true;
}
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
if (action.equals("hide")) {
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
webView.postMessage("splashscreen", "hide");
}
});
} else if (action.equals("show")) {
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
webView.postMessage("splashscreen", "show");
}
});
} else if (action.equals("spinnerStart")) {
final String title = args.getString(0);
final String message = args.getString(1);
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
spinnerStart(title, message);
}
});
} else {
return false;
}
callbackContext.success();
return true;
}
@Override
public Object onMessage(String id, Object data) {
if ("splashscreen".equals(id)) {
if ("hide".equals(data.toString())) {
this.removeSplashScreen();
} else {
this.showSplashScreen(false);
}
} else if ("spinner".equals(id)) {
if ("stop".equals(data.toString())) {
this.spinnerStop();
webView.setVisibility(View.VISIBLE);
}
} else if ("onReceivedError".equals(id)) {
spinnerStop();
}
return null;
}
private void removeSplashScreen() {
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
if (splashDialog != null && splashDialog.isShowing()) {
splashDialog.dismiss();
splashDialog = null;
}
}
});
}
/**
* Shows the splash screen over the full Activity
*/
@SuppressWarnings("deprecation")
private void showSplashScreen(final boolean hideAfterDelay) {
final int splashscreenTime = preferences.getInteger("SplashScreenDelay", 3000);
final int drawableId = preferences.getInteger("SplashDrawableId", 0);
// If the splash dialog is showing don't try to show it again
if (this.splashDialog != null && splashDialog.isShowing()) {
return;
}
if (drawableId == 0 || (splashscreenTime <= 0 && hideAfterDelay)) {
return;
}
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
// Get reference to display
Display display = cordova.getActivity().getWindowManager().getDefaultDisplay();
Context context = webView.getContext();
// Create the layout for the dialog
LinearLayout root = new LinearLayout(context);
root.setMinimumHeight(display.getHeight());
root.setMinimumWidth(display.getWidth());
root.setOrientation(LinearLayout.VERTICAL);
// TODO: Use the background color of the webview's parent instead of using the
// preference.
root.setBackgroundColor(preferences.getInteger("backgroundColor", Color.BLACK));
root.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT, 0.0F));
root.setBackgroundResource(drawableId);
// Create and show the dialog
splashDialog = new Dialog(context, android.R.style.Theme_Translucent_NoTitleBar);
// check to see if the splash screen should be full screen
if ((cordova.getActivity().getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_FULLSCREEN)
== WindowManager.LayoutParams.FLAG_FULLSCREEN) {
splashDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
splashDialog.setContentView(root);
splashDialog.setCancelable(false);
splashDialog.show();
// Set Runnable to remove splash screen just in case
if (hideAfterDelay) {
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
public void run() {
removeSplashScreen();
}
}, splashscreenTime);
}
}
});
}
/*
* Load the spinner
*/
private void loadSpinner() {
// If loadingDialog property, then show the App loading dialog for first page of app
String loading = null;
if (webView.canGoBack()) {
loading = preferences.getString("LoadingDialog", null);
}
else {
loading = preferences.getString("LoadingPageDialog", null);
}
if (loading != null) {
String title = "";
String message = "Loading Application...";
if (loading.length() > 0) {
int comma = loading.indexOf(',');
if (comma > 0) {
title = loading.substring(0, comma);
message = loading.substring(comma + 1);
}
else {
title = "";
message = loading;
}
}
spinnerStart(title, message);
}
}
private void spinnerStart(final String title, final String message) {
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
spinnerStop();
spinnerDialog = ProgressDialog.show(webView.getContext(), title, message, true, true,
new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
spinnerDialog = null;
}
});
}
});
}
private void spinnerStop() {
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
if (spinnerDialog != null && spinnerDialog.isShowing()) {
spinnerDialog.dismiss();
spinnerDialog = null;
}
}
});
}
}

View File

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

View File

@@ -1,29 +0,0 @@
{
"name": "cordova-android",
"version": "3.7.2",
"description": "cordova-android release",
"main": "bin/create",
"repository": {
"type": "git",
"url": "https://git-wip-us.apache.org/repos/asf/cordova-android.git"
},
"keywords": [
"android",
"cordova",
"apache"
],
"scripts": {
"test": "jasmine-node --color spec",
"test-build": "rm -rf \"test create\"; ./bin/create \"test create\" com.test.app 応用 && \"./test create/cordova/build\" && rm -rf \"test create\""
},
"author": "Apache Software Foundation",
"license": "Apache version 2.0",
"dependencies": {
"q": "^0.9.0",
"shelljs": "^0.2.6"
},
"devDependencies": {
"jasmine-node": "~1",
"promise-matchers": "~0"
}
}

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"?> <?xml version="1.0" encoding="UTF-8"?>
<classpath> <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="src"/>
<classpathentry kind="src" path="gen"/> <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"/> <classpathentry kind="output" path="bin/classes"/>
</classpath> </classpath>

View File

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

View File

@@ -26,15 +26,13 @@ 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. 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 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 project.properties has a library reference to ../framework. However, Robotium has to be
has to be installed for the onScrollChanged tests to work correctly. It can be installed for the onScrollChanged tests to work correctly. It can be found at https://code.google.com/p/robotium/
found at https://code.google.com/p/robotium/ and the jar should be put in the
'libs' directory'.
To run manually from command line: To run manually from command line:
0. Build by entering `ant debug install` 0. Build by entering `ant debug install`
0. Run tests by clicking on "CordovaNativeTests" app icon on the device 0. Run tests by clicking on "CordovaTest" icon on device
To run from Eclipse: To run from Eclipse:

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) { 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 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the KIND, either express or implied. See the License for the
specific language governing permissions and limitations specific language governing permissions and limitations
under the License. under the License.$
--> -->
<!DOCTYPE HTML> This is an error page.
<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>

View File

@@ -36,7 +36,7 @@
</head> </head>
<body onload="init();" id="stage" class="theme"> <body onload="init();" id="stage" class="theme">
<h1>Cordova Android Native Tests</h1> <h1>Cordova Android Tests</h1>
<div id="info"> <div id="info">
<h4>Cordova: <span id="cordova"> &nbsp;</span></h4> <h4>Cordova: <span id="cordova"> &nbsp;</span></h4>
<h4>Deviceready: <span id="deviceready"> &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> <h4>Deviceready: <span id="deviceready"> &nbsp;</span></h4>
</div> </div>
<div id="info"> <div id="info">
<h4>The options menu items should be:</h4> <h4>The menu items should be:</h4>
<li>Item1<br> <li>Item1<br>
<li>Item2<br> <li>Item2<br>
<li>Item3<br> <li>Item3<br>

View File

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

View File

@@ -1,22 +1,4 @@
<?xml version='1.0' encoding='utf-8'?> <?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"> <widget id="io.cordova.helloCordova" version="2.0.0" xmlns="http://www.w3.org/ns/widgets">
<name>Hello Cordova</name> <name>Hello Cordova</name>
<description> <description>
@@ -26,19 +8,17 @@
Apache Cordova Team Apache Cordova Team
</author> </author>
<access origin="*.apache.org" /> <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" /> <content src="index.html" />
<preference name="loglevel" value="DEBUG" /> <preference name="loglevel" value="DEBUG" />
<preference name="useBrowserHistory" value="true" /> <preference name="useBrowserHistory" value="true" />
<preference name="exit-on-suspend" value="false" /> <preference name="exit-on-suspend" value="false" />
<preference name="showTitle" value="true" />
<feature name="Activity"> <feature name="Activity">
<param name="android-package" value="org.apache.cordova.test.ActivityPlugin" /> <param name="android-package" value="org.apache.cordova.test.ActivityPlugin" />
</feature> </feature>
<feature name="PluginStub"> <feature name="PluginStub">
<param name="android-package" value="org.apache.cordova.pluginApi.pluginStub" /> <param name="android-package" value="org.apache.cordova.pluginApi.pluginStub" />
</feature> </feature>
<feature name="App">
<param name="android-package" value="org.apache.cordova.App" />
</feature>
</widget> </widget>

View File

@@ -18,11 +18,12 @@
*/ */
package org.apache.cordova.test; package org.apache.cordova.test;
import org.apache.cordova.CordovaActivity; import org.apache.cordova.DroidGap;
import android.app.Activity;
import android.os.Bundle; import android.os.Bundle;
public class MainTestActivity extends CordovaActivity { public class CordovaActivity extends DroidGap {
/** Called when the activity is first created. */ /** Called when the activity is first created. */
@Override @Override
public void onCreate(Bundle savedInstanceState) { 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.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import org.apache.cordova.Config;
import org.apache.cordova.CordovaChromeClient;
import org.apache.cordova.CordovaWebView; import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CordovaInterface; import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin; 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;
import org.apache.cordova.test.R.id;
import org.apache.cordova.test.R.layout;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
@@ -47,12 +47,7 @@ public class CordovaWebViewTestActivity extends Activity implements CordovaInter
setContentView(R.layout.main); 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 = (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"); cordovaWebView.loadUrl("file:///android_asset/www/index.html");
@@ -105,4 +100,4 @@ public class CordovaWebViewTestActivity extends Activity implements CordovaInter
cordovaWebView.handleDestroy(); 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 android.os.Bundle;
import org.apache.cordova.*; import org.apache.cordova.*;
public class backbuttonmultipage extends CordovaActivity { public class backbuttonmultipage extends DroidGap {
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);

View File

@@ -22,12 +22,12 @@ import android.os.Bundle;
import org.apache.cordova.*; import org.apache.cordova.*;
public class background extends CordovaActivity { public class background extends DroidGap {
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
//super.init(new FixWebView(this), new CordovaWebViewClient(this), new CordovaChromeClient(this)); //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"); super.loadUrl("file:///android_asset/www/background/index.html");
} }
} }

View File

@@ -22,15 +22,18 @@ import android.graphics.Color;
import android.os.Bundle; import android.os.Bundle;
import org.apache.cordova.*; import org.apache.cordova.*;
public class backgroundcolor extends CordovaActivity { public class backgroundcolor extends DroidGap {
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(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 // 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" /> // <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"); super.loadUrl("file:///android_asset/www/backgroundcolor/index.html");
} }

View File

@@ -21,7 +21,7 @@ package org.apache.cordova.test;
import android.os.Bundle; import android.os.Bundle;
import org.apache.cordova.*; import org.apache.cordova.*;
public class basicauth extends CordovaActivity { public class basicauth extends DroidGap {
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@@ -31,13 +31,11 @@ public class basicauth extends CordovaActivity {
AuthenticationToken token = new AuthenticationToken(); AuthenticationToken token = new AuthenticationToken();
token.setUserName("test"); token.setUserName("test");
token.setPassword("test"); token.setPassword("test");
// classic webview includes port in hostname, Chromium webview does not. Handle both here. super.setAuthenticationToken(token, "browserspy.dk:80", "BrowserSpy.dk - HTTP Password Test");
// 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 // Add web site to whitelist
Config.getWhitelist().addWhiteListEntry("http://browserspy.dk/*", true); Config.init();
Config.addWhiteListEntry("http://browserspy.dk*", true);
// Load test // Load test
super.loadUrl("file:///android_asset/www/basicauth/index.html"); 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 android.os.Bundle;
import org.apache.cordova.*; import org.apache.cordova.*;
public class errorurl extends CordovaActivity { public class errorurl extends DroidGap {
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(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"); 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 android.os.Bundle;
import org.apache.cordova.*; import org.apache.cordova.*;
public class fullscreen extends CordovaActivity { public class fullscreen extends DroidGap {
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@@ -30,7 +30,7 @@ public class fullscreen extends CordovaActivity {
// fullscreen can also be set in cordova.xml. For example, // fullscreen can also be set in cordova.xml. For example,
// <preference name="fullscreen" value="true" /> // <preference name="fullscreen" value="true" />
preferences.set("fullscreen", true); super.setBooleanProperty("fullscreen", true);
super.init(); super.init();
super.loadUrl("file:///android_asset/www/fullscreen/index.html"); 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 android.os.Bundle;
import org.apache.cordova.*; import org.apache.cordova.*;
public class htmlnotfound extends CordovaActivity { public class htmlnotfound extends DroidGap {
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);

View File

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

View File

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

View File

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

View File

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

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