Compare commits

...

94 Commits
2.5.0 ... 2.7.0

Author SHA1 Message Date
Joe Bowser
909e6645e7 Updating JS for Android 2013-04-25 14:55:11 -07:00
Joe Bowser
a308100288 Prep for 2.7.0 final 2013-04-25 13:42:20 -07:00
Joe Bowser
fea8c5d204 Merge branch '2.7.x' of https://git-wip-us.apache.org/repos/asf/cordova-android into 2.7.x 2013-04-25 10:20:07 -07:00
Joe Bowser
a890c8156e This should be 2.7.0rc1 2013-04-25 10:19:48 -07:00
Shravan Narayan
665c1bd8bb [CB-3226] Fix: plugins can intercept urls with "?", "#", "%20"(cherry picked from commit 53982272d6) 2013-04-25 13:11:02 -04:00
Joe Bowser
b22990ca78 cordova-js wasn't properly auto-incremented either 2013-04-19 17:56:26 -07:00
Joe Bowser
832998b67f 2.7.0rc1 prep 2013-04-18 13:05:03 -07:00
Andrew Grieve
c798d131bb Update JS snapshot for 2.7.0 2013-04-18 15:28:00 -04:00
Ian Clelland
bf3e024648 [CB-3066] Fire onNativeReady from JS, as bridge is available immediately 2013-04-18 14:20:32 -04:00
Andrew Grieve
191f31baa7 [CB-2432] Don't try and write exif info for images from picasa 2013-04-17 16:40:00 -04:00
Joe Bowser
d3b7903af8 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/cordova-android 2013-04-17 13:37:48 -07:00
Joe Bowser
99e7d1e161 Merge branches 'sunshine' and 'master' 2013-04-17 13:37:19 -07:00
Andrew Grieve
b13166f5d9 [CB-2432] Fix Camera.getPicture() for picasa images 2013-04-17 16:12:28 -04:00
Andrew Grieve
80fe4458c6 Use FileHelper in IceCreamCordovaWebViewClient.
Removes some duplicate logic and makes it a bit more robust.
2013-04-17 15:51:37 -04:00
Andrew Grieve
791574c26e Make URL parsing more robust in FileHelper.
Fixes some cases when query parameters mess things up.
2013-04-17 15:50:29 -04:00
Joe Bowser
ac61ebf2d5 Merge branch 'master' of github.com:SunshineTech/cordova-android into sunshine 2013-04-17 11:44:49 -07:00
avidmich
cb99ed0a01 Fixing URL transformation algorithm
It didn't work with URL like this:
http://host.com/path/to/file.txt#/foo?bar=baz
When hash sign is in front of question mark - it only strips the question mark, leaving the hash and breaking the whole app.
2013-04-16 15:45:32 -04:00
Andrew Grieve
4864d52966 [CB-2202] Remove Plugin.java (was deprecated). 2013-04-16 15:29:16 -04:00
Andrew Grieve
b2d61679fb [CB-2963] Re-enable sending messages in batches.
Disabled by CB-1745, which is now reverted.
2013-04-16 15:04:56 -04:00
lorinbeer
383b3dadd5 [CB-3024] expanded help string for cl create script 2013-04-12 08:32:22 -07:00
Joe Bowser
c65c259123 CB-2200: Remove deprecated Android device.name 2013-04-11 13:53:12 -07:00
Joe Bowser
e7e2730929 Fixing CB-2955, breaking CB-2085, use localStorage, NOT WebSQL 2013-04-08 15:53:14 -07:00
Joe Bowser
bb9615eed0 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/cordova-android 2013-04-04 16:06:03 -07:00
Joe Bowser
18877bf80e Adding additional condition that the phone must be made by HTC for the setNavDump to be set to true to try and work around CB-2907, if it's still a problem on HTC devices running Android 2.2, that's just the luck of the draw. 2013-04-04 16:05:35 -07:00
HUANG Menghuai
778b784eb6 [CB-2908] Fix the DroidGap activity Lifecycle broken issue
Attempting to invoke the Activity's finish() onDestroy breaks an Activity's lifecycle
flag. OnDestroy can be called by the system, for instance, on restarting an Activity,
it's definitely different from a normal finish().
Finish() incorrectly in onDestroy results in another DroidGap derived activity
is started, while the original one is not yet onDestroy. This issue could be
found when the system is trying to restart the activity upon, for instance,
receiving immediately successive device Config changes.
2013-04-04 16:47:03 -04:00
Andrew Grieve
5ff900f7ec Fixup for CB-2654. 2013-04-04 16:45:39 -04:00
Steren
ba31424604 Keep the splashscreen image ratio instead of streatching it.
An ImageView is used to be able to use ScaleType.CENTER_CROP, which is similar to the background-size:cover CSS property
2013-04-04 16:19:12 -04:00
Ian Clelland
1782111d45 [CB-2654] Delay executeScript/insertCSS callback until resources have loaded; pass JS results to callback 2013-04-04 14:34:58 -04:00
Max Woghiren
1fa63300aa [CB-2666] Added check for null arguments.
If null arguments are received, send an error and an explanation.
2013-04-02 11:51:25 -04:00
Joe Bowser
b42c918973 Prep for 2.6.0 final 2013-04-01 14:54:51 -07:00
Joe Bowser
f12262ea96 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/cordova-android 2013-03-28 10:02:46 -07:00
Joe Bowser
334cf45d6d 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-28 10:02:18 -07:00
Joe Bowser
b7bb72294a CB-1796: Let's make sure we actually write the file instead of just writing EXIF to NOTHING 2013-03-28 10:02:02 -07:00
Joe Bowser
64ff204371 Updating JS 2013-03-28 10:01:15 -07:00
Ian Clelland
282367c6d5 [CB-1517] Properly report download progress for GZIP-encoded resources 2013-03-27 17:00:22 -04:00
James Jong
36c33a5889 CB-1944: Better error messages for Create script
- fixed to detect missing packages individually
- added a specific message for each missing package
- messages include how to correct and package download link
2013-03-27 15:46:45 -04:00
JasonM23
5ee7e81ff9 [CB-51] Added httpMethod for upload (defaults to POST) 2013-03-27 14:22:41 -04:00
Shravan Narayan
f4859444dd Fixed protocol regex bug. Unknown protocol support Added whitelist support for unknown protocols 2013-03-26 00:20:11 -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
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
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
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
SunshineTech
2bf6509e1d Modify Issue CB-2273. 2013-02-17 21:50:33 +08:00
37 changed files with 2214 additions and 982 deletions

3
.gitignore vendored
View File

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

View File

@@ -1 +1 @@
2.5.0
2.7.0

View File

@@ -25,8 +25,11 @@ set -e
if [ -z "$1" ] || [ "$1" == "-h" ]
then
echo 'usage: create path package activity'
echo "Usage: $0 <path_to_new_project> <package_name> <project_name>"
echo "Make sure the Android SDK tools folder is in your PATH!"
echo " <path_to_new_project>: Path to your new Cordova iOS project"
echo " <package_name>: Package name, following reverse-domain style convention"
echo " <project_name>: Project name"
exit 0
fi
@@ -59,10 +62,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 {
@@ -108,7 +111,7 @@ 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
@@ -121,8 +124,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

@@ -16,17 +16,37 @@
:: under the License.
@ECHO OFF
IF NOT DEFINED JAVA_HOME GOTO MISSING
IF NOT DEFINED JAVA_HOME GOTO MISSING_JAVA_HOME
FOR %%X in (java.exe javac.exe ant.bat android.bat) do (
SET FOUND=%%~$PATH:X
IF NOT DEFINED FOUND GOTO MISSING
IF [%%~$PATH:X]==[] (
ECHO Cannot locate %%X using the PATH environment variable.
ECHO Retry after adding directory containing %%X to the PATH variable.
ECHO Remember to open a new command window after updating the PATH variable.
IF "%%X"=="java.exe" GOTO GET_JAVA
IF "%%X"=="javac.exe" GOTO GET_JAVA
IF "%%X"=="ant.bat" GOTO GET_ANT
IF "%%X"=="android.bat" GOTO GET_ANDROID
GOTO ERROR
)
)
cscript "%~dp0\create.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
:MISSING_JAVA_HOME
ECHO The JAVA_HOME environment variable is not set.
ECHO Set JAVA_HOME to an existing JRE directory.
ECHO Remember to also add JAVA_HOME to the PATH variable.
ECHO After updating system variables, open a new command window and retry.
GOTO ERROR
:GET_JAVA
ECHO Visit http://java.oracle.com if you need to install Java (JDK).
GOTO ERROR
:GET_ANT
ECHO Visit http://ant.apache.org if you need to install Apache Ant.
GOTO ERROR
:GET_ANDROID
ECHO Visit http://developer.android.com if you need to install the Android SDK.
GOTO ERROR
:ERROR
EXIT /B 1
:END

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.5.0.js"></script>
<script type="text/javascript" src="cordova-2.7.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.5.0.js"></script>
<script src="cordova-2.7.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

@@ -42,6 +42,7 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Rect;
import android.media.MediaScannerConnection;
import android.media.MediaScannerConnection.MediaScannerConnectionClient;
import android.net.Uri;
@@ -289,7 +290,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");
@@ -312,10 +313,12 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
// If sending filename back
else if (destType == FILE_URI || destType == NATIVE_URI) {
if (!this.saveToPhotoAlbum) {
uri = Uri.fromFile(new File(DirectoryManager.getTempDirectoryPath(this.cordova.getActivity()), System.currentTimeMillis() + ".jpg"));
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) {
@@ -329,7 +332,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);
@@ -344,7 +347,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();
}
@@ -394,19 +397,21 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
(destType == FILE_URI || destType == NATIVE_URI) && !this.correctOrientation) {
this.callbackContext.success(uri.toString());
} else {
String uriString = uri.toString();
// Get the path to the image. Makes loading so much easier.
String imagePath = FileUtils.getRealPathFromURI(uri, this.cordova);
String mimeType = FileUtils.getMimeType(imagePath);
// Log.d(LOG_TAG, "Real path = " + imagePath);
// Log.d(LOG_TAG, "mime type = " + mimeType);
String mimeType = FileHelper.getMimeType(uriString, this.cordova);
// If we don't have a valid image so quit.
if (imagePath == null || mimeType == null ||
!(mimeType.equalsIgnoreCase("image/jpeg") || mimeType.equalsIgnoreCase("image/png"))) {
if (!("image/jpeg".equalsIgnoreCase(mimeType) || "image/png".equalsIgnoreCase(mimeType))) {
Log.d(LOG_TAG, "I either have a null image path or bitmap");
this.failPicture("Unable to retrieve path to picture!");
return;
}
Bitmap bitmap = getScaledBitmap(imagePath);
Bitmap bitmap = null;
try {
bitmap = getScaledBitmap(uriString);
} catch (IOException e) {
e.printStackTrace();
}
if (bitmap == null) {
Log.d(LOG_TAG, "I either have a null image path or bitmap");
this.failPicture("Unable to create bitmap!");
@@ -414,14 +419,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
}
if (this.correctOrientation) {
String[] cols = { MediaStore.Images.Media.ORIENTATION };
Cursor cursor = this.cordova.getActivity().getContentResolver().query(intent.getData(),
cols, null, null, null);
if (cursor != null) {
cursor.moveToPosition(0);
rotate = cursor.getInt(0);
cursor.close();
}
rotate = getImageOrientation(uri);
if (rotate != 0) {
Matrix matrix = new Matrix();
matrix.setRotate(rotate);
@@ -441,15 +439,17 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
try {
// Create an ExifHelper to save the exif data that is lost during compression
String resizePath = DirectoryManager.getTempDirectoryPath(this.cordova.getActivity()) + "/resize.jpg";
// Some content: URIs do not map to file paths (e.g. picasa).
String realPath = FileHelper.getRealPath(uri, this.cordova);
ExifHelper exif = new ExifHelper();
try {
if (this.encodingType == JPEG) {
exif.createInFile(resizePath);
if (realPath != null && this.encodingType == JPEG) {
try {
exif.createInFile(realPath);
exif.readExifData();
rotate = exif.getOrientation();
} catch (IOException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
OutputStream os = new FileOutputStream(resizePath);
@@ -457,8 +457,8 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
os.close();
// Restore exif data to file
if (this.encodingType == JPEG) {
exif.createOutFile(FileUtils.getRealPathFromURI(uri, this.cordova));
if (realPath != null && this.encodingType == JPEG) {
exif.createOutFile(resizePath);
exif.writeExifData();
}
@@ -491,6 +491,19 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
}
}
private int getImageOrientation(Uri uri) {
String[] cols = { MediaStore.Images.Media.ORIENTATION };
Cursor cursor = cordova.getActivity().getContentResolver().query(uri,
cols, null, null, null);
int rotate = 0;
if (cursor != null) {
cursor.moveToPosition(0);
rotate = cursor.getInt(0);
cursor.close();
}
return rotate;
}
/**
* Figure out if the bitmap should be rotated. For instance if the picture was taken in
* portrait mode
@@ -521,7 +534,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;
@@ -561,17 +574,18 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
*
* @param imagePath
* @return
* @throws IOException
*/
private Bitmap getScaledBitmap(String imagePath) {
private Bitmap getScaledBitmap(String imageUrl) throws IOException {
// If no new width or height were specified return the original bitmap
if (this.targetWidth <= 0 && this.targetHeight <= 0) {
return BitmapFactory.decodeFile(imagePath);
return BitmapFactory.decodeStream(FileHelper.getInputStreamFromUriString(imageUrl, cordova));
}
// figure out the original width and height of the image
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imagePath, options);
BitmapFactory.decodeStream(FileHelper.getInputStreamFromUriString(imageUrl, cordova), null, options);
//CB-2292: WTF? Why is the width null?
if(options.outWidth == 0 || options.outHeight == 0)
@@ -585,7 +599,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
// Load in the smallest bitmap possible that is closest to the size we want
options.inJustDecodeBounds = false;
options.inSampleSize = calculateSampleSize(options.outWidth, options.outHeight, this.targetWidth, this.targetHeight);
Bitmap unscaledBitmap = BitmapFactory.decodeFile(imagePath, options);
Bitmap unscaledBitmap = BitmapFactory.decodeStream(FileHelper.getInputStreamFromUriString(imageUrl, cordova), null, options);
if (unscaledBitmap == null) {
return null;
}
@@ -685,7 +699,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");
@@ -161,7 +171,7 @@ public class Config {
LOG.i("CordovaLog", "Found start page location: %s", src);
if (src != null) {
Pattern schemeRegex = Pattern.compile("^[a-z]+://");
Pattern schemeRegex = Pattern.compile("^[a-z-]+://");
Matcher matcher = schemeRegex.matcher(src);
if (matcher.find()) {
startUrl = src;
@@ -210,19 +220,33 @@ public class Config {
} else { // specific access
// check if subdomains should be included
// TODO: we should not add more domains if * has already been added
Pattern schemeRegex = Pattern.compile("^[a-z-]+://");
Matcher matcher = schemeRegex.matcher(origin);
if (subdomains) {
// XXX making it stupid friendly for people who forget to include protocol/SSL
// Check for http or https protocols
if (origin.startsWith("http")) {
this.whiteList.add(Pattern.compile(origin.replaceFirst("https?://", "^https?://(.*\\.)?")));
} else {
}
// Check for other protocols
else if(matcher.find()){
this.whiteList.add(Pattern.compile("^" + origin.replaceFirst("//", "//(.*\\.)?")));
}
// XXX making it stupid friendly for people who forget to include protocol/SSL
else {
this.whiteList.add(Pattern.compile("^https?://(.*\\.)?" + origin));
}
LOG.d(TAG, "Origin to allow with subdomains: %s", origin);
} else {
// XXX making it stupid friendly for people who forget to include protocol/SSL
// Check for http or https protocols
if (origin.startsWith("http")) {
this.whiteList.add(Pattern.compile(origin.replaceFirst("https?://", "^https?://")));
} else {
}
// Check for other protocols
else if(matcher.find()){
this.whiteList.add(Pattern.compile("^" + origin));
}
// XXX making it stupid friendly for people who forget to include protocol/SSL
else {
this.whiteList.add(Pattern.compile("^https?://" + origin));
}
LOG.d(TAG, "Origin to allow: %s", origin);

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

@@ -205,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

@@ -22,6 +22,8 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import org.apache.cordova.api.CallbackContext;
@@ -42,6 +44,8 @@ public class CordovaLocationListener implements LocationListener {
public HashMap<String, CallbackContext> watches = new HashMap<String, CallbackContext>();
private List<CallbackContext> callbacks = new ArrayList<CallbackContext>();
private Timer timer = null;
private String TAG = "[Cordova Location Listener]";
@@ -52,11 +56,12 @@ public class CordovaLocationListener implements LocationListener {
}
protected void fail(int code, String message) {
this.cancelTimer();
for (CallbackContext callbackContext: this.callbacks)
{
this.owner.fail(code, message, callbackContext);
this.owner.fail(code, message, callbackContext, false);
}
if(this.owner.isGlobalListener(this))
if(this.owner.isGlobalListener(this) && this.watches.size() == 0)
{
Log.d(TAG, "Stopping global listener");
this.stop();
@@ -65,16 +70,17 @@ public class CordovaLocationListener implements LocationListener {
Iterator<CallbackContext> it = this.watches.values().iterator();
while (it.hasNext()) {
this.owner.fail(code, message, it.next());
this.owner.fail(code, message, it.next(), true);
}
}
private void win(Location loc) {
this.cancelTimer();
for (CallbackContext callbackContext: this.callbacks)
{
this.owner.win(loc, callbackContext);
this.owner.win(loc, callbackContext, false);
}
if(this.owner.isGlobalListener(this))
if(this.owner.isGlobalListener(this) && this.watches.size() == 0)
{
Log.d(TAG, "Stopping global listener");
this.stop();
@@ -83,7 +89,7 @@ public class CordovaLocationListener implements LocationListener {
Iterator<CallbackContext> it = this.watches.values().iterator();
while (it.hasNext()) {
this.owner.win(loc, it.next());
this.owner.win(loc, it.next(), true);
}
}
@@ -155,8 +161,12 @@ public class CordovaLocationListener implements LocationListener {
this.start();
}
}
public void addCallback(CallbackContext callbackContext) {
this.callbacks.add(callbackContext);
public void addCallback(CallbackContext callbackContext, int timeout) {
if(this.timer == null) {
this.timer = new Timer();
}
this.timer.schedule(new LocationTimeoutTask(callbackContext, this), timeout);
this.callbacks.add(callbackContext);
if (this.size() == 1) {
this.start();
}
@@ -173,7 +183,7 @@ public class CordovaLocationListener implements LocationListener {
/**
* Destroy listener.
*/
public void destroy() {
public void destroy() {
this.stop();
}
@@ -199,9 +209,43 @@ public class CordovaLocationListener implements LocationListener {
* Stop receiving location updates.
*/
private void stop() {
this.cancelTimer();
if (this.running) {
this.locationManager.removeUpdates(this);
this.running = false;
}
}
private void cancelTimer() {
if(this.timer != null) {
this.timer.cancel();
this.timer.purge();
this.timer = null;
}
}
private class LocationTimeoutTask extends TimerTask {
private CallbackContext callbackContext = null;
private CordovaLocationListener listener = null;
public LocationTimeoutTask(CallbackContext callbackContext, CordovaLocationListener listener) {
this.callbackContext = callbackContext;
this.listener = listener;
}
@Override
public void run() {
for (CallbackContext callbackContext: listener.callbacks) {
if(this.callbackContext == callbackContext) {
listener.callbacks.remove(callbackContext);
break;
}
}
if(listener.size() == 0) {
listener.stop();
}
}
}
}

View File

@@ -24,6 +24,7 @@ import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Stack;
import java.util.regex.Pattern;
import org.apache.cordova.Config;
import org.apache.cordova.api.CordovaInterface;
@@ -47,6 +48,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;
@@ -74,12 +76,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;
@@ -211,7 +208,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));
}
@@ -240,7 +238,11 @@ public class CordovaWebView extends WebView {
// Set the nav dump for HTC 2.x devices (disabling for ICS, deprecated entirely for Jellybean 4.2)
try {
Method gingerbread_getMethod = WebSettings.class.getMethod("setNavDump", new Class[] { boolean.class });
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB)
String manufacturer = android.os.Build.MANUFACTURER;
Log.d(TAG, "CordovaWebView is running on device made by: " + manufacturer);
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB &&
android.os.Build.MANUFACTURER.contains("HTC"))
{
gingerbread_getMethod.invoke(settings, true);
}
@@ -254,14 +256,20 @@ 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.setDatabaseEnabled(true);
settings.setDatabasePath(databasePath);
settings.setGeolocationDatabasePath(databasePath);
// Enable DOM storage
@@ -360,7 +368,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
@@ -381,7 +389,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
@@ -399,21 +407,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;
@@ -468,7 +463,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);
}
}
@@ -484,7 +479,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
@@ -532,25 +527,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)
@@ -561,37 +537,15 @@ public class CordovaWebView extends WebView {
// Check webview first to see if there is a history
// This is needed to support curPage#diffLink, since they are added to appView's history, but not our history url array (JQMobile behavior)
if (super.canGoBack() && this.useBrowserHistory) {
if (super.canGoBack()) {
printBackForwardList();
super.goBack();
return true;
}
// If our managed history has prev url
else 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() && this.useBrowserHistory) {
return true;
}
else if (this.urls.size() > 1) {
return true;
}
return false;
}
/**
* Load the specified URL in the Cordova webview or a new browser instance.
@@ -615,14 +569,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);
}
@@ -659,13 +607,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);
@@ -719,13 +660,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);

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.
@@ -294,15 +314,6 @@ public class CordovaWebViewClient extends WebViewClient {
// Clear timeout flag
this.appView.loadUrlTimeout++;
// Try firing the onNativeReady event in JS. If it fails because the JS is
// not loaded yet then just set a flag so that the onNativeReady can be fired
// from the JS side when the JS gets to that code.
if (!url.equals("about:blank")) {
LOG.d(TAG, "Trying to fire onNativeReady");
this.appView.loadUrl("javascript:try{ cordova.require('cordova/channel').onNativeReady.fire();}catch(e){_nativeReady = true;}");
this.appView.postMessage("onNativeReady", null);
}
// Broadcast message that page has loaded
this.appView.postMessage("onPageFinished", url);
@@ -359,11 +370,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 +403,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 +423,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 +435,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

@@ -38,7 +38,7 @@ import android.telephony.TelephonyManager;
public class Device extends CordovaPlugin {
public static final String TAG = "Device";
public static String cordovaVersion = "2.5.0"; // Cordova version
public static String cordovaVersion = "2.7.0"; // Cordova version
public static String platform = "Android"; // Device OS
public static String uuid; // Device UUID
@@ -77,7 +77,6 @@ public class Device extends CordovaPlugin {
r.put("uuid", Device.uuid);
r.put("version", this.getOSVersion());
r.put("platform", Device.platform);
r.put("name", this.getProductName());
r.put("cordova", Device.cordovaVersion);
r.put("model", this.getModel());
callbackContext.success(r);

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;
@@ -50,6 +51,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import android.webkit.ValueCallback;
import android.webkit.WebViewClient;
import android.widget.LinearLayout;
@@ -332,6 +334,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()");
@@ -349,6 +352,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);
@@ -709,7 +718,7 @@ public class DroidGap extends Activity implements CordovaInterface {
appView.handleDestroy();
}
else {
this.endActivity();
this.activityState = ACTIVITY_EXITING;
}
}
@@ -1023,7 +1032,13 @@ public class DroidGap extends Activity implements CordovaInterface {
root.setBackgroundColor(that.getIntegerProperty("backgroundColor", Color.BLACK));
root.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.FILL_PARENT, 0.0F));
root.setBackgroundResource(that.splashscreen);
// We want the splashscreen to keep its ratio,
// for this we need to use an ImageView and not simply the background of the LinearLayout
ImageView splashscreenView = new ImageView(that.getActivity());
splashscreenView.setImageResource(that.splashscreen);
splashscreenView.setScaleType(ImageView.ScaleType.CENTER_CROP); // similar to the background-size:cover CSS property
splashscreenView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
root.addView(splashscreenView);
// Create and show the dialog
splashDialog = new Dialog(that, android.R.style.Theme_Translucent_NoTitleBar);
@@ -1054,7 +1069,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);
@@ -1074,7 +1090,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,6 +20,7 @@ package org.apache.cordova;
import android.webkit.JavascriptInterface;
import org.apache.cordova.api.PluginManager;
import org.apache.cordova.api.PluginResult;
import org.json.JSONException;
/**
@@ -39,6 +40,12 @@ import org.json.JSONException;
@JavascriptInterface
public String exec(String service, String action, String callbackId, String arguments) throws JSONException {
// If the arguments weren't received, send a message back to JS. It will switch bridge modes and try again. See CB-2666.
// We send a message meant specifically for this case. It starts with "@" so no other message can be encoded into the same string.
if (arguments == null) {
return "@Null arguments.";
}
jsMessageQueue.setPaused(true);
try {
boolean wasSync = pluginManager.exec(service, action, callbackId, arguments);

View File

@@ -0,0 +1,149 @@
/*
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;
import java.net.URLConnection;
import java.util.Locale;
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/")) {
Uri uri = Uri.parse(uriString);
String relativePath = uri.getPath().substring(15);
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;
Uri uri = Uri.parse(uriString);
if (uriString.startsWith("content://")) {
mimeType = cordova.getActivity().getContentResolver().getType(uri);
} else {
// MimeTypeMap.getFileExtensionFromUrl() fails when there are query parameters.
String extension = uri.getPath();
int lastDot = extension.lastIndexOf('.');
if (lastDot != -1) {
extension = extension.substring(lastDot + 1);
}
// Convert the URI string to lower case to ensure compatibility with MimeTypeMap (see CB-2185).
extension = extension.toLowerCase();
if (extension.equals("3ga")) {
mimeType = "audio/3gpp";
} else {
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
}
}
return mimeType;
}
}

View File

@@ -18,6 +18,7 @@
*/
package org.apache.cordova;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
@@ -27,7 +28,9 @@ 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;
@@ -38,6 +41,8 @@ import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Iterator;
import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
@@ -96,11 +101,84 @@ public class FileTransfer extends CordovaPlugin {
}
}
/**
* Adds an interface method to an InputStream to return the number of bytes
* read from the raw stream. This is used to track total progress against
* the HTTP Content-Length header value from the server.
*/
private static abstract class TrackingInputStream extends FilterInputStream {
public TrackingInputStream(final InputStream in) {
super(in);
}
public abstract long getTotalRawBytesRead();
}
private static class ExposedGZIPInputStream extends GZIPInputStream {
public ExposedGZIPInputStream(final InputStream in) throws IOException {
super(in);
}
public Inflater getInflater() {
return inf;
}
}
/**
* Provides raw bytes-read tracking for a GZIP input stream. Reports the
* total number of compressed bytes read from the input, rather than the
* number of uncompressed bytes.
*/
private static class TrackingGZIPInputStream extends TrackingInputStream {
private ExposedGZIPInputStream gzin;
public TrackingGZIPInputStream(final ExposedGZIPInputStream gzin) throws IOException {
super(gzin);
this.gzin = gzin;
}
public long getTotalRawBytesRead() {
return gzin.getInflater().getBytesRead();
}
}
/**
* Provides simple total-bytes-read tracking for an existing InputStream
*/
private static class TrackingHTTPInputStream extends TrackingInputStream {
private long bytesRead = 0;
public TrackingHTTPInputStream(InputStream stream) {
super(stream);
}
private int updateBytesRead(int newBytesRead) {
if (newBytesRead != -1) {
bytesRead += newBytesRead;
}
return newBytesRead;
}
@Override
public int read() throws IOException {
return updateBytesRead(super.read());
}
@Override
public int read(byte[] buffer) throws IOException {
return updateBytesRead(super.read(buffer));
}
@Override
public int read(byte[] bytes, int offset, int count) throws IOException {
return updateBytesRead(super.read(bytes, offset, count));
}
public long getTotalRawBytesRead() {
return bytesRead;
}
}
/**
* Works around a bug on Android 2.3.
* http://code.google.com/p/android/issues/detail?id=14562
*/
private static final class DoneHandlerInputStream extends FilterInputStream {
private static final class DoneHandlerInputStream extends TrackingHTTPInputStream {
private boolean done;
public DoneHandlerInputStream(InputStream stream) {
@@ -155,6 +233,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
@@ -182,6 +279,7 @@ public class FileTransfer extends CordovaPlugin {
// Look for headers on the params map for backwards compatibility with older Cordova versions.
final JSONObject headers = args.optJSONObject(8) == null ? params.optJSONObject("headers") : args.optJSONObject(8);
final String objectId = args.getString(9);
final String httpMethod = getArgument(args, 10, "POST");
Log.d(LOG_TAG, "fileKey: " + fileKey);
Log.d(LOG_TAG, "fileName: " + fileName);
@@ -191,12 +289,13 @@ public class FileTransfer extends CordovaPlugin {
Log.d(LOG_TAG, "chunkedMode: " + chunkedMode);
Log.d(LOG_TAG, "headers: " + headers);
Log.d(LOG_TAG, "objectId: " + objectId);
Log.d(LOG_TAG, "httpMethod: " + httpMethod);
final URL url;
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;
@@ -258,7 +357,7 @@ public class FileTransfer extends CordovaPlugin {
conn.setUseCaches(false);
// Use a post method.
conn.setRequestMethod("POST");
conn.setRequestMethod(httpMethod);
conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + BOUNDARY);
// Set the cookies on the response
@@ -269,22 +368,7 @@ 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);
}
/*
@@ -400,7 +484,7 @@ public class FileTransfer extends CordovaPlugin {
int responseCode = conn.getResponseCode();
Log.d(LOG_TAG, "response code: " + responseCode);
Log.d(LOG_TAG, "response headers: " + conn.getHeaderFields());
InputStream inStream = null;
TrackingInputStream inStream = null;
try {
inStream = getInputStream(conn);
synchronized (context) {
@@ -476,11 +560,15 @@ public class FileTransfer extends CordovaPlugin {
}
}
private static InputStream getInputStream(URLConnection conn) throws IOException {
private static TrackingInputStream getInputStream(URLConnection conn) throws IOException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
return new DoneHandlerInputStream(conn.getInputStream());
}
return conn.getInputStream();
String encoding = conn.getContentEncoding();
if (encoding != null && encoding.equalsIgnoreCase("gzip")) {
return new TrackingGZIPInputStream(new ExposedGZIPInputStream(conn.getInputStream()));
}
return new TrackingHTTPInputStream(conn.getInputStream());
}
// always verify the host - don't check for certificate
@@ -530,18 +618,33 @@ public class FileTransfer extends CordovaPlugin {
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 {
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);
}
/**
@@ -549,13 +652,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);
}
@@ -594,12 +701,13 @@ 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;
@@ -608,7 +716,7 @@ public class FileTransfer extends CordovaPlugin {
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;
}
@@ -671,20 +779,29 @@ public class FileTransfer extends CordovaPlugin {
{
connection.setRequestProperty("cookie", cookie);
}
// This must be explicitly set for gzip progress tracking to work.
connection.setRequestProperty("Accept-Encoding", "gzip");
// Handle the other headers
if (headers != null) {
addHeadersToRequest(connection, headers);
}
connection.connect();
Log.d(LOG_TAG, "Download file:" + url);
FileProgressResult progress = new FileProgressResult();
if (connection.getContentEncoding() == null) {
// Only trust content-length header if no gzip etc
if (connection.getContentEncoding() == null || connection.getContentEncoding().equalsIgnoreCase("gzip")) {
// Only trust content-length header if we understand
// the encoding -- identity or gzip
progress.setLengthComputable(true);
progress.setTotal(connection.getContentLength());
}
FileOutputStream outputStream = null;
InputStream inputStream = null;
TrackingInputStream inputStream = null;
try {
inputStream = getInputStream(connection);
@@ -699,12 +816,10 @@ public class FileTransfer extends CordovaPlugin {
// write bytes to file
byte[] buffer = new byte[MAX_BUFFER_SIZE];
int bytesRead = 0;
long totalBytes = 0;
while ((bytesRead = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, bytesRead);
totalBytes += bytesRead;
// Send a progress event.
progress.setLoaded(totalBytes);
progress.setLoaded(inputStream.getTotalRawBytesRead());
PluginResult progressResult = new PluginResult(PluginResult.Status.OK, progress.toJSONObject());
progressResult.setKeepCallback(true);
context.sendPluginResult(progressResult);
@@ -718,8 +833,7 @@ 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);
result = new PluginResult(PluginResult.Status.OK, fileEntry);
} catch (FileNotFoundException e) {
@@ -826,7 +940,7 @@ public class FileTransfer extends CordovaPlugin {
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,23 +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.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;
@@ -75,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.
*/
@@ -111,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));
@@ -237,11 +234,11 @@ public class FileUtils extends CordovaPlugin {
* @param filePath the path to check
*/
private void notifyDelete(String filePath) {
String newFilePath = getRealPathFromURI(Uri.parse(filePath), cordova);
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
@@ -344,8 +341,8 @@ public class FileUtils extends CordovaPlugin {
* @throws FileExistsException
*/
private JSONObject transferTo(String fileName, String newParent, String newName, boolean move) throws JSONException, NoModificationAllowedException, IOException, InvalidModificationException, EncodingException, FileExistsException {
String newFileName = getRealPathFromURI(Uri.parse(fileName), cordova);
newParent = getRealPathFromURI(Uri.parse(newParent), cordova);
String newFileName = FileHelper.getRealPath(fileName, cordova);
newParent = FileHelper.getRealPath(newParent, cordova);
// Check for invalid file name
if (newName != null && newName.contains(":")) {
@@ -384,14 +381,14 @@ public class FileUtils extends CordovaPlugin {
}
} else {
if (move) {
JSONObject newFileEntry = 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);
}
// If we've moved a file given its content URI, we need to clean up.
if (fileName.startsWith("content://")) {
notifyDelete(fileName);
}
return newFileEntry;
return newFileEntry;
} else {
return copyFile(source, destination);
}
@@ -748,7 +745,7 @@ public class FileUtils extends CordovaPlugin {
if (fileName.startsWith("/")) {
fp = new File(fileName);
} else {
dirPath = getRealPathFromURI(Uri.parse(dirPath), cordova);
dirPath = FileHelper.getRealPath(dirPath, cordova);
fp = new File(dirPath + File.separator + fileName);
}
return fp;
@@ -763,7 +760,7 @@ public class FileUtils extends CordovaPlugin {
* @throws JSONException
*/
private JSONObject getParent(String filePath) throws JSONException {
filePath = getRealPathFromURI(Uri.parse(filePath), cordova);
filePath = FileHelper.getRealPath(filePath, cordova);
if (atRootDirectory(filePath)) {
return getEntry(filePath);
@@ -779,7 +776,7 @@ public class FileUtils extends CordovaPlugin {
* @return true if we are at the root, false otherwise.
*/
private boolean atRootDirectory(String filePath) {
filePath = getRealPathFromURI(Uri.parse(filePath), cordova);
filePath = FileHelper.getRealPath(filePath, cordova);
if (filePath.equals(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/" + cordova.getActivity().getPackageName() + "/cache") ||
filePath.equals(Environment.getExternalStorageDirectory().getAbsolutePath()) ||
@@ -789,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
*
@@ -809,7 +793,7 @@ public class FileUtils extends CordovaPlugin {
* @return
*/
private File createFileObject(String filePath) {
filePath = getRealPathFromURI(Uri.parse(filePath), cordova);
filePath = FileHelper.getRealPath(filePath, cordova);
File file = new File(filePath);
return file;
@@ -849,7 +833,7 @@ 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", filePath);
metadata.put("lastModifiedDate", file.lastModified());
@@ -900,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;
}
@@ -930,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;
}
/**
@@ -1060,11 +1004,11 @@ public class FileUtils extends CordovaPlugin {
*/
/**/
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");
}
if (filename.startsWith("content://")) {
throw new NoModificationAllowedException("Couldn't write to file given its content URI");
}
filename = getRealPathFromURI(Uri.parse(filename), cordova);
filename = FileHelper.getRealPath(filename, cordova);
boolean append = false;
if (offset > 0) {
@@ -1093,11 +1037,11 @@ public class FileUtils extends CordovaPlugin {
* @throws NoModificationAllowedException
*/
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");
}
if (filename.startsWith("content://")) {
throw new NoModificationAllowedException("Couldn't truncate file given its content URI");
}
filename = getRealPathFromURI(Uri.parse(filename), cordova);
filename = FileHelper.getRealPath(filename, cordova);
RandomAccessFile raf = new RandomAccessFile(filename, "rw");
try {
@@ -1112,48 +1056,4 @@ public class FileUtils extends CordovaPlugin {
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 = getRealPathFromURI(Uri.parse(path), cordova);
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 == null) {
return contentUri.toString();
} else 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();
}
}
}

20
framework/src/org/apache/cordova/GeoBroker.java Executable file → Normal file
View File

@@ -38,7 +38,7 @@ import android.location.LocationManager;
public class GeoBroker extends CordovaPlugin {
private GPSListener gpsListener;
private NetworkListener networkListener;
private LocationManager locationManager;
private LocationManager locationManager;
/**
* Constructor.
@@ -73,7 +73,7 @@ public class GeoBroker extends CordovaPlugin {
PluginResult result = new PluginResult(PluginResult.Status.OK, this.returnLocationJSON(last));
callbackContext.sendPluginResult(result);
} else {
this.getCurrentLocation(callbackContext, enableHighAccuracy);
this.getCurrentLocation(callbackContext, enableHighAccuracy, args.optInt(2, 60000));
}
}
else if (action.equals("addWatch")) {
@@ -102,11 +102,11 @@ public class GeoBroker extends CordovaPlugin {
this.networkListener.clearWatch(id);
}
private void getCurrentLocation(CallbackContext callbackContext, boolean enableHighAccuracy) {
private void getCurrentLocation(CallbackContext callbackContext, boolean enableHighAccuracy, int timeout) {
if (enableHighAccuracy) {
this.gpsListener.addCallback(callbackContext);
this.gpsListener.addCallback(callbackContext, timeout);
} else {
this.networkListener.addCallback(callbackContext);
this.networkListener.addCallback(callbackContext, timeout);
}
}
@@ -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
@@ -160,8 +160,9 @@ public class GeoBroker extends CordovaPlugin {
return o;
}
public void win(Location loc, CallbackContext callbackContext) {
PluginResult result = new PluginResult(PluginResult.Status.OK, this.returnLocationJSON(loc));
public void win(Location loc, CallbackContext callbackContext, boolean keepCallback) {
PluginResult result = new PluginResult(PluginResult.Status.OK, this.returnLocationJSON(loc));
result.setKeepCallback(keepCallback);
callbackContext.sendPluginResult(result);
}
@@ -172,7 +173,7 @@ public class GeoBroker extends CordovaPlugin {
* @param msg The error message
* @throws JSONException
*/
public void fail(int code, String msg, CallbackContext callbackContext) {
public void fail(int code, String msg, CallbackContext callbackContext, boolean keepCallback) {
JSONObject obj = new JSONObject();
String backup = null;
try {
@@ -189,6 +190,7 @@ public class GeoBroker extends CordovaPlugin {
result = new PluginResult(PluginResult.Status.ERROR, backup);
}
result.setKeepCallback(keepCallback);
callbackContext.sendPluginResult(result);
}

View File

@@ -24,11 +24,12 @@ import java.io.InputStream;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.LOG;
import android.content.res.AssetManager;
import android.net.Uri;
import android.annotation.TargetApi;
import android.os.Build;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
@@ -42,35 +43,20 @@ public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
if(url.contains("?") || url.contains("#")){
return generateWebResourceResponse(url);
} else {
return super.shouldInterceptRequest(view, url);
//Check if plugins intercept the request
WebResourceResponse ret = super.shouldInterceptRequest(view, url);
if(ret == null && (url.contains("?") || url.contains("#") || needsIceCreamSpaceInAssetUrlFix(url))){
ret = generateWebResourceResponse(url);
}
return ret;
}
private WebResourceResponse generateWebResourceResponse(String url) {
final String ANDROID_ASSET = "file:///android_asset/";
if (url.startsWith(ANDROID_ASSET)) {
String niceUrl = url;
niceUrl = url.replaceFirst(ANDROID_ASSET, "");
if(niceUrl.contains("?")){
niceUrl = niceUrl.split("\\?")[0];
}
else if(niceUrl.contains("#"))
{
niceUrl = niceUrl.split("#")[0];
}
String mimetype = null;
if(niceUrl.endsWith(".html")){
mimetype = "text/html";
}
if (url.startsWith("file:///android_asset/")) {
String mimetype = FileHelper.getMimeType(url, cordova);
try {
AssetManager assets = cordova.getActivity().getAssets();
Uri uri = Uri.parse(niceUrl);
InputStream stream = assets.open(uri.getPath(), AssetManager.ACCESS_STREAMING);
InputStream stream = FileHelper.getInputStreamFromUriString(url, cordova);
WebResourceResponse response = new WebResourceResponse(mimetype, "UTF-8", stream);
return response;
} catch (IOException e) {
@@ -80,4 +66,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

@@ -35,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;
@@ -48,6 +50,8 @@ 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.JsPromptResult;
import android.webkit.WebSettings;
import android.webkit.WebStorage;
import android.webkit.WebView;
@@ -69,6 +73,8 @@ 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;
@@ -76,6 +82,7 @@ public class InAppBrowser extends CordovaPlugin {
private EditText edittext;
private boolean showLocationBar = true;
private CallbackContext callbackContext;
private String buttonLabel = "Done";
/**
* Executes the request and returns PluginResult.
@@ -86,12 +93,9 @@ public class InAppBrowser extends CordovaPlugin {
* @return A PluginResult object with a status and message.
*/
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
PluginResult.Status status = PluginResult.Status.OK;
String result = "";
this.callbackContext = callbackContext;
try {
if (action.equals("open")) {
this.callbackContext = callbackContext;
String url = args.getString(0);
String target = args.optString(1);
if (target == null || target.equals("") || target.equals(NULL)) {
@@ -102,6 +106,7 @@ public class InAppBrowser extends CordovaPlugin {
Log.d(LOG_TAG, "target = " + target);
url = updateUrl(url);
String result = "";
// SELF
if (SELF.equals(target)) {
@@ -137,26 +142,89 @@ public class InAppBrowser extends CordovaPlugin {
Log.d(LOG_TAG, "in blank");
result = this.showWebPage(url, features);
}
PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, result);
pluginResult.setKeepCallback(true);
this.callbackContext.sendPluginResult(pluginResult);
}
else if (action.equals("close")) {
closeDialog();
PluginResult pluginResult = new PluginResult(PluginResult.Status.OK);
pluginResult.setKeepCallback(false);
this.callbackContext.sendPluginResult(pluginResult);
this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK));
}
else if (action.equals("injectScriptCode")) {
String jsWrapper = null;
if (args.getBoolean(1)) {
jsWrapper = String.format("prompt(JSON.stringify([eval(%%s)]), 'gap-iab://%s')", callbackContext.getCallbackId());
}
injectDeferredObject(args.getString(0), jsWrapper);
}
else if (action.equals("injectScriptFile")) {
String jsWrapper;
if (args.getBoolean(1)) {
jsWrapper = String.format("(function(d) { var c = d.createElement('script'); c.src = %%s; c.onload = function() { prompt('', 'gap-iab://%s'); }; d.body.appendChild(c); })(document)", callbackContext.getCallbackId());
} else {
jsWrapper = "(function(d) { var c = d.createElement('script'); c.src = %s; d.body.appendChild(c); })(document)";
}
injectDeferredObject(args.getString(0), jsWrapper);
}
else if (action.equals("injectStyleCode")) {
String jsWrapper;
if (args.getBoolean(1)) {
jsWrapper = String.format("(function(d) { var c = d.createElement('style'); c.innerHTML = %%s; d.body.appendChild(c); prompt('', 'gap-iab://%s');})(document)", callbackContext.getCallbackId());
} else {
jsWrapper = "(function(d) { var c = d.createElement('style'); c.innerHTML = %s; d.body.appendChild(c); })(document)";
}
injectDeferredObject(args.getString(0), jsWrapper);
}
else if (action.equals("injectStyleFile")) {
String jsWrapper;
if (args.getBoolean(1)) {
jsWrapper = String.format("(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %%s; d.head.appendChild(c); prompt('', 'gap-iab://%s');})(document)", callbackContext.getCallbackId());
} else {
jsWrapper = "(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %s; d.head.appendChild(c); })(document)";
}
injectDeferredObject(args.getString(0), jsWrapper);
}
else {
status = PluginResult.Status.INVALID_ACTION;
return false;
}
PluginResult pluginResult = new PluginResult(status, result);
pluginResult.setKeepCallback(true);
this.callbackContext.sendPluginResult(pluginResult);
} catch (JSONException e) {
this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
}
return true;
}
/**
* Inject an object (script or style) into the InAppBrowser WebView.
*
* This is a helper method for the inject{Script|Style}{Code|File} API calls, which
* provides a consistent method for injecting JavaScript code into the document.
*
* If a wrapper string is supplied, then the source string will be JSON-encoded (adding
* quotes) and wrapped using string formatting. (The wrapper string should have a single
* '%s' marker)
*
* @param source The source object (filename or script/style text) to inject into
* the document.
* @param jsWrapper A JavaScript string to wrap the source string in, so that the object
* is properly injected, or null if the source string is JavaScript text
* which should be executed directly.
*/
private void injectDeferredObject(String source, String jsWrapper) {
String scriptToInject;
if (jsWrapper != null) {
org.json.JSONArray jsonEsc = new org.json.JSONArray();
jsonEsc.put(source);
String jsonRepr = jsonEsc.toString();
String jsonSourceString = jsonRepr.substring(1, jsonRepr.length()-1);
scriptToInject = String.format(jsWrapper, jsonSourceString);
} else {
scriptToInject = source;
}
// This action will have the side-effect of blurring the currently focused element
this.inAppWebView.loadUrl("javascript:" + scriptToInject);
}
/**
* Put the list of features into a hash map
*
@@ -174,8 +242,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;
@@ -221,6 +293,7 @@ public class InAppBrowser extends CordovaPlugin {
*/
private void closeDialog() {
try {
this.inAppWebView.loadUrl("about:blank");
JSONObject obj = new JSONObject();
obj.put("type", EXIT_EVENT);
@@ -289,7 +362,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;
@@ -405,7 +481,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();
@@ -415,7 +491,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 InAppChromeClient());
inAppWebView.setWebChromeClient(new InAppChromeClient(thatWebView));
WebViewClient client = new InAppBrowserClient(thatWebView, edittext);
inAppWebView.setWebViewClient(client);
WebSettings settings = inAppWebView.getSettings();
@@ -428,10 +504,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);
@@ -472,18 +556,33 @@ 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 {
private CordovaWebView webView;
public InAppChromeClient(CordovaWebView webView) {
super();
this.webView = webView;
}
/**
* Handle database quota exceeded notification.
*
@@ -514,6 +613,69 @@ public class InAppBrowser extends CordovaPlugin {
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);
}
/**
* Tell the client to display a prompt dialog to the user.
* If the client returns true, WebView will assume that the client will
* handle the prompt dialog and call the appropriate JsPromptResult method.
*
* The prompt bridge provided for the InAppBrowser is capable of executing any
* oustanding callback belonging to the InAppBrowser plugin. Care has been
* taken that other callbacks cannot be triggered, and that no other code
* execution is possible.
*
* To trigger the bridge, the prompt default value should be of the form:
*
* gap-iab://<callbackId>
*
* where <callbackId> is the string id of the callback to trigger (something
* like "InAppBrowser0123456789")
*
* If present, the prompt message is expected to be a JSON-encoded value to
* pass to the callback. A JSON_EXCEPTION is returned if the JSON is invalid.
*
* @param view
* @param url
* @param message
* @param defaultValue
* @param result
*/
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
// See if the prompt string uses the 'gap-iab' protocol. If so, the remainder should be the id of a callback to execute.
if (defaultValue != null && defaultValue.startsWith("gap-iab://")) {
PluginResult scriptResult;
String scriptCallbackId = defaultValue.substring(10);
if (scriptCallbackId.startsWith("InAppBrowser")) {
if(message == null || message.length() == 0) {
scriptResult = new PluginResult(PluginResult.Status.OK, new JSONArray());
} else {
try {
scriptResult = new PluginResult(PluginResult.Status.OK, new JSONArray(message));
} catch(JSONException e) {
scriptResult = new PluginResult(PluginResult.Status.JSON_EXCEPTION, e.getMessage());
}
}
this.webView.sendPluginResult(scriptResult, scriptCallbackId);
result.confirm("");
return true;
}
}
return false;
}
}
/**
@@ -543,10 +705,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;
}
@@ -578,5 +792,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

@@ -50,13 +50,10 @@ public class NativeToJsMessageQueue {
// exec() is asynchronous. Set this to true when running bridge benchmarks.
static final boolean DISABLE_EXEC_CHAINING = false;
// Upper limit for how much data to send to JS in one shot.
// TODO(agrieve): This is currently disable. It should be re-enabled once we
// remove support for returning values from exec() calls. This was
// deprecated in 2.2.0.
// Also, this currently only chops up on message boundaries. It may be useful
// Arbitrarily chosen upper limit for how much data to send to JS in one shot.
// This currently only chops up on message boundaries. It may be useful
// to allow it to break up messages.
private static int MAX_PAYLOAD_SIZE = -1; //50 * 1024 * 10240;
private static int MAX_PAYLOAD_SIZE = 50 * 1024 * 10240;
/**
* The index into registeredListeners to treat as active.
@@ -409,6 +406,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 +451,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 +477,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

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

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

@@ -1,177 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova.api;
import org.apache.cordova.CordovaWebView;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Legacy Plugin class. This acts as a shim to support the old execute() signature.
* New plugins should extend CordovaPlugin directly.
*/
@Deprecated
public abstract class Plugin extends CordovaPlugin {
public LegacyContext ctx; // LegacyContext object
public abstract PluginResult execute(String action, JSONArray args, String callbackId);
public boolean isSynch(String action) {
return false;
}
@Override
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
super.initialize(cordova, webView);
this.setContext(cordova);
this.setView(webView);
}
/**
* Sets the context of the Plugin. This can then be used to do things like
* get file paths associated with the Activity.
*
* @param ctx The context of the main Activity.
*/
public void setContext(CordovaInterface ctx) {
this.cordova = ctx;
this.ctx = new LegacyContext(cordova);
}
/**
* Sets the main View of the application, this is the WebView within which
* a Cordova app runs.
*
* @param webView The Cordova WebView
*/
public void setView(CordovaWebView webView) {
this.webView = webView;
}
@Override
public boolean execute(final String action, final JSONArray args, final CallbackContext callbackContext) throws JSONException {
final String callbackId = callbackContext.getCallbackId();
boolean runAsync = !isSynch(action);
if (runAsync) {
// Run this on a different thread so that this one can return back to JS
cordova.getThreadPool().execute(new Runnable() {
public void run() {
PluginResult cr;
try {
cr = execute(action, args, callbackId);
} catch (Throwable e) {
cr = new PluginResult(PluginResult.Status.ERROR, e.getMessage());
}
sendPluginResult(cr, callbackId);
}
});
} else {
PluginResult cr = execute(action, args, callbackId);
// Interpret a null response as NO_RESULT, which *does* clear the callbacks on the JS side.
if (cr == null) {
cr = new PluginResult(PluginResult.Status.NO_RESULT);
}
callbackContext.sendPluginResult(cr);
}
return true;
}
/**
* Send generic JavaScript statement back to JavaScript.
* sendPluginResult() should be used instead where possible.
*/
public void sendJavascript(String statement) {
this.webView.sendJavascript(statement);
}
/**
* Send generic JavaScript statement back to JavaScript.
*/
public void sendPluginResult(PluginResult pluginResult, String callbackId) {
this.webView.sendPluginResult(pluginResult, callbackId);
}
/**
* Call the JavaScript success callback for this plugin.
*
* This can be used if the execute code for the plugin is asynchronous meaning
* that execute should return null and the callback from the async operation can
* call success(...) or error(...)
*
* @param pluginResult The result to return.
* @param callbackId The callback id used when calling back into JavaScript.
*/
public void success(PluginResult pluginResult, String callbackId) {
this.webView.sendPluginResult(pluginResult, callbackId);
}
/**
* Helper for success callbacks that just returns the Status.OK by default
*
* @param message The message to add to the success result.
* @param callbackId The callback id used when calling back into JavaScript.
*/
public void success(JSONObject message, String callbackId) {
this.webView.sendPluginResult(new PluginResult(PluginResult.Status.OK, message), callbackId);
}
/**
* Helper for success callbacks that just returns the Status.OK by default
*
* @param message The message to add to the success result.
* @param callbackId The callback id used when calling back into JavaScript.
*/
public void success(String message, String callbackId) {
this.webView.sendPluginResult(new PluginResult(PluginResult.Status.OK, message), callbackId);
}
/**
* Call the JavaScript error callback for this plugin.
*
* @param pluginResult The result to return.
* @param callbackId The callback id used when calling back into JavaScript.
*/
public void error(PluginResult pluginResult, String callbackId) {
this.webView.sendPluginResult(pluginResult, callbackId);
}
/**
* Helper for error callbacks that just returns the Status.ERROR by default
*
* @param message The message to add to the error result.
* @param callbackId The callback id used when calling back into JavaScript.
*/
public void error(JSONObject message, String callbackId) {
this.webView.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, message), callbackId);
}
/**
* Helper for error callbacks that just returns the Status.ERROR by default
*
* @param message The message to add to the error result.
* @param callbackId The callback id used when calling back into JavaScript.
*/
public void error(String message, String callbackId) {
this.webView.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, message), callbackId);
}
}

View File

@@ -30,6 +30,9 @@ 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.
*
@@ -118,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")) {
@@ -211,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;
@@ -366,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",