Compare commits

...

85 Commits
2.4.0 ... 2.6.0

Author SHA1 Message Date
Joe Bowser
34b606b9fc Prep for 2.6.0 final 2013-04-01 14:55:10 -07:00
Joe Bowser
d1932ad473 CB-1796: Let's make sure we actually write the file instead of just writing EXIF to NOTHING 2013-03-27 15:11:11 -07:00
Joe Bowser
ff6ef1eea5 JS Changed once again, this will require a retag of 2.6.0rc1 2013-03-27 13:25:53 -07:00
Joe Bowser
b7e67ffb3c Merge branch '2.6.x' of https://git-wip-us.apache.org/repos/asf/cordova-android into 2.6.x 2013-03-25 16:04:44 -07:00
Joe Bowser
4ab4606ad2 Fixing CB-1700, we had the file names reversed, so exif was never being written right. Needed to upload a file to debug this thing 2013-03-25 16:04:22 -07:00
Joe Bowser
957f2fd10b Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/cordova-android into 2.6.x 2013-03-25 10:50:28 -07:00
Andrew Grieve
cdbe9c7204 Fix NPE in InAppBrowser.
When cordova.getActivity().getIntent().getExtras() == null.
2013-03-23 14:09:32 -04:00
Andrew Grieve
73c7994cd1 Fix NPE in InAppBrowser.
When cordova.getActivity().getIntent().getExtras() == null.
2013-03-23 14:07:57 -04:00
Andrew Grieve
0c74090953 Log a message when exec() is made to an unregistered plugin. 2013-03-23 14:07:22 -04:00
Ian Clelland
f60d54eae4 [CB-2305] Add InAppBrowser injectSriptCode command to support InAppBrowser.executeScript and InAppBrowser.insertCSS APIs 2013-03-23 13:13:21 -04:00
Joe Bowser
85c25630a8 Grabbed the wrong JS 2013-03-21 10:49:51 -07:00
Joe Bowser
31bc015cdd Pre-2.6 prep 2013-03-21 10:35:09 -07:00
mbillau
b028ad3604 CB-2675: Add prompt dialog to Notification API 2013-03-20 23:14:42 -04:00
Max Woghiren
d2e4e35c37 [CB-2715] Simplified readAsBinaryHelper.
Also fixed some comments and other minor things.
2013-03-20 22:42:29 -04:00
Ian Clelland
1f37200bb6 [CB-1957] Stop any playing media when closing InAppBrowser 2013-03-20 15:56:27 -04:00
Ian Clelland
77178daad3 [CB-2308] [android] Report errors when InAppBrowser fails to load page 2013-03-20 15:42:08 -04:00
Richard Burton
1648f161d9 Implemented a conditional check to support providing the duration limit for the Android platform SDK 8 and above. The value is passed using the string literal value to ensure the logic is not dependent on SDK version specifics. 2013-03-18 11:23:21 -04:00
Richard Burton
9fa6cea69b Implemented a conditional check to allow for the duration to be provided on the Android platform for SDK 8 and above. 2013-03-18 11:23:21 -04:00
Andrew Grieve
66b827e502 [CB-2632] Implement FileReader.readAsBinaryString 2013-03-15 16:47:33 -04:00
Andrew Grieve
7755a902dd Add a new type to the Native->JS bridge for binary strings.
It's needed since the bridge truncates strings that have null
characters in them :(.
2013-03-15 16:47:04 -04:00
Max Woghiren
d25b73f47d [CB-2546] Moved read calls to a background thread. 2013-03-15 16:01:02 -04:00
Max Woghiren
ac2969c3f8 [CB-2435] Split common methods out of FileUtils into FileHelpers
Also included in this change:
- Fixed getMimeType for content:// URIs.
- Made getRealPath take a URI string.
- Added basic android_asset handling.
There is no such thing as a "real path" for a file:///android_asset URI.  However, it is possible to get an input stream to one.

And even more minor changes:
- removed unused FileReader/FileWriter instance variables
- added logging when getRealPath fails
- fixed indentation issues
- removed a try/catch in favor of throwing
- removed a null check in favor of throwing
- moved getEntry back to FilePlugin
2013-03-14 12:39:51 -04:00
Dave E
ee38b2ef03 Use pushd/popd instead of subshell
Improves the error message that happens when ant is not installed.
2013-03-14 12:31:56 -04:00
Max Woghiren
0f70e04e6e [CB-1933] Changed button labels to an array.
This allows commas to be included in button label text.
2013-03-14 11:34:29 -04:00
Joe Bowser
9fc1e7272e CB-2668: Thanks for supplying a patch, but please make sure it actually builds. 2013-03-13 16:02:06 -07:00
Yavor Georgiev
0d4d0b8a37 Adding workaround for ICS asset URLs with spaces 2013-03-13 19:52:49 +02:00
Andrew Grieve
fcd2c989a2 [CB-2418] Fix geolocation's velocity field broken on Android 2013-03-12 20:54:24 -04:00
Simon MacDonald
e0d0d6c455 CB-2459: Customize InAppBrowser location bar 2013-03-10 10:33:42 -04:00
Simon MacDonald
ce1a961b99 CB-2640: Allow InAppBrowser to open tel, sms, market urls 2013-03-08 14:14:41 -05:00
Joe Bowser
c71a08a9d9 Merge branches 'leon' and 'master' 2013-03-07 13:06:10 -08:00
Joe Bowser
17bfcea65a Merge branch 'master' of git://github.com/sweetleon/cordova-android into leon 2013-03-07 11:20:43 -08:00
Joe Bowser
5e8959bab1 Removing baseURL because it doesn't actually do anything. If we want to make sure remote websites work, we whitelist them 2013-03-07 08:52:02 -08:00
Joe Bowser
9924dc0f92 So much for squashing, I should have branched this. 2013-03-06 15:31:25 -08:00
Joe Bowser
7388c036d7 Making framework only apply for http resources for now, so we don't break non-http handling. I had to squash this to make it pretty 2013-03-06 15:29:40 -08:00
Joe Bowser
ad4512801f Making framework only apply for http resources for now, so we don't break non-http handling 2013-03-06 15:26:13 -08:00
Joe Bowser
409b9af398 CB-2099: Android Whitelisting now blocks images and JS with an empty response 2013-03-06 14:56:27 -08:00
Braden Shepherdson
7cc8fd7e87 Allow plugins to capture shouldInterceptRequest() 2013-03-06 11:53:10 -05:00
Joe Bowser
42c8105f13 CB-2623: Updated windows script, now it works here too for once 2013-03-05 16:13:43 -08:00
Joe Bowser
9a71cc5b4e CB-2623: Added partial work on update script 2013-03-05 14:36:52 -08:00
Joe Bowser
c543b7469d CB-2623 Adding update script to Android 2013-03-05 11:30:38 -08:00
Joe Bowser
7caac3265a Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/cordova-android 2013-03-04 16:09:55 -08:00
Joe Bowser
5d68d5a246 CB-2198: Removing option to use our broken URL stack as a history as per deprecation policy. 2013-03-04 16:09:37 -08:00
Braden Shepherdson
7187f87eae Add readAsBinaryString and readAsArrayBuffer support 2013-03-04 17:36:40 -05:00
Joe Bowser
fb81f3e77e CB-2596: Fixing the menubutton for text fields 2013-03-04 14:26:28 -08:00
lenny
0ae49ed098 moveFile handles absolute paths by not pre-pending anything to them 2013-03-01 22:11:29 -08:00
lenny
b8e5aaf754 ignore IntelliJ files 2013-03-01 21:56:57 -08:00
Tommy-Carlos Williams
aa4820c3b7 [CB-861] Header support for FileTransfer download
Added support for an optional options object as the final arg. Currently only handles the options.headers object (as per the issue).

`FileTransfer.download(source, target, successCallback, errorCallback, trustAllHosts, options)`

This is needed for using FileTransfer.download with Basic Authentication, etc. Sadly since Android 2.x doesn't support XHR2, this is needed in FileTransfer.

I have only added support to Android and iOS (see other PR's).
2013-02-28 13:10:59 -05:00
Joe Bowser
5d79d6e134 Merge branch 'next' 2013-02-27 11:07:55 -08:00
Joe Bowser
fb1455a17b Preparing to tag 2.5.0 2013-02-26 13:49:47 -08:00
Shravan Narayan
c668eeba0f Added CallbackContext success message with an int parameter
Added a small utility function to convert JSONArray to List<String>
2013-02-25 15:17:40 -05:00
Michal Mocny
62421ee49d CB-2530: Update callbackFromNative syntax to args
New callbackFromNative syntax expects an array of arguments instead of a
single message.
2013-02-25 13:28:26 -05:00
Joe Bowser
e791f29ce1 CB-2333: Probably should be re-factored as a do..while, but need to handle when there is no EOL char in buffer 2013-02-22 11:58:37 -08:00
Joe Bowser
06947cc453 CB-2333: Adding body property to FileTransferError object on Android 2013-02-22 11:48:17 -08:00
Joe Bowser
8c97474524 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/cordova-android 2013-02-21 16:49:19 -08:00
Joe Bowser
77a8568b28 CB-2522: We used buttons in older versions to send the post, not the submit event. Disabled form saving 2013-02-21 16:48:57 -08:00
Braden Shepherdson
e2dadbd7fe Fix return types of getJSONObject and optJSONObject in CordovaArgs 2013-02-21 15:53:15 -05:00
Joe Bowser
17b668a115 CB-2085: Fixing deleted database for ChildBrowser 2013-02-21 11:53:06 -08:00
Joe Bowser
a30c2b6a75 CB-2504: Merged overscroll disallowance, needed to deal with merge conflict with the InAppStorage toggle 2013-02-21 10:45:50 -08:00
Joe Bowser
2660eebec2 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/cordova-android 2013-02-21 10:31:00 -08:00
Max Woghiren
f415664b6d [CB-2504] Allow the disabling of overscroll glow. 2013-02-21 12:47:59 -05:00
Takeshi Sone
5092b29312 [CB-2518] Enable Geolocation in InAppBrowser 2013-02-21 09:07:06 -05:00
Joe Bowser
d5be901bc2 Merge branch 'next' 2013-02-20 13:53:15 -08:00
Joe Bowser
5462eddfdb Updating the JS for 2.5.0rc1 2013-02-20 13:52:29 -08:00
Joe Bowser
fef51f12c6 Apparently the JS was wrong when tagging 2013-02-20 13:06:13 -08:00
Joe Bowser
fdb3679cf5 Merge branch 'next' 2013-02-20 11:25:32 -08:00
Joe Bowser
11beb37c50 Setting to turn off Online Storage 2013-02-20 11:21:17 -08:00
Joe Bowser
5cd17730b1 Tagging 2.5.0rc1. Updating files 2013-02-19 11:53:36 -08:00
mbillau
cb192056f8 CB-2458: gracefully exit with back button
If users do extra initialization, we can get NPEs when hitting the back
button before loadUrl() has been called.
-Null fenced code in startOfHistory() that gave us an NPE when hitting
Back button with useBrowserHistory=true
-Call finish() in Back button code when no history since with
useBrowserHistory=true it would just hang while the app inits
-Call loadUrlIntoView() first in handleDestory() since with
useBrowserHistory=false, the default behavior would try to use the baseUrl
which is null
2013-02-18 16:42:14 -05:00
Joe Bowser
892f96e305 CB-2282: Turning on AppCache 2013-02-18 11:37:28 -08:00
Joe Bowser
13ef58a5bb Updated XML for the test project 2013-02-18 11:26:40 -08:00
Joe Bowser
a45d5a98dd CB-1605: Going through Eclipse warnings, removing the unused imports 2013-02-15 15:46:36 -08:00
Joe Bowser
a31714f8a4 CB-2447: This is a non-trivial task. Migrated test plugin to modern plugin API. 2013-02-15 14:25:56 -08:00
Joe Bowser
23d2a806f0 Merge branch 'CB-2447' of git://github.com/jamesjong/cordova-android 2013-02-15 14:02:23 -08:00
Joe Bowser
c20b2330ab CB-2446: Ugly, but workable fix 2013-02-15 13:57:34 -08:00
Fil Maj
8613551aec minor readme stuff 2013-02-15 13:18:03 -08:00
Fil Maj
2ab01dadc0 Adding link to cordova.io in readme 2013-02-15 13:12:19 -08:00
James Jong
790636c8cd CB-2447: Remove use of deprecate org.apache.cordova.api.Plugin 2013-02-15 13:18:58 -05:00
Andrew Grieve
23938830f7 [CB-2213] Add NATIVE_URI to the quick-return logic. 2013-02-14 10:50:37 -05:00
Benn Mapes
674b87057a [CB-2297] Fix for geolocaion database error 2013-02-12 14:23:01 -08:00
Richard Kolkovich
83d9248ec8 Update framework/src/org/apache/cordova/ContactAccessorSdk5.java
`Im.PROTOCOL` is a String, not an int. Treat it as such to prevent an Exception being thrown when parsing `null`.
2013-02-12 16:18:50 -05:00
Joe Bowser
f9d27e4a67 CB-2408: There should be a check for the browser history, but the logic intentionally doesn't match 2013-02-12 11:16:01 -08:00
patrick kettner
2683803ef3 Add check for build targets in create script
I installed the android sdk via homebrew, which does not install any build targets by default.

That resulted in the create throwing the following somewhat cryptic error.

An unexpected error occurred: "$ANDROID_BIN" create project --target $TARGET --path "$PROJECT_PATH" --package $PACKAGE --activity $ACTIVITY >&/dev/null exited with 1

This just adds a check that the variables that are set to the values of the build targets are properly set, and exit if they are not.
2013-02-12 11:07:51 -05:00
Andrew Grieve
dd86d7a5ed [CB-2095] Delete file on FileTransfer.download fail 2013-02-12 10:55:33 -05:00
Max Woghiren
1246a81d39 [CB-2213] Added NATIVE_URI support.
This is mostly the same as FILE_URI on Android.

Also replaced calls to `stripFileProtocol` with the more general-purpose `getRealPathFromURI`.  This helps support some operations on content URIs.
2013-02-11 22:45:14 -05:00
Andrew Grieve
8ab7278db2 Code clean-up of FileTransfer
- Fix warnings about toLowerCase()
- Don't assume connections are HTTP (fails for file://)
- Use StringBuilder
- Remove no-ops of disconnect() & keep-alive
2013-02-11 22:35:17 -05:00
43 changed files with 2124 additions and 1100 deletions

3
.gitignore vendored
View File

@@ -30,3 +30,6 @@ Desktop.ini
*.swp
*.class
*.jar
# IntelliJ IDEA files
*.iml
.idea

View File

@@ -5,7 +5,7 @@ Cordova Android is an Android application library that allows for Cordova-based
projects to be built for the Android Platform. Cordova based applications are,
at the core, applications written with web technology: HTML, CSS and JavaScript.
Apache Cordova is a project at The Apache Software Foundation (ASF).
[Apache Cordova](http://cordova.io) is a project at The Apache Software Foundation (ASF).
Requires
@@ -23,7 +23,7 @@ Test Requirements
Building
---
To create your cordova.jar, copy the commons codec:
To create your `cordova.jar` file, copy the commons codec:
mv commons-codec-1.7.jar framework/libs

View File

@@ -1 +1 @@
2.4.0
2.6.0

View File

@@ -59,10 +59,10 @@ function on_exit {
}
function createAppInfoJar {
(cd "$BUILD_PATH"/bin/templates/cordova/ApplicationInfo &&
javac ApplicationInfo.java &&
jar -cfe ../appinfo.jar ApplicationInfo ApplicationInfo.class
)
pushd "$BUILD_PATH"/bin/templates/cordova/ApplicationInfo > /dev/null
javac ApplicationInfo.java
jar -cfe ../appinfo.jar ApplicationInfo ApplicationInfo.class
popd > /dev/null
}
function on_error {
@@ -98,10 +98,17 @@ MANIFEST_PATH="$PROJECT_PATH"/AndroidManifest.xml
TARGET=$("$ANDROID_BIN" list targets | grep id: | tail -1 | cut -f 2 -d ' ' )
API_LEVEL=$("$ANDROID_BIN" list target | grep "API level:" | tail -n 1 | cut -f 2 -d ':' | tr -d ' ')
# check that build targets exist
if [ -z "$TARGET" ] || [ -z "$API_LEVEL" ]
then
echo "No Android Targets are installed. Please install at least one via the android SDK"
exit 1
fi
# if this a distribution release no need to build a jar
if [ ! -e "$BUILD_PATH"/cordova-$VERSION.jar ] && [ -d "$BUILD_PATH"/framework ]
then
# update the cordova-android framework for the desired target
# update the cordova-android framework for the desired target
"$ANDROID_BIN" update project --target $TARGET --path "$BUILD_PATH"/framework &> /dev/null
if [ ! -e "$BUILD_PATH"/framework/libs/commons-codec-1.7.jar ]; then
@@ -114,8 +121,10 @@ then
rm commons-codec-1.7-bin.zip && rm -rf commons-codec-1.7
fi
# compile cordova.js and cordova.jar
(cd "$BUILD_PATH"/framework && ant jar &> /dev/null )
# compile cordova.js and cordova.jar
pushd "$BUILD_PATH"/framework > /dev/null
ant jar > /dev/null
popd > /dev/null
fi
# create new android project

View File

@@ -19,7 +19,7 @@
-->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta charset="utf-8" />
<meta name="format-detection" content="telephone=no" />
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
<link rel="stylesheet" type="text/css" href="css/index.css" />
@@ -33,7 +33,7 @@
<p class="event received">Device is Ready</p>
</div>
</div>
<script type="text/javascript" src="cordova-2.4.0.js"></script>
<script type="text/javascript" src="cordova-2.6.0.js"></script>
<script type="text/javascript" src="js/index.js"></script>
<script type="text/javascript">
app.initialize();

139
bin/update Executable file
View File

@@ -0,0 +1,139 @@
#! /bin/bash
# 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.
#
# update a cordova/android project's command line tools
#
# USAGE
# ./update [path]
#
set -e
if [ -z "$1" ] || [ "$1" == "-h" ]
then
echo 'usage: update path'
echo "Make sure the Android SDK tools folder is in your PATH!"
exit 0
fi
BUILD_PATH="$( cd "$( dirname "$0" )/.." && pwd )"
VERSION=$(cat "$BUILD_PATH"/VERSION)
PROJECT_PATH="${1:-'./example'}"
if [ ! -d "$PROJECT_PATH" ]
then
echo "The project path has to exist for it to be updated"
exit 0
fi
# cleanup after exit and/or on error
function on_exit {
if [ -f "$BUILD_PATH"/framework/assets/www/cordova-$VERSION.js ]
then
rm "$BUILD_PATH"/framework/assets/www/cordova-$VERSION.js
fi
if [ -f "$BUILD_PATH"/framework/cordova-$VERSION.jar ]
then
rm "$BUILD_PATH"/framework/cordova-$VERSION.jar
fi
}
function createAppInfoJar {
(cd "$BUILD_PATH"/bin/templates/cordova/ApplicationInfo &&
javac ApplicationInfo.java &&
jar -cfe ../appinfo.jar ApplicationInfo ApplicationInfo.class
)
}
function on_error {
echo "An unexpected error occurred: $previous_command exited with $?"
echo "Deleting project..."
[ -d "$PROJECT_PATH" ] && rm -rf "$PROJECT_PATH"
exit 1
}
function replace {
local pattern=$1
local filename=$2
# Mac OS X requires -i argument
if [[ "$OSTYPE" =~ "darwin" ]]
then
/usr/bin/sed -i '' -e $pattern "$filename"
elif [[ "$OSTYPE" =~ "linux" ]]
then
/bin/sed -i -e $pattern "$filename"
fi
}
# we do not want the script to silently fail
trap 'previous_command=$this_command; this_command=$BASH_COMMAND' DEBUG
trap on_error ERR
trap on_exit EXIT
ANDROID_BIN="${ANDROID_BIN:=$( which android )}"
TARGET=$("$ANDROID_BIN" list targets | grep id: | tail -1 | cut -f 2 -d ' ' )
API_LEVEL=$("$ANDROID_BIN" list target | grep "API level:" | tail -n 1 | cut -f 2 -d ':' | tr -d ' ')
# check that build targets exist
if [ -z "$TARGET" ] || [ -z "$API_LEVEL" ]
then
echo "No Android Targets are installed. Please install at least one via the android SDK"
exit 1
fi
# if this a distribution release no need to build a jar
if [ ! -e "$BUILD_PATH"/cordova-$VERSION.jar ] && [ -d "$BUILD_PATH"/framework ]
then
# update the cordova-android framework for the desired target
"$ANDROID_BIN" update project --target $TARGET --path "$BUILD_PATH"/framework &> /dev/null
if [ ! -e "$BUILD_PATH"/framework/libs/commons-codec-1.7.jar ]; then
# Use curl to get the jar (TODO: Support Apache Mirrors)
curl -OL http://archive.apache.org/dist/commons/codec/binaries/commons-codec-1.7-bin.zip &> /dev/null
unzip commons-codec-1.7-bin.zip &> /dev/null
mkdir -p "$BUILD_PATH"/framework/libs
cp commons-codec-1.7/commons-codec-1.7.jar "$BUILD_PATH"/framework/libs
# cleanup yo
rm commons-codec-1.7-bin.zip && rm -rf commons-codec-1.7
fi
# compile cordova.js and cordova.jar
(cd "$BUILD_PATH"/framework && ant jar &> /dev/null )
fi
# copy cordova.js, cordova.jar and res/xml
if [ -d "$BUILD_PATH"/framework ]
then
cp "$BUILD_PATH"/framework/assets/www/cordova-$VERSION.js "$PROJECT_PATH"/assets/www/cordova-$VERSION.js
cp "$BUILD_PATH"/framework/cordova-$VERSION.jar "$PROJECT_PATH"/libs/cordova-$VERSION.jar
else
cp "$BUILD_PATH"/cordova-$VERSION.js "$PROJECT_PATH"/assets/www/cordova-$VERSION.js
cp "$BUILD_PATH"/cordova-$VERSION.jar "$PROJECT_PATH"/libs/cordova-$VERSION.jar
fi
# creating cordova folder and copying run/build/log/launch scripts
cp "$BUILD_PATH"/bin/templates/cordova/appinfo.jar "$PROJECT_PATH"/cordova/appinfo.jar
cp "$BUILD_PATH"/bin/templates/cordova/cordova "$PROJECT_PATH"/cordova/cordova
cp "$BUILD_PATH"/bin/templates/cordova/build "$PROJECT_PATH"/cordova/build
cp "$BUILD_PATH"/bin/templates/cordova/release "$PROJECT_PATH"/cordova/release
cp "$BUILD_PATH"/bin/templates/cordova/clean "$PROJECT_PATH"/cordova/clean
cp "$BUILD_PATH"/bin/templates/cordova/log "$PROJECT_PATH"/cordova/log
cp "$BUILD_PATH"/bin/templates/cordova/run "$PROJECT_PATH"/cordova/run

32
bin/update.bat Normal file
View File

@@ -0,0 +1,32 @@
:: Licensed to the Apache Software Foundation (ASF) under one
:: or more contributor license agreements. See the NOTICE file
:: distributed with this work for additional information
:: regarding copyright ownership. The ASF licenses this file
:: to you under the Apache License, Version 2.0 (the
:: "License"); you may not use this file except in compliance
:: with the License. You may obtain a copy of the License at
::
:: http://www.apache.org/licenses/LICENSE-2.0
::
:: Unless required by applicable law or agreed to in writing,
:: software distributed under the License is distributed on an
:: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
:: KIND, either express or implied. See the License for the
:: specific language governing permissions and limitations
:: under the License.
@ECHO OFF
IF NOT DEFINED JAVA_HOME GOTO MISSING
FOR %%X in (java.exe javac.exe ant.bat android.bat) do (
SET FOUND=%%~$PATH:X
IF NOT DEFINED FOUND GOTO MISSING
)
cscript "%~dp0\update.js" %*
GOTO END
:MISSING
ECHO Missing one of the following:
ECHO JDK: http://java.oracle.com
ECHO Android SDK: http://developer.android.com
ECHO Apache ant: http://ant.apache.org
EXIT /B 1
:END

193
bin/update.js Normal file
View File

@@ -0,0 +1,193 @@
/*
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.
*/
/*
* create a cordova/android project
*
* USAGE
* ./update [path]
*/
var fso = WScript.CreateObject('Scripting.FileSystemObject');
function read(filename) {
var fso=WScript.CreateObject("Scripting.FileSystemObject");
var f=fso.OpenTextFile(filename, 1);
var s=f.ReadAll();
f.Close();
return s;
}
function checkTargets(targets) {
if(!targets) {
WScript.Echo("You do not have any android targets setup. Please create at least one target with the `android` command");
WScript.Quit(69);
}
}
function setTarget() {
var targets = shell.Exec('android.bat list targets').StdOut.ReadAll().match(/id:\s\d+/g);
checkTargets(targets);
return targets[targets.length - 1].replace(/id: /, ""); // TODO: give users the option to set their target
}
function setApiLevel() {
var targets = shell.Exec('android.bat list targets').StdOut.ReadAll().match(/API level:\s\d+/g);
checkTargets(targets);
return targets[targets.length - 1].replace(/API level: /, "");
}
function write(filename, contents) {
var fso=WScript.CreateObject("Scripting.FileSystemObject");
var f=fso.OpenTextFile(filename, 2, true);
f.Write(contents);
f.Close();
}
function replaceInFile(filename, regexp, replacement) {
write(filename, read(filename).replace(regexp, replacement));
}
function exec(command) {
var oShell=shell.Exec(command);
while (oShell.Status == 0) {
if(!oShell.StdOut.AtEndOfStream) {
var line = oShell.StdOut.ReadLine();
// XXX: Change to verbose mode
// WScript.StdOut.WriteLine(line);
}
WScript.sleep(100);
}
}
function createAppInfoJar() {
if(!fso.FileExists(ROOT+"\\bin\\templates\\cordova\\appinfo.jar")) {
WScript.Echo("Creating appinfo.jar...");
var cur = shell.CurrentDirectory;
shell.CurrentDirectory = ROOT+"\\bin\\templates\\cordova\\ApplicationInfo";
exec("javac ApplicationInfo.java");
exec("jar -cfe ..\\appinfo.jar ApplicationInfo ApplicationInfo.class");
shell.CurrentDirectory = cur;
}
}
function cleanup() {
if(fso.FileExists(ROOT + '\\framework\\cordova-'+VERSION+'.jar')) {
fso.DeleteFile(ROOT + '\\framework\\cordova-'+VERSION+'.jar');
}
if(fso.FileExists(ROOT + '\\framework\\assets\\www\\cordova-'+VERSION+'.js')) {
fso.DeleteFile(ROOT + '\\framework\\assets\\www\\cordova-'+VERSION+'.js');
}
}
function downloadCommonsCodec() {
if (!fso.FileExists(ROOT + '\\framework\\libs\\commons-codec-1.7.jar')) {
// We need the .jar
var url = 'http://archive.apache.org/dist/commons/codec/binaries/commons-codec-1.7-bin.zip';
var libsPath = ROOT + '\\framework\\libs';
var savePath = libsPath + '\\commons-codec-1.7-bin.zip';
if (!fso.FileExists(savePath)) {
if(!fso.FolderExists(ROOT + '\\framework\\libs')) {
fso.CreateFolder(libsPath);
}
// We need the zip to get the jar
var xhr = WScript.CreateObject('MSXML2.XMLHTTP');
xhr.open('GET', url, false);
xhr.send();
if (xhr.status == 200) {
var stream = WScript.CreateObject('ADODB.Stream');
stream.Open();
stream.Type = 1;
stream.Write(xhr.ResponseBody);
stream.Position = 0;
stream.SaveToFile(savePath);
stream.Close();
} else {
WScript.Echo('Could not retrieve the commons-codec. Please download it yourself and put into the framework/libs directory. This process may fail now. Sorry.');
}
}
var app = WScript.CreateObject('Shell.Application');
var source = app.NameSpace(savePath).Items();
var target = app.NameSpace(ROOT + '\\framework\\libs');
target.CopyHere(source, 256);
// Move the jar into libs
fso.MoveFile(ROOT + '\\framework\\libs\\commons-codec-1.7\\commons-codec-1.7.jar', ROOT + '\\framework\\libs\\commons-codec-1.7.jar');
// Clean up
fso.DeleteFile(ROOT + '\\framework\\libs\\commons-codec-1.7-bin.zip');
fso.DeleteFolder(ROOT + '\\framework\\libs\\commons-codec-1.7', true);
}
}
var args = WScript.Arguments, PROJECT_PATH="example",
shell=WScript.CreateObject("WScript.Shell");
// working dir
var ROOT = WScript.ScriptFullName.split('\\bin\\update.js').join('');
if (args.Count() == 1) {
PROJECT_PATH=args(0);
}
if(!fso.FolderExists(PROJECT_PATH)) {
WScript.Echo("Project doesn't exist!");
WScript.Quit(1);
}
var TARGET=setTarget();
var API_LEVEL=setApiLevel();
var VERSION=read(ROOT+'\\VERSION').replace(/\r\n/,'').replace(/\n/,'');
// build from source. distro should have these files
if (!fso.FileExists(ROOT+'\\cordova-'+VERSION+'.jar') &&
!fso.FileExists(ROOT+'\\cordova-'+VERSION+'.js')) {
WScript.Echo("Building jar and js files...");
// update the cordova framework project to a target that exists on this machine
exec('android.bat update project --target '+TARGET+' --path '+ROOT+'\\framework');
// pull down commons codec if necessary
downloadCommonsCodec();
exec('ant.bat -f \"'+ ROOT +'\\framework\\build.xml\" jar');
}
// check if we have the source or the distro files
WScript.Echo("Copying js, jar & config.xml files...");
if(fso.FolderExists(ROOT + '\\framework')) {
exec('%comspec% /c copy "'+ROOT+'"\\framework\\assets\\www\\cordova-'+VERSION+'.js '+PROJECT_PATH+'\\assets\\www\\cordova-'+VERSION+'.js /Y');
exec('%comspec% /c copy "'+ROOT+'"\\framework\\cordova-'+VERSION+'.jar '+PROJECT_PATH+'\\libs\\cordova-'+VERSION+'.jar /Y');
} else {
// copy in cordova.js
exec('%comspec% /c copy "'+ROOT+'"\\cordova-'+VERSION+'.js '+PROJECT_PATH+'\\assets\\www\\cordova-'+VERSION+'.js /Y');
// copy in cordova.jar
exec('%comspec% /c copy "'+ROOT+'"\\cordova-'+VERSION+'.jar '+PROJECT_PATH+'\\libs\\cordova-'+VERSION+'.jar /Y');
// copy in xml
}
// update cordova scripts
createAppInfoJar();
WScript.Echo("Copying cordova command tools...");
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\appinfo.jar ' + PROJECT_PATH + '\\cordova\\appinfo.jar /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\cordova.js ' + PROJECT_PATH + '\\cordova\\cordova.js /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\cordova.bat ' + PROJECT_PATH + '\\cordova\\cordova.bat /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\clean.bat ' + PROJECT_PATH + '\\cordova\\clean.bat /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\build.bat ' + PROJECT_PATH + '\\cordova\\build.bat /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\log.bat ' + PROJECT_PATH + '\\cordova\\log.bat /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\run.bat ' + PROJECT_PATH + '\\cordova\\run.bat /Y');
cleanup();

File diff suppressed because it is too large Load Diff

View File

@@ -19,7 +19,7 @@
<html>
<head>
<title></title>
<script src="cordova-2.4.0.js"></script>
<script src="cordova-2.6.0.js"></script>
</head>
<body>

View File

@@ -68,13 +68,13 @@ public class AudioHandler extends CordovaPlugin {
String result = "";
if (action.equals("startRecordingAudio")) {
this.startRecordingAudio(args.getString(0), FileUtils.stripFileProtocol(args.getString(1)));
this.startRecordingAudio(args.getString(0), FileHelper.stripFileProtocol(args.getString(1)));
}
else if (action.equals("stopRecordingAudio")) {
this.stopRecordingAudio(args.getString(0));
}
else if (action.equals("startPlayingAudio")) {
this.startPlayingAudio(args.getString(0), FileUtils.stripFileProtocol(args.getString(1)));
this.startPlayingAudio(args.getString(0), FileHelper.stripFileProtocol(args.getString(1)));
}
else if (action.equals("seekToAudio")) {
this.seekToAudio(args.getString(0), args.getInt(1));
@@ -102,7 +102,7 @@ public class AudioHandler extends CordovaPlugin {
}
else if (action.equals("create")) {
String id = args.getString(0);
String src = FileUtils.stripFileProtocol(args.getString(1));
String src = FileHelper.stripFileProtocol(args.getString(1));
AudioPlayer audio = new AudioPlayer(this, id, src);
this.players.put(id, audio);
}

View File

@@ -167,13 +167,18 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
public void moveFile(String file) {
/* this is a hack to save the file as the specified name */
File f = new File(this.tempFile);
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
f.renameTo(new File(Environment.getExternalStorageDirectory().getAbsolutePath()
+ File.separator + file));
} else {
f.renameTo(new File("/data/data/" + handler.cordova.getActivity().getPackageName() + "/cache/" + file));
if (!file.startsWith("/")) {
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
file = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + file;
} else {
file = "/data/data/" + handler.cordova.getActivity().getPackageName() + "/cache/" + file;
}
}
String logMsg = "renaming " + this.tempFile + " to " + file;
Log.d(LOG_TAG, logMsg);
if (!f.renameTo(new File(file))) Log.e(LOG_TAG, "FAILED " + logMsg);
}
/**

View File

@@ -58,6 +58,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
private static final int DATA_URL = 0; // Return base64 encoded string
private static final int FILE_URI = 1; // Return file uri (content://media/external/images/media/2 for Android)
private static final int NATIVE_URI = 2; // On Android, this is the same as FILE_URI
private static final int PHOTOLIBRARY = 0; // Choose image from picture library (same as SAVEDPHOTOALBUM for Android)
private static final int CAMERA = 1; // Take picture from camera
@@ -288,7 +289,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
// If sending base64 image back
if (destType == DATA_URL) {
bitmap = getScaledBitmap(FileUtils.stripFileProtocol(imageUri.toString()));
bitmap = getScaledBitmap(FileHelper.stripFileProtocol(imageUri.toString()));
if (bitmap == null) {
// Try to get the bitmap from intent.
bitmap = (Bitmap)intent.getExtras().get("data");
@@ -310,11 +311,13 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
}
// If sending filename back
else if (destType == FILE_URI) {
if (!this.saveToPhotoAlbum) {
uri = Uri.fromFile(new File(DirectoryManager.getTempDirectoryPath(this.cordova.getActivity()), System.currentTimeMillis() + ".jpg"));
else if (destType == FILE_URI || destType == NATIVE_URI) {
if (this.saveToPhotoAlbum) {
Uri inputUri = getUriFromMediaStore();
//Just because we have a media URI doesn't mean we have a real file, we need to make it
uri = Uri.fromFile(new File(FileHelper.getRealPath(inputUri, this.cordova)));
} else {
uri = getUriFromMediaStore();
uri = Uri.fromFile(new File(DirectoryManager.getTempDirectoryPath(this.cordova.getActivity()), System.currentTimeMillis() + ".jpg"));
}
if (uri == null) {
@@ -328,7 +331,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
this.callbackContext.success(uri.toString());
} else {
bitmap = getScaledBitmap(FileUtils.stripFileProtocol(imageUri.toString()));
bitmap = getScaledBitmap(FileHelper.stripFileProtocol(imageUri.toString()));
if (rotate != 0 && this.correctOrientation) {
bitmap = getRotatedBitmap(rotate, bitmap, exif);
@@ -343,7 +346,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
if (this.encodingType == JPEG) {
String exifPath;
if (this.saveToPhotoAlbum) {
exifPath = FileUtils.getRealPathFromURI(uri, this.cordova);
exifPath = FileHelper.getRealPath(uri, this.cordova);
} else {
exifPath = uri.getPath();
}
@@ -388,14 +391,14 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
}
else {
// This is a special case to just return the path as no scaling,
// rotating or compression needs to be done
// rotating, nor compressing needs to be done
if (this.targetHeight == -1 && this.targetWidth == -1 &&
this.mQuality == 100 && destType == FILE_URI && !this.correctOrientation) {
(destType == FILE_URI || destType == NATIVE_URI) && !this.correctOrientation) {
this.callbackContext.success(uri.toString());
} else {
// Get the path to the image. Makes loading so much easier.
String imagePath = FileUtils.getRealPathFromURI(uri, this.cordova);
String mimeType = FileUtils.getMimeType(imagePath);
String imagePath = FileHelper.getRealPath(uri, this.cordova);
String mimeType = FileHelper.getMimeType(imagePath, this.cordova);
// Log.d(LOG_TAG, "Real path = " + imagePath);
// Log.d(LOG_TAG, "mime type = " + mimeType);
// If we don't have a valid image so quit.
@@ -434,7 +437,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
}
// If sending filename back
else if (destType == FILE_URI) {
else if (destType == FILE_URI || destType == NATIVE_URI) {
// Do we need to scale the returned file
if (this.targetHeight > 0 && this.targetWidth > 0) {
try {
@@ -443,7 +446,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
ExifHelper exif = new ExifHelper();
try {
if (this.encodingType == JPEG) {
exif.createInFile(resizePath);
exif.createInFile(FileHelper.getRealPath(uri, this.cordova));
exif.readExifData();
rotate = exif.getOrientation();
}
@@ -457,7 +460,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
// Restore exif data to file
if (this.encodingType == JPEG) {
exif.createOutFile(FileUtils.getRealPathFromURI(uri, this.cordova));
exif.createOutFile(resizePath);
exif.writeExifData();
}
@@ -520,7 +523,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
*/
private void writeUncompressedImage(Uri uri) throws FileNotFoundException,
IOException {
FileInputStream fis = new FileInputStream(FileUtils.stripFileProtocol(imageUri.toString()));
FileInputStream fis = new FileInputStream(FileHelper.stripFileProtocol(imageUri.toString()));
OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
byte[] buffer = new byte[4096];
int len;
@@ -684,7 +687,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
}
// Clean up initial camera-written image file.
(new File(FileUtils.stripFileProtocol(oldImage.toString()))).delete();
(new File(FileHelper.stripFileProtocol(oldImage.toString()))).delete();
checkForDuplicateImage(imageType);
// Scan for the gallery to update pic refs in gallery

View File

@@ -23,6 +23,7 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import android.os.Build;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.LOG;
@@ -128,7 +129,7 @@ public class Capture extends CordovaPlugin {
// If the mimeType isn't set the rest will fail
// so let's see if we can determine it.
if (mimeType == null || mimeType.equals("") || "null".equals(mimeType)) {
mimeType = FileUtils.getMimeType(filePath);
mimeType = FileHelper.getMimeType(filePath, cordova);
}
Log.d(LOG_TAG, "Mime type = " + mimeType);
@@ -155,7 +156,7 @@ public class Capture extends CordovaPlugin {
private JSONObject getImageData(String filePath, JSONObject obj) throws JSONException {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(FileUtils.stripFileProtocol(filePath), options);
BitmapFactory.decodeFile(FileHelper.stripFileProtocol(filePath), options);
obj.put("height", options.outHeight);
obj.put("width", options.outWidth);
return obj;
@@ -216,9 +217,10 @@ public class Capture extends CordovaPlugin {
*/
private void captureVideo(double duration) {
Intent intent = new Intent(android.provider.MediaStore.ACTION_VIDEO_CAPTURE);
// Introduced in API 8
//intent.putExtra(android.provider.MediaStore.EXTRA_DURATION_LIMIT, duration);
if(Build.VERSION.SDK_INT > 8){
intent.putExtra("android.intent.extra.durationLimit", duration);
}
this.cordova.startActivityForResult((CordovaPlugin) this, intent, CAPTURE_VIDEO);
}
@@ -346,7 +348,7 @@ public class Capture extends CordovaPlugin {
* @throws IOException
*/
private JSONObject createMediaFile(Uri data) {
File fp = new File(FileUtils.getRealPathFromURI(data, this.cordova));
File fp = new File(FileHelper.getRealPath(data, this.cordova));
JSONObject obj = new JSONObject();
try {
@@ -363,7 +365,7 @@ public class Capture extends CordovaPlugin {
obj.put("type", VIDEO_3GPP);
}
} else {
obj.put("type", FileUtils.getMimeType(fp.getAbsolutePath()));
obj.put("type", FileHelper.getMimeType(fp.getAbsolutePath(), cordova));
}
obj.put("lastModifiedDate", fp.lastModified());

View File

@@ -143,6 +143,16 @@ public class Config {
boolean value = xml.getAttributeValue(null, "value").equals("true");
action.getIntent().putExtra(name, value);
}
else if(name.equals("InAppBrowserStorageEnabled"))
{
boolean value = xml.getAttributeValue(null, "value").equals("true");
action.getIntent().putExtra(name, value);
}
else if(name.equals("disallowOverscroll"))
{
boolean value = xml.getAttributeValue(null, "value").equals("true");
action.getIntent().putExtra(name, value);
}
else
{
String value = xml.getAttributeValue(null, "value");

View File

@@ -18,7 +18,6 @@ package org.apache.cordova;
import java.util.HashMap;
import android.content.Context;
import android.util.Log;
import android.webkit.WebView;

View File

@@ -860,8 +860,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
im.put("id", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Im._ID)));
im.put("pref", false); // Android does not store pref attribute
im.put("value", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA)));
String type = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Im.PROTOCOL));
im.put("type", getImType(new Integer(type).intValue()));
im.put("type", getImType(cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Im.PROTOCOL))));
} catch (JSONException e) {
Log.e(LOG_TAG, e.getMessage(), e);
}

View File

@@ -20,6 +20,7 @@ package org.apache.cordova;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.util.Base64;
@@ -52,7 +53,7 @@ public class CordovaArgs {
return baseArgs.getJSONArray(index);
}
public Object getJSONObject(int index) throws JSONException {
public JSONObject getJSONObject(int index) throws JSONException {
return baseArgs.getJSONObject(index);
}
@@ -85,7 +86,7 @@ public class CordovaArgs {
return baseArgs.optJSONArray(index);
}
public Object optJSONObject(int index) {
public JSONObject optJSONObject(int index) {
return baseArgs.optJSONObject(index);
}

View File

@@ -24,12 +24,10 @@ import org.json.JSONArray;
import org.json.JSONException;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
@@ -207,7 +205,7 @@ public class CordovaChromeClient extends WebChromeClient {
// 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://") || url.indexOf(this.appView.baseUrl) == 0 || Config.isUrlWhiteListed(url)) {
if (url.startsWith("file://") || Config.isUrlWhiteListed(url)) {
reqOk = true;
}

View File

@@ -27,7 +27,6 @@ import java.util.Stack;
import org.apache.cordova.Config;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.LOG;
import org.apache.cordova.api.PluginManager;
import org.apache.cordova.api.PluginResult;
@@ -48,6 +47,7 @@ import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.webkit.WebBackForwardList;
import android.webkit.WebHistoryItem;
import android.webkit.WebChromeClient;
@@ -75,12 +75,7 @@ public class CordovaWebView extends WebView {
@SuppressWarnings("unused")
private CordovaChromeClient chromeClient;
//This is for the polyfill history
private String url;
String baseUrl;
private Stack<String> urls = new Stack<String>();
boolean useBrowserHistory = true;
// Flag to track that a loadUrl timeout occurred
int loadUrlTimeout = 0;
@@ -212,7 +207,8 @@ public class CordovaWebView extends WebView {
private void initWebViewClient(CordovaInterface cordova) {
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB)
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB ||
android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.JELLY_BEAN_MR1)
{
this.setWebViewClient(new CordovaWebViewClient(this.cordova, this));
}
@@ -255,14 +251,24 @@ public class CordovaWebView extends WebView {
Log.d(TAG, "This should never happen: InvocationTargetException means this isn't Android anymore.");
}
//We don't save any form data in the application
settings.setSaveFormData(false);
settings.setSavePassword(false);
// Jellybean rightfully tried to lock this down. Too bad they didn't give us a whitelist
// while we do this
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
Level16Apis.enableUniversalAccess(settings);
// Enable database
settings.setDatabaseEnabled(true);
// We keep this disabled because we use or shim to get around DOM_EXCEPTION_ERROR_16
String databasePath = this.cordova.getActivity().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
settings.setDatabasePath(databasePath);
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB)
{
settings.setDatabaseEnabled(true);
settings.setDatabasePath(databasePath);
}
settings.setGeolocationDatabasePath(databasePath);
// Enable DOM storage
settings.setDomStorageEnabled(true);
@@ -270,6 +276,13 @@ public class CordovaWebView extends WebView {
// Enable built-in geolocation
settings.setGeolocationEnabled(true);
// Enable AppCache
// Fix for CB-2282
settings.setAppCacheMaxSize(5 * 1048576);
String pathToCache = this.cordova.getActivity().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
settings.setAppCachePath(pathToCache);
settings.setAppCacheEnabled(true);
// Fix for CB-1405
// Google issue 4641
this.updateUserAgentString();
@@ -353,7 +366,7 @@ public class CordovaWebView extends WebView {
String initUrl = this.getProperty("url", null);
// If first page of app, then set URL to load to be the one passed in
if (initUrl == null || (this.urls.size() > 0)) {
if (initUrl == null) {
this.loadUrlIntoView(url);
}
// Otherwise use the URL specified in the activity's extras bundle
@@ -374,7 +387,7 @@ public class CordovaWebView extends WebView {
String initUrl = this.getProperty("url", null);
// If first page of app, then set URL to load to be the one passed in
if (initUrl == null || (this.urls.size() > 0)) {
if (initUrl == null) {
this.loadUrlIntoView(url, time);
}
// Otherwise use the URL specified in the activity's extras bundle
@@ -392,21 +405,8 @@ public class CordovaWebView extends WebView {
LOG.d(TAG, ">>> loadUrl(" + url + ")");
this.url = url;
if (this.baseUrl == null) {
int i = url.lastIndexOf('/');
if (i > 0) {
this.baseUrl = url.substring(0, i + 1);
}
else {
this.baseUrl = this.url + "/";
}
this.pluginManager.init();
this.pluginManager.init();
if (!this.useBrowserHistory) {
this.urls.push(url);
}
}
// Create a timeout timer for loadUrl
final CordovaWebView me = this;
@@ -461,7 +461,7 @@ public class CordovaWebView extends WebView {
if (LOG.isLoggable(LOG.DEBUG) && !url.startsWith("javascript:")) {
LOG.d(TAG, ">>> loadUrlNow()");
}
if (url.startsWith("file://") || url.indexOf(this.baseUrl) == 0 || url.startsWith("javascript:") || Config.isUrlWhiteListed(url)) {
if (url.startsWith("file://") || url.startsWith("javascript:") || Config.isUrlWhiteListed(url)) {
super.loadUrl(url);
}
}
@@ -477,7 +477,7 @@ public class CordovaWebView extends WebView {
// If not first page of app, then load immediately
// Add support for browser history if we use it.
if ((url.startsWith("javascript:")) || this.urls.size() > 0 || this.canGoBack()) {
if ((url.startsWith("javascript:")) || this.canGoBack()) {
}
// If first page, then show splashscreen
@@ -525,25 +525,6 @@ public class CordovaWebView extends WebView {
}
}
/**
* Returns the top url on the stack without removing it from
* the stack.
*/
public String peekAtUrlStack() {
if (this.urls.size() > 0) {
return this.urls.peek();
}
return "";
}
/**
* Add a url to the stack
*
* @param url
*/
public void pushUrl(String url) {
this.urls.push(url);
}
/**
* Go to previous page in history. (We manage our own history)
@@ -560,32 +541,9 @@ public class CordovaWebView extends WebView {
return true;
}
// If our managed history has prev url
if (this.urls.size() > 1 && !this.useBrowserHistory) {
this.urls.pop(); // Pop current url
String url = this.urls.pop(); // Pop prev url that we want to load, since it will be added back by loadUrl()
this.loadUrl(url);
return true;
}
return false;
}
/**
* Return true if there is a history item.
*
* @return
*/
public boolean canGoBack() {
if (super.canGoBack()) {
return true;
}
if (this.urls.size() > 1) {
return true;
}
return false;
}
/**
* Load the specified URL in the Cordova webview or a new browser instance.
@@ -609,14 +567,8 @@ public class CordovaWebView extends WebView {
if (!openExternal) {
// Make sure url is in whitelist
if (url.startsWith("file://") || url.indexOf(this.baseUrl) == 0 || Config.isUrlWhiteListed(url)) {
if (url.startsWith("file://") || Config.isUrlWhiteListed(url)) {
// TODO: What about params?
// Clear out current url from history, since it will be replacing it
if (clearHistory) {
this.urls.clear();
}
// Load new URL
this.loadUrl(url);
}
@@ -653,13 +605,6 @@ public class CordovaWebView extends WebView {
* <log level="DEBUG" />
*/
private void loadConfiguration() {
// Config has already been loaded, and it stores these preferences on the Intent.
if("false".equals(this.getProperty("useBrowserHistory", "true")))
{
//Switch back to the old browser history and state the six month policy
this.useBrowserHistory = false;
Log.w(TAG, "useBrowserHistory=false is deprecated as of Cordova 2.2.0 and will be removed six months after the 2.2.0 release. Please use the browser history and use history.back().");
}
if ("true".equals(this.getProperty("fullscreen", "false"))) {
this.cordova.getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
@@ -713,13 +658,20 @@ public class CordovaWebView extends WebView {
}
else if(keyCode == KeyEvent.KEYCODE_BACK)
{
//Because exit is fired on the keyDown and not the key up on Android 4.x
//we need to check for this.
//Also, I really wished "canGoBack" worked!
if(this.useBrowserHistory)
return !(this.startOfHistory()) || this.bound;
else
return this.urls.size() > 1 || this.bound;
return !(this.startOfHistory()) || this.bound;
}
else if(keyCode == KeyEvent.KEYCODE_MENU)
{
//How did we get here? Is there a childView?
View childView = this.getFocusedChild();
if(childView != null)
{
//Make sure we close the keyboard if it's present
InputMethodManager imm = (InputMethodManager) cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(childView.getWindowToken(), 0);
cordova.getActivity().openOptionsMenu();
}
return true;
}
return super.onKeyDown(keyCode, event);
@@ -749,7 +701,9 @@ public class CordovaWebView extends WebView {
// If not, then invoke default behaviour
else {
//this.activityState = ACTIVITY_EXITING;
return false;
//return false;
// If they hit back button when app is initializing, app should exit instead of hang until initilazation (CB2-458)
this.cordova.getActivity().finish();
}
}
}
@@ -847,7 +801,8 @@ public class CordovaWebView extends WebView {
public void handleDestroy()
{
// Send destroy event to JavaScript
this.loadUrl("javascript:try{cordova.require('cordova/channel').onDestroy.fire();}catch(e){console.log('exception firing destroy event from native');};");
// Since baseUrl is set in loadUrlIntoView, if user hit Back button before loadUrl was called, we'll get an NPE on baseUrl (CB-2458)
this.loadUrlIntoView("javascript:try{cordova.require('cordova/channel').onDestroy.fire();}catch(e){console.log('exception firing destroy event from native');};");
// Load blank page so that JavaScript onunload is called
this.loadUrl("about:blank");
@@ -910,11 +865,14 @@ public class CordovaWebView extends WebView {
{
WebBackForwardList currentList = this.copyBackForwardList();
WebHistoryItem item = currentList.getItemAtIndex(0);
String url = item.getUrl();
String currentUrl = this.getUrl();
LOG.d(TAG, "The current URL is: " + currentUrl);
LOG.d(TAG, "The URL at item 0 is:" + url);
return currentUrl.equals(url);
if( item!=null){ // Null-fence in case they haven't called loadUrl yet (CB-2458)
String url = item.getUrl();
String currentUrl = this.getUrl();
LOG.d(TAG, "The current URL is: " + currentUrl);
LOG.d(TAG, "The URL at item 0 is:" + url);
return currentUrl.equals(url);
}
return false;
}
public void showCustomView(View view, WebChromeClient.CustomViewCallback callback) {

View File

@@ -18,6 +18,7 @@
*/
package org.apache.cordova;
import java.io.ByteArrayInputStream;
import java.util.Hashtable;
import org.apache.cordova.api.CordovaInterface;
@@ -38,6 +39,7 @@ 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;
@@ -66,7 +68,7 @@ public class CordovaWebViewClient extends WebViewClient {
/**
* Constructor.
*
*
* @param cordova
* @param view
*/
@@ -77,7 +79,7 @@ public class CordovaWebViewClient extends WebViewClient {
/**
* Constructor.
*
*
* @param view
*/
public void setWebView(CordovaWebView view) {
@@ -101,8 +103,8 @@ public class CordovaWebViewClient extends WebViewClient {
String callbackId = url.substring(idx3 + 1, idx4);
String jsonArgs = url.substring(idx4 + 1);
appView.pluginManager.exec(service, action, callbackId, jsonArgs);
}
}
/**
* Give the host application a chance to take over the control when a new url
* is about to be loaded in the current WebView.
@@ -192,12 +194,8 @@ public class CordovaWebViewClient extends WebViewClient {
// 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:") || url.indexOf(this.appView.baseUrl) == 0 || Config.isUrlWhiteListed(url)) {
//This will fix iFrames
if (appView.useBrowserHistory || url.startsWith("data:"))
return false;
else
this.appView.loadUrl(url);
if (url.startsWith("file://") || url.startsWith("data:") || Config.isUrlWhiteListed(url)) {
return false;
}
// If not our application, let default viewer handle
@@ -214,6 +212,34 @@ public class CordovaWebViewClient extends WebViewClient {
return true;
}
/**
* Check for intercepting any requests for resources.
* This includes images and scripts and so on, not just top-level pages.
* @param view The WebView.
* @param url The URL to be loaded.
* @return Either null to proceed as normal, or a WebResourceResponse.
*/
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
//If something isn't whitelisted, just send a blank response
if(!Config.isUrlWhiteListed(url) && (url.startsWith("http://") || url.startsWith("https://")))
{
return getWhitelistResponse();
}
if (this.appView.pluginManager != null) {
return this.appView.pluginManager.shouldInterceptRequest(url);
}
return null;
}
private WebResourceResponse getWhitelistResponse()
{
WebResourceResponse emptyResponse;
String empty = "";
ByteArrayInputStream data = new ByteArrayInputStream(empty.getBytes());
return new WebResourceResponse("text/plain", "UTF-8", data);
}
/**
* 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
@@ -230,7 +256,7 @@ public class CordovaWebViewClient extends WebViewClient {
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);
@@ -238,22 +264,16 @@ public class CordovaWebViewClient extends WebViewClient {
}
/**
* 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.
*
* 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) {
// Clear history so history.back() doesn't do anything.
// So we can reinit() native side CallbackServer & PluginManager.
if (!this.appView.useBrowserHistory) {
view.clearHistory();
this.doClearHistory = true;
}
// Flush stale messages.
this.appView.jsMessageQueue.reset();
@@ -270,7 +290,7 @@ public class CordovaWebViewClient extends WebViewClient {
/**
* 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.
@@ -359,11 +379,11 @@ public class CordovaWebViewClient extends WebViewClient {
}
/**
* 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.
* 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.
@@ -392,27 +412,10 @@ public class CordovaWebViewClient extends WebViewClient {
}
}
/**
* Notify the host application to update its visited links database.
*
* @param view The WebView that is initiating the callback.
* @param url The url being visited.
* @param isReload True if this url is being reloaded.
*/
@Override
public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
/*
* If you do a document.location.href the url does not get pushed on the stack
* so we do a check here to see if the url should be pushed.
*/
if (!this.appView.peekAtUrlStack().equals(url)) {
this.appView.pushUrl(url);
}
}
/**
* Sets the authentication token.
*
*
* @param authenticationToken
* @param host
* @param realm
@@ -429,10 +432,10 @@ public class CordovaWebViewClient extends WebViewClient {
/**
* 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) {
@@ -441,16 +444,16 @@ public class CordovaWebViewClient extends WebViewClient {
/**
* 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) {

View File

@@ -24,7 +24,6 @@ import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.LOG;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -39,7 +38,7 @@ import android.telephony.TelephonyManager;
public class Device extends CordovaPlugin {
public static final String TAG = "Device";
public static String cordovaVersion = "2.4.0"; // Cordova version
public static String cordovaVersion = "2.6.0"; // Cordova version
public static String platform = "Android"; // Device OS
public static String uuid; // Device UUID

View File

@@ -28,6 +28,7 @@ import org.apache.cordova.api.LOG;
import org.json.JSONException;
import org.json.JSONObject;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -51,7 +52,6 @@ import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.webkit.ValueCallback;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.LinearLayout;
@@ -333,6 +333,7 @@ public class DroidGap extends Activity implements CordovaInterface {
* @param webViewClient
* @param webChromeClient
*/
@SuppressLint("NewApi")
public void init(CordovaWebView webView, CordovaWebViewClient webViewClient, CordovaChromeClient webChromeClient) {
LOG.d(TAG, "DroidGap.init()");
@@ -350,6 +351,12 @@ public class DroidGap extends Activity implements CordovaInterface {
ViewGroup.LayoutParams.MATCH_PARENT,
1.0F));
if (this.getBooleanProperty("disallowOverscroll", false)) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD) {
this.appView.setOverScrollMode(CordovaWebView.OVER_SCROLL_NEVER);
}
}
// Add web view but make it invisible while loading URL
this.appView.setVisibility(View.INVISIBLE);
this.root.addView(this.appView);
@@ -1055,7 +1062,8 @@ public class DroidGap extends Activity implements CordovaInterface {
{
//Get whatever has focus!
View childView = appView.getFocusedChild();
if ((appView.isCustomViewShowing() || childView != null ) && keyCode == KeyEvent.KEYCODE_BACK) {
if ((appView.isCustomViewShowing() || childView != null ) &&
(keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU)) {
return appView.onKeyUp(keyCode, event);
} else {
return super.onKeyUp(keyCode, event);
@@ -1075,7 +1083,7 @@ public class DroidGap extends Activity implements CordovaInterface {
//Get whatever has focus!
View childView = appView.getFocusedChild();
//Determine if the focus is on the current view or not
if (childView != null && keyCode == KeyEvent.KEYCODE_BACK) {
if (childView != null && (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU)) {
return appView.onKeyDown(keyCode, event);
}
else

View File

@@ -20,7 +20,6 @@ package org.apache.cordova;
import android.webkit.JavascriptInterface;
import org.apache.cordova.api.PluginManager;
import org.apache.cordova.api.PluginResult;
import org.json.JSONException;
/**

View File

@@ -0,0 +1,142 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import android.database.Cursor;
import android.net.Uri;
import android.webkit.MimeTypeMap;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.LOG;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class FileHelper {
private static final String LOG_TAG = "FileUtils";
private static final String _DATA = "_data";
/**
* Returns the real path of the given URI string.
* If the given URI string represents a content:// URI, the real path is retrieved from the media store.
*
* @param uriString the URI string of the audio/image/video
* @param cordova the current application context
* @return the full path to the file
*/
@SuppressWarnings("deprecation")
public static String getRealPath(String uriString, CordovaInterface cordova) {
String realPath = null;
if (uriString.startsWith("content://")) {
String[] proj = { _DATA };
Cursor cursor = cordova.getActivity().managedQuery(Uri.parse(uriString), proj, null, null, null);
int column_index = cursor.getColumnIndexOrThrow(_DATA);
cursor.moveToFirst();
realPath = cursor.getString(column_index);
if (realPath == null) {
LOG.e(LOG_TAG, "Could get real path for URI string %s", uriString);
}
} else if (uriString.startsWith("file://")) {
realPath = uriString.substring(7);
if (realPath.startsWith("/android_asset/")) {
LOG.e(LOG_TAG, "Cannot get real path for URI string %s because it is a file:///android_asset/ URI.", uriString);
realPath = null;
}
} else {
realPath = uriString;
}
return realPath;
}
/**
* Returns the real path of the given URI.
* If the given URI is a content:// URI, the real path is retrieved from the media store.
*
* @param uri the URI of the audio/image/video
* @param cordova the current application context
* @return the full path to the file
*/
public static String getRealPath(Uri uri, CordovaInterface cordova) {
return FileHelper.getRealPath(uri.toString(), cordova);
}
/**
* Returns an input stream based on given URI string.
*
* @param uriString the URI string from which to obtain the input stream
* @param cordova the current application context
* @return an input stream into the data at the given URI or null if given an invalid URI string
* @throws IOException
*/
public static InputStream getInputStreamFromUriString(String uriString, CordovaInterface cordova) throws IOException {
if (uriString.startsWith("content")) {
Uri uri = Uri.parse(uriString);
return cordova.getActivity().getContentResolver().openInputStream(uri);
} else if (uriString.startsWith("file:///android_asset/")) {
String relativePath = uriString.substring(22);
return cordova.getActivity().getAssets().open(relativePath);
} else {
return new FileInputStream(getRealPath(uriString, cordova));
}
}
/**
* Removes the "file://" prefix from the given URI string, if applicable.
* If the given URI string doesn't have a "file://" prefix, it is returned unchanged.
*
* @param uriString the URI string to operate on
* @return a path without the "file://" prefix
*/
public static String stripFileProtocol(String uriString) {
if (uriString.startsWith("file://")) {
uriString = uriString.substring(7);
}
return uriString;
}
/**
* Returns the mime type of the data specified by the given URI string.
*
* @param uriString the URI string of the data
* @return the mime type of the specified data
*/
public static String getMimeType(String uriString, CordovaInterface cordova) {
String mimeType = null;
if (uriString.startsWith("content://")) {
Uri uri = Uri.parse(uriString);
mimeType = cordova.getActivity().getContentResolver().getType(uri);
} else {
// MimeTypeMap.getFileExtensionFromUrl has a bug that occurs when the filename has a space, so we encode it.
// We also convert the URI string to lower case to ensure compatibility with MimeTypeMap (see CB-2185).
String encodedUriString = uriString.replace(" ", "%20").toLowerCase();
String extension = MimeTypeMap.getFileExtensionFromUrl(encodedUriString);
if (extension.equals("3ga")) {
mimeType = "audio/3gpp";
} else {
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
}
}
return mimeType;
}
}

View File

@@ -18,9 +18,9 @@
*/
package org.apache.cordova;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -28,11 +28,14 @@ import java.io.FileOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
@@ -77,6 +80,7 @@ public class FileTransfer extends CordovaPlugin {
private static final class RequestContext {
String source;
String target;
File targetFile;
CallbackContext callbackContext;
InputStream currentInputStream;
OutputStream currentOutputStream;
@@ -154,6 +158,25 @@ public class FileTransfer extends CordovaPlugin {
return false;
}
private static void addHeadersToRequest(URLConnection connection, JSONObject headers) {
try {
for (Iterator<?> iter = headers.keys(); iter.hasNext(); ) {
String headerKey = iter.next().toString();
JSONArray headerValues = headers.optJSONArray(headerKey);
if (headerValues == null) {
headerValues = new JSONArray();
headerValues.put(headers.getString(headerKey));
}
connection.setRequestProperty(headerKey, headerValues.getString(0));
for (int i = 1; i < headerValues.length(); ++i) {
connection.addRequestProperty(headerKey, headerValues.getString(i));
}
}
} catch (JSONException e1) {
// No headers to be manipulated!
}
}
/**
* Uploads the specified file to the server URL provided using an HTTP multipart request.
* @param source Full path of the file on the file system
@@ -195,12 +218,12 @@ public class FileTransfer extends CordovaPlugin {
try {
url = new URL(target);
} catch (MalformedURLException e) {
JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target, 0);
JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target, null, 0);
Log.e(LOG_TAG, error.toString(), e);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error));
return;
}
final boolean useHttps = url.getProtocol().toLowerCase().equals("https");
final boolean useHttps = url.getProtocol().equals("https");
final RequestContext context = new RequestContext(source, target, callbackContext);
synchronized (activeRequests) {
@@ -258,7 +281,6 @@ public class FileTransfer extends CordovaPlugin {
// Use a post method.
conn.setRequestMethod("POST");
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + BOUNDARY);
// Set the cookies on the response
@@ -269,59 +291,42 @@ public class FileTransfer extends CordovaPlugin {
// Handle the other headers
if (headers != null) {
try {
for (Iterator<?> iter = headers.keys(); iter.hasNext(); ) {
String headerKey = iter.next().toString();
JSONArray headerValues = headers.optJSONArray(headerKey);
if (headerValues == null) {
headerValues = new JSONArray();
headerValues.put(headers.getString(headerKey));
}
conn.setRequestProperty(headerKey, headerValues.getString(0));
for (int i = 1; i < headerValues.length(); ++i) {
conn.addRequestProperty(headerKey, headerValues.getString(i));
}
}
} catch (JSONException e1) {
// No headers to be manipulated!
}
addHeadersToRequest(conn, headers);
}
/*
* Store the non-file portions of the multipart data as a string, so that we can add it
* to the contentSize, since it is part of the body of the HTTP request.
*/
String extraParams = "";
StringBuilder beforeData = new StringBuilder();
try {
for (Iterator<?> iter = params.keys(); iter.hasNext();) {
Object key = iter.next();
if(!String.valueOf(key).equals("headers"))
{
extraParams += LINE_START + BOUNDARY + LINE_END;
extraParams += "Content-Disposition: form-data; name=\"" + key.toString() + "\";";
extraParams += LINE_END + LINE_END;
extraParams += params.getString(key.toString());
extraParams += LINE_END;
beforeData.append(LINE_START).append(BOUNDARY).append(LINE_END);
beforeData.append("Content-Disposition: form-data; name=\"").append(key.toString()).append('"');
beforeData.append(LINE_END).append(LINE_END);
beforeData.append(params.getString(key.toString()));
beforeData.append(LINE_END);
}
}
} catch (JSONException e) {
Log.e(LOG_TAG, e.getMessage(), e);
}
extraParams += LINE_START + BOUNDARY + LINE_END;
extraParams += "Content-Disposition: form-data; name=\"" + fileKey + "\";" + " filename=\"";
byte[] extraBytes = extraParams.getBytes("UTF-8");
String midParams = "\"" + LINE_END + "Content-Type: " + mimeType + LINE_END + LINE_END;
String tailParams = LINE_END + LINE_START + BOUNDARY + LINE_START + LINE_END;
byte[] fileNameBytes = fileName.getBytes("UTF-8");
beforeData.append(LINE_START).append(BOUNDARY).append(LINE_END);
beforeData.append("Content-Disposition: form-data; name=\"").append(fileKey).append("\";");
beforeData.append(" filename=\"").append(fileName).append('"').append(LINE_END);
beforeData.append("Content-Type: ").append(mimeType).append(LINE_END).append(LINE_END);
byte[] beforeDataBytes = beforeData.toString().getBytes("UTF-8");
byte[] tailParamsBytes = (LINE_END + LINE_START + BOUNDARY + LINE_START + LINE_END).getBytes("UTF-8");
// Get a input stream of the file on the phone
InputStream sourceInputStream = getPathFromUri(source);
int stringLength = extraBytes.length + midParams.length() + tailParams.length() + fileNameBytes.length;
Log.d(LOG_TAG, "String Length: " + stringLength);
int stringLength = beforeDataBytes.length + tailParamsBytes.length;
if (sourceInputStream instanceof FileInputStream) {
fixedLength = (int) ((FileInputStream)sourceInputStream).getChannel().size() + stringLength;
progress.setLengthComputable(true);
@@ -333,7 +338,7 @@ public class FileTransfer extends CordovaPlugin {
// It also causes OOM if HTTPS is used, even on newer devices.
boolean useChunkedMode = chunkedMode && (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO || useHttps);
useChunkedMode = useChunkedMode || (fixedLength == -1);
if (useChunkedMode) {
conn.setChunkedStreamingMode(MAX_BUFFER_SIZE);
// Although setChunkedStreamingMode sets this header, setting it explicitly here works
@@ -343,19 +348,20 @@ public class FileTransfer extends CordovaPlugin {
conn.setFixedLengthStreamingMode(fixedLength);
}
DataOutputStream dos = null;
conn.connect();
OutputStream sendStream = null;
try {
dos = new DataOutputStream( conn.getOutputStream() );
sendStream = conn.getOutputStream();
synchronized (context) {
if (context.aborted) {
return;
}
context.currentOutputStream = dos;
context.currentOutputStream = sendStream;
}
//We don't want to change encoding, we just want this to write for all Unicode.
dos.write(extraBytes);
dos.write(fileNameBytes);
dos.writeBytes(midParams);
sendStream.write(beforeDataBytes);
totalBytes += beforeDataBytes.length;
// create a buffer of maximum size
int bytesAvailable = sourceInputStream.available();
@@ -367,9 +373,9 @@ public class FileTransfer extends CordovaPlugin {
long prevBytesRead = 0;
while (bytesRead > 0) {
totalBytes += bytesRead;
result.setBytesSent(totalBytes);
dos.write(buffer, 0, bytesRead);
sendStream.write(buffer, 0, bytesRead);
totalBytes += bytesRead;
if (totalBytes > prevBytesRead + 102400) {
prevBytesRead = totalBytes;
Log.d(LOG_TAG, "Uploaded " + totalBytes + " of " + fixedLength + " bytes");
@@ -386,17 +392,21 @@ public class FileTransfer extends CordovaPlugin {
}
// send multipart form data necessary after file data...
dos.writeBytes(tailParams);
dos.flush();
sendStream.write(tailParamsBytes);
totalBytes += tailParamsBytes.length;
sendStream.flush();
} finally {
safeClose(sourceInputStream);
safeClose(dos);
safeClose(sendStream);
}
context.currentOutputStream = null;
Log.d(LOG_TAG, "Sent " + totalBytes + " of " + fixedLength);
//------------------ read the SERVER RESPONSE
String responseString;
int responseCode = conn.getResponseCode();
Log.d(LOG_TAG, "response code: " + responseCode);
Log.d(LOG_TAG, "response headers: " + conn.getHeaderFields());
InputStream inStream = null;
try {
inStream = getInputStream(conn);
@@ -407,8 +417,7 @@ public class FileTransfer extends CordovaPlugin {
context.currentInputStream = inStream;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream out = new ByteArrayOutputStream(Math.max(1024, conn.getContentLength()));
byte[] buffer = new byte[1024];
int bytesRead = 0;
// write bytes to file
@@ -459,8 +468,6 @@ public class FileTransfer extends CordovaPlugin {
https.setHostnameVerifier(oldHostnameVerifier);
https.setSSLSocketFactory(oldSocketFactory);
}
conn.disconnect();
}
}
}
@@ -476,7 +483,7 @@ public class FileTransfer extends CordovaPlugin {
}
}
private static InputStream getInputStream(HttpURLConnection conn) throws IOException {
private static InputStream getInputStream(URLConnection conn) throws IOException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
return new DoneHandlerInputStream(conn.getInputStream());
}
@@ -527,19 +534,36 @@ public class FileTransfer extends CordovaPlugin {
return oldFactory;
}
private static JSONObject createFileTransferError(int errorCode, String source, String target, HttpURLConnection connection) {
Integer httpStatus = null;
private static JSONObject createFileTransferError(int errorCode, String source, String target, URLConnection connection) {
int httpStatus = 0;
StringBuilder bodyBuilder = new StringBuilder();
String body = null;
if (connection != null) {
try {
httpStatus = connection.getResponseCode();
if (connection instanceof HttpURLConnection) {
httpStatus = ((HttpURLConnection)connection).getResponseCode();
InputStream err = ((HttpURLConnection) connection).getErrorStream();
if(err != null)
{
BufferedReader reader = new BufferedReader(new InputStreamReader(err, "UTF-8"));
String line = reader.readLine();
while(line != null)
{
bodyBuilder.append(line);
line = reader.readLine();
if(line != null)
bodyBuilder.append('\n');
}
body = bodyBuilder.toString();
}
}
} catch (IOException e) {
Log.w(LOG_TAG, "Error getting HTTP status code from connection.", e);
}
}
return createFileTransferError(errorCode, source, target, httpStatus);
return createFileTransferError(errorCode, source, target, body, httpStatus);
}
/**
@@ -547,13 +571,17 @@ public class FileTransfer extends CordovaPlugin {
* @param errorCode the error
* @return JSONObject containing the error
*/
private static JSONObject createFileTransferError(int errorCode, String source, String target, Integer httpStatus) {
private static JSONObject createFileTransferError(int errorCode, String source, String target, String body, Integer httpStatus) {
JSONObject error = null;
try {
error = new JSONObject();
error.put("code", errorCode);
error.put("source", source);
error.put("target", target);
if(body != null)
{
error.put("body", body);
}
if (httpStatus != null) {
error.put("http_status", httpStatus);
}
@@ -592,21 +620,22 @@ public class FileTransfer extends CordovaPlugin {
final boolean trustEveryone = args.optBoolean(2);
final String objectId = args.getString(3);
final JSONObject headers = args.optJSONObject(4);
final URL url;
try {
url = new URL(source);
} catch (MalformedURLException e) {
JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target, 0);
JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target, null, 0);
Log.e(LOG_TAG, error.toString(), e);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error));
return;
}
final boolean useHttps = url.getProtocol().toLowerCase().equals("https");
final boolean useHttps = url.getProtocol().equals("https");
if (!Config.isUrlWhiteListed(source)) {
Log.w(LOG_TAG, "Source URL is not in white list: '" + source + "'");
JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, 401);
JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, null, 401);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error));
return;
}
@@ -622,14 +651,16 @@ public class FileTransfer extends CordovaPlugin {
if (context.aborted) {
return;
}
HttpURLConnection connection = null;
URLConnection connection = null;
HostnameVerifier oldHostnameVerifier = null;
SSLSocketFactory oldSocketFactory = null;
File file = null;
PluginResult result = null;
try {
file = getFileFromPath(target);
context.targetFile = file;
// create needed directories
File file = getFileFromPath(target);
file.getParentFile().mkdirs();
// connect to server
@@ -654,10 +685,12 @@ public class FileTransfer extends CordovaPlugin {
}
// Return a standard HTTP connection
else {
connection = (HttpURLConnection) url.openConnection();
connection = url.openConnection();
}
connection.setRequestMethod("GET");
if (connection instanceof HttpURLConnection) {
((HttpURLConnection)connection).setRequestMethod("GET");
}
//Add cookie support
String cookie = CookieManager.getInstance().getCookie(source);
@@ -665,6 +698,11 @@ public class FileTransfer extends CordovaPlugin {
{
connection.setRequestProperty("cookie", cookie);
}
// Handle the other headers
if (headers != null) {
addHeadersToRequest(connection, headers);
}
connection.connect();
@@ -712,25 +750,24 @@ public class FileTransfer extends CordovaPlugin {
Log.d(LOG_TAG, "Saved file: " + target);
// create FileEntry object
FileUtils fileUtil = new FileUtils();
JSONObject fileEntry = fileUtil.getEntry(file);
JSONObject fileEntry = FileUtils.getEntry(file);
context.sendPluginResult(new PluginResult(PluginResult.Status.OK, fileEntry));
result = new PluginResult(PluginResult.Status.OK, fileEntry);
} catch (FileNotFoundException e) {
JSONObject error = createFileTransferError(FILE_NOT_FOUND_ERR, source, target, connection);
Log.e(LOG_TAG, error.toString(), e);
context.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error));
result = new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
} catch (IOException e) {
JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, connection);
Log.e(LOG_TAG, error.toString(), e);
context.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error));
result = new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
} catch (JSONException e) {
Log.e(LOG_TAG, e.getMessage(), e);
context.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
result = new PluginResult(PluginResult.Status.JSON_EXCEPTION);
} catch (Throwable e) {
JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, connection);
Log.e(LOG_TAG, error.toString(), e);
context.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error));
result = new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
} finally {
synchronized (activeRequests) {
activeRequests.remove(objectId);
@@ -743,9 +780,16 @@ public class FileTransfer extends CordovaPlugin {
https.setHostnameVerifier(oldHostnameVerifier);
https.setSSLSocketFactory(oldSocketFactory);
}
connection.disconnect();
}
if (result == null) {
result = new PluginResult(PluginResult.Status.ERROR, createFileTransferError(CONNECTION_ERR, source, target, connection));
}
// Remove incomplete download.
if (result.getStatus() != PluginResult.Status.OK.ordinal() && file != null) {
file.delete();
}
context.sendPluginResult(result);
}
}
});
@@ -808,8 +852,12 @@ public class FileTransfer extends CordovaPlugin {
context = activeRequests.remove(objectId);
}
if (context != null) {
File file = context.targetFile;
if (file != null) {
file.delete();
}
// Trigger the abort callback immediately to minimize latency between it and abort() being called.
JSONObject error = createFileTransferError(ABORTED_ERR, context.source, context.target, -1);
JSONObject error = createFileTransferError(ABORTED_ERR, context.source, context.target, null, -1);
synchronized (context) {
context.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, error));
context.aborted = true;

View File

@@ -15,18 +15,17 @@
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.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.channels.FileChannel;
import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import org.apache.commons.codec.binary.Base64;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.PluginResult;
import org.apache.cordova.file.EncodingException;
@@ -38,24 +37,25 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
//import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import android.webkit.MimeTypeMap;
//import android.app.Activity;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.channels.FileChannel;
/**
* This class provides SD card file and directory services to JavaScript.
* Only files on the SD card can be accessed.
*/
public class FileUtils extends CordovaPlugin {
@SuppressWarnings("unused")
private static final String LOG_TAG = "FileUtils";
private static final String _DATA = "_data"; // The column name where the file path is stored
public static int NOT_FOUND_ERR = 1;
public static int SECURITY_ERR = 2;
@@ -76,9 +76,6 @@ public class FileUtils extends CordovaPlugin {
public static int RESOURCE = 2;
public static int APPLICATION = 3;
FileReader f_in;
FileWriter f_out;
/**
* Constructor.
*/
@@ -89,7 +86,7 @@ public class FileUtils extends CordovaPlugin {
* Executes the request and returns whether the action was valid.
*
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param args JSONArray of arguments for the plugin.
* @param callbackContext The callback context used when calling back into JavaScript.
* @return True if the action was valid, false otherwise.
*/
@@ -112,30 +109,29 @@ public class FileUtils extends CordovaPlugin {
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b));
}
else if (action.equals("readAsText")) {
int start = 0;
int end = Integer.MAX_VALUE;
if (args.length() >= 3) {
start = args.getInt(2);
}
if (args.length() >= 4) {
end = args.getInt(3);
}
String encoding = args.getString(1);
int start = args.getInt(2);
int end = args.getInt(3);
String s = this.readAsText(args.getString(0), args.getString(1), start, end);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, s));
this.readFileAs(args.getString(0), start, end, callbackContext, encoding, PluginResult.MESSAGE_TYPE_STRING);
}
else if (action.equals("readAsDataURL")) {
int start = 0;
int end = Integer.MAX_VALUE;
if (args.length() >= 2) {
start = args.getInt(1);
}
if (args.length() >= 3) {
end = args.getInt(2);
}
int start = args.getInt(1);
int end = args.getInt(2);
String s = this.readAsDataURL(args.getString(0), start, end);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, s));
this.readFileAs(args.getString(0), start, end, callbackContext, null, -1);
}
else if (action.equals("readAsArrayBuffer")) {
int start = args.getInt(1);
int end = args.getInt(2);
this.readFileAs(args.getString(0), start, end, callbackContext, null, PluginResult.MESSAGE_TYPE_ARRAYBUFFER);
}
else if (action.equals("readAsBinaryString")) {
int start = args.getInt(1);
int end = args.getInt(2);
this.readFileAs(args.getString(0), start, end, callbackContext, null, PluginResult.MESSAGE_TYPE_BINARYSTRING);
}
else if (action.equals("write")) {
long fileSize = this.write(args.getString(0), args.getString(1), args.getInt(2));
@@ -238,11 +234,11 @@ public class FileUtils extends CordovaPlugin {
* @param filePath the path to check
*/
private void notifyDelete(String filePath) {
String newFilePath = stripFileProtocol(filePath);
String newFilePath = FileHelper.getRealPath(filePath, cordova);
try {
this.cordova.getActivity().getContentResolver().delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
MediaStore.Images.Media.DATA + " = ?",
new String[] { newFilePath });
MediaStore.Images.Media.DATA + " = ?",
new String[] { newFilePath });
} catch (UnsupportedOperationException t) {
// Was seeing this on the File mobile-spec tests on 4.0.3 x86 emulator.
// The ContentResolver applies only when the file was registered in the
@@ -342,18 +338,18 @@ public class FileUtils extends CordovaPlugin {
* @throws InvalidModificationException
* @throws EncodingException
* @throws JSONException
* @throws FileExistsException
* @throws FileExistsException
*/
private JSONObject transferTo(String fileName, String newParent, String newName, boolean move) throws JSONException, NoModificationAllowedException, IOException, InvalidModificationException, EncodingException, FileExistsException {
fileName = stripFileProtocol(fileName);
newParent = stripFileProtocol(newParent);
String newFileName = FileHelper.getRealPath(fileName, cordova);
newParent = FileHelper.getRealPath(newParent, cordova);
// Check for invalid file name
if (newName != null && newName.contains(":")) {
throw new EncodingException("Bad file name");
}
File source = new File(fileName);
File source = new File(newFileName);
if (!source.exists()) {
// The file/directory we are copying doesn't exist so we should fail.
@@ -385,7 +381,14 @@ public class FileUtils extends CordovaPlugin {
}
} else {
if (move) {
return moveFile(source, destination);
JSONObject newFileEntry = moveFile(source, destination);
// If we've moved a file given its content URI, we need to clean up.
if (fileName.startsWith("content://")) {
notifyDelete(fileName);
}
return newFileEntry;
} else {
return copyFile(source, destination);
}
@@ -483,7 +486,7 @@ public class FileUtils extends CordovaPlugin {
if (!destinationDir.exists()) {
if (!destinationDir.mkdir()) {
// If we can't create the directory then fail
throw new NoModificationAllowedException("Couldn't create the destination direcotry");
throw new NoModificationAllowedException("Couldn't create the destination directory");
}
}
@@ -561,8 +564,8 @@ public class FileUtils extends CordovaPlugin {
* @throws JSONException
* @throws IOException
* @throws InvalidModificationException
* @throws NoModificationAllowedException
* @throws FileExistsException
* @throws NoModificationAllowedException
* @throws FileExistsException
*/
private JSONObject moveDirectory(File srcDir, File destinationDir) throws IOException, JSONException, InvalidModificationException, NoModificationAllowedException, FileExistsException {
// Renaming a file to an existing directory should fail
@@ -742,7 +745,7 @@ public class FileUtils extends CordovaPlugin {
if (fileName.startsWith("/")) {
fp = new File(fileName);
} else {
dirPath = stripFileProtocol(dirPath);
dirPath = FileHelper.getRealPath(dirPath, cordova);
fp = new File(dirPath + File.separator + fileName);
}
return fp;
@@ -757,7 +760,7 @@ public class FileUtils extends CordovaPlugin {
* @throws JSONException
*/
private JSONObject getParent(String filePath) throws JSONException {
filePath = stripFileProtocol(filePath);
filePath = FileHelper.getRealPath(filePath, cordova);
if (atRootDirectory(filePath)) {
return getEntry(filePath);
@@ -773,7 +776,7 @@ public class FileUtils extends CordovaPlugin {
* @return true if we are at the root, false otherwise.
*/
private boolean atRootDirectory(String filePath) {
filePath = stripFileProtocol(filePath);
filePath = FileHelper.getRealPath(filePath, cordova);
if (filePath.equals(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/" + cordova.getActivity().getPackageName() + "/cache") ||
filePath.equals(Environment.getExternalStorageDirectory().getAbsolutePath()) ||
@@ -783,19 +786,6 @@ public class FileUtils extends CordovaPlugin {
return false;
}
/**
* This method removes the "file://" from the passed in filePath
*
* @param filePath to be checked.
* @return
*/
public static String stripFileProtocol(String filePath) {
if (filePath.startsWith("file://")) {
filePath = filePath.substring(7);
}
return filePath;
}
/**
* Create a File object from the passed in path
*
@@ -803,7 +793,7 @@ public class FileUtils extends CordovaPlugin {
* @return
*/
private File createFileObject(String filePath) {
filePath = stripFileProtocol(filePath);
filePath = FileHelper.getRealPath(filePath, cordova);
File file = new File(filePath);
return file;
@@ -843,9 +833,9 @@ public class FileUtils extends CordovaPlugin {
JSONObject metadata = new JSONObject();
metadata.put("size", file.length());
metadata.put("type", getMimeType(filePath));
metadata.put("type", FileHelper.getMimeType(filePath, cordova));
metadata.put("name", file.getName());
metadata.put("fullPath", file.getAbsolutePath());
metadata.put("fullPath", filePath);
metadata.put("lastModifiedDate", file.lastModified());
return metadata;
@@ -894,21 +884,21 @@ public class FileUtils extends CordovaPlugin {
}
/**
* Returns a JSON Object representing a directory on the device's file system
* Returns a JSON object representing the given File.
*
* @param path to the directory
* @return
* @param file the File to convert
* @return a JSON representation of the given File
* @throws JSONException
*/
public JSONObject getEntry(File file) throws JSONException {
public static JSONObject getEntry(File file) throws JSONException {
JSONObject entry = new JSONObject();
entry.put("isFile", file.isFile());
entry.put("isDirectory", file.isDirectory());
entry.put("name", file.getName());
entry.put("fullPath", "file://" + file.getAbsolutePath());
// I can't add the next thing it as it would be an infinite loop
//entry.put("filesystem", null);
// The file system can't be specified, as it would lead to an infinite loop.
// entry.put("filesystem", null);
return entry;
}
@@ -924,123 +914,83 @@ public class FileUtils extends CordovaPlugin {
return getEntry(new File(path));
}
/**
* Identifies if action to be executed returns a value and should be run synchronously.
*
* @param action The action to execute
* @return T=returns value
*/
public boolean isSynch(String action) {
if (action.equals("testSaveLocationExists")) {
return true;
}
else if (action.equals("getFreeDiskSpace")) {
return true;
}
else if (action.equals("testFileExists")) {
return true;
}
else if (action.equals("testDirectoryExists")) {
return true;
}
return false;
}
//--------------------------------------------------------------------------
// LOCAL METHODS
//--------------------------------------------------------------------------
/**
* Read content of text file.
* Read the contents of a file.
* This is done in a background thread; the result is sent to the callback.
*
* @param filename The name of the file.
* @param encoding The encoding to return contents as. Typical value is UTF-8.
* (see http://www.iana.org/assignments/character-sets)
* @param start Start position in the file.
* @param end End position to stop at (exclusive).
* @return Contents of file.
* @throws FileNotFoundException, IOException
* @param filename The name of the file.
* @param start Start position in the file.
* @param end End position to stop at (exclusive).
* @param callbackContext The context through which to send the result.
* @param encoding The encoding to return contents as. Typical value is UTF-8. (see http://www.iana.org/assignments/character-sets)
* @param resultType The desired type of data to send to the callback.
* @return Contents of file.
*/
public String readAsText(String filename, String encoding, int start, int end) throws FileNotFoundException, IOException {
int diff = end - start;
byte[] bytes = new byte[1000];
BufferedInputStream bis = new BufferedInputStream(getPathFromUri(filename), 1024);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int numRead = 0;
public void readFileAs(final String filename, final int start, final int end, final CallbackContext callbackContext, final String encoding, final int resultType) {
this.cordova.getThreadPool().execute(new Runnable() {
public void run() {
try {
byte[] bytes = readAsBinaryHelper(filename, start, end);
PluginResult result;
switch (resultType) {
case PluginResult.MESSAGE_TYPE_STRING:
result = new PluginResult(PluginResult.Status.OK, new String(bytes, encoding));
break;
case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
result = new PluginResult(PluginResult.Status.OK, bytes);
break;
case PluginResult.MESSAGE_TYPE_BINARYSTRING:
result = new PluginResult(PluginResult.Status.OK, bytes, true);
break;
default: // Base64.
String contentType = FileHelper.getMimeType(filename, cordova);
byte[] base64 = Base64.encodeBase64(bytes);
String s = "data:" + contentType + ";base64," + new String(base64, "US-ASCII");
result = new PluginResult(PluginResult.Status.OK, s);
}
if (start > 0) {
bis.skip(start);
}
while ( diff > 0 && (numRead = bis.read(bytes, 0, Math.min(1000, diff))) >= 0) {
diff -= numRead;
bos.write(bytes, 0, numRead);
}
return new String(bos.toByteArray(), encoding);
}
/**
* Read content of text file and return as base64 encoded data url.
*
* @param filename The name of the file.
* @return Contents of file = data:<media type>;base64,<data>
* @throws FileNotFoundException, IOException
*/
public String readAsDataURL(String filename, int start, int end) throws FileNotFoundException, IOException {
int diff = end - start;
byte[] bytes = new byte[1000];
BufferedInputStream bis = new BufferedInputStream(getPathFromUri(filename), 1024);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int numRead = 0;
if (start > 0) {
bis.skip(start);
}
while (diff > 0 && (numRead = bis.read(bytes, 0, Math.min(1000, diff))) >= 0) {
diff -= numRead;
bos.write(bytes, 0, numRead);
}
// Determine content type from file name
String contentType = null;
if (filename.startsWith("content:")) {
Uri fileUri = Uri.parse(filename);
contentType = this.cordova.getActivity().getContentResolver().getType(fileUri);
}
else {
contentType = getMimeType(filename);
}
byte[] base64 = Base64.encodeBase64(bos.toByteArray());
String data = "data:" + contentType + ";base64," + new String(base64);
return data;
}
/**
* Looks up the mime type of a given file name.
*
* @param filename
* @return a mime type
*/
public static String getMimeType(String filename) {
if (filename != null) {
// Stupid bug in getFileExtensionFromUrl when the file name has a space
// So we need to replace the space with a url encoded %20
// CB-2185: Stupid bug not putting JPG extension in the mime-type map
String url = filename.replace(" ", "%20").toLowerCase();
MimeTypeMap map = MimeTypeMap.getSingleton();
String extension = MimeTypeMap.getFileExtensionFromUrl(url);
if (extension.toLowerCase().equals("3ga")) {
return "audio/3gpp";
} else {
return map.getMimeTypeFromExtension(extension);
callbackContext.sendPluginResult(result);
} catch (FileNotFoundException e) {
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, NOT_FOUND_ERR));
} catch (IOException e) {
Log.d(LOG_TAG, e.getLocalizedMessage());
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, NOT_READABLE_ERR));
}
}
} else {
return "";
});
}
/**
* Read the contents of a file as binary.
* This is done synchronously; the result is returned.
*
* @param filename The name of the file.
* @param start Start position in the file.
* @param end End position to stop at (exclusive).
* @return Contents of the file as a byte[].
* @throws IOException
*/
private byte[] readAsBinaryHelper(String filename, int start, int end) throws IOException {
int numBytesToRead = end - start;
byte[] bytes = new byte[numBytesToRead];
InputStream inputStream = FileHelper.getInputStreamFromUriString(filename, cordova);
int numBytesRead = 0;
if (start > 0) {
inputStream.skip(start);
}
while (numBytesToRead > 0 && (numBytesRead = inputStream.read(bytes, numBytesRead, numBytesToRead)) >= 0) {
numBytesToRead -= numBytesRead;
}
return bytes;
}
/**
@@ -1050,10 +1000,15 @@ public class FileUtils extends CordovaPlugin {
* @param data The contents of the file.
* @param offset The position to begin writing the file.
* @throws FileNotFoundException, IOException
* @throws NoModificationAllowedException
*/
/**/
public long write(String filename, String data, int offset) throws FileNotFoundException, IOException {
filename = stripFileProtocol(filename);
public long write(String filename, String data, int offset) throws FileNotFoundException, IOException, NoModificationAllowedException {
if (filename.startsWith("content://")) {
throw new NoModificationAllowedException("Couldn't write to file given its content URI");
}
filename = FileHelper.getRealPath(filename, cordova);
boolean append = false;
if (offset > 0) {
@@ -1079,9 +1034,14 @@ public class FileUtils extends CordovaPlugin {
* @param filename
* @param size
* @throws FileNotFoundException, IOException
* @throws NoModificationAllowedException
*/
private long truncateFile(String filename, long size) throws FileNotFoundException, IOException {
filename = stripFileProtocol(filename);
private long truncateFile(String filename, long size) throws FileNotFoundException, IOException, NoModificationAllowedException {
if (filename.startsWith("content://")) {
throw new NoModificationAllowedException("Couldn't truncate file given its content URI");
}
filename = FileHelper.getRealPath(filename, cordova);
RandomAccessFile raf = new RandomAccessFile(filename, "rw");
try {
@@ -1090,52 +1050,10 @@ public class FileUtils extends CordovaPlugin {
channel.truncate(size);
return size;
}
return raf.length();
} finally {
raf.close();
}
}
/**
* Get an input stream based on file path or content:// uri
*
* @param path
* @return an input stream
* @throws FileNotFoundException
*/
private InputStream getPathFromUri(String path) throws FileNotFoundException {
if (path.startsWith("content")) {
Uri uri = Uri.parse(path);
return cordova.getActivity().getContentResolver().openInputStream(uri);
}
else {
path = stripFileProtocol(path);
return new FileInputStream(path);
}
}
/**
* Queries the media store to find out what the file path is for the Uri we supply
*
* @param contentUri the Uri of the audio/image/video
* @param cordova the current application context
* @return the full path to the file
*/
@SuppressWarnings("deprecation")
protected static String getRealPathFromURI(Uri contentUri, CordovaInterface cordova) {
final String scheme = contentUri.getScheme();
if (scheme.compareTo("content") == 0) {
String[] proj = { _DATA };
Cursor cursor = cordova.getActivity().managedQuery(contentUri, proj, null, null, null);
int column_index = cursor.getColumnIndexOrThrow(_DATA);
cursor.moveToFirst();
return cursor.getString(column_index);
} else if (scheme.compareTo("file") == 0) {
return contentUri.getPath();
} else {
return contentUri.toString();
}
}
}

View File

@@ -150,7 +150,7 @@ public class GeoBroker extends CordovaPlugin {
o.put("altitude", (loc.hasAltitude() ? loc.getAltitude() : null));
o.put("accuracy", loc.getAccuracy());
o.put("heading", (loc.hasBearing() ? (loc.hasSpeed() ? loc.getBearing() : null) : null));
o.put("speed", loc.getSpeed());
o.put("velocity", loc.getSpeed());
o.put("timestamp", loc.getTime());
} catch (JSONException e) {
// TODO Auto-generated catch block

View File

@@ -42,7 +42,7 @@ public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
if(url.contains("?") || url.contains("#")){
if(url.contains("?") || url.contains("#") || needsIceCreamSpaceInAssetUrlFix(url)){
return generateWebResourceResponse(url);
} else {
return super.shouldInterceptRequest(view, url);
@@ -80,4 +80,18 @@ public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
return null;
}
private static boolean needsIceCreamSpaceInAssetUrlFix(String url) {
if (!url.contains("%20")){
return false;
}
switch(android.os.Build.VERSION.SDK_INT){
case android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH:
case android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1:
return true;
default:
return false;
}
}
}

View File

@@ -23,6 +23,7 @@ import java.util.StringTokenizer;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.LOG;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
@@ -34,7 +35,9 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.text.InputType;
import android.util.Log;
import android.util.TypedValue;
@@ -47,7 +50,9 @@ import android.view.WindowManager.LayoutParams;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.webkit.WebChromeClient;
import android.webkit.GeolocationPermissions.Callback;
import android.webkit.WebSettings;
import android.webkit.WebStorage;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
@@ -67,13 +72,17 @@ public class InAppBrowser extends CordovaPlugin {
private static final String EXIT_EVENT = "exit";
private static final String LOAD_START_EVENT = "loadstart";
private static final String LOAD_STOP_EVENT = "loadstop";
private static final String LOAD_ERROR_EVENT = "loaderror";
private static final String CLOSE_BUTTON_CAPTION = "closebuttoncaption";
private long MAX_QUOTA = 100 * 1024 * 1024;
private Dialog dialog;
private WebView inAppWebView;
private EditText edittext;
private boolean showLocationBar = true;
private CallbackContext callbackContext;
private String buttonLabel = "Done";
/**
* Executes the request and returns PluginResult.
*
@@ -108,6 +117,17 @@ public class InAppBrowser extends CordovaPlugin {
|| Config.isUrlWhiteListed(url)) {
this.webView.loadUrl(url);
}
//Load the dialer
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(LOG_TAG, "Error dialing " + url + ": " + e.toString());
}
}
// load in InAppBrowser
else {
result = this.showWebPage(url, features);
@@ -131,6 +151,21 @@ public class InAppBrowser extends CordovaPlugin {
pluginResult.setKeepCallback(false);
this.callbackContext.sendPluginResult(pluginResult);
}
else if (action.equals("injectScriptCode")) {
String source = args.getString(0);
org.json.JSONArray jsonEsc = new org.json.JSONArray();
jsonEsc.put(source);
String jsonRepr = jsonEsc.toString();
String jsonSourceString = jsonRepr.substring(1, jsonRepr.length()-1);
String scriptEnclosure = "(function(d){var c=d.createElement('script');c.type='text/javascript';c.innerText="
+ jsonSourceString
+ ";d.getElementsByTagName('head')[0].appendChild(c);})(document)";
this.inAppWebView.loadUrl("javascript:" + scriptEnclosure);
PluginResult pluginResult = new PluginResult(PluginResult.Status.OK);
this.callbackContext.sendPluginResult(pluginResult);
}
else {
status = PluginResult.Status.INVALID_ACTION;
}
@@ -160,8 +195,12 @@ public class InAppBrowser extends CordovaPlugin {
option = new StringTokenizer(features.nextToken(), "=");
if (option.hasMoreElements()) {
String key = option.nextToken();
Boolean value = option.nextToken().equals("no") ? Boolean.FALSE : Boolean.TRUE;
map.put(key, value);
if (key.equalsIgnoreCase(CLOSE_BUTTON_CAPTION)) {
this.buttonLabel = option.nextToken();
} else {
Boolean value = option.nextToken().equals("no") ? Boolean.FALSE : Boolean.TRUE;
map.put(key, value);
}
}
}
return map;
@@ -207,6 +246,7 @@ public class InAppBrowser extends CordovaPlugin {
*/
private void closeDialog() {
try {
this.inAppWebView.loadUrl("about:blank");
JSONObject obj = new JSONObject();
obj.put("type", EXIT_EVENT);
@@ -275,7 +315,10 @@ public class InAppBrowser extends CordovaPlugin {
// Determine if we should hide the location bar.
showLocationBar = true;
if (features != null) {
showLocationBar = features.get(LOCATION).booleanValue();
Boolean show = features.get(LOCATION);
if (show != null) {
showLocationBar = show.booleanValue();
}
}
final CordovaWebView thatWebView = this.webView;
@@ -391,7 +434,7 @@ public class InAppBrowser extends CordovaPlugin {
close.setLayoutParams(closeLayoutParams);
forward.setContentDescription("Close Button");
close.setId(5);
close.setText("Done");
close.setText(buttonLabel);
close.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
closeDialog();
@@ -401,7 +444,7 @@ public class InAppBrowser extends CordovaPlugin {
// WebView
inAppWebView = new WebView(cordova.getActivity());
inAppWebView.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
inAppWebView.setWebChromeClient(new WebChromeClient());
inAppWebView.setWebChromeClient(new InAppChromeClient());
WebViewClient client = new InAppBrowserClient(thatWebView, edittext);
inAppWebView.setWebViewClient(client);
WebSettings settings = inAppWebView.getSettings();
@@ -414,10 +457,18 @@ public class InAppBrowser extends CordovaPlugin {
*/
// @TODO: replace with settings.setPluginState(android.webkit.WebSettings.PluginState.ON)
settings.setPluginsEnabled(true);
settings.setDatabaseEnabled(true);
String databasePath = cordova.getActivity().getApplicationContext().getDir("inAppBrowserDB", Context.MODE_PRIVATE).getPath();
settings.setDatabasePath(databasePath);
//Toggle whether this is enabled or not!
Bundle appSettings = cordova.getActivity().getIntent().getExtras();
boolean enableDatabase = appSettings == null ? true : appSettings.getBoolean("InAppBrowserStorageEnabled", true);
if(enableDatabase)
{
String databasePath = cordova.getActivity().getApplicationContext().getDir("inAppBrowserDB", Context.MODE_PRIVATE).getPath();
settings.setDatabasePath(databasePath);
settings.setDatabaseEnabled(true);
}
settings.setDomStorageEnabled(true);
inAppWebView.loadUrl(url);
inAppWebView.setId(6);
inAppWebView.getSettings().setLoadWithOverviewMode(true);
@@ -458,16 +509,70 @@ public class InAppBrowser extends CordovaPlugin {
}
/**
* Create a new plugin result and send it back to JavaScript
* Create a new plugin success result and send it back to JavaScript
*
* @param obj a JSONObject contain event payload information
*/
private void sendUpdate(JSONObject obj, boolean keepCallback) {
PluginResult result = new PluginResult(PluginResult.Status.OK, obj);
sendUpdate(obj, keepCallback, PluginResult.Status.OK);
}
/**
* Create a new plugin result and send it back to JavaScript
*
* @param obj a JSONObject contain event payload information
* @param status the status code to return to the JavaScript environment
*/ private void sendUpdate(JSONObject obj, boolean keepCallback, PluginResult.Status status) {
PluginResult result = new PluginResult(status, obj);
result.setKeepCallback(keepCallback);
this.callbackContext.sendPluginResult(result);
}
public class InAppChromeClient extends WebChromeClient {
/**
* Handle database quota exceeded notification.
*
* @param url
* @param databaseIdentifier
* @param currentQuota
* @param estimatedSize
* @param totalUsedQuota
* @param quotaUpdater
*/
@Override
public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize,
long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater)
{
LOG.d(LOG_TAG, "onExceededDatabaseQuota estimatedSize: %d currentQuota: %d totalUsedQuota: %d", estimatedSize, currentQuota, totalUsedQuota);
if (estimatedSize < MAX_QUOTA)
{
//increase for 1Mb
long newQuota = estimatedSize;
LOG.d(LOG_TAG, "calling quotaUpdater.updateQuota newQuota: %d", newQuota);
quotaUpdater.updateQuota(newQuota);
}
else
{
// Set the quota to whatever it is and force an error
// TODO: get docs on how to handle this properly
quotaUpdater.updateQuota(currentQuota);
}
}
/**
* 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
*/
@Override
public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
super.onGeolocationPermissionsShowPrompt(origin, callback);
callback.invoke(origin, true, false);
}
}
/**
* The webview client receives notifications about appView
*/
@@ -495,10 +600,62 @@ public class InAppBrowser extends CordovaPlugin {
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
String newloc;
String newloc = "";
if (url.startsWith("http:") || url.startsWith("https:") || url.startsWith("file:")) {
newloc = url;
} else {
}
// 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));
cordova.getActivity().startActivity(intent);
} catch (android.content.ActivityNotFoundException e) {
LOG.e(LOG_TAG, "Error dialing " + url + ": " + e.toString());
}
}
else if (url.startsWith("geo:") || url.startsWith(WebView.SCHEME_MAILTO) || url.startsWith("market:")) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
cordova.getActivity().startActivity(intent);
} catch (android.content.ActivityNotFoundException e) {
LOG.e(LOG_TAG, "Error with " + 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");
cordova.getActivity().startActivity(intent);
} catch (android.content.ActivityNotFoundException e) {
LOG.e(LOG_TAG, "Error sending sms " + url + ":" + e.toString());
}
}
else {
newloc = "http://" + url;
}
@@ -530,5 +687,22 @@ public class InAppBrowser extends CordovaPlugin {
Log.d(LOG_TAG, "Should never happen");
}
}
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
super.onReceivedError(view, errorCode, description, failingUrl);
try {
JSONObject obj = new JSONObject();
obj.put("type", LOAD_ERROR_EVENT);
obj.put("url", failingUrl);
obj.put("code", errorCode);
obj.put("message", description);
sendUpdate(obj, true, PluginResult.Status.ERROR);
} catch (JSONException ex) {
Log.d(LOG_TAG, "Should never happen");
}
}
}
}

View File

@@ -0,0 +1,24 @@
package org.apache.cordova;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONException;
public class JSONUtils {
public static List<String> toStringList(JSONArray array) throws JSONException {
if(array == null) {
return null;
}
else {
List<String> list = new ArrayList<String>();
for (int i = 0; i < array.length(); i++) {
list.add(array.get(i).toString());
}
return list;
}
}
}

View File

@@ -409,6 +409,9 @@ public class NativeToJsMessageQueue {
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;
@@ -451,7 +454,11 @@ public class NativeToJsMessageQueue {
sb.append('s');
sb.append(pluginResult.getStrMessage());
break;
case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
case PluginResult.MESSAGE_TYPE_BINARYSTRING: // S
sb.append('S');
sb.append(pluginResult.getMessage());
break;
case PluginResult.MESSAGE_TYPE_ARRAYBUFFER: // A
sb.append('A');
sb.append(pluginResult.getMessage());
break;
@@ -473,9 +480,9 @@ public class NativeToJsMessageQueue {
.append(success)
.append(",")
.append(status)
.append(",")
.append(",[")
.append(pluginResult.getMessage())
.append(",")
.append("],")
.append(pluginResult.getKeepCallback())
.append(");");
}

View File

@@ -24,6 +24,7 @@ import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
@@ -32,6 +33,7 @@ import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Vibrator;
import android.widget.EditText;
/**
* This class provides access to notifications on the device.
@@ -68,7 +70,11 @@ public class Notification extends CordovaPlugin {
return true;
}
else if (action.equals("confirm")) {
this.confirm(args.getString(0), args.getString(1), args.getString(2), callbackContext);
this.confirm(args.getString(0), args.getString(1), args.getJSONArray(2), callbackContext);
return true;
}
else if (action.equals("prompt")) {
this.prompt(args.getString(0), args.getString(1), args.getJSONArray(2), callbackContext);
return true;
}
else if (action.equals("activityStart")) {
@@ -170,7 +176,7 @@ public class Notification extends CordovaPlugin {
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 0));
}
});
dlg.create();
dlg.show();
};
@@ -188,10 +194,9 @@ public class Notification extends CordovaPlugin {
* @param buttonLabels A comma separated list of button labels (Up to 3 buttons)
* @param callbackContext The callback context.
*/
public synchronized void confirm(final String message, final String title, String buttonLabels, final CallbackContext callbackContext) {
public synchronized void confirm(final String message, final String title, final JSONArray buttonLabels, final CallbackContext callbackContext) {
final CordovaInterface cordova = this.cordova;
final String[] fButtons = buttonLabels.split(",");
Runnable runnable = new Runnable() {
public void run() {
@@ -201,37 +206,43 @@ public class Notification extends CordovaPlugin {
dlg.setCancelable(true);
// First button
if (fButtons.length > 0) {
dlg.setNegativeButton(fButtons[0],
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 1));
}
});
if (buttonLabels.length() > 0) {
try {
dlg.setNegativeButton(buttonLabels.getString(0),
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 1));
}
});
} catch (JSONException e) { }
}
// Second button
if (fButtons.length > 1) {
dlg.setNeutralButton(fButtons[1],
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 2));
}
});
if (buttonLabels.length() > 1) {
try {
dlg.setNeutralButton(buttonLabels.getString(1),
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 2));
}
});
} catch (JSONException e) { }
}
// Third button
if (fButtons.length > 2) {
dlg.setPositiveButton(fButtons[2],
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 3));
}
}
);
if (buttonLabels.length() > 2) {
try {
dlg.setPositiveButton(buttonLabels.getString(2),
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 3));
}
}
);
} catch (JSONException e) { }
}
dlg.setOnCancelListener(new AlertDialog.OnCancelListener() {
public void onCancel(DialogInterface dialog)
@@ -248,6 +259,104 @@ public class Notification extends CordovaPlugin {
this.cordova.getActivity().runOnUiThread(runnable);
}
/**
* Builds and shows a native Android prompt dialog with given title, message, buttons.
* This dialog only shows up to 3 buttons. Any labels after that will be ignored.
* The following results are returned to the JavaScript callback identified by callbackId:
* buttonIndex Index number of the button selected
* input1 The text entered in the prompt dialog box
*
* @param message The message the dialog should display
* @param title The title of the dialog
* @param buttonLabels A comma separated list of button labels (Up to 3 buttons)
* @param callbackContext The callback context.
*/
public synchronized void prompt(final String message, final String title, final JSONArray buttonLabels, final CallbackContext callbackContext) {
final CordovaInterface cordova = this.cordova;
final EditText promptInput = new EditText(cordova.getActivity());
Runnable runnable = new Runnable() {
public void run() {
AlertDialog.Builder dlg = new AlertDialog.Builder(cordova.getActivity());
dlg.setMessage(message);
dlg.setTitle(title);
dlg.setCancelable(true);
dlg.setView(promptInput);
final JSONObject result = new JSONObject();
// First button
if (buttonLabels.length() > 0) {
try {
dlg.setNegativeButton(buttonLabels.getString(0),
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
try {
result.put("buttonIndex",1);
result.put("input1", promptInput.getText());
} catch (JSONException e) { e.printStackTrace(); }
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, result));
}
});
} catch (JSONException e) { }
}
// Second button
if (buttonLabels.length() > 1) {
try {
dlg.setNeutralButton(buttonLabels.getString(1),
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
try {
result.put("buttonIndex",2);
result.put("input1", promptInput.getText());
} catch (JSONException e) { e.printStackTrace(); }
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, result));
}
});
} catch (JSONException e) { }
}
// Third button
if (buttonLabels.length() > 2) {
try {
dlg.setPositiveButton(buttonLabels.getString(2),
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
try {
result.put("buttonIndex",3);
result.put("input1", promptInput.getText());
} catch (JSONException e) { e.printStackTrace(); }
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, result));
}
}
);
} catch (JSONException e) { }
}
dlg.setOnCancelListener(new AlertDialog.OnCancelListener() {
public void onCancel(DialogInterface dialog)
{
dialog.dismiss();
try {
result.put("buttonIndex",0);
result.put("input1", promptInput.getText());
} catch (JSONException e) { e.printStackTrace(); }
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, result));
}
});
dlg.create();
dlg.show();
};
};
this.cordova.getActivity().runOnUiThread(runnable);
}
/**
* Show the spinner.
*

View File

@@ -22,7 +22,6 @@ import java.io.File;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

View File

@@ -79,6 +79,15 @@ public class CallbackContext {
public void success(byte[] message) {
sendPluginResult(new PluginResult(PluginResult.Status.OK, message));
}
/**
* Helper for success callbacks that just returns the Status.OK by default
*
* @param message The message to add to the success result.
*/
public void success(int message) {
sendPluginResult(new PluginResult(PluginResult.Status.OK, message));
}
/**
* Helper for success callbacks that just returns the Status.OK by default

View File

@@ -19,7 +19,6 @@
package org.apache.cordova.api;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import java.util.concurrent.ExecutorService;

View File

@@ -22,7 +22,12 @@ import org.apache.cordova.CordovaArgs;
import org.apache.cordova.CordovaWebView;
import org.json.JSONArray;
import org.json.JSONException;
import android.annotation.TargetApi;
import android.content.Intent;
import android.os.Build;
import android.util.Log;
import android.webkit.WebResourceResponse;
/**
* Plugins must extend this class and override one of the execute methods.
@@ -150,7 +155,7 @@ public class CordovaPlugin {
}
/**
* By specifying a <url-filter> in plugins.xml you can map a URL (using startsWith atm) to this method.
* By specifying a <url-filter> in config.xml you can map a URL (using startsWith atm) to this method.
*
* @param url The URL that is trying to be loaded in the Cordova webview.
* @return Return true to prevent the URL from loading. Default is false.
@@ -159,6 +164,17 @@ public class CordovaPlugin {
return false;
}
/**
* By specifying a <url-filter> in config.xml you can map a URL prefix to this method. It applies to all resources loaded in the WebView, not just top-level navigation.
*
* @param url The URL of the resource to be loaded.
* @return Return a WebResourceResponse for the resource, or null to let the WebView handle it normally.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public WebResourceResponse shouldInterceptRequest(String url) {
return null;
}
/**
* Called when the WebView does a top-level navigation or refreshes.
*

View File

@@ -22,17 +22,16 @@ import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.cordova.CordovaWebView;
import org.json.JSONArray;
import org.json.JSONException;
import org.xmlpull.v1.XmlPullParserException;
import android.content.Intent;
import android.content.res.XmlResourceParser;
import android.util.Log;
import android.webkit.WebResourceResponse;
/**
* PluginManager is exposed to JavaScript in the Cordova WebView.
@@ -122,7 +121,7 @@ public class PluginManager {
// System.out.println("Plugin: "+name+" => "+value);
onload = "true".equals(xml.getAttributeValue(null, "onload"));
entry = new PluginEntry(service, pluginClass, onload);
this.addService(entry);
this.addService(entry);
}
//What is this?
else if (strNode.equals("url-filter")) {
@@ -215,6 +214,7 @@ public class PluginManager {
public boolean exec(String service, String action, String callbackId, String rawArgs) {
CordovaPlugin plugin = this.getPlugin(service);
if (plugin == null) {
Log.d(TAG, "exec() call to unknown plugin: " + service);
PluginResult cr = new PluginResult(PluginResult.Status.CLASS_NOT_FOUND_EXCEPTION);
app.sendPluginResult(cr, callbackId);
return true;
@@ -370,6 +370,25 @@ public class PluginManager {
return false;
}
/**
* Called when the WebView is loading any resource, top-level or not.
*
* Uses the same url-filter tag as onOverrideUrlLoading.
*
* @param url The URL of the resource to be loaded.
* @return Return a WebResourceResponse with the resource, or null if the WebView should handle it.
*/
public WebResourceResponse shouldInterceptRequest(String url) {
Iterator<Entry<String, String>> it = this.urlMap.entrySet().iterator();
while (it.hasNext()) {
HashMap.Entry<String, String> pairs = it.next();
if (url.startsWith(pairs.getKey())) {
return this.getPlugin(pairs.getValue()).shouldInterceptRequest(url);
}
}
return null;
}
/**
* Called when the app navigates or refreshes.
*/

View File

@@ -71,11 +71,15 @@ public class PluginResult {
}
public PluginResult(Status status, byte[] data) {
this.status = status.ordinal();
this.messageType = MESSAGE_TYPE_ARRAYBUFFER;
this.encodedMessage = Base64.encodeToString(data, Base64.NO_WRAP);
this(status, data, false);
}
public PluginResult(Status status, byte[] data, boolean binaryString) {
this.status = status.ordinal();
this.messageType = binaryString ? MESSAGE_TYPE_BINARYSTRING : MESSAGE_TYPE_ARRAYBUFFER;
this.encodedMessage = Base64.encodeToString(data, Base64.NO_WRAP);
}
public void setKeepCallback(boolean b) {
this.keepCallback = b;
}
@@ -143,6 +147,9 @@ public class PluginResult {
public static final int MESSAGE_TYPE_BOOLEAN = 4;
public static final int MESSAGE_TYPE_NULL = 5;
public static final int MESSAGE_TYPE_ARRAYBUFFER = 6;
// Use BINARYSTRING when your string may contain null characters.
// This is required to work around a bug in the platform :(.
public static final int MESSAGE_TYPE_BINARYSTRING = 7;
public static String[] StatusMessages = new String[] {
"No result",

View File

@@ -45,12 +45,13 @@
<plugin name="NetworkStatus" value="org.apache.cordova.NetworkManager"/>
<plugin name="Notification" value="org.apache.cordova.Notification"/>
<plugin name="Storage" value="org.apache.cordova.Storage"/>
<plugin name="Temperature" value="org.apache.cordova.TempListener"/>
<plugin name="FileTransfer" value="org.apache.cordova.FileTransfer"/>
<plugin name="Capture" value="org.apache.cordova.Capture"/>
<plugin name="Battery" value="org.apache.cordova.BatteryListener"/>
<plugin name="SplashScreen" value="org.apache.cordova.SplashScreen"/>
<plugin name="Echo" value="org.apache.cordova.Echo" />
<plugin name="Globalization" value="org.apache.cordova.Globalization"/>
<plugin name="InAppBrowser" value="org.apache.cordova.InAppBrowser"/>
</plugins>
</cordova>

View File

@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<cordova>
<!--
access elements control the Android whitelist.
Domains are assumed blocked unless set otherwise
-->
<access origin="http://127.0.0.1*"/> <!-- allow local pages -->
<!-- <access origin="https://example.com" /> allow any secure requests to example.com -->
<!-- <access origin="https://example.com" subdomains="true" /> such as above, but including subdomains, such as www -->
<!-- <access origin=".*"/> Allow all domains, suggested development use only -->
<log level="DEBUG"/>
<preference name="useBrowserHistory" value="true" />
</cordova>

View File

@@ -1,39 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<plugins>
<plugin name="App" value="org.apache.cordova.App"/>
<plugin name="Activity" value="org.apache.cordova.test.ActivityPlugin"/>
<plugin name="Geolocation" value="org.apache.cordova.GeoBroker"/>
<plugin name="Device" value="org.apache.cordova.Device"/>
<plugin name="Accelerometer" value="org.apache.cordova.AccelListener"/>
<plugin name="Compass" value="org.apache.cordova.CompassListener"/>
<plugin name="Media" value="org.apache.cordova.AudioHandler"/>
<plugin name="Camera" value="org.apache.cordova.CameraLauncher"/>
<plugin name="Contacts" value="org.apache.cordova.ContactManager"/>
<plugin name="File" value="org.apache.cordova.FileUtils"/>
<plugin name="NetworkStatus" value="org.apache.cordova.NetworkManager"/>
<plugin name="Notification" value="org.apache.cordova.Notification"/>
<plugin name="Storage" value="org.apache.cordova.Storage"/>
<plugin name="Temperature" value="org.apache.cordova.TempListener"/>
<plugin name="FileTransfer" value="org.apache.cordova.FileTransfer"/>
<plugin name="Capture" value="org.apache.cordova.Capture"/>
<plugin name="Battery" value="org.apache.cordova.BatteryListener"/>
<plugin name="SplashScreen" value="org.apache.cordova.SplashScreen"/>
</plugins>

View File

@@ -18,19 +18,21 @@
*/
package org.apache.cordova.test;
import org.apache.cordova.CordovaArgs;
import org.apache.cordova.api.LOG;
import org.json.JSONArray;
import org.json.JSONException;
import android.content.Intent;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.PluginResult;
/**
* This class provides a service.
*/
public class ActivityPlugin extends Plugin {
public class ActivityPlugin extends CordovaPlugin {
static String TAG = "ActivityPlugin";
@@ -48,19 +50,21 @@ public class ActivityPlugin extends Plugin {
* @param callbackId The callback id used when calling back into JavaScript.
* @return A PluginResult object with a status and message.
*/
@Override
public PluginResult execute(String action, JSONArray args, String callbackId) {
PluginResult.Status status = PluginResult.Status.OK;
String result = "";
public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext) {
PluginResult result = new PluginResult(PluginResult.Status.OK, "");
try {
if (action.equals("start")) {
this.startActivity(args.getString(0));
callbackContext.sendPluginResult(result);
callbackContext.success();
return true;
}
return new PluginResult(status, result);
} catch (JSONException e) {
return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
result = new PluginResult(PluginResult.Status.JSON_EXCEPTION, "JSON Exception");
callbackContext.sendPluginResult(result);
return false;
}
return false;
}
// --------------------------------------------------------------------------
@@ -69,9 +73,9 @@ public class ActivityPlugin extends Plugin {
public void startActivity(String className) {
try {
Intent intent = new Intent().setClass(this.ctx.getActivity(), Class.forName(className));
Intent intent = new Intent().setClass(this.cordova.getActivity(), Class.forName(className));
LOG.d(TAG, "Starting activity %s", className);
this.ctx.getActivity().startActivity(intent);
this.cordova.getActivity().startActivity(intent);
} catch (ClassNotFoundException e) {
e.printStackTrace();
LOG.e(TAG, "Error starting activity %s", className);