Compare commits

...

122 Commits
1.9.0 ... 2.1.0

Author SHA1 Message Date
Joe Bowser
311a2f6023 Updating JS with feeling - See CB-1420 2012-09-12 12:58:30 -07:00
Simon MacDonald
59a3cf93e6 Tagging 2.1.0 2012-09-12 11:44:53 -04:00
Simon MacDonald
a42f095cef CB-1409: Clicking the menu button crashes Jellybean 2012-09-11 09:04:42 -04:00
Simon MacDonald
a29340523f CB-1293: Camera.getPicture crashes when selecting from a Picasa album on Android 2012-09-07 15:20:31 -04:00
Joe Bowser
5ad7a7c014 Forgot to put back echo 2012-09-06 17:12:36 -07:00
Joe Bowser
c6fa7e4aad Reverting to the previously known to work config.xml, we'll introduce the new format in 2.2 2012-09-06 15:47:53 -07:00
Joe Bowser
d4b248fbe3 Merge conflict on the plugins 2012-09-06 15:26:38 -07:00
Joe Bowser
48881d081a Adding support for data uri 2012-09-06 12:19:42 -07:00
Joe Bowser
331024414e Fixed the backbutton issues with the keyboard without causing issues with the menu button so far 2012-09-05 16:10:48 -07:00
Simon MacDonald
9d0c5349bb CB-1304: Update PhoneGap References to Apache Cordova 2012-09-03 14:31:35 -04:00
Simon MacDonald
dc40d8afac CB-1335: Update www/ Application 2012-09-03 13:42:15 -04:00
Simon MacDonald
005877b4b8 CB-1303: Remove References to the Callback Project 2012-09-03 13:22:07 -04:00
Simon MacDonald
774d21747a Tagging 2.1.0rc2 2012-08-31 16:44:53 -04:00
Simon MacDonald
12e5b39c05 Fixing failing 'should return MediaError for bad filename' Media test case 2012-08-31 12:50:23 -04:00
Simon MacDonald
4d5e452ece CB-1358: Getting Force Close in incoming Cal while recording the Audio? 2012-08-31 12:45:01 -04:00
Michael Brooks
1ba3ecbef3 [#1301] Remove releasenotes.md
The file has not been updated in 5 months. If we choose to have
platform-level CHANGELOG's then it should be started across all
projects and named consistently as CHANGELOG.
2012-08-30 16:08:16 -07:00
Michael Brooks
db6695cb02 [#1305] Remove guides/ directory
The guides are now documented in incubator-cordova-docs.
2012-08-30 15:34:57 -07:00
Simon MacDonald
b3f5e039f2 Fixing 'FileTransfer.download() - should handle unknown host' failing mobile-spec test case 2012-08-30 17:01:16 -04:00
Andrew Grieve
c3e17fb185 Fix broken file upload in mobile-spec tests (CB-1290).
The explicit transfer-encoding: chunk that was added breaks in
fixed-length streaming mode. It is, however, still required to
work-around an OOM bug in HTTPS mode. The new logic works for both the
mobile-spec and the HTTPS large-file test that I used before.

Commit adding the header: 999c548e6e
2012-08-30 13:37:19 -04:00
macdonst
f7ae7fe43a CB=1293: Camera.getPicture crashes when selecting from a Picasa album on Android 2012-08-29 12:46:12 -04:00
macdonst
e07822350e CB-1289: The menubutton event fires twice 2012-08-28 11:55:12 -04:00
Andrew Grieve
07439ff99c Refactor to avoid adb warnings of VFY: on start-up. 2012-08-27 15:05:20 -04:00
macdonst
f111c245c1 Tagging 2.1.0rc1 2012-08-24 16:44:18 -04:00
Anis Kadri
c3502da4a0 arg forgot dest file 2012-08-24 13:40:23 -07:00
Anis Kadri
4012108d48 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android 2012-08-24 12:15:02 -07:00
Anis Kadri
4a0605e09b CB-1235 Fixing Android create script on Windows 2012-08-24 12:14:58 -07:00
Andrew Grieve
250380d73e Implement LOAD_URL exec bridge.
Also refactors PluginManager.exec to return the PluginResult instead of
a string.
2012-08-24 14:19:41 -04:00
Andrew Grieve
b30f5d782d Fix JS timers being disabled on pause and never re-enabled.
Was broken in this change:
b234b0bded
2012-08-23 15:35:08 -04:00
macdonst
b00cd9b557 Updating the .gitignore file 2012-08-23 12:38:40 -04:00
Braden Shepherdson
e11f8f646b Greatly improve speed of fetching contacts.
We were selecting every column in a fairly wide table before. This code fetches
only those columns necessary to populate the data requested by the Javascript
code. In experiments with coworkers' and my own contact lists, the time to fetch
~1440 contacts has gone from over 40 seconds to less than 10 seconds. I have not
tested with fewer than 1400 contacts, but I expect at least a small improvement.
2012-08-22 10:46:32 -04:00
Andrew Grieve
92b1de8cf8 Update cordova.android.js to pull in exec changes. 2012-08-22 09:50:40 -04:00
Andrew Grieve
bbafe53a2b Added Native-JS bridge mode that uses private WebView APIs. 2012-08-22 09:46:30 -04:00
Andrew Grieve
e239fd970f Implement the online events based Native->JS bridge. 2012-08-22 09:46:30 -04:00
Andrew Grieve
7fa4515c28 Implements the LOAD_URL Native->JS bridge mode
(without keyboard work-around)
2012-08-22 09:46:30 -04:00
Andrew Grieve
b40eb0a454 Fix API lint warnings in CordovaChromeClient and CordovaWebViewClient. 2012-08-22 09:46:30 -04:00
Andrew Grieve
5e3e9ddb8e Refactor Native->JS messaging logic into its own class.
This will make it easy to add more modes.
This also adds logic to set the move via a prompt() from JS.
2012-08-22 09:46:30 -04:00
macdonst
a9a5284a67 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android 2012-08-20 16:24:09 -04:00
macdonst
afe504dbbf CB-1267: Reuse Media object for recording 2012-08-20 16:23:19 -04:00
Andrew Grieve
0c484ddcf7 Make Eclipse recognize framework as a library project
Instructions are here:
http://developer.android.com/tools/projects/projects-eclipse.html#SettingUpLibraryProject
2012-08-20 14:22:48 -04:00
macdonst
8d0e80620a CB-1264: Media.stop() puts media into an unplayable state 2012-08-20 12:06:39 -04:00
Joe Bowser
1d28506b09 Fix for CB-1257 2012-08-17 14:35:08 -07:00
Andrew Grieve
1b33dbe2ae Provide an addJavascriptInterface() exec object.
-Disabled for 2.3 emulator to avoid a crash bug.
2012-08-17 11:15:19 -04:00
Andrew Grieve
80654c059d Add an Echo plugin for benchmarking purposes. 2012-08-17 11:10:16 -04:00
Andrew Grieve
999c548e6e Fix FileTransfer running out of memory over HTTPS (CB-312).
Setting the Transfer-Encoding header fixes running out of memory when
using HTTPS.
This CL also adds a bit of logging so that upload progress is logged.
2012-08-16 10:30:04 -04:00
Andrew Grieve
e42913ae8a Fix API Level linter errors in CordovaWebView.java 2012-08-16 09:36:43 -04:00
Joe Bowser
ee07cbecba Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android 2012-08-14 10:57:21 -07:00
Joe Bowser
fffaa9bced Forgot to add the Apache headers on IceCreamCordovaWebViewClient.java 2012-08-14 10:56:54 -07:00
macdonst
6195b2c99d CB-930: DirectoryReader creates null error code for inaccessible directory 2012-08-14 13:50:59 -04:00
macdonst
2dc0727e36 CB-1151: FileTransfer.download decodes the URL, resulting in a 404 error 2012-08-13 15:43:55 -04:00
macdonst
a219feaa60 Downgrade min sdk version of sample app 2012-08-13 13:30:27 -04:00
macdonst
f3a09da340 Modify min sdk version in AndroidManifest.xml 2012-08-13 13:27:34 -04:00
macdonst
946e345a3f Add service methods to legacy ctx varialbe 2012-08-10 13:14:42 -04:00
macdonst
6cb8d11b22 CB-1196: No onSuccess callback after a complete play of the local mp3 file 2012-08-10 10:53:52 -04:00
macdonst
fdcf9c5327 Reapply: CB-1211: Media record uses a .mp3 extension when it is a .3gp file 2012-08-10 09:27:37 -04:00
doggerelverse
45c714cbb5 reset of seekOnPrepared changed to properly reset only once playback has begun 2012-08-09 14:18:55 -07:00
macdonst
7352a309a0 CB-1217: Clicking back button does not dismiss navigator.notification.alert() dialog 2012-08-09 10:54:35 -04:00
Andrew Grieve
b297fe6f59 Fix a NPE in GeoBroker when there is no previous location. 2012-08-08 23:39:02 -04:00
Evgeni Petrov
e575212c49 GeoBroker checks if location service avialable for device first. 2012-08-08 23:23:01 -04:00
macdonst
c52dc10c9e CB-1212: When camera is started, and then cancelled with no photo, attempt to read exif data results in fatal error 2012-08-08 15:53:48 -04:00
macdonst
d35c913249 CB-1211: Media record uses a .mp3 extension when it is a .3gp file 2012-08-08 15:34:26 -04:00
macdonst
9bac59b952 CB-1206: file uri not handled correctly by Media Player 2012-08-08 15:33:01 -04:00
macdonst
5016253922 Update to use latest Android SDK 2012-08-07 15:40:05 -04:00
Joe Bowser
03893071fc Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android 2012-08-03 14:08:14 -07:00
Joe Bowser
d3dc94c04b CB-578 - Adding a test of pause and resume to make sure that they're being called. Need to elaborate on this 2012-08-03 14:07:01 -07:00
Andrew Grieve
af0feabb6a Prefer setFixedLengthStreamingMode over setChunkedStreamingMode in FileTransfer.
setFixedLengthStreamingMode causes the Content-Length header to be set,
which some servers require.

We now use setChunkedStreamingMode only on Eclair devices
since there is a bug with setFixedLengthStreamingMode in that version
of the OS.
2012-08-03 12:29:37 -04:00
Joe Bowser
81ab0a414f I forgot that Jellybean has some wacky changes on the onKeyUp and onKeyDown events, fixing CB-1181 2012-08-02 12:51:48 -07:00
Joe Bowser
ecd6ca0172 Moving the fix for # and ? to a new class to fix CB-995 2012-07-31 16:16:57 -07:00
Joe Bowser
db7ee192f7 This was a red herring. There was an XML error on the example when compared with master 2012-07-31 12:20:31 -07:00
Joe Bowser
2ec0b601fa Commenting out this past code, turns out this breaks more things than it fixes: CB-1101 2012-07-31 11:55:01 -07:00
Joe Bowser
79feb6d5d2 CB-1101: Specifying Jellybean means that we have to override the default CORS policy 2012-07-31 11:48:38 -07:00
Joe Bowser
8013b760e3 Adding functionality to read a config.xml file with plugin details implemented inside of it 2012-07-30 16:31:33 -07:00
Joe Bowser
a29b8e5b36 Caught error with the back button again. backHistory actually goes back in history, used canGoBack() instead 2012-07-30 13:55:22 -07:00
Anis Kadri
9ef487a7a5 fixing replace for mountain lion 2012-07-28 08:13:31 -07:00
Anis Kadri
563fa46ba4 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android 2012-07-27 18:30:59 -07:00
Anis Kadri
7865c06863 CB-1148 fix for directories with spaces 2012-07-27 18:30:39 -07:00
Andrew Grieve
3d53b9244d Adds FileTransfer support for upload headers.
-Support previously existed via options.params.headers. This CL
deprecates this (undocumented) way of adding headers and adds support
for options.headers.
-This also adds support for multiple headers via:
    options.headers = {"Name": ["Value1", "Value2"]}.
2012-07-27 20:54:31 -04:00
Joe Bowser
f2afa4dd50 Tweaking the Android Manfest to cope with new target changes: CB-1147 2012-07-27 13:31:25 -07:00
Joe Bowser
893ecec55e Minor fix to deal with weird keyboard focus issues and the back button. CB-1146 2012-07-27 10:33:38 -07:00
Joe Bowser
401584dbd8 Throwing code over the fence for CB-1128, We need a Samsung Galaxy S running 2.3.5 to confirm this fix 2012-07-25 12:54:03 -07:00
Joe Bowser
b234b0bded Utility Methods based on Feedback 2012-07-24 15:54:01 -07:00
Joe Bowser
b9b2c6a013 Updating sample project 2012-07-23 10:43:23 -07:00
Joe Bowser
1d2efa0d25 Fixing leaking sockets 2012-07-20 16:02:43 -07:00
Joe Bowser
93ec092eaf Forgot to add the example app. Works with the script 2012-07-20 09:57:26 -07:00
Joe Bowser
29ae492983 Upping the version to 2.0.0 2012-07-18 16:58:03 -07:00
Joe Bowser
b9f6a59a20 Fix for CB-1085 2012-07-18 14:37:45 -07:00
Joe Bowser
d74551216f Throwing code over the fence to fix CB-1087, I can't repro on my Galaxy Nexus 2012-07-18 14:30:45 -07:00
Andrew Grieve
d4302ae51b Remove cordova.xml and plugins.xml from framework/res/xml.
They have been superseded by config.xml, and having them stick around
was confusing.
2012-07-16 15:06:12 -04:00
Joe Bowser
9d5fb0b201 Tagging 2.0.0rc1 2012-07-13 15:46:09 -07:00
Fil Maj
e0a5fe4002 [CB-574] Added backbutton automated unit test for android. 2012-07-13 14:57:40 -07:00
macdonst
f9d9a0a4bd Adding deprecation notice to LegacyContext 2012-07-13 15:36:56 -04:00
Joe Bowser
78f0c7b119 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android into audio 2012-07-12 14:41:56 -07:00
Fil Maj
c6d8343de2 [CB-1035] Including newest JS built based on refactored common device module. 2012-07-12 13:37:08 -07:00
Anis Kadri
0ccd11e587 CB-1031 android create script fails 2012-07-11 14:00:42 -07:00
Joe Bowser
b486711d68 Combining plugins.xml and cordova.xml to make config.xml 2012-07-11 11:23:31 -07:00
Fil Maj
2eb4c5e960 [CB-1022] Reverted nanoTime back to currentTimeMillis. Updated mobile-spec tests as well. This passes all accel tests. 2012-07-11 10:26:14 -07:00
Fil Maj
85aa740c98 [CB-481] Removed todo comment introduced by bryce, clarified what is going on 2012-07-11 09:35:29 -07:00
Joe Bowser
6415848383 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android into audio 2012-07-11 09:14:04 -07:00
macdonst
beb9460538 CB-952: Android showSplashScreen crashes 2012-07-10 20:52:07 -04:00
Joe Bowser
c030770be7 Working with Lorin's change 2012-07-10 14:37:26 -07:00
macdonst
0180342dff CB-993: Android plugin problems upgrading to 1.9.0 2012-07-10 16:26:52 -04:00
macdonst
b97748d3dc CB-1005: Can not remove contact phonenumber values 2012-07-10 11:25:20 -04:00
Joe Bowser
9d4977db00 Fixing bug on ICS where the super.onKeyDown wasn't being called 2012-07-09 14:42:29 -07:00
macdonst
f095284faa CB-1016: Zero width or height in getPicture throws java.lang.ArithmeticException 2012-07-07 22:19:55 -04:00
macdonst
401c2f42f9 Modify PluginResult(status) so it generates a JSON string that works with JSON.parse() 2012-07-06 17:39:04 -04:00
macdonst
eb0348d47c CB-1014: Out of Memory error when getting image from photo library 2012-07-06 12:37:08 -04:00
macdonst
1f46240ba9 CB-999: When getting images from the PHOTOLIBRARY apply the correctOrientation fix 2012-07-05 16:04:47 -04:00
macdonst
14870726e0 CB-1008: Camera with targetHeight, targetWidth loses image aspect ratio 2012-07-05 15:32:55 -04:00
macdonst
c7d6a2eecb CB-992: Camera tries to add temp photo to gallery 2012-07-05 14:02:20 -04:00
macdonst
ce61eb2174 Implementing CordovaInterface.getContext in test folder classes 2012-07-03 11:36:04 -04:00
macdonst
f3df21ef0a Fix mis-spelling in upgrade guide 2012-07-03 10:33:48 -04:00
macdonst
5eb554e008 CB-993: Android plugin problems upgrading to 1.9.0
The DroidGap.getContext() method causes an infinite loop and eventually a stack overflow error.
2012-07-02 16:37:14 -04:00
Lorin Beer
3ea72e5d21 added deleted tempfile setup 2012-06-28 16:17:00 -07:00
Lorin Beer
762854ad7a changed handling of stopRecording to reflect handling of create message 2012-06-28 15:53:47 -07:00
Lorin Beer
10465066ee Merge branches 'master' and 'dev' 2012-06-28 15:37:11 -07:00
Lorin Beer
0cf9f51816 use enums to track internal states instead of int. Fixed 'unknown state' bug with the addition of loading state. Mega commit, lost some history. 2012-06-28 15:36:28 -07:00
Lorin Beer
3d5e2340ca update to use ordinal instead of enum value 2012-06-28 15:29:23 -07:00
Lorin Beer
d9e7984279 fixed seek behaviour, but introduces a bunch of new problems 2012-06-18 10:29:56 -07:00
Lorin Beer
e5b9900d3b halfway through refactor 2012-06-17 23:59:13 -07:00
Lorin Beer
fc3f1431b2 made internal status static variables final as well, specifically so that they can be used in switch statements 2012-06-17 22:56:22 -07:00
Lorin Beer
c8bf2f4cb1 removed audio load code from startPlaying to a private function 2012-06-17 22:37:12 -07:00
Lorin Beer
d16555ec4b added file requirement to constructor, all references to AudioPlayer constructor had direct access to file, so this caused no other changes 2012-06-17 22:19:33 -07:00
Lorin Beer
3c9415b1c2 added create message handler, updated AudioPlayer constructor usage 2012-06-17 22:18:09 -07:00
84 changed files with 6242 additions and 1879 deletions

13
.gitignore vendored
View File

@@ -2,6 +2,7 @@
default.properties
gen
assets/www/cordova.js
framework/assets/www/.tmp*
local.properties
framework/proguard.cfg
framework/cordova.jar
@@ -12,9 +13,19 @@ framework/test/org/apache/cordova/*.class
framework/assets/www/.DS_Store
framework/assets/www/cordova-*.js
framework/assets/www/phonegap-*.js
framework/libs
example
./test
tmp
test/bin
test/assets/www/.tmp*
tmp/**
*.tmp
test/libs/*.jar
bin/node_modules
.metadata
*.bak
tmp/**/*
*.swp
Thumbs.db
Desktop.ini

View File

@@ -82,15 +82,10 @@ Start adb logcat (console.log calls output here):
./cordova/log
Running the [callback/callback-test](http://github.com/callback/callback-test) tests:
---
./bin/test
Creating a new Cordova Android Project
---
./bin/create ~/Desktop/myapp com.phonegap.special MyApp
./bin/create ~/Desktop/myapp com.myapp.special MyApp
Importing a Cordova Android Project into Eclipse
----
@@ -110,5 +105,5 @@ Further Reading
---
- [http://developer.android.com](http://developer.android.com)
- [http://docs.phonegap.com](http://docs.phonegap.com)
- [http://wiki.phonegap.com](http://wiki.phonegap.com)
- [http://incubator.apache.org/cordova/](http://incubator.apache.org/cordova/)
- [http://wiki.apache.org/cordova/](http://wiki.apache.org/cordova/)

View File

@@ -1 +1 @@
1.9.0
2.1.0

View File

@@ -31,14 +31,14 @@ then
fi
BUILD_PATH=$( cd "$( dirname "$0" )/.." && pwd )
VERSION=$(cat $BUILD_PATH/VERSION)
VERSION=$(cat "$BUILD_PATH"/VERSION)
PROJECT_PATH=${1:-"./example"}
PROJECT_PATH=${1:-'./example'}
PACKAGE=${2:-"org.apache.cordova.example"}
ACTIVITY=${3:-"cordovaExample"}
# clobber any existing example
if [ -d $PROJECT_PATH ]
if [ -d "$PROJECT_PATH" ]
then
echo "Project already exists! Delete and recreate"
exit 1
@@ -46,21 +46,34 @@ fi
# cleanup after exit and/or on error
function on_exit {
# [ -f $BUILD_PATH/framework/libs/commons-codec-1.6.jar ] && rm $BUILD_PATH/framework/libs/commons-codec-1.6.jar
# [ -d $BUILD_PATH/framework/libs ] && rmdir $BUILD_PATH/framework/libs
if [ -f $BUILD_PATH/framework/assets/www/cordova-$VERSION.js ]
# [ -f "$BUILD_PATH"/framework/libs/commons-codec-1.6.jar ] && rm "$BUILD_PATH"/framework/libs/commons-codec-1.6.jar
# [ -d "$BUILD_PATH"/framework/libs ] && rmdir "$BUILD_PATH"/framework/libs
if [ -f "$BUILD_PATH"/framework/assets/www/cordova-$VERSION.js ]
then
rm $BUILD_PATH/framework/assets/www/cordova-$VERSION.js
rm "$BUILD_PATH"/framework/assets/www/cordova-$VERSION.js
fi
if [ -f $BUILD_PATH/framework/cordova-$VERSION.jar ]
if [ -f "$BUILD_PATH"/framework/cordova-$VERSION.jar ]
then
rm $BUILD_PATH/framework/cordova-$VERSION.jar
rm "$BUILD_PATH"/framework/cordova-$VERSION.jar
fi
}
function on_error {
echo "An error occured. Deleting project..."
[ -d $PROJECT_PATH ] && rm -rf $PROJECT_PATH
[ -d "$PROJECT_PATH" ] && rm -rf "$PROJECT_PATH"
}
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
@@ -69,66 +82,65 @@ trap on_exit EXIT
ANDROID_BIN=$( which android )
PACKAGE_AS_PATH=$(echo $PACKAGE | sed 's/\./\//g')
ACTIVITY_PATH=$PROJECT_PATH/src/$PACKAGE_AS_PATH/$ACTIVITY.java
MANIFEST_PATH=$PROJECT_PATH/AndroidManifest.xml
ACTIVITY_PATH="$PROJECT_PATH"/src/$PACKAGE_AS_PATH/$ACTIVITY.java
MANIFEST_PATH="$PROJECT_PATH"/AndroidManifest.xml
TARGET=$($ANDROID_BIN list targets | grep id: | tail -1 | cut -f 2 -d ' ' )
# if this a distribution release no need to build a jar
if [ ! -e $BUILD_PATH/cordova-$VERSION.jar ] && [ -d $BUILD_PATH/framework ]
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
$ANDROID_BIN update project --target $TARGET --path "$BUILD_PATH"/framework &> /dev/null
if [ ! -e $BUILD_PATH/framework/libs/commons-codec-1.6.jar ]; then
if [ ! -e "$BUILD_PATH"/framework/libs/commons-codec-1.6.jar ]; then
# Use curl to get the jar (TODO: Support Apache Mirrors)
curl -OL http://mirror.symnds.com/software/Apache//commons/codec/binaries/commons-codec-1.6-bin.zip &> /dev/null
unzip commons-codec-1.6-bin.zip &> /dev/null
mkdir -p $BUILD_PATH/framework/libs
cp commons-codec-1.6/commons-codec-1.6.jar $BUILD_PATH/framework/libs
mkdir -p "$BUILD_PATH"/framework/libs
cp commons-codec-1.6/commons-codec-1.6.jar "$BUILD_PATH"/framework/libs
# cleanup yo
rm commons-codec-1.6-bin.zip && rm -rf commons-codec-1.6
fi
# compile cordova.js and cordova.jar
(cd $BUILD_PATH/framework && ant jar &> /dev/null )
(cd "$BUILD_PATH"/framework && ant jar &> /dev/null )
fi
# create new android project
$ANDROID_BIN create project --target $TARGET --path $PROJECT_PATH --package $PACKAGE --activity $ACTIVITY &> /dev/null
$ANDROID_BIN create project --target $TARGET --path "$PROJECT_PATH" --package $PACKAGE --activity $ACTIVITY &> /dev/null
# copy project template
cp -r $BUILD_PATH/bin/templates/project/assets $PROJECT_PATH
cp -r $BUILD_PATH/bin/templates/project/res $PROJECT_PATH
cp -r "$BUILD_PATH"/bin/templates/project/assets "$PROJECT_PATH"
cp -r "$BUILD_PATH"/bin/templates/project/res "$PROJECT_PATH"
# copy cordova.js, cordova.jar and res/xml
if [ -d $BUILD_PATH/framework ]
if [ -d "$BUILD_PATH"/framework ]
then
cp -r $BUILD_PATH/framework/res/xml $PROJECT_PATH/res
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
cp -r "$BUILD_PATH"/framework/res/xml "$PROJECT_PATH"/res
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 -r $BUILD_PATH/xml $PROJECT_PATH/res/xml
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
cp -r "$BUILD_PATH"/xml "$PROJECT_PATH"/res/xml
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
# interpolate the activity name and package
cp $BUILD_PATH/bin/templates/project/Activity.java $ACTIVITY_PATH
sed -i '' -e "s/__ACTIVITY__/${ACTIVITY}/g" $ACTIVITY_PATH
sed -i '' -e "s/__ID__/${PACKAGE}/g" $ACTIVITY_PATH
cp "$BUILD_PATH"/bin/templates/project/Activity.java "$ACTIVITY_PATH"
replace "s/__ACTIVITY__/${ACTIVITY}/g" "$ACTIVITY_PATH"
replace "s/__ID__/${PACKAGE}/g" "$ACTIVITY_PATH"
cp $BUILD_PATH/bin/templates/project/AndroidManifest.xml $MANIFEST_PATH
sed -i '' -e "s/__ACTIVITY__/${ACTIVITY}/g" $MANIFEST_PATH
sed -i '' -e "s/__PACKAGE__/${PACKAGE}/g" $MANIFEST_PATH
cp "$BUILD_PATH"/bin/templates/project/AndroidManifest.xml "$MANIFEST_PATH"
replace "s/__ACTIVITY__/${ACTIVITY}/g" "$MANIFEST_PATH"
replace "s/__PACKAGE__/${PACKAGE}/g" "$MANIFEST_PATH"
# creating cordova folder and copying emulate/debug/log/launch scripts
mkdir $PROJECT_PATH/cordova
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/debug $PROJECT_PATH/cordova/debug
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/emulate $PROJECT_PATH/cordova/emulate
cp $BUILD_PATH/bin/templates/cordova/BOOM $PROJECT_PATH/cordova/BOOM
mkdir "$PROJECT_PATH"/cordova
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/debug "$PROJECT_PATH"/cordova/debug
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/emulate "$PROJECT_PATH"/cordova/emulate
cp "$BUILD_PATH"/bin/templates/cordova/BOOM "$PROJECT_PATH"/cordova/BOOM

View File

@@ -154,8 +154,7 @@ 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');
fso.CreateFolder(PROJECT_PATH + '\\res\\xml');
exec('%comspec% /c copy '+ROOT+'\\framework\\res\\xml\\cordova.xml ' + PROJECT_PATH + '\\res\\xml\\cordova.xml /Y');
exec('%comspec% /c copy '+ROOT+'\\framework\\res\\xml\\plugins.xml ' + PROJECT_PATH + '\\res\\xml\\plugins.xml /Y');
exec('%comspec% /c copy '+ROOT+'\\framework\\res\\xml\\config.xml ' + PROJECT_PATH + '\\res\\xml\\config.xml /Y');
} else {
// copy in cordova.js
exec('%comspec% /c copy '+ROOT+'\\cordova-'+VERSION+'.js '+PROJECT_PATH+'\\assets\\www\\cordova-'+VERSION+'.js /Y');
@@ -163,8 +162,7 @@ if(fso.FolderExists(ROOT + '\\framework')) {
exec('%comspec% /c copy '+ROOT+'\\cordova-'+VERSION+'.jar '+PROJECT_PATH+'\\libs\\cordova-'+VERSION+'.jar /Y');
// copy in xml
fso.CreateFolder(PROJECT_PATH + '\\res\\xml');
exec('%comspec% /c copy '+ROOT+'\\xml\\cordova.xml ' + PROJECT_PATH + '\\res\\xml\\cordova.xml /Y');
exec('%comspec% /c copy '+ROOT+'\\xml\\plugins.xml ' + PROJECT_PATH + '\\res\\xml\\plugins.xml /Y');
exec('%comspec% /c copy '+ROOT+'\\xml\\config.xml ' + PROJECT_PATH + '\\res\\xml\\config.xml /Y');
}
// copy cordova scripts

View File

@@ -48,8 +48,8 @@
<application android:icon="@drawable/icon" android:label="@string/app_name"
android:debuggable="true">
<activity android:name="__ACTIVITY__" android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden">
<activity android:name="__ACTIVITY__" android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@@ -57,5 +57,5 @@
</activity>
</application>
<uses-sdk android:minSdkVersion="5" />
<uses-sdk android:minSdkVersion="7" />
</manifest>

View File

@@ -0,0 +1,115 @@
/*
* 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.
*/
* {
-webkit-touch-callout: none; /* prevent callout to copy image, etc when tap to hold */
-webkit-text-size-adjust: none; /* prevent webkit from resizing text to fit */
-webkit-tap-highlight-color: rgba(0,0,0,0); /* make transparent link selection, adjust last value opacity 0 to 1.0 */
-webkit-user-select: none; /* prevent copy paste, to allow, change 'none' to 'text' */
}
body {
background-color:#E4E4E4;
background-image:linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%);
background-image:-webkit-linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%);
background-image:-ms-linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%);
background-image:-webkit-gradient(
linear,
left top,
left bottom,
color-stop(0, #A7A7A7),
color-stop(0.51, #E4E4E4)
);
background-attachment:fixed;
font-family:'HelveticaNeue-Light', 'HelveticaNeue', Helvetica, Arial, sans-serif;
font-size:12px;
height:100%;
margin:0px;
padding:0px;
text-transform:uppercase;
width:100%;
}
/* Portrait layout (default) */
.app {
background:url(../img/logo.png) no-repeat center top; /* 170px x 200px */
position:absolute; /* position in the center of the screen */
left:50%;
top:50%;
height:50px; /* text area height */
width:225px; /* text area width */
text-align:center;
padding:180px 0px 0px 0px; /* image height is 200px (bottom 20px are overlapped with text) */
margin:-115px 0px 0px -112px; /* offset vertical: half of image height and text area height */
/* offset horizontal: half of text area width */
}
/* Landscape layout (with min-width) */
@media screen and (min-aspect-ratio: 1/1) and (min-width:400px) {
.app {
background-position:left center;
padding:75px 0px 75px 170px; /* padding-top + padding-bottom + text area = image height */
margin:-90px 0px 0px -198px; /* offset vertical: half of image height */
/* offset horizontal: half of image width and text area width */
}
}
h1 {
font-size:24px;
font-weight:normal;
margin:0px;
overflow:visible;
padding:0px;
text-align:center;
}
.event {
border-radius:4px;
-webkit-border-radius:4px;
color:#FFFFFF;
font-size:12px;
margin:0px 30px;
padding:2px 0px;
}
.event.listening {
background-color:#333333;
display:block;
}
.event.received {
background-color:#4B946A;
display:none;
}
@keyframes fade {
from { opacity: 1.0; }
50% { opacity: 0.4; }
to { opacity: 1.0; }
}
@-webkit-keyframes fade {
from { opacity: 1.0; }
50% { opacity: 0.4; }
to { opacity: 1.0; }
}
.blink {
animation:fade 3000ms infinite;
-webkit-animation:fade 3000ms infinite;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -1,60 +1,42 @@
<!DOCTYPE HTML>
<!DOCTYPE html>
<!--
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
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
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.
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.
-->
<html>
<head>
<meta name="viewport" content="width=320; user-scalable=no" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>PhoneGap</title>
<link rel="stylesheet" href="master.css" type="text/css" media="screen" title="no title">
<script type="text/javascript" charset="utf-8" src="cordova-1.9.0.js"></script>
<script type="text/javascript" charset="utf-8" src="main.js"></script>
</head>
<body onload="init();" id="stage" class="theme">
<h1>Welcome to Cordova!</h1>
<h2>this file is located at assets/www/index.html</h2>
<div id="info">
<h4>Platform: <span id="platform"> &nbsp;</span>, Version: <span id="version">&nbsp;</span></h4>
<h4>UUID: <span id="uuid"> &nbsp;</span>, Name: <span id="name">&nbsp;</span></h4>
<h4>Width: <span id="width"> &nbsp;</span>, Height: <span id="height">&nbsp;
</span>, Color Depth: <span id="colorDepth"></span></h4>
</div>
<dl id="accel-data">
<dt>X:</dt><dd id="x">&nbsp;</dd>
<dt>Y:</dt><dd id="y">&nbsp;</dd>
<dt>Z:</dt><dd id="z">&nbsp;</dd>
</dl>
<a href="#" class="btn large" onclick="toggleAccel();">Toggle Accelerometer</a>
<a href="#" class="btn large" onclick="getLocation();">Get Location</a>
<a href="tel:411" class="btn large">Call 411</a>
<a href="#" class="btn large" onclick="beep();">Beep</a>
<a href="#" class="btn large" onclick="vibrate();">Vibrate</a>
<a href="#" class="btn large" onclick="show_pic();">Get a Picture</a>
<a href="#" class="btn large" onclick="get_contacts();return false;">Get Phone's Contacts</a>
<a href="#" class="btn large" onclick="check_network();return false;">Check Network</a>
<dl>
<dt>Compass Heading:</dt><dd id="h">Off</dd>
</dl>
<a href="#" class="btn large" onclick="toggleCompass();return false;">Toggle Compass</a>
<div id="viewport" class="viewport" style="display: none;">
<img style="width:60px;height:60px" id="test_img" src="" />
</div>
</body>
<head>
<meta http-equiv="Content-Type" content="text/html; 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" />
<title>Hello World</title>
</head>
<body>
<div class="app">
<h1>Apache Cordova</h1>
<div id="deviceready" class="blink">
<p class="event listening">Connecting to Device</p>
<p class="event received">Device is Ready</p>
</div>
</div>
<script type="text/javascript" src="cordova-2.1.0.js"></script>
<script type="text/javascript" src="js/index.js"></script>
<script type="text/javascript">
app.initialize();
</script>
</body>
</html>

View File

@@ -0,0 +1,49 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
var app = {
// Application Constructor
initialize: function() {
this.bindEvents();
},
// Bind Event Listeners
//
// Bind any events that are required on startup. Common events are:
// 'load', 'deviceready', 'offline', and 'online'.
bindEvents: function() {
document.addEventListener('deviceready', this.onDeviceReady, false);
},
// deviceready Event Handler
//
// The scope of 'this' is the event. In order to call the 'receivedEvent'
// function, we must explicity call 'app.receivedEvent(...);'
onDeviceReady: function() {
app.receivedEvent('deviceready');
},
// Update DOM on a Received Event
receivedEvent: function(id) {
var parentElement = document.getElementById(id);
var listeningElement = parentElement.querySelector('.listening');
var receivedElement = parentElement.querySelector('.received');
listeningElement.setAttribute('style', 'display:none;');
receivedElement.setAttribute('style', 'display:block;');
console.log('Received Event: ' + id);
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 KiB

View File

@@ -0,0 +1,68 @@
<!DOCTYPE html>
<!--
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.
-->
<html>
<head>
<title>Jasmine Spec Runner</title>
<!-- jasmine source -->
<link rel="shortcut icon" type="image/png" href="spec/lib/jasmine-1.2.0/jasmine_favicon.png">
<link rel="stylesheet" type="text/css" href="spec/lib/jasmine-1.2.0/jasmine.css">
<script type="text/javascript" src="spec/lib/jasmine-1.2.0/jasmine.js"></script>
<script type="text/javascript" src="spec/lib/jasmine-1.2.0/jasmine-html.js"></script>
<!-- include source files here... -->
<script type="text/javascript" src="js/index.js"></script>
<!-- include spec files here... -->
<script type="text/javascript" src="spec/helper.js"></script>
<script type="text/javascript" src="spec/index.js"></script>
<script type="text/javascript">
(function() {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000;
var htmlReporter = new jasmine.HtmlReporter();
jasmineEnv.addReporter(htmlReporter);
jasmineEnv.specFilter = function(spec) {
return htmlReporter.specFilter(spec);
};
var currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
execJasmine();
};
function execJasmine() {
jasmineEnv.execute();
}
})();
</script>
</head>
<body>
<div id="stage" style="display:none;"></div>
</body>
</html>

View File

@@ -0,0 +1,33 @@
/*
* 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.
*/
afterEach(function() {
document.getElementById('stage').innerHTML = '';
});
var helper = {
trigger: function(obj, name) {
var e = document.createEvent('Event');
e.initEvent(name, true, true);
obj.dispatchEvent(e);
},
getComputedStyle: function(querySelector, property) {
var element = document.querySelector(querySelector);
return window.getComputedStyle(element).getPropertyValue(property);
}
};

View File

@@ -0,0 +1,67 @@
/*
* 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.
*/
describe('app', function() {
describe('initialize', function() {
it('should bind deviceready', function() {
runs(function() {
spyOn(app, 'onDeviceReady');
app.initialize();
helper.trigger(window.document, 'deviceready');
});
waitsFor(function() {
return (app.onDeviceReady.calls.length > 0);
}, 'onDeviceReady should be called once', 500);
runs(function() {
expect(app.onDeviceReady).toHaveBeenCalled();
});
});
});
describe('onDeviceReady', function() {
it('should report that it fired', function() {
spyOn(app, 'receivedEvent');
app.onDeviceReady();
expect(app.receivedEvent).toHaveBeenCalledWith('deviceready');
});
});
describe('receivedEvent', function() {
beforeEach(function() {
var el = document.getElementById('stage');
el.innerHTML = ['<div id="deviceready">',
' <p class="event listening">Listening</p>',
' <p class="event received">Received</p>',
'</div>'].join('\n');
});
it('should hide the listening element', function() {
app.receivedEvent('deviceready');
var displayStyle = helper.getComputedStyle('#deviceready .listening', 'display');
expect(displayStyle).toEqual('none');
});
it('should show the received element', function() {
app.receivedEvent('deviceready');
var displayStyle = helper.getComputedStyle('#deviceready .received', 'display');
expect(displayStyle).toEqual('block');
});
});
});

View File

@@ -0,0 +1,20 @@
Copyright (c) 2008-2011 Pivotal Labs
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,616 @@
jasmine.HtmlReporterHelpers = {};
jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) {
var el = document.createElement(type);
for (var i = 2; i < arguments.length; i++) {
var child = arguments[i];
if (typeof child === 'string') {
el.appendChild(document.createTextNode(child));
} else {
if (child) {
el.appendChild(child);
}
}
}
for (var attr in attrs) {
if (attr == "className") {
el[attr] = attrs[attr];
} else {
el.setAttribute(attr, attrs[attr]);
}
}
return el;
};
jasmine.HtmlReporterHelpers.getSpecStatus = function(child) {
var results = child.results();
var status = results.passed() ? 'passed' : 'failed';
if (results.skipped) {
status = 'skipped';
}
return status;
};
jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) {
var parentDiv = this.dom.summary;
var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite';
var parent = child[parentSuite];
if (parent) {
if (typeof this.views.suites[parent.id] == 'undefined') {
this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views);
}
parentDiv = this.views.suites[parent.id].element;
}
parentDiv.appendChild(childElement);
};
jasmine.HtmlReporterHelpers.addHelpers = function(ctor) {
for(var fn in jasmine.HtmlReporterHelpers) {
ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn];
}
};
jasmine.HtmlReporter = function(_doc) {
var self = this;
var doc = _doc || window.document;
var reporterView;
var dom = {};
// Jasmine Reporter Public Interface
self.logRunningSpecs = false;
self.reportRunnerStarting = function(runner) {
var specs = runner.specs() || [];
if (specs.length == 0) {
return;
}
createReporterDom(runner.env.versionString());
doc.body.appendChild(dom.reporter);
reporterView = new jasmine.HtmlReporter.ReporterView(dom);
reporterView.addSpecs(specs, self.specFilter);
};
self.reportRunnerResults = function(runner) {
reporterView && reporterView.complete();
};
self.reportSuiteResults = function(suite) {
reporterView.suiteComplete(suite);
};
self.reportSpecStarting = function(spec) {
if (self.logRunningSpecs) {
self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
}
};
self.reportSpecResults = function(spec) {
reporterView.specComplete(spec);
};
self.log = function() {
var console = jasmine.getGlobal().console;
if (console && console.log) {
if (console.log.apply) {
console.log.apply(console, arguments);
} else {
console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
}
}
};
self.specFilter = function(spec) {
if (!focusedSpecName()) {
return true;
}
return spec.getFullName().indexOf(focusedSpecName()) === 0;
};
return self;
function focusedSpecName() {
var specName;
(function memoizeFocusedSpec() {
if (specName) {
return;
}
var paramMap = [];
var params = doc.location.search.substring(1).split('&');
for (var i = 0; i < params.length; i++) {
var p = params[i].split('=');
paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
}
specName = paramMap.spec;
})();
return specName;
}
function createReporterDom(version) {
dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' },
dom.banner = self.createDom('div', { className: 'banner' },
self.createDom('span', { className: 'title' }, "Jasmine "),
self.createDom('span', { className: 'version' }, version)),
dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}),
dom.alert = self.createDom('div', {className: 'alert'}),
dom.results = self.createDom('div', {className: 'results'},
dom.summary = self.createDom('div', { className: 'summary' }),
dom.details = self.createDom('div', { id: 'details' }))
);
}
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);jasmine.HtmlReporter.ReporterView = function(dom) {
this.startedAt = new Date();
this.runningSpecCount = 0;
this.completeSpecCount = 0;
this.passedCount = 0;
this.failedCount = 0;
this.skippedCount = 0;
this.createResultsMenu = function() {
this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'},
this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'),
' | ',
this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing'));
this.summaryMenuItem.onclick = function() {
dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, '');
};
this.detailsMenuItem.onclick = function() {
showDetails();
};
};
this.addSpecs = function(specs, specFilter) {
this.totalSpecCount = specs.length;
this.views = {
specs: {},
suites: {}
};
for (var i = 0; i < specs.length; i++) {
var spec = specs[i];
this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views);
if (specFilter(spec)) {
this.runningSpecCount++;
}
}
};
this.specComplete = function(spec) {
this.completeSpecCount++;
if (isUndefined(this.views.specs[spec.id])) {
this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom);
}
var specView = this.views.specs[spec.id];
switch (specView.status()) {
case 'passed':
this.passedCount++;
break;
case 'failed':
this.failedCount++;
break;
case 'skipped':
this.skippedCount++;
break;
}
specView.refresh();
this.refresh();
};
this.suiteComplete = function(suite) {
var suiteView = this.views.suites[suite.id];
if (isUndefined(suiteView)) {
return;
}
suiteView.refresh();
};
this.refresh = function() {
if (isUndefined(this.resultsMenu)) {
this.createResultsMenu();
}
// currently running UI
if (isUndefined(this.runningAlert)) {
this.runningAlert = this.createDom('a', {href: "?", className: "runningAlert bar"});
dom.alert.appendChild(this.runningAlert);
}
this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount);
// skipped specs UI
if (isUndefined(this.skippedAlert)) {
this.skippedAlert = this.createDom('a', {href: "?", className: "skippedAlert bar"});
}
this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
if (this.skippedCount === 1 && isDefined(dom.alert)) {
dom.alert.appendChild(this.skippedAlert);
}
// passing specs UI
if (isUndefined(this.passedAlert)) {
this.passedAlert = this.createDom('span', {href: "?", className: "passingAlert bar"});
}
this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount);
// failing specs UI
if (isUndefined(this.failedAlert)) {
this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"});
}
this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount);
if (this.failedCount === 1 && isDefined(dom.alert)) {
dom.alert.appendChild(this.failedAlert);
dom.alert.appendChild(this.resultsMenu);
}
// summary info
this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount);
this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing";
};
this.complete = function() {
dom.alert.removeChild(this.runningAlert);
this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
if (this.failedCount === 0) {
dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount)));
} else {
showDetails();
}
dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"));
};
return this;
function showDetails() {
if (dom.reporter.className.search(/showDetails/) === -1) {
dom.reporter.className += " showDetails";
}
}
function isUndefined(obj) {
return typeof obj === 'undefined';
}
function isDefined(obj) {
return !isUndefined(obj);
}
function specPluralizedFor(count) {
var str = count + " spec";
if (count > 1) {
str += "s"
}
return str;
}
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView);
jasmine.HtmlReporter.SpecView = function(spec, dom, views) {
this.spec = spec;
this.dom = dom;
this.views = views;
this.symbol = this.createDom('li', { className: 'pending' });
this.dom.symbolSummary.appendChild(this.symbol);
this.summary = this.createDom('div', { className: 'specSummary' },
this.createDom('a', {
className: 'description',
href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
title: this.spec.getFullName()
}, this.spec.description)
);
this.detail = this.createDom('div', { className: 'specDetail' },
this.createDom('a', {
className: 'description',
href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
title: this.spec.getFullName()
}, this.spec.getFullName())
);
};
jasmine.HtmlReporter.SpecView.prototype.status = function() {
return this.getSpecStatus(this.spec);
};
jasmine.HtmlReporter.SpecView.prototype.refresh = function() {
this.symbol.className = this.status();
switch (this.status()) {
case 'skipped':
break;
case 'passed':
this.appendSummaryToSuiteDiv();
break;
case 'failed':
this.appendSummaryToSuiteDiv();
this.appendFailureDetail();
break;
}
};
jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() {
this.summary.className += ' ' + this.status();
this.appendToSummary(this.spec, this.summary);
};
jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() {
this.detail.className += ' ' + this.status();
var resultItems = this.spec.results().getItems();
var messagesDiv = this.createDom('div', { className: 'messages' });
for (var i = 0; i < resultItems.length; i++) {
var result = resultItems[i];
if (result.type == 'log') {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
} else if (result.type == 'expect' && result.passed && !result.passed()) {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
if (result.trace.stack) {
messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
}
}
}
if (messagesDiv.childNodes.length > 0) {
this.detail.appendChild(messagesDiv);
this.dom.details.appendChild(this.detail);
}
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) {
this.suite = suite;
this.dom = dom;
this.views = views;
this.element = this.createDom('div', { className: 'suite' },
this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(this.suite.getFullName()) }, this.suite.description)
);
this.appendToSummary(this.suite, this.element);
};
jasmine.HtmlReporter.SuiteView.prototype.status = function() {
return this.getSpecStatus(this.suite);
};
jasmine.HtmlReporter.SuiteView.prototype.refresh = function() {
this.element.className += " " + this.status();
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView);
/* @deprecated Use jasmine.HtmlReporter instead
*/
jasmine.TrivialReporter = function(doc) {
this.document = doc || document;
this.suiteDivs = {};
this.logRunningSpecs = false;
};
jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
var el = document.createElement(type);
for (var i = 2; i < arguments.length; i++) {
var child = arguments[i];
if (typeof child === 'string') {
el.appendChild(document.createTextNode(child));
} else {
if (child) { el.appendChild(child); }
}
}
for (var attr in attrs) {
if (attr == "className") {
el[attr] = attrs[attr];
} else {
el.setAttribute(attr, attrs[attr]);
}
}
return el;
};
jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
var showPassed, showSkipped;
this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' },
this.createDom('div', { className: 'banner' },
this.createDom('div', { className: 'logo' },
this.createDom('span', { className: 'title' }, "Jasmine"),
this.createDom('span', { className: 'version' }, runner.env.versionString())),
this.createDom('div', { className: 'options' },
"Show ",
showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
)
),
this.runnerDiv = this.createDom('div', { className: 'runner running' },
this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
);
this.document.body.appendChild(this.outerDiv);
var suites = runner.suites();
for (var i = 0; i < suites.length; i++) {
var suite = suites[i];
var suiteDiv = this.createDom('div', { className: 'suite' },
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
this.suiteDivs[suite.id] = suiteDiv;
var parentDiv = this.outerDiv;
if (suite.parentSuite) {
parentDiv = this.suiteDivs[suite.parentSuite.id];
}
parentDiv.appendChild(suiteDiv);
}
this.startedAt = new Date();
var self = this;
showPassed.onclick = function(evt) {
if (showPassed.checked) {
self.outerDiv.className += ' show-passed';
} else {
self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
}
};
showSkipped.onclick = function(evt) {
if (showSkipped.checked) {
self.outerDiv.className += ' show-skipped';
} else {
self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
}
};
};
jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
var results = runner.results();
var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
this.runnerDiv.setAttribute("class", className);
//do it twice for IE
this.runnerDiv.setAttribute("className", className);
var specs = runner.specs();
var specCount = 0;
for (var i = 0; i < specs.length; i++) {
if (this.specFilter(specs[i])) {
specCount++;
}
}
var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
};
jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
var results = suite.results();
var status = results.passed() ? 'passed' : 'failed';
if (results.totalCount === 0) { // todo: change this to check results.skipped
status = 'skipped';
}
this.suiteDivs[suite.id].className += " " + status;
};
jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
if (this.logRunningSpecs) {
this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
}
};
jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
var results = spec.results();
var status = results.passed() ? 'passed' : 'failed';
if (results.skipped) {
status = 'skipped';
}
var specDiv = this.createDom('div', { className: 'spec ' + status },
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
this.createDom('a', {
className: 'description',
href: '?spec=' + encodeURIComponent(spec.getFullName()),
title: spec.getFullName()
}, spec.description));
var resultItems = results.getItems();
var messagesDiv = this.createDom('div', { className: 'messages' });
for (var i = 0; i < resultItems.length; i++) {
var result = resultItems[i];
if (result.type == 'log') {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
} else if (result.type == 'expect' && result.passed && !result.passed()) {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
if (result.trace.stack) {
messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
}
}
}
if (messagesDiv.childNodes.length > 0) {
specDiv.appendChild(messagesDiv);
}
this.suiteDivs[spec.suite.id].appendChild(specDiv);
};
jasmine.TrivialReporter.prototype.log = function() {
var console = jasmine.getGlobal().console;
if (console && console.log) {
if (console.log.apply) {
console.log.apply(console, arguments);
} else {
console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
}
}
};
jasmine.TrivialReporter.prototype.getLocation = function() {
return this.document.location;
};
jasmine.TrivialReporter.prototype.specFilter = function(spec) {
var paramMap = {};
var params = this.getLocation().search.substring(1).split('&');
for (var i = 0; i < params.length; i++) {
var p = params[i].split('=');
paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
}
if (!paramMap.spec) {
return true;
}
return spec.getFullName().indexOf(paramMap.spec) === 0;
};

View File

@@ -0,0 +1,81 @@
body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; }
#HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; }
#HTMLReporter a { text-decoration: none; }
#HTMLReporter a:hover { text-decoration: underline; }
#HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; }
#HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; }
#HTMLReporter #jasmine_content { position: fixed; right: 100%; }
#HTMLReporter .version { color: #aaaaaa; }
#HTMLReporter .banner { margin-top: 14px; }
#HTMLReporter .duration { color: #aaaaaa; float: right; }
#HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; }
#HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; }
#HTMLReporter .symbolSummary li.passed { font-size: 14px; }
#HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; }
#HTMLReporter .symbolSummary li.failed { line-height: 9px; }
#HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; }
#HTMLReporter .symbolSummary li.skipped { font-size: 14px; }
#HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; }
#HTMLReporter .symbolSummary li.pending { line-height: 11px; }
#HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; }
#HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
#HTMLReporter .runningAlert { background-color: #666666; }
#HTMLReporter .skippedAlert { background-color: #aaaaaa; }
#HTMLReporter .skippedAlert:first-child { background-color: #333333; }
#HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; }
#HTMLReporter .passingAlert { background-color: #a6b779; }
#HTMLReporter .passingAlert:first-child { background-color: #5e7d00; }
#HTMLReporter .failingAlert { background-color: #cf867e; }
#HTMLReporter .failingAlert:first-child { background-color: #b03911; }
#HTMLReporter .results { margin-top: 14px; }
#HTMLReporter #details { display: none; }
#HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; }
#HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
#HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
#HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
#HTMLReporter.showDetails .summary { display: none; }
#HTMLReporter.showDetails #details { display: block; }
#HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
#HTMLReporter .summary { margin-top: 14px; }
#HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; }
#HTMLReporter .summary .specSummary.passed a { color: #5e7d00; }
#HTMLReporter .summary .specSummary.failed a { color: #b03911; }
#HTMLReporter .description + .suite { margin-top: 0; }
#HTMLReporter .suite { margin-top: 14px; }
#HTMLReporter .suite a { color: #333333; }
#HTMLReporter #details .specDetail { margin-bottom: 28px; }
#HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; }
#HTMLReporter .resultMessage { padding-top: 14px; color: #333333; }
#HTMLReporter .resultMessage span.result { display: block; }
#HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; }
#TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ }
#TrivialReporter a:visited, #TrivialReporter a { color: #303; }
#TrivialReporter a:hover, #TrivialReporter a:active { color: blue; }
#TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; }
#TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; }
#TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; }
#TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; }
#TrivialReporter .runner.running { background-color: yellow; }
#TrivialReporter .options { text-align: right; font-size: .8em; }
#TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; }
#TrivialReporter .suite .suite { margin: 5px; }
#TrivialReporter .suite.passed { background-color: #dfd; }
#TrivialReporter .suite.failed { background-color: #fdd; }
#TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; }
#TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; }
#TrivialReporter .spec.failed { background-color: #fbb; border-color: red; }
#TrivialReporter .spec.passed { background-color: #bfb; border-color: green; }
#TrivialReporter .spec.skipped { background-color: #bbb; }
#TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; }
#TrivialReporter .passed { background-color: #cfc; display: none; }
#TrivialReporter .failed { background-color: #fbb; }
#TrivialReporter .skipped { color: #777; background-color: #eee; display: none; }
#TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; }
#TrivialReporter .resultMessage .mismatch { color: black; }
#TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; }
#TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; }
#TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; }
#TrivialReporter #jasmine_content { position: fixed; right: 100%; }
#TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; }

File diff suppressed because it is too large Load Diff

View File

@@ -4,5 +4,6 @@
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry kind="lib" path="libs/commons-codec-1.6.jar"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry kind="output" path="bin/classes"/>
</classpath>

View File

@@ -51,7 +51,7 @@
<application android:icon="@drawable/icon" android:label="@string/app_name"
android:debuggable="true">
<activity android:name=".StandAlone" android:windowSoftInputMode="adjustPan"
android:label="@string/app_name" android:configChanges="orientation|keyboardHidden">
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@@ -64,5 +64,5 @@
</activity>
</application>
<uses-sdk android:minSdkVersion="2" />
<uses-sdk android:minSdkVersion="7" />
</manifest>

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -10,6 +10,7 @@
# Indicates whether an apk should be generated for each density.
split.density=false
# Project target.
target=Google Inc.:Google APIs:15
target=Google Inc.:Google APIs:16
apk-configurations=
renderscript.opt.level=O0
android.library=true

View File

@@ -17,6 +17,21 @@
specific language governing permissions and limitations
under the License.
-->
<cordova>
<!--
access elements control the Android whitelist.
Domains are assumed blocked unless set otherwise
-->
<access origin="http://127.0.0.1*"/> <!-- allow local pages -->
<!-- <access origin="https://example.com" /> allow any secure requests to example.com -->
<!-- <access origin="https://example.com" subdomains="true" /> such as above, but including subdomains, such as www -->
<access origin=".*"/>
<log level="DEBUG"/>
<preference name="useBrowserHistory" value="false" />
<preference name="exit-on-suspend" value="false" />
<plugins>
<plugin name="App" value="org.apache.cordova.App"/>
<plugin name="Geolocation" value="org.apache.cordova.GeoBroker"/>
@@ -35,4 +50,7 @@
<plugin name="Capture" value="org.apache.cordova.Capture"/>
<plugin name="Battery" value="org.apache.cordova.BatteryListener"/>
<plugin name="SplashScreen" value="org.apache.cordova.SplashScreen"/>
<plugin name="Echo" value="org.apache.cordova.Echo" />
</plugins>
</cordova>

View File

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

View File

@@ -1,27 +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 com.phonegap.api;
/**
* Plugin interface must be implemented by any plugin classes.
*
* The execute method is called by the PluginManager.
*/
public interface IPlugin extends org.apache.cordova.api.IPlugin {
}

View File

@@ -1,28 +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 com.phonegap.api;
/**
* Log to Android logging system.
*
* Log message can be a string or a printf formatted string with arguments.
* See http://developer.android.com/reference/java/util/Formatter.html
*/
public class LOG extends org.apache.cordova.api.LOG {
}

View File

@@ -1,28 +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 com.phonegap.api;
import android.app.Activity;
/**
* The Cordova activity abstract class that is extended by DroidGap.
* It is used to isolate plugin development, and remove dependency on entire Cordova library.
*/
public abstract class PhonegapActivity extends Activity implements org.apache.cordova.api.CordovaInterface {
}

View File

@@ -1,27 +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 com.phonegap.api;
/**
* Plugin interface must be implemented by any plugin classes.
*
* The execute method is called by the PluginManager.
*/
public abstract class Plugin extends org.apache.cordova.api.Plugin {
}

View File

@@ -1,37 +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 com.phonegap.api;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.api.CordovaInterface;
import android.webkit.WebView;
/**
* PluginManager is exposed to JavaScript in the Cordova WebView.
*
* Calling native plugin code can be done by calling PluginManager.exec(...)
* from JavaScript.
*/
public class PluginManager extends org.apache.cordova.api.PluginManager {
public PluginManager(WebView app, CordovaInterface ctx) throws Exception {
super((CordovaWebView) app, ctx);
}
}

View File

@@ -1,53 +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 com.phonegap.api;
import org.json.JSONArray;
import org.json.JSONObject;
public class PluginResult extends org.apache.cordova.api.PluginResult {
public PluginResult(Status status) {
super(status);
}
public PluginResult(Status status, String message) {
super(status, message);
}
public PluginResult(Status status, JSONArray message) {
super(status, message);
}
public PluginResult(Status status, JSONObject message) {
super(status, message);
}
public PluginResult(Status status, int i) {
super(status, i);
}
public PluginResult(Status status, float f) {
super(status, f);
}
public PluginResult(Status status, boolean b) {
super(status, b);
}
}

View File

@@ -215,7 +215,7 @@ public class AccelListener extends Plugin implements SensorEventListener {
if (this.accuracy >= SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM) {
// Save time that event was received
this.timestamp = System.nanoTime();
this.timestamp = System.currentTimeMillis();
this.x = event.values[0];
this.y = event.values[1];
this.z = event.values[2];

View File

@@ -48,7 +48,10 @@ public class App extends Plugin {
if (action.equals("clearCache")) {
this.clearCache();
}
else if (action.equals("show")) { // TODO @bc - Not in master branch. When should this be called?
else if (action.equals("show")) {
// This gets called from JavaScript onCordovaReady to show the webview.
// I recommend we change the name of the Message as spinner/stop is not
// indicative of what this actually does (shows the webview).
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
webView.postMessage("spinner", "stop");
@@ -173,7 +176,11 @@ public class App extends Plugin {
* This is the same as pressing the backbutton on Android device.
*/
public void backHistory() {
this.webView.backHistory();
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
webView.backHistory();
}
});
}
/**

27
framework/src/org/apache/cordova/AudioHandler.java Executable file → Normal file
View File

@@ -20,6 +20,7 @@ package org.apache.cordova;
import android.content.Context;
import android.media.AudioManager;
import java.util.ArrayList;
import org.apache.cordova.api.Plugin;
@@ -66,13 +67,13 @@ public class AudioHandler extends Plugin {
try {
if (action.equals("startRecordingAudio")) {
this.startRecordingAudio(args.getString(0), args.getString(1));
this.startRecordingAudio(args.getString(0), FileUtils.stripFileProtocol(args.getString(1)));
}
else if (action.equals("stopRecordingAudio")) {
this.stopRecordingAudio(args.getString(0));
}
else if (action.equals("startPlayingAudio")) {
this.startPlayingAudio(args.getString(0), args.getString(1));
this.startPlayingAudio(args.getString(0), FileUtils.stripFileProtocol(args.getString(1)));
}
else if (action.equals("seekToAudio")) {
this.seekToAudio(args.getString(0), args.getInt(1));
@@ -96,6 +97,12 @@ public class AudioHandler extends Plugin {
float f = this.getDurationAudio(args.getString(0), args.getString(1));
return new PluginResult(status, f);
}
else if (action.equals("create")) {
String id = args.getString(0);
String src = FileUtils.stripFileProtocol(args.getString(1));
AudioPlayer audio = new AudioPlayer(this, id, src);
this.players.put(id, audio);
}
else if (action.equals("release")) {
boolean b = this.release(args.getString(0));
return new PluginResult(status, b);
@@ -149,7 +156,7 @@ public class AudioHandler extends Plugin {
// Get all audio players and pause them
for (AudioPlayer audio : this.players.values()) {
if (audio.getState() == AudioPlayer.MEDIA_RUNNING) {
if (audio.getState() == AudioPlayer.STATE.MEDIA_RUNNING.ordinal()) {
this.pausedForPhone.add(audio);
audio.pausePlaying();
}
@@ -192,12 +199,11 @@ public class AudioHandler extends Plugin {
* @param file The name of the file
*/
public void startRecordingAudio(String id, String file) {
// If already recording, then just return;
if (this.players.containsKey(id)) {
return;
AudioPlayer audio = this.players.get(id);
if ( audio == null) {
audio = new AudioPlayer(this, id, file);
this.players.put(id, audio);
}
AudioPlayer audio = new AudioPlayer(this, id);
this.players.put(id, audio);
audio.startRecording(file);
}
@@ -209,7 +215,6 @@ public class AudioHandler extends Plugin {
AudioPlayer audio = this.players.get(id);
if (audio != null) {
audio.stopRecording();
this.players.remove(id);
}
}
@@ -221,7 +226,7 @@ public class AudioHandler extends Plugin {
public void startPlayingAudio(String id, String file) {
AudioPlayer audio = this.players.get(id);
if (audio == null) {
audio = new AudioPlayer(this, id);
audio = new AudioPlayer(this, id, file);
this.players.put(id, audio);
}
audio.startPlaying(file);
@@ -292,7 +297,7 @@ public class AudioHandler extends Plugin {
// If not already open, then open the file
else {
audio = new AudioPlayer(this, id);
audio = new AudioPlayer(this, id, file);
this.players.put(id, audio);
return (audio.getDuration(file));
}

400
framework/src/org/apache/cordova/AudioPlayer.java Executable file → Normal file
View File

@@ -31,6 +31,8 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import org.apache.cordova.AudioPlayer.MODE;
/**
* This class implements the audio playback and recording capabilities used by Cordova.
* It is called by the AudioHandler Cordova class.
@@ -42,14 +44,19 @@ import java.io.IOException;
*/
public class AudioPlayer implements OnCompletionListener, OnPreparedListener, OnErrorListener {
private static final String LOG_TAG = "AudioPlayer";
// AudioPlayer modes
public enum MODE { NONE, PLAY, RECORD };
// AudioPlayer states
public static int MEDIA_NONE = 0;
public static int MEDIA_STARTING = 1;
public static int MEDIA_RUNNING = 2;
public static int MEDIA_PAUSED = 3;
public static int MEDIA_STOPPED = 4;
public enum STATE { MEDIA_NONE,
MEDIA_STARTING,
MEDIA_RUNNING,
MEDIA_PAUSED,
MEDIA_STOPPED,
MEDIA_LOADING
};
private static final String LOG_TAG = "AudioPlayer";
// AudioPlayer message ids
private static int MEDIA_STATE = 1;
@@ -63,33 +70,40 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
private static int MEDIA_ERR_NETWORK = 2;
private static int MEDIA_ERR_DECODE = 3;
private static int MEDIA_ERR_NONE_SUPPORTED = 4;
private AudioHandler handler; // The AudioHandler object
private String id; // The id of this player (used to identify Media object in JavaScript)
private int state = MEDIA_NONE; // State of recording or playback
private String audioFile = null; // File name to play or record to
private float duration = -1; // Duration of audio
private MediaRecorder recorder = null; // Audio recording object
private String tempFile = null; // Temporary recording file name
private MediaPlayer mPlayer = null; // Audio player object
private boolean prepareOnly = false;
private AudioHandler handler; // The AudioHandler object
private String id; // The id of this player (used to identify Media object in JavaScript)
private MODE mode = MODE.NONE; // Playback or Recording mode
private STATE state = STATE.MEDIA_NONE; // State of recording or playback
private String audioFile = null; // File name to play or record to
private float duration = -1; // Duration of audio
private MediaRecorder recorder = null; // Audio recording object
private String tempFile = null; // Temporary recording file name
private MediaPlayer player = null; // Audio player object
private boolean prepareOnly = true; // playback after file prepare flag
private int seekOnPrepared = 0; // seek to this location once media is prepared
/**
* Constructor.
*
*
* @param handler The audio handler object
* @param id The id of this audio player
*/
public AudioPlayer(AudioHandler handler, String id) {
public AudioPlayer(AudioHandler handler, String id, String file) {
this.handler = handler;
this.id = id;
this.audioFile = file;
this.recorder = new MediaRecorder();
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
this.tempFile = Environment.getExternalStorageDirectory().getAbsolutePath() + "/tmprecording.mp3";
this.tempFile = Environment.getExternalStorageDirectory().getAbsolutePath() + "/tmprecording.3gp";
} else {
this.tempFile = "/data/data/" + handler.cordova.getActivity().getPackageName() + "/cache/tmprecording.mp3";
this.tempFile = "/data/data/" + handler.cordova.getActivity().getPackageName() + "/cache/tmprecording.3gp";
}
}
/**
@@ -97,13 +111,13 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
*/
public void destroy() {
// Stop any play or record
if (this.mPlayer != null) {
if ((this.state == MEDIA_RUNNING) || (this.state == MEDIA_PAUSED)) {
this.mPlayer.stop();
this.setState(MEDIA_STOPPED);
if (this.player != null) {
if ((this.state == STATE.MEDIA_RUNNING) || (this.state == STATE.MEDIA_PAUSED)) {
this.player.stop();
this.setState(STATE.MEDIA_STOPPED);
}
this.mPlayer.release();
this.mPlayer = null;
this.player.release();
this.player = null;
}
if (this.recorder != null) {
this.stopRecording();
@@ -114,18 +128,17 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
/**
* Start recording the specified file.
*
*
* @param file The name of the file
*/
public void startRecording(String file) {
if (this.mPlayer != null) {
switch (this.mode) {
case PLAY:
Log.d(LOG_TAG, "AudioPlayer Error: Can't record in play mode.");
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});");
}
// Make sure we're not already recording
else if (this.recorder == null) {
break;
case NONE:
this.audioFile = file;
this.recorder = new MediaRecorder();
this.recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
this.recorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); // THREE_GPP);
this.recorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT); //AMR_NB);
@@ -133,7 +146,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
try {
this.recorder.prepare();
this.recorder.start();
this.setState(MEDIA_RUNNING);
this.setState(STATE.MEDIA_RUNNING);
return;
} catch (IllegalStateException e) {
e.printStackTrace();
@@ -141,8 +154,8 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
e.printStackTrace();
}
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});");
}
else {
break;
case RECORD:
Log.d(LOG_TAG, "AudioPlayer Error: Already recording.");
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});");
}
@@ -171,10 +184,11 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
public void stopRecording() {
if (this.recorder != null) {
try{
if (this.state == MEDIA_RUNNING) {
if (this.state == STATE.MEDIA_RUNNING) {
this.recorder.stop();
this.setState(MEDIA_STOPPED);
this.setState(STATE.MEDIA_STOPPED);
}
this.recorder.reset();
this.moveFile(this.audioFile);
}
catch (Exception e) {
@@ -183,81 +197,22 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
}
}
//==========================================================================
// Playback
//==========================================================================
/**
* Start or resume playing audio file.
*
*
* @param file The name of the audio file.
*/
public void startPlaying(String file) {
if (this.recorder != null) {
Log.d(LOG_TAG, "AudioPlayer Error: Can't play in record mode.");
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});");
}
// If this is a new request to play audio, or stopped
else if ((this.mPlayer == null) || (this.state == MEDIA_STOPPED)) {
try {
// If stopped, then reset player
if (this.mPlayer != null) {
this.mPlayer.reset();
}
// Otherwise, create a new one
else {
this.mPlayer = new MediaPlayer();
}
this.audioFile = file;
// If streaming file
if (this.isStreaming(file)) {
this.mPlayer.setDataSource(file);
this.mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
this.setState(MEDIA_STARTING);
this.mPlayer.setOnPreparedListener(this);
this.mPlayer.prepareAsync();
}
// If local file
else {
if (file.startsWith("/android_asset/")) {
String f = file.substring(15);
android.content.res.AssetFileDescriptor fd = this.handler.cordova.getActivity().getAssets().openFd(f);
this.mPlayer.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
}
else {
File fp = new File(file);
if (fp.exists()) {
FileInputStream fileInputStream = new FileInputStream(file);
this.mPlayer.setDataSource(fileInputStream.getFD());
}
else {
this.mPlayer.setDataSource("/sdcard/" + file);
}
}
this.setState(MEDIA_STARTING);
this.mPlayer.setOnPreparedListener(this);
this.mPlayer.prepare();
// Get duration
this.duration = getDurationInSeconds();
}
} catch (Exception e) {
e.printStackTrace();
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});");
}
}
// If we have already have created an audio player
else {
// If player has been paused, then resume playback
if ((this.state == MEDIA_PAUSED) || (this.state == MEDIA_STARTING)) {
this.mPlayer.start();
this.setState(MEDIA_RUNNING);
}
else {
Log.d(LOG_TAG, "AudioPlayer Error: startPlaying() called during invalid state: " + this.state);
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});");
}
if (this.readyPlayer(file) && this.player != null) {
this.player.start();
this.setState(STATE.MEDIA_RUNNING);
this.seekOnPrepared = 0; //insures this is always reset
} else {
this.prepareOnly = false;
}
}
@@ -265,11 +220,14 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
* Seek or jump to a new time in the track.
*/
public void seekToPlaying(int milliseconds) {
if (this.mPlayer != null) {
this.mPlayer.seekTo(milliseconds);
if (this.readyPlayer(this.audioFile)) {
this.player.seekTo(milliseconds);
Log.d(LOG_TAG, "Send a onStatus update for the new seek");
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_POSITION + ", " + milliseconds / 1000.0f + ");");
}
else {
this.seekOnPrepared = milliseconds;
}
}
/**
@@ -278,12 +236,12 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
public void pausePlaying() {
// If playing, then pause
if (this.state == MEDIA_RUNNING) {
this.mPlayer.pause();
this.setState(MEDIA_PAUSED);
if (this.state == STATE.MEDIA_RUNNING && this.player != null) {
this.player.pause();
this.setState(STATE.MEDIA_PAUSED);
}
else {
Log.d(LOG_TAG, "AudioPlayer Error: pausePlaying() called during invalid state: " + this.state);
Log.d(LOG_TAG, "AudioPlayer Error: pausePlaying() called during invalid state: " + this.state.ordinal());
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_NONE_ACTIVE + "});");
}
}
@@ -292,33 +250,36 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
* Stop playing the audio file.
*/
public void stopPlaying() {
if ((this.state == MEDIA_RUNNING) || (this.state == MEDIA_PAUSED)) {
this.mPlayer.stop();
this.setState(MEDIA_STOPPED);
if ((this.state == STATE.MEDIA_RUNNING) || (this.state == STATE.MEDIA_PAUSED)) {
this.player.pause();
this.player.seekTo(0);
Log.d(LOG_TAG, "stopPlaying is calling stopped");
this.setState(STATE.MEDIA_STOPPED);
}
else {
Log.d(LOG_TAG, "AudioPlayer Error: stopPlaying() called during invalid state: " + this.state);
Log.d(LOG_TAG, "AudioPlayer Error: stopPlaying() called during invalid state: " + this.state.ordinal());
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_NONE_ACTIVE + "});");
}
}
/**
* Callback to be invoked when playback of a media source has completed.
*
* @param mPlayer The MediaPlayer that reached the end of the file
*
* @param player The MediaPlayer that reached the end of the file
*/
public void onCompletion(MediaPlayer mPlayer) {
this.setState(MEDIA_STOPPED);
public void onCompletion(MediaPlayer player) {
Log.d(LOG_TAG, "on completion is calling stopped");
this.setState(STATE.MEDIA_STOPPED);
}
/**
* Get current position of playback.
*
*
* @return position in msec or -1 if not playing
*/
public long getCurrentPosition() {
if ((this.state == MEDIA_RUNNING) || (this.state == MEDIA_PAUSED)) {
int curPos = this.mPlayer.getCurrentPosition();
if ((this.state == STATE.MEDIA_RUNNING) || (this.state == STATE.MEDIA_PAUSED)) {
int curPos = this.player.getCurrentPosition();
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_POSITION + ", " + curPos / 1000.0f + ");");
return curPos;
}
@@ -330,7 +291,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
/**
* Determine if playback file is streaming or local.
* It is streaming if file name starts with "http://"
*
*
* @param file The file name
* @return T=streaming, F=local
*/
@@ -345,7 +306,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
/**
* Get the duration of the audio file.
*
*
* @param file The name of the audio file.
* @return The duration in msec.
* -1=can't be determined
@@ -359,7 +320,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
}
// If audio file already loaded and started, then return duration
if (this.mPlayer != null) {
if (this.player != null) {
return this.duration;
}
@@ -375,56 +336,55 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
}
/**
* Callback to be invoked when the media source is ready for playback.
*
* @param mPlayer The MediaPlayer that is ready for playback
* Callback to be invoked when the media source is ready for playback.
*
* @param player The MediaPlayer that is ready for playback
*/
public void onPrepared(MediaPlayer mPlayer) {
public void onPrepared(MediaPlayer player) {
// Listen for playback completion
this.mPlayer.setOnCompletionListener(this);
this.player.setOnCompletionListener(this);
// seek to any location received while not prepared
this.seekToPlaying(this.seekOnPrepared);
// If start playing after prepared
if (!this.prepareOnly) {
// Start playing
this.mPlayer.start();
// Set player init flag
this.setState(MEDIA_RUNNING);
this.player.start();
this.setState(STATE.MEDIA_RUNNING);
this.seekOnPrepared = 0; //reset only when played
} else {
this.setState(STATE.MEDIA_STARTING);
}
// Save off duration
this.duration = getDurationInSeconds();
this.prepareOnly = false;
// reset prepare only flag
this.prepareOnly = true;
// Send status notification to JavaScript
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_DURATION + "," + this.duration + ");");
}
/**
* By default Android returns the length of audio in mills but we want seconds
*
*
* @return length of clip in seconds
*/
private float getDurationInSeconds() {
return (this.mPlayer.getDuration() / 1000.0f);
return (this.player.getDuration() / 1000.0f);
}
/**
* Callback to be invoked when there has been an error during an asynchronous operation
* (other errors will throw exceptions at method call time).
*
* @param mPlayer the MediaPlayer the error pertains to
*
* @param player the MediaPlayer the error pertains to
* @param arg1 the type of error that has occurred: (MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_SERVER_DIED)
* @param arg2 an extra code, specific to the error.
*/
public boolean onError(MediaPlayer mPlayer, int arg1, int arg2) {
public boolean onError(MediaPlayer player, int arg1, int arg2) {
Log.d(LOG_TAG, "AudioPlayer.onError(" + arg1 + ", " + arg2 + ")");
// TODO: Not sure if this needs to be sent?
this.mPlayer.stop();
this.mPlayer.release();
this.player.stop();
this.player.release();
// Send error notification to JavaScript
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', { \"code\":" + arg1 + "});");
@@ -433,24 +393,36 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
/**
* Set the state and send it to JavaScript.
*
*
* @param state
*/
private void setState(int state) {
private void setState(STATE state) {
if (this.state != state) {
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_STATE + ", " + state + ");");
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_STATE + ", " + state.ordinal() + ");");
}
this.state = state;
}
/**
* Set the mode and send it to JavaScript.
*
* @param state
*/
private void setMode(MODE mode) {
if (this.mode != mode) {
//mode is not part of the expected behaviour, so no notification
//this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_STATE + ", " + mode + ");");
}
this.mode = mode;
}
/**
* Get the audio state.
*
*
* @return int
*/
public int getState() {
return this.state;
return this.state.ordinal();
}
/**
@@ -459,6 +431,120 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
* @param volume
*/
public void setVolume(float volume) {
this.mPlayer.setVolume(volume, volume);
this.player.setVolume(volume, volume);
}
/**
* attempts to put the player in play mode
* @return true if in playmode, false otherwise
*/
private boolean playMode() {
switch(this.mode) {
case NONE:
this.setMode(MODE.PLAY);
break;
case PLAY:
break;
case RECORD:
Log.d(LOG_TAG, "AudioPlayer Error: Can't play in record mode.");
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});");
return false; //player is not ready
}
return true;
}
/**
* attempts to initialize the media player for playback
* @param file the file to play
* @return false if player not ready, reports if in wrong mode or state
*/
private boolean readyPlayer(String file) {
if (playMode()) {
switch (this.state) {
case MEDIA_NONE:
if (this.player == null) {
this.player = new MediaPlayer();
}
try {
this.loadAudioFile(file);
} catch (Exception e) {
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});");
}
return false;
case MEDIA_LOADING:
//cordova js is not aware of MEDIA_LOADING, so we send MEDIA_STARTING instead
Log.d(LOG_TAG, "AudioPlayer Loading: startPlaying() called during media preparation: " + STATE.MEDIA_STARTING.ordinal());
this.prepareOnly = false;
return false;
case MEDIA_STARTING:
case MEDIA_RUNNING:
case MEDIA_PAUSED:
return true;
case MEDIA_STOPPED:
//if we are readying the same file
if (this.audioFile.compareTo(file) == 0) {
//reset the audio file
player.seekTo(0);
player.pause();
return true;
} else {
//reset the player
this.player.reset();
try {
this.loadAudioFile(file);
} catch (Exception e) {
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});");
}
//if we had to prepare= the file, we won't be in the correct state for playback
return false;
}
default:
Log.d(LOG_TAG, "AudioPlayer Error: startPlaying() called during invalid state: " + this.state);
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});");
}
}
return false;
}
/**
* load audio file
* @throws IOException
* @throws IllegalStateException
* @throws SecurityException
* @throws IllegalArgumentException
*/
private void loadAudioFile(String file) throws IllegalArgumentException, SecurityException, IllegalStateException, IOException {
if (this.isStreaming(file)) {
this.player.setDataSource(file);
this.player.setAudioStreamType(AudioManager.STREAM_MUSIC);
//if it's a streaming file, play mode is implied
this.setMode(MODE.PLAY);
this.setState(STATE.MEDIA_STARTING);
this.player.setOnPreparedListener(this);
this.player.prepareAsync();
}
else {
if (file.startsWith("/android_asset/")) {
String f = file.substring(15);
android.content.res.AssetFileDescriptor fd = this.handler.ctx.getActivity().getAssets().openFd(f);
this.player.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
}
else {
File fp = new File(file);
if (fp.exists()) {
FileInputStream fileInputStream = new FileInputStream(file);
this.player.setDataSource(fileInputStream.getFD());
}
else {
this.player.setDataSource("/sdcard/" + file);
}
}
this.setState(STATE.MEDIA_STARTING);
this.player.setOnPreparedListener(this);
this.player.prepare();
// Get duration
this.duration = getDurationInSeconds();
}
}
}

View File

@@ -55,11 +55,13 @@ public class CallbackServer implements Runnable {
@SuppressWarnings("unused")
private static final String LOG_TAG = "CallbackServer";
private ServerSocket waitSocket;
/**
* The list of JavaScript statements to be sent to JavaScript.
* This can be null when there are no messages available.
*/
private LinkedList<String> javascript;
private NativeToJsMessageQueue jsMessageQueue;
/**
* The port to listen on.
@@ -76,10 +78,6 @@ public class CallbackServer implements Runnable {
*/
private boolean active;
/**
* Indicates that the JavaScript statements list is empty
*/
private boolean empty;
/**
* Indicates that polling should be used instead of XHR.
@@ -97,9 +95,7 @@ public class CallbackServer implements Runnable {
public CallbackServer() {
//Log.d(LOG_TAG, "CallbackServer()");
this.active = false;
this.empty = true;
this.port = 0;
this.javascript = new LinkedList<String>();
}
/**
@@ -112,10 +108,8 @@ public class CallbackServer implements Runnable {
*/
public void init(String url) {
//System.out.println("CallbackServer.start("+url+")");
this.active = false;
this.empty = true;
this.stopServer();
this.port = 0;
this.javascript = new LinkedList<String>();
// Determine if XHR or polling is to be used
if ((url != null) && !url.startsWith("file://")) {
@@ -132,16 +126,6 @@ public class CallbackServer implements Runnable {
}
}
/**
* Re-init when loading a new HTML page into webview.
*
* @param url The URL of the Cordova app being loaded
*/
public void reinit(String url) {
this.stopServer();
this.init(url);
}
/**
* Return if polling is being used instead of XHR.
* @return
@@ -200,7 +184,7 @@ public class CallbackServer implements Runnable {
try {
this.active = true;
String request;
ServerSocket waitSocket = new ServerSocket(0);
waitSocket = new ServerSocket(0);
this.port = waitSocket.getLocalPort();
//Log.d(LOG_TAG, "CallbackServer -- using port " +this.port);
this.token = java.util.UUID.randomUUID().toString();
@@ -223,11 +207,19 @@ public class CallbackServer implements Runnable {
// Must have security token
if ((requestParts.length == 3) && (requestParts[1].substring(1).equals(this.token))) {
//Log.d(LOG_TAG, "CallbackServer -- Processing GET request");
String js = null;
// Wait until there is some data to send, or send empty data every 10 sec
// to prevent XHR timeout on the client
synchronized (this) {
while (this.empty) {
while (this.active) {
if (jsMessageQueue != null) {
// TODO(agrieve): Should this use popAll() instead?
js = jsMessageQueue.pop();
if (js != null) {
break;
}
}
try {
this.wait(10000); // prevent timeout from happening
//Log.d(LOG_TAG, "CallbackServer>>> break <<<");
@@ -241,17 +233,14 @@ public class CallbackServer implements Runnable {
if (this.active) {
// If no data, then send 404 back to client before it times out
if (this.empty) {
if (js == null) {
//Log.d(LOG_TAG, "CallbackServer -- sending data 0");
response = "HTTP/1.1 404 NO DATA\r\n\r\n "; // need to send content otherwise some Android devices fail, so send space
}
else {
//Log.d(LOG_TAG, "CallbackServer -- sending item");
response = "HTTP/1.1 200 OK\r\n\r\n";
String js = this.getJavascript();
if (js != null) {
response += encode(js, "UTF-8");
}
response += encode(js, "UTF-8");
}
}
else {
@@ -288,7 +277,9 @@ public class CallbackServer implements Runnable {
//Log.d(LOG_TAG, "CallbackServer.stopServer()");
if (this.active) {
this.active = false;
try { waitSocket.close(); } catch (IOException ignore) {}
// Break out of server wait
synchronized (this) {
this.notify();
@@ -302,46 +293,10 @@ public class CallbackServer implements Runnable {
public void destroy() {
this.stopServer();
}
/**
* Get the number of JavaScript statements.
*
* @return int
*/
public int getSize() {
public void onNativeToJsMessageAvailable(NativeToJsMessageQueue queue) {
synchronized (this) {
int size = this.javascript.size();
return size;
}
}
/**
* Get the next JavaScript statement and remove from list.
*
* @return String
*/
public String getJavascript() {
synchronized (this) {
if (this.javascript.size() == 0) {
return null;
}
String statement = this.javascript.remove(0);
if (this.javascript.size() == 0) {
this.empty = true;
}
return statement;
}
}
/**
* Add a JavaScript statement to the list.
*
* @param statement
*/
public void sendJavascript(String statement) {
synchronized (this) {
this.javascript.add(statement);
this.empty = false;
this.jsMessageQueue = queue;
this.notify();
}
}

View File

@@ -27,7 +27,6 @@ import java.io.IOException;
import java.io.OutputStream;
import org.apache.commons.codec.binary.Base64;
//import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.LOG;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.PluginResult;
@@ -36,7 +35,6 @@ import org.json.JSONException;
import android.app.Activity;
import android.content.ContentValues;
//import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
@@ -90,6 +88,7 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
private int numPics;
private MediaScannerConnection conn; // Used to update gallery app with newly-written files
private Uri scanMe; // Uri of image to be added to content store
//This should never be null!
//private CordovaInterface cordova;
@@ -143,6 +142,15 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
this.correctOrientation = args.getBoolean(8);
this.saveToPhotoAlbum = args.getBoolean(9);
// If the user specifies a 0 or smaller width/height
// make it -1 so later comparrisions succeed
if (this.targetWidth < 1) {
this.targetWidth = -1;
}
if (this.targetHeight < 1) {
this.targetHeight = -1;
}
if (srcType == CAMERA) {
this.takePicture(destType, encodingType);
}
@@ -265,21 +273,23 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
// If CAMERA
if (srcType == CAMERA) {
// Create an ExifHelper to save the exif data that is lost during compression
ExifHelper exif = new ExifHelper();
try {
if (this.encodingType == JPEG) {
exif.createInFile(DirectoryManager.getTempDirectoryPath(this.cordova.getActivity()) + "/.Pic.jpg");
exif.readExifData();
rotate = exif.getOrientation();
}
} catch (IOException e) {
e.printStackTrace();
}
// If image available
if (resultCode == Activity.RESULT_OK) {
try {
// Create an ExifHelper to save the exif data that is lost during compression
ExifHelper exif = new ExifHelper();
try {
if (this.encodingType == JPEG) {
exif.createInFile(DirectoryManager.getTempDirectoryPath(this.cordova.getActivity()) + "/.Pic.jpg");
exif.readExifData();
rotate = exif.getOrientation();
}
} catch (IOException e) {
e.printStackTrace();
}
Bitmap bitmap = null;
Uri uri = null;
// If sending base64 image back
if (destType == DATA_URL) {
@@ -295,9 +305,8 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
// If sending filename back
else if (destType == FILE_URI) {
Uri uri;
if (!this.saveToPhotoAlbum) {
uri = Uri.fromFile(new File("/data/data/" + this.cordova.getActivity().getPackageName() + "/", (new File(FileUtils.stripFileProtocol(this.imageUri.toString()))).getName()));
uri = Uri.fromFile(new File(DirectoryManager.getTempDirectoryPath(this.cordova.getActivity()), System.currentTimeMillis() + ".jpg"));
} else {
uri = getUriFromMediaStore();
}
@@ -324,7 +333,6 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
os.close();
// Restore exif data to file
if (this.encodingType == JPEG) {
String exifPath;
if (this.saveToPhotoAlbum) {
@@ -338,16 +346,10 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
}
// Send Uri back to JavaScript for viewing image
if (saveToPhotoAlbum) {
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
} else {
// If you don't want to save the file to the photo album you need to append a timestamp
// to the returned URI to do some cache busting.
this.success(new PluginResult(PluginResult.Status.OK, uri.toString() + "?" + System.currentTimeMillis()), this.callbackId);
}
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
}
this.cleanup(FILE_URI, this.imageUri, bitmap);
this.cleanup(FILE_URI, this.imageUri, uri, bitmap);
bitmap = null;
} catch (IOException e) {
@@ -378,73 +380,95 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
}
else {
// Get the path to the image. Makes loading so much easier.
String imagePath = FileUtils.getRealPathFromURI(uri, this.cordova);
Bitmap bitmap = getScaledBitmap(imagePath);
// If sending base64 image back
if (destType == DATA_URL) {
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();
// This is a special case to just return the path as no scaling,
// rotating or compression needs to be done
if (this.targetHeight == -1 && this.targetWidth == -1 &&
this.mQuality == 100 && destType == FILE_URI && !this.correctOrientation) {
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
} else {
// Get the path to the image. Makes loading so much easier.
String imagePath = FileUtils.getRealPathFromURI(uri, this.cordova);
Log.d(LOG_TAG, "Real path = " + imagePath);
// If we don't have a valid image so quit.
if (imagePath == null) {
Log.d(LOG_TAG, "I either have a null image path or bitmap");
this.failPicture("Unable to retreive path to picture!");
return;
}
if (rotate != 0) {
Matrix matrix = new Matrix();
matrix.setRotate(rotate);
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
Bitmap bitmap = getScaledBitmap(imagePath);
if (bitmap == null) {
Log.d(LOG_TAG, "I either have a null image path or bitmap");
this.failPicture("Unable to create bitmap!");
return;
}
this.processPicture(bitmap);
}
// If sending filename back
else if (destType == FILE_URI) {
// Do we need to scale the returned file
if (this.targetHeight > 0 && this.targetWidth > 0) {
try {
// Create an ExifHelper to save the exif data that is lost during compression
String resizePath = DirectoryManager.getTempDirectoryPath(this.cordova.getActivity()) + "/resize.jpg";
ExifHelper exif = new ExifHelper();
try {
if (this.encodingType == JPEG) {
exif.createInFile(resizePath);
exif.readExifData();
rotate = exif.getOrientation();
}
} catch (IOException e) {
e.printStackTrace();
}
OutputStream os = new FileOutputStream(resizePath);
bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os);
os.close();
// Restore exif data to file
if (this.encodingType == JPEG) {
exif.createOutFile(FileUtils.getRealPathFromURI(uri, this.cordova));
exif.writeExifData();
}
// The resized image is cached by the app in order to get around this and not have to delete you
// application cache I'm adding the current system time to the end of the file url.
this.success(new PluginResult(PluginResult.Status.OK, ("file://" + resizePath + "?" + System.currentTimeMillis())), this.callbackId);
} catch (Exception e) {
e.printStackTrace();
this.failPicture("Error retrieving image.");
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();
}
if (rotate != 0) {
Matrix matrix = new Matrix();
matrix.setRotate(rotate);
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}
}
else {
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
// If sending base64 image back
if (destType == DATA_URL) {
this.processPicture(bitmap);
}
// If sending filename back
else if (destType == FILE_URI) {
// Do we need to scale the returned file
if (this.targetHeight > 0 && this.targetWidth > 0) {
try {
// Create an ExifHelper to save the exif data that is lost during compression
String resizePath = DirectoryManager.getTempDirectoryPath(this.cordova.getActivity()) + "/resize.jpg";
ExifHelper exif = new ExifHelper();
try {
if (this.encodingType == JPEG) {
exif.createInFile(resizePath);
exif.readExifData();
rotate = exif.getOrientation();
}
} catch (IOException e) {
e.printStackTrace();
}
OutputStream os = new FileOutputStream(resizePath);
bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os);
os.close();
// Restore exif data to file
if (this.encodingType == JPEG) {
exif.createOutFile(FileUtils.getRealPathFromURI(uri, this.cordova));
exif.writeExifData();
}
// The resized image is cached by the app in order to get around this and not have to delete you
// application cache I'm adding the current system time to the end of the file url.
this.success(new PluginResult(PluginResult.Status.OK, ("file://" + resizePath + "?" + System.currentTimeMillis())), this.callbackId);
} catch (Exception e) {
e.printStackTrace();
this.failPicture("Error retrieving image.");
}
}
else {
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
}
}
if (bitmap != null) {
bitmap.recycle();
bitmap = null;
}
System.gc();
}
bitmap.recycle();
bitmap = null;
System.gc();
}
}
else if (resultCode == Activity.RESULT_CANCELED) {
@@ -533,20 +557,79 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
return BitmapFactory.decodeFile(imagePath);
}
Bitmap unscaledBitmap = decodeFile(imagePath,
this.targetWidth, this.targetHeight);
return Bitmap.createScaledBitmap(unscaledBitmap, this.targetWidth, this.targetHeight, true);
}
public static Bitmap decodeFile(String pathName, int dstWidth, int dstHeight) {
// figure out the original width and height of the image
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(pathName, options);
options.inJustDecodeBounds = false;
options.inSampleSize = calculateSampleSize(options.outWidth, options.outHeight, dstWidth, dstHeight);
return BitmapFactory.decodeFile(pathName, options);
}
BitmapFactory.decodeFile(imagePath, options);
// determine the correct aspect ratio
int[] widthHeight = calculateAspectRatio(options.outWidth, options.outHeight);
// 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);
return Bitmap.createScaledBitmap(unscaledBitmap, widthHeight[0], widthHeight[1], true);
}
/**
* Maintain the aspect ratio so the resulting image does not look smooshed
*
* @param origWidth
* @param origHeight
* @return
*/
public int[] calculateAspectRatio(int origWidth, int origHeight) {
int newWidth = this.targetWidth;
int newHeight = this.targetHeight;
// If no new width or height were specified return the original bitmap
if (newWidth <= 0 && newHeight <= 0) {
newWidth = origWidth;
newHeight = origHeight;
}
// Only the width was specified
else if (newWidth > 0 && newHeight <= 0) {
newHeight = (newWidth * origHeight) / origWidth;
}
// only the height was specified
else if (newWidth <= 0 && newHeight > 0) {
newWidth = (newHeight * origWidth) / origHeight;
}
// If the user specified both a positive width and height
// (potentially different aspect ratio) then the width or height is
// scaled so that the image fits while maintaining aspect ratio.
// Alternatively, the specified width and height could have been
// kept and Bitmap.SCALE_TO_FIT specified when scaling, but this
// would result in whitespace in the new image.
else {
double newRatio = newWidth / (double) newHeight;
double origRatio = origWidth / (double) origHeight;
if (origRatio > newRatio) {
newHeight = (newWidth * origHeight) / origWidth;
} else if (origRatio < newRatio) {
newWidth = (newHeight * origWidth) / origHeight;
}
}
int[] retval = new int[2];
retval[0] = newWidth;
retval[1] = newHeight;
return retval;
}
/**
* Figure out what ratio we can load our image into memory at while still being bigger than
* our desired width and height
*
* @param srcWidth
* @param srcHeight
* @param dstWidth
* @param dstHeight
* @return
*/
public static int calculateSampleSize(int srcWidth, int srcHeight, int dstWidth, int dstHeight) {
final float srcAspect = (float)srcWidth / (float)srcHeight;
final float dstAspect = (float)dstWidth / (float)dstHeight;
@@ -574,8 +657,9 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
/**
* Cleans up after picture taking. Checking for duplicates and that kind of stuff.
* @param newImage
*/
private void cleanup(int imageType, Uri oldImage, Bitmap bitmap) {
private void cleanup(int imageType, Uri oldImage, Uri newImage, Bitmap bitmap) {
if (bitmap != null) {
bitmap.recycle();
}
@@ -585,7 +669,9 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
checkForDuplicateImage(imageType);
// Scan for the gallery to update pic refs in gallery
this.scanForGallery();
if (this.saveToPhotoAlbum && newImage != null) {
this.scanForGallery(newImage);
}
System.gc();
}
@@ -610,7 +696,10 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
// delete the duplicate file if the difference is 2 for file URI or 1 for Data URL
if ((currentNumOfImages - numPics) == diff) {
cursor.moveToLast();
int id = Integer.valueOf(cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media._ID))) - 1;
int id = Integer.valueOf(cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media._ID)));
if (diff == 2) {
id--;
}
Uri uri = Uri.parse(contentStore + "/" + id);
this.cordova.getActivity().getContentResolver().delete(uri, null, null);
}
@@ -660,18 +749,20 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
this.error(new PluginResult(PluginResult.Status.ERROR, err), this.callbackId);
}
private void scanForGallery() {
if(this.conn!=null) this.conn.disconnect();
private void scanForGallery(Uri newImage) {
this.scanMe = newImage;
if(this.conn != null) {
this.conn.disconnect();
}
this.conn = new MediaScannerConnection(this.ctx.getActivity().getApplicationContext(), this);
conn.connect();
}
public void onMediaScannerConnected() {
try{
this.conn.scanFile(this.imageUri.toString(), "image/*");
this.conn.scanFile(this.scanMe.toString(), "image/*");
} catch (java.lang.IllegalStateException e){
e.printStackTrace();
LOG.d(LOG_TAG, "Can;t scan file in MediaScanner aftering taking picture");
LOG.e(LOG_TAG, "Can't scan file in MediaScanner aftering taking picture");
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -20,15 +20,14 @@ package org.apache.cordova;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.LOG;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
//import android.app.Activity;
import android.annotation.TargetApi;
import android.app.AlertDialog;
//import android.content.Context;
import android.content.DialogInterface;
import android.view.KeyEvent;
//import android.view.View;
import android.webkit.ConsoleMessage;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
@@ -101,7 +100,7 @@ public class CordovaChromeClient extends WebChromeClient {
dlg.setOnCancelListener(
new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
result.confirm();
result.cancel();
}
});
dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {
@@ -204,16 +203,23 @@ public class CordovaChromeClient extends WebChromeClient {
String action = array.getString(1);
String callbackId = array.getString(2);
boolean async = array.getBoolean(3);
String r = this.appView.pluginManager.exec(service, action, callbackId, message, async);
result.confirm(r);
PluginResult r = this.appView.pluginManager.exec(service, action, callbackId, message, async);
result.confirm(r == null ? "" : r.getJSONString());
} catch (JSONException e) {
e.printStackTrace();
}
}
// Sets the native->JS bridge mode.
else if (reqOk && defaultValue != null && defaultValue.equals("gap_bridge_mode:")) {
this.appView.jsMessageQueue.setBridgeMode(Integer.parseInt(message));
result.confirm("");
}
// Polling for JavaScript messages
else if (reqOk && defaultValue != null && defaultValue.equals("gap_poll:")) {
String r = this.appView.callbackServer.getJavascript();
// TODO(agrieve): Use popAll() here.
String r = this.appView.jsMessageQueue.pop();
result.confirm(r);
}
@@ -310,6 +316,7 @@ public class CordovaChromeClient extends WebChromeClient {
super.onConsoleMessage(message, lineNumber, sourceID);
}
@TargetApi(8)
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage)
{

View File

@@ -30,12 +30,17 @@ import java.util.regex.Pattern;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.LOG;
import org.apache.cordova.api.PluginManager;
import org.apache.cordova.api.PluginResult;
import org.json.JSONException;
import org.xmlpull.v1.XmlPullParserException;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.content.res.XmlResourceParser;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
@@ -57,6 +62,7 @@ public class CordovaWebView extends WebView {
public PluginManager pluginManager;
public CallbackServer callbackServer;
private boolean paused;
/** Actvities and other important classes **/
@@ -81,6 +87,10 @@ public class CordovaWebView extends WebView {
private boolean volumeupBound;
private boolean handleButton = false;
NativeToJsMessageQueue jsMessageQueue;
/**
* Constructor.
*
@@ -117,7 +127,7 @@ public class CordovaWebView extends WebView {
Log.d(TAG, "Your activity must implement CordovaInterface to work");
}
this.setWebChromeClient(new CordovaChromeClient(this.cordova, this));
this.setWebViewClient(new CordovaWebViewClient(this.cordova, this));
this.initWebViewClient(this.cordova);
this.loadConfiguration();
this.setup();
}
@@ -141,7 +151,7 @@ public class CordovaWebView extends WebView {
Log.d(TAG, "Your activity must implement CordovaInterface to work");
}
this.setWebChromeClient(new CordovaChromeClient(this.cordova, this));
this.setWebViewClient(new CordovaWebViewClient(this.cordova, this));
this.initWebViewClient(this.cordova);
this.loadConfiguration();
this.setup();
}
@@ -154,7 +164,8 @@ public class CordovaWebView extends WebView {
* @param defStyle
* @param privateBrowsing
*/
public CordovaWebView(Context context, AttributeSet attrs, int defStyle, boolean privateBrowsing) {
@TargetApi(11)
public CordovaWebView(Context context, AttributeSet attrs, int defStyle, boolean privateBrowsing) {
super(context, attrs, defStyle, privateBrowsing);
if (CordovaInterface.class.isInstance(context))
{
@@ -165,17 +176,31 @@ public class CordovaWebView extends WebView {
Log.d(TAG, "Your activity must implement CordovaInterface to work");
}
this.setWebChromeClient(new CordovaChromeClient(this.cordova));
this.setWebViewClient(new CordovaWebViewClient(this.cordova));
this.initWebViewClient(this.cordova);
this.loadConfiguration();
this.setup();
}
private void initWebViewClient(CordovaInterface cordova) {
if(android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.HONEYCOMB)
{
this.setWebViewClient(new CordovaWebViewClient(this.cordova, this));
}
else
{
this.setWebViewClient(new IceCreamCordovaWebViewClient(this.cordova, this));
}
}
/**
* Initialize webview.
*/
@SuppressWarnings("deprecation")
@SuppressLint("NewApi")
private void setup() {
jsMessageQueue = new NativeToJsMessageQueue(this, cordova);
this.setInitialScale(0);
this.setVerticalScrollBarEnabled(false);
this.requestFocusFromTouch();
@@ -186,9 +211,14 @@ public class CordovaWebView extends WebView {
settings.setJavaScriptCanOpenWindowsAutomatically(true);
settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
//Set the nav dump for HTC
settings.setNavDump(true);
//Set the nav dump for HTC 2.x devices (disabling for ICS/Jellybean)
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB)
settings.setNavDump(true);
// 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);
String databasePath = this.cordova.getActivity().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
@@ -207,6 +237,22 @@ public class CordovaWebView extends WebView {
// TODO Auto-generated catch block
e.printStackTrace();
}
exposeJsInterface();
}
private void exposeJsInterface() {
// addJavascriptInterface crashes on the 2.3 emulator.
if (Build.VERSION.RELEASE.startsWith("2.3") && Build.MANUFACTURER.equals("unknown")) {
Log.i(TAG, "Disabled addJavascriptInterface() bridge callback due to a bug on the 2.3 emulator");
return;
}
this.addJavascriptInterface(new Object() {
@SuppressWarnings("unused")
public String exec(String service, String action, String callbackId, String arguments) throws JSONException {
PluginResult r = pluginManager.exec(service, action, callbackId, arguments, true /* async */);
return r == null ? "" : r.getJSONString();
}
}, "_cordovaExec");
}
/**
@@ -414,7 +460,7 @@ public class CordovaWebView extends WebView {
*
* @param url
*/
private void loadUrlNow(String url) {
void loadUrlNow(String url) {
LOG.d(TAG, ">>> loadUrlNow()");
super.loadUrl(url);
}
@@ -445,7 +491,7 @@ public class CordovaWebView extends WebView {
// Load url
this.loadUrlIntoView(url);
}
/**
* Send JavaScript statement back to JavaScript.
* (This is a convenience method)
@@ -453,9 +499,7 @@ public class CordovaWebView extends WebView {
* @param message
*/
public void sendJavascript(String statement) {
if (this.callbackServer != null) {
this.callbackServer.sendJavascript(statement);
}
this.jsMessageQueue.add(statement);
}
/**
@@ -596,7 +640,12 @@ public class CordovaWebView extends WebView {
* <log level="DEBUG" />
*/
private void loadConfiguration() {
int id = getResources().getIdentifier("cordova", "xml", this.cordova.getActivity().getPackageName());
int id = getResources().getIdentifier("config", "xml", this.cordova.getActivity().getPackageName());
if(id == 0)
{
id = getResources().getIdentifier("cordova", "xml", this.cordova.getActivity().getPackageName());
Log.i("CordovaLog", "config.xml missing, reverting to cordova.xml");
}
if (id == 0) {
LOG.i("CordovaLog", "cordova.xml missing. Ignoring...");
return;
@@ -676,7 +725,6 @@ public class CordovaWebView extends WebView {
/*
* onKeyDown
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
@@ -696,18 +744,16 @@ public class CordovaWebView extends WebView {
}
else
{
//Do some other stuff!
return super.onKeyDown(keyCode, event);
}
}
return false;
return super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event)
{
Log.d(TAG, "KeyDown has been triggered on the view");
// If back key
if (keyCode == KeyEvent.KEYCODE_BACK) {
// If back key is bound, then send event to JavaScript
@@ -740,13 +786,14 @@ public class CordovaWebView extends WebView {
else if(keyUpCodes.contains(keyCode))
{
//What the hell should this do?
return super.onKeyUp(keyCode, event);
}
Log.d(TAG, "KeyUp has been triggered on the view");
return false;
//Does webkit change this behaviour?
return super.onKeyUp(keyCode, event);
}
public void bindButton(boolean override)
{
this.bound = override;
@@ -777,4 +824,80 @@ public class CordovaWebView extends WebView {
{
return this.bound;
}
public void handlePause(boolean keepRunning)
{
LOG.d(TAG, "Handle the pause");
// Send pause event to JavaScript
this.loadUrl("javascript:try{cordova.fireDocumentEvent('pause');}catch(e){console.log('exception firing pause event from native');};");
// Forward to plugins
if (this.pluginManager != null) {
this.pluginManager.onPause(keepRunning);
}
// If app doesn't want to run in background
if (!keepRunning) {
// Pause JavaScript timers (including setInterval)
this.pauseTimers();
}
paused = true;
}
public void handleResume(boolean keepRunning, boolean activityResultKeepRunning)
{
// Send resume event to JavaScript
this.loadUrl("javascript:try{cordova.fireDocumentEvent('resume');}catch(e){console.log('exception firing resume event from native');};");
// Forward to plugins
if (this.pluginManager != null) {
this.pluginManager.onResume(keepRunning);
}
// Resume JavaScript timers (including setInterval)
this.resumeTimers();
paused = false;
}
public void handleDestroy()
{
// Send destroy event to JavaScript
this.loadUrl("javascript:try{cordova.require('cordova/channel').onDestroy.fire();}catch(e){console.log('exception firing destroy event from native');};");
// Load blank page so that JavaScript onunload is called
this.loadUrl("about:blank");
// Forward to plugins
if (this.pluginManager != null) {
this.pluginManager.onDestroy();
}
}
public void onNewIntent(Intent intent)
{
//Forward to plugins
if (this.pluginManager != null) {
this.pluginManager.onNewIntent(intent);
}
}
public boolean isPaused()
{
return paused;
}
public boolean hadKeyEvent() {
return handleButton;
}
// Wrapping these functions in their own class prevents warnings in adb like:
// VFY: unable to resolve virtual method 285: Landroid/webkit/WebSettings;.setAllowUniversalAccessFromFileURLs
@TargetApi(16)
private static class Level16Apis {
static void enableUniversalAccess(WebSettings settings) {
settings.setAllowUniversalAccessFromFileURLs(true);
}
}
}

View File

@@ -21,6 +21,8 @@ package org.apache.cordova;
import java.util.Hashtable;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.PluginResult;
import java.io.IOException;
import java.io.InputStream;
@@ -28,6 +30,7 @@ import org.apache.cordova.api.LOG;
import org.json.JSONException;
import org.json.JSONObject;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -37,6 +40,7 @@ import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.net.Uri;
import android.net.http.SslError;
import android.util.Log;
import android.view.View;
import android.webkit.HttpAuthHandler;
import android.webkit.SslErrorHandler;
@@ -49,7 +53,11 @@ import android.webkit.WebViewClient;
*/
public class CordovaWebViewClient extends WebViewClient {
private static final String TAG = "Cordova";
private static final String TAG = "Cordova";
// Disable URL-based exec() bridge by default since it's a bit of a
// security concern.
private static boolean ENABLE_LOCATION_CHANGE_EXEC_MODE = false;
private static final String CORDOVA_EXEC_URL_PREFIX = "http://cdv_exec/";
CordovaInterface cordova;
CordovaWebView appView;
private boolean doClearHistory = false;
@@ -86,6 +94,29 @@ public class CordovaWebViewClient extends WebViewClient {
this.appView = view;
}
// Parses commands sent by setting the webView's URL to:
// cdvbrg:service/action/callbackId#jsonArgs
private void handleExecUrl(String url) {
int idx1 = CORDOVA_EXEC_URL_PREFIX.length();
int idx2 = url.indexOf('#', idx1 + 1);
int idx3 = url.indexOf('#', idx2 + 1);
int idx4 = url.indexOf('#', idx3 + 1);
if (idx1 == -1 || idx2 == -1 || idx3 == -1 || idx4 == -1) {
Log.e(TAG, "Could not decode URL command: " + url);
return;
}
String service = url.substring(idx1, idx2);
String action = url.substring(idx2 + 1, idx3);
String callbackId = url.substring(idx3 + 1, idx4);
String jsonArgs = url.substring(idx4 + 1);
PluginResult r = appView.pluginManager.exec(service, action, callbackId, jsonArgs, true /* async */);
String callbackString = r.toCallbackString(callbackId);
if (r != null) {
appView.sendJavascript(callbackString);
}
}
/**
* Give the host application a chance to take over the control when a new url
* is about to be loaded in the current WebView.
@@ -94,11 +125,15 @@ public class CordovaWebViewClient extends WebViewClient {
* @param url The url to be loaded.
* @return true to override, false for default behavior
*/
@Override
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// Check if it's an exec() bridge command message.
if (ENABLE_LOCATION_CHANGE_EXEC_MODE && url.startsWith(CORDOVA_EXEC_URL_PREFIX)) {
handleExecUrl(url);
}
// First give any plugins the chance to handle the url themselves
if ((this.appView.pluginManager != null) && this.appView.pluginManager.onOverrideUrlLoading(url)) {
// Give plugins the chance to handle the url
else if ((this.appView.pluginManager != null) && this.appView.pluginManager.onOverrideUrlLoading(url)) {
}
// If dialing phone (tel:5551212)
@@ -171,9 +206,9 @@ 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.indexOf(this.appView.baseUrl) == 0 || this.appView.isUrlWhiteListed(url)) {
if (url.startsWith("file://") || url.startsWith("data:") || url.indexOf(this.appView.baseUrl) == 0 || this.appView.isUrlWhiteListed(url)) {
//This will fix iFrames
if (appView.useBrowserHistory)
if (appView.useBrowserHistory || url.startsWith("data:"))
return false;
else
this.appView.loadUrl(url);
@@ -230,14 +265,14 @@ public class CordovaWebViewClient extends WebViewClient {
this.doClearHistory = true;
}
// Create callback server and plugin manager
// Flush stale messages.
this.appView.jsMessageQueue.reset();
// Create callback server
if (this.appView.callbackServer == null) {
this.appView.callbackServer = new CallbackServer();
this.appView.callbackServer.init(url);
}
else {
this.appView.callbackServer.reinit(url);
}
this.appView.callbackServer.init(url);
// Broadcast message that page has loaded
this.appView.postMessage("onPageStarted", url);
@@ -346,6 +381,7 @@ public class CordovaWebViewClient extends WebViewClient {
* @param handler An SslErrorHandler object that will handle the user's response.
* @param error The SSL error object.
*/
@TargetApi(8)
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
@@ -459,43 +495,4 @@ public class CordovaWebViewClient extends WebViewClient {
this.authenticationTokens.clear();
}
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
if(url.contains("?") || url.contains("#")){
return generateWebResourceResponse(url);
} else {
return super.shouldInterceptRequest(view, url);
}
}
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";
}
try {
AssetManager assets = cordova.getActivity().getAssets();
Uri uri = Uri.parse(niceUrl);
InputStream stream = assets.open(uri.getPath(), AssetManager.ACCESS_STREAMING);
WebResourceResponse response = new WebResourceResponse(mimetype, "UTF-8", stream);
return response;
} catch (IOException e) {
LOG.e("generateWebResourceResponse", e.getMessage(), e);
}
}
return null;
}
}

View File

@@ -38,7 +38,7 @@ import android.telephony.TelephonyManager;
public class Device extends Plugin {
public static final String TAG = "Device";
public static String cordovaVersion = "1.9.0"; // Cordova version
public static String cordovaVersion = "2.1.0"; // Cordova version
public static String platform = "Android"; // Device OS
public static String uuid; // Device UUID

View File

@@ -38,6 +38,7 @@ import android.graphics.Color;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.Display;
import android.view.KeyEvent;
import android.view.Menu;
@@ -46,6 +47,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.LinearLayout;
@@ -600,6 +602,8 @@ public class DroidGap extends Activity implements CordovaInterface {
protected void onPause() {
super.onPause();
LOG.d(TAG, "Paused the application!");
// Don't process pause if shutting down, since onDestroy() will be called
if (this.activityState == ACTIVITY_EXITING) {
return;
@@ -608,21 +612,13 @@ public class DroidGap extends Activity implements CordovaInterface {
if (this.appView == null) {
return;
}
// Send pause event to JavaScript
this.appView.loadUrl("javascript:try{cordova.fireDocumentEvent('pause');}catch(e){console.log('exception firing pause event from native');};");
// Forward to plugins
if (this.appView.pluginManager != null) {
this.appView.pluginManager.onPause(this.keepRunning);
else
{
this.appView.handlePause(this.keepRunning);
}
// If app doesn't want to run in background
if (!this.keepRunning) {
// Pause JavaScript timers (including setInterval)
this.appView.pauseTimers();
}
// hide the splash screen to avoid leaking a window
this.removeSplashScreen();
}
@Override
@@ -631,11 +627,9 @@ public class DroidGap extends Activity implements CordovaInterface {
**/
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
//Forward to plugins
if ((this.appView != null) && (this.appView.pluginManager != null)) {
this.appView.pluginManager.onNewIntent(intent);
}
if (this.appView != null)
this.appView.onNewIntent(intent);
}
@Override
@@ -645,6 +639,7 @@ public class DroidGap extends Activity implements CordovaInterface {
protected void onResume() {
super.onResume();
LOG.d(TAG, "Resuming the App");
if (this.activityState == ACTIVITY_STARTING) {
this.activityState = ACTIVITY_RUNNING;
return;
@@ -654,13 +649,7 @@ public class DroidGap extends Activity implements CordovaInterface {
return;
}
// Send resume event to JavaScript
this.appView.loadUrl("javascript:try{cordova.fireDocumentEvent('resume');}catch(e){console.log('exception firing resume event from native');};");
// Forward to plugins
if (this.appView.pluginManager != null) {
this.appView.pluginManager.onResume(this.keepRunning || this.activityResultKeepRunning);
}
this.appView.handleResume(this.keepRunning, this.activityResultKeepRunning);
// If app doesn't want to run in background
if (!this.keepRunning || this.activityResultKeepRunning) {
@@ -670,9 +659,6 @@ public class DroidGap extends Activity implements CordovaInterface {
this.keepRunning = this.activityResultKeepRunning;
this.activityResultKeepRunning = false;
}
// Resume JavaScript timers (including setInterval)
this.appView.resumeTimers();
}
}
@@ -684,18 +670,11 @@ public class DroidGap extends Activity implements CordovaInterface {
LOG.d(TAG, "onDestroy()");
super.onDestroy();
// hide the splash screen to avoid leaking a window
this.removeSplashScreen();
if (this.appView != null) {
// Send destroy event to JavaScript
this.appView.loadUrl("javascript:try{cordova.require('cordova/channel').onDestroy.fire();}catch(e){console.log('exception firing destroy event from native');};");
// Load blank page so that JavaScript onunload is called
this.appView.loadUrl("about:blank");
// Forward to plugins
if (this.appView.pluginManager != null) {
this.appView.pluginManager.onDestroy();
}
appView.handleDestroy();
}
else {
this.endActivity();
@@ -736,8 +715,8 @@ public class DroidGap extends Activity implements CordovaInterface {
* @param message
*/
public void sendJavascript(String statement) {
if (this.appView != null && this.appView.callbackServer != null) {
this.appView.callbackServer.sendJavascript(statement);
if (this.appView != null) {
this.appView.jsMessageQueue.add(statement);
}
}
@@ -762,10 +741,10 @@ public class DroidGap extends Activity implements CordovaInterface {
}
/**
* Stop spinner.
* Stop spinner - Must be called from UI thread
*/
public void spinnerStop() {
if (this.spinnerDialog != null) {
if (this.spinnerDialog != null && this.spinnerDialog.isShowing()) {
this.spinnerDialog.dismiss();
this.spinnerDialog = null;
}
@@ -779,7 +758,7 @@ public class DroidGap extends Activity implements CordovaInterface {
super.finish();
}
/**
* Launch an activity for which you would like a result when it finished. When this activity exits,
* your onActivityResult() method will be called.
@@ -834,9 +813,6 @@ public class DroidGap extends Activity implements CordovaInterface {
public void onReceivedError(final int errorCode, final String description, final String failingUrl) {
final DroidGap me = this;
// Stop "app loading" spinner if showing
this.spinnerStop();
// If errorUrl specified, then load it
final String errorUrl = me.getStringProperty("errorUrl", null);
if ((errorUrl != null) && (errorUrl.startsWith("file://") || errorUrl.indexOf(me.baseUrl) == 0 || this.appView.isUrlWhiteListed(errorUrl)) && (!failingUrl.equals(errorUrl))) {
@@ -844,6 +820,8 @@ public class DroidGap extends Activity implements CordovaInterface {
// Load URL on UI thread
me.runOnUiThread(new Runnable() {
public void run() {
// Stop "app loading" spinner if showing
me.spinnerStop();
me.appView.showWebPage(errorUrl, false, true, null);
}
});
@@ -941,7 +919,7 @@ public class DroidGap extends Activity implements CordovaInterface {
*/
public Context getContext() {
LOG.d(TAG, "This will be deprecated December 2012");
return this.getContext();
return this;
}
/**
@@ -966,7 +944,7 @@ public class DroidGap extends Activity implements CordovaInterface {
* Removes the Dialog that displays the splash screen
*/
public void removeSplashScreen() {
if (splashDialog != null) {
if (splashDialog != null && splashDialog.isShowing()) {
splashDialog.dismiss();
splashDialog = null;
}
@@ -1018,6 +996,20 @@ public class DroidGap extends Activity implements CordovaInterface {
this.runOnUiThread(runnable);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event)
{
//Determine if the focus is on the current view or not
if (appView.getHitTestResult() != null &&
appView.getHitTestResult().getType() == WebView.HitTestResult.EDIT_TEXT_TYPE &&
keyCode == KeyEvent.KEYCODE_BACK) {
return appView.onKeyUp(keyCode, event);
}
else
return super.onKeyUp(keyCode, event);
}
/**
* Called when a message is sent to plugin.
*
@@ -1055,5 +1047,4 @@ public class DroidGap extends Activity implements CordovaInterface {
}
return null;
}
}

View File

@@ -0,0 +1,57 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
public class Echo extends Plugin {
/**
* Executes the request and returns PluginResult.
*
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param callbackId The callback id used when calling back into JavaScript.
* @return A PluginResult object with a status and message.
*/
public PluginResult execute(String action, JSONArray args, String callbackId) {
try {
String result = args.getString(0);
if ("echo".equals(action) || "echoAsync".equals(action)) {
return new PluginResult(PluginResult.Status.OK, result);
}
return new PluginResult(PluginResult.Status.INVALID_ACTION);
} catch (JSONException e) {
return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
}
}
/**
* 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) {
return "echo".equals(action);
}
}

View File

@@ -49,6 +49,7 @@ import org.json.JSONException;
import org.json.JSONObject;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import android.webkit.CookieManager;
@@ -74,7 +75,7 @@ public class FileTransfer extends Plugin {
String source = null;
String target = null;
try {
source = URLDecoder.decode(args.getString(0));
source = args.getString(0);
target = args.getString(1);
} catch (JSONException e) {
Log.d(LOG_TAG, "Missing source or target");
@@ -82,7 +83,7 @@ public class FileTransfer extends Plugin {
}
if (action.equals("upload")) {
return upload(source, target, args);
return upload(URLDecoder.decode(source), target, args);
} else if (action.equals("download")) {
return download(source, target);
} else {
@@ -115,6 +116,11 @@ public class FileTransfer extends Plugin {
if (params == null) params = new JSONObject();
boolean trustEveryone = args.optBoolean(6);
boolean chunkedMode = args.optBoolean(7) || args.isNull(7); //Always use chunked mode unless set to false as per API
JSONObject headers = args.optJSONObject(8);
// Look for headers on the params map for backwards compatibility with older Cordova versions.
if (headers == null && params != null) {
headers = params.optJSONObject("headers");
}
Log.d(LOG_TAG, "fileKey: " + fileKey);
Log.d(LOG_TAG, "fileName: " + fileName);
@@ -122,6 +128,7 @@ public class FileTransfer extends Plugin {
Log.d(LOG_TAG, "params: " + params);
Log.d(LOG_TAG, "trustEveryone: " + trustEveryone);
Log.d(LOG_TAG, "chunkedMode: " + chunkedMode);
Log.d(LOG_TAG, "headers: " + headers);
// Create return object
FileUploadResult result = new FileUploadResult();
@@ -139,9 +146,9 @@ public class FileTransfer extends Plugin {
//------------------ CLIENT REQUEST
// open a URL connection to the server
URL url = new URL(target);
boolean useHttps = url.getProtocol().toLowerCase().equals("https");
// Open a HTTP connection to the URL based on protocol
if (url.getProtocol().toLowerCase().equals("https")) {
if (useHttps) {
// Using standard HTTPS connection. Will not allow self signed certificate
if (!trustEveryone) {
conn = (HttpsURLConnection) url.openConnection();
@@ -178,24 +185,31 @@ public class FileTransfer extends Plugin {
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + BOUNDARY);
// Handle the other headers
try {
JSONObject headers = params.getJSONObject("headers");
for (Iterator iter = headers.keys(); iter.hasNext();)
{
String headerKey = iter.next().toString();
conn.setRequestProperty(headerKey, headers.getString(headerKey));
}
} catch (JSONException e1) {
// No headers to be manipulated!
}
// Set the cookies on the response
String cookie = CookieManager.getInstance().getCookie(target);
if (cookie != null) {
conn.setRequestProperty("Cookie", cookie);
}
// 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!
}
}
/*
* Store the non-file portions of the multipart data as a string, so that we can add it
@@ -226,19 +240,23 @@ public class FileTransfer extends Plugin {
String tailParams = LINE_END + LINE_START + BOUNDARY + LINE_START + LINE_END;
byte[] fileNameBytes = fileName.getBytes("UTF-8");
// Should set this up as an option
int stringLength = extraBytes.length + midParams.length() + tailParams.length() + fileNameBytes.length;
Log.d(LOG_TAG, "String Length: " + stringLength);
int fixedLength = (int) fileInputStream.getChannel().size() + stringLength;
Log.d(LOG_TAG, "Content Length: " + fixedLength);
// setFixedLengthStreamingMode causes and OutOfMemoryException on pre-Froyo devices.
// http://code.google.com/p/android/issues/detail?id=3164
// It also causes OOM if HTTPS is used, even on newer devices.
chunkedMode = chunkedMode && (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO || useHttps);
if (chunkedMode) {
conn.setChunkedStreamingMode(maxBufferSize);
// Although setChunkedStreamingMode sets this header, setting it explicitly here works
// around an OutOfMemoryException when using https.
conn.setRequestProperty("Transfer-Encoding", "chunked");
} else {
conn.setFixedLengthStreamingMode(fixedLength);
}
else
{
int stringLength = extraBytes.length + midParams.length() + tailParams.length() + fileNameBytes.length;
Log.d(LOG_TAG, "String Length: " + stringLength);
int fixedLength = (int) fileInputStream.getChannel().size() + stringLength;
Log.d(LOG_TAG, "Content Length: " + fixedLength);
conn.setFixedLengthStreamingMode(fixedLength);
}
dos = new DataOutputStream( conn.getOutputStream() );
//We don't want to change encoding, we just want this to write for all Unicode.
@@ -255,10 +273,15 @@ public class FileTransfer extends Plugin {
bytesRead = fileInputStream.read(buffer, 0, bufferSize);
totalBytes = 0;
long prevBytesRead = 0;
while (bytesRead > 0) {
totalBytes += bytesRead;
result.setBytesSent(totalBytes);
dos.write(buffer, 0, bufferSize);
if (totalBytes > prevBytesRead + 102400) {
prevBytesRead = totalBytes;
Log.d(LOG_TAG, "Uploaded " + totalBytes + " of " + fixedLength + " bytes");
}
bytesAvailable = fileInputStream.available();
bufferSize = Math.min(bytesAvailable, maxBufferSize);
bytesRead = fileInputStream.read(buffer, 0, bufferSize);
@@ -467,8 +490,14 @@ public class FileTransfer extends Plugin {
connection.connect();
Log.d(LOG_TAG, "Download file:" + url);
InputStream inputStream;
try {
inputStream = connection.getInputStream();
} catch(FileNotFoundException e) {
Log.e(LOG_TAG, e.toString(), e);
throw new IOException("Received error from server");
}
InputStream inputStream = connection.getInputStream();
byte[] buffer = new byte[1024];
int bytesRead = 0;
@@ -498,6 +527,7 @@ public class FileTransfer extends Plugin {
} catch (FileNotFoundException e) {
JSONObject error = createFileTransferError(FILE_NOT_FOUND_ERR, source, target, connection);
Log.d(LOG_TAG, "I got a file not found exception");
Log.e(LOG_TAG, error.toString(), e);
return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
} catch (MalformedURLException e) {

View File

@@ -298,7 +298,9 @@ public class FileUtils extends Plugin {
if (fp.isDirectory()) {
File[] files = fp.listFiles();
for (int i = 0; i < files.length; i++) {
entries.put(getEntry(files[i]));
if (files[i].canRead()) {
entries.put(getEntry(files[i]));
}
}
}

View File

@@ -59,36 +59,43 @@ public class GeoBroker extends Plugin {
this.networkListener = new NetworkListener(this.locationManager, this);
this.gpsListener = new GPSListener(this.locationManager, this);
}
PluginResult.Status status = PluginResult.Status.NO_RESULT;
String message = "";
String message = "Location API is not available for this device.";
PluginResult result = new PluginResult(status, message);
result.setKeepCallback(true);
try {
if (action.equals("getLocation")) {
boolean enableHighAccuracy = args.getBoolean(0);
int maximumAge = args.getInt(1);
Location last = this.locationManager.getLastKnownLocation((enableHighAccuracy ? LocationManager.GPS_PROVIDER : LocationManager.NETWORK_PROVIDER));
// Check if we can use lastKnownLocation to get a quick reading and use less battery
if ((System.currentTimeMillis() - last.getTime()) <= maximumAge) {
result = new PluginResult(PluginResult.Status.OK, this.returnLocationJSON(last));
} else {
this.getCurrentLocation(callbackId, enableHighAccuracy);
}
}
else if (action.equals("addWatch")) {
String id = args.getString(0);
boolean enableHighAccuracy = args.getBoolean(1);
this.addWatch(id, callbackId, enableHighAccuracy);
}
else if (action.equals("clearWatch")) {
String id = args.getString(0);
this.clearWatch(id);
}
} catch (JSONException e) {
result = new PluginResult(PluginResult.Status.JSON_EXCEPTION, e.getMessage());
if ( locationManager.isProviderEnabled( LocationManager.GPS_PROVIDER ) ||
locationManager.isProviderEnabled( LocationManager.NETWORK_PROVIDER )) {
result.setKeepCallback(true);
try {
if (action.equals("getLocation")) {
boolean enableHighAccuracy = args.getBoolean(0);
int maximumAge = args.getInt(1);
Location last = this.locationManager.getLastKnownLocation((enableHighAccuracy ? LocationManager.GPS_PROVIDER : LocationManager.NETWORK_PROVIDER));
// Check if we can use lastKnownLocation to get a quick reading and use less battery
if (last != null && (System.currentTimeMillis() - last.getTime()) <= maximumAge) {
result = new PluginResult(PluginResult.Status.OK, this.returnLocationJSON(last));
} else {
this.getCurrentLocation(callbackId, enableHighAccuracy);
}
}
else if (action.equals("addWatch")) {
String id = args.getString(0);
boolean enableHighAccuracy = args.getBoolean(1);
this.addWatch(id, callbackId, enableHighAccuracy);
}
else if (action.equals("clearWatch")) {
String id = args.getString(0);
this.clearWatch(id);
}
} catch (JSONException e) {
result = new PluginResult(PluginResult.Status.JSON_EXCEPTION, e.getMessage());
}
}
return result;
}
private void clearWatch(String id) {

View File

@@ -0,0 +1,83 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import java.io.IOException;
import java.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.webkit.WebResourceResponse;
import android.webkit.WebView;
public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
public IceCreamCordovaWebViewClient(CordovaInterface cordova) {
super(cordova);
}
public IceCreamCordovaWebViewClient(CordovaInterface cordova, CordovaWebView view) {
super(cordova, view);
}
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
if(url.contains("?") || url.contains("#")){
return generateWebResourceResponse(url);
} else {
return super.shouldInterceptRequest(view, url);
}
}
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";
}
try {
AssetManager assets = cordova.getActivity().getAssets();
Uri uri = Uri.parse(niceUrl);
InputStream stream = assets.open(uri.getPath(), AssetManager.ACCESS_STREAMING);
WebResourceResponse response = new WebResourceResponse(mimetype, "UTF-8", stream);
return response;
} catch (IOException e) {
LOG.e("generateWebResourceResponse", e.getMessage(), e);
}
}
return null;
}
}

View File

@@ -0,0 +1,254 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedList;
import org.apache.cordova.api.CordovaInterface;
import android.os.Message;
import android.util.Log;
import android.webkit.WebView;
/**
* Holds the list of messages to be sent to the WebView.
*/
public class NativeToJsMessageQueue {
private static final String LOG_TAG = "JsMessageQueue";
// This must match the default value in incubator-cordova-js/lib/android/exec.js
private static final int DEFAULT_BRIDGE_MODE = 1;
/**
* The index into registeredListeners to treat as active.
*/
private int activeListenerIndex;
/**
* The list of JavaScript statements to be sent to JavaScript.
*/
private final LinkedList<String> queue = new LinkedList<String>();
/**
* The array of listeners that can be used to send messages to JS.
*/
private final BridgeMode[] registeredListeners;
private final CordovaInterface cordova;
private final CordovaWebView webView;
public NativeToJsMessageQueue(CordovaWebView webView, CordovaInterface cordova) {
this.cordova = cordova;
this.webView = webView;
registeredListeners = new BridgeMode[5];
registeredListeners[0] = null; // Polling. Requires no logic.
registeredListeners[1] = new CallbackBridgeMode();
registeredListeners[2] = new LoadUrlBridgeMode();
registeredListeners[3] = new OnlineEventsBridgeMode();
registeredListeners[4] = new PrivateApiBridgeMode();
reset();
}
/**
* Changes the bridge mode.
*/
public void setBridgeMode(int value) {
if (value < 0 || value >= registeredListeners.length) {
Log.d(LOG_TAG, "Invalid NativeToJsBridgeMode: " + value);
} else {
if (value != activeListenerIndex) {
Log.d(LOG_TAG, "Set native->JS mode to " + value);
synchronized (this) {
activeListenerIndex = value;
BridgeMode activeListener = registeredListeners[value];
if (!queue.isEmpty() && activeListener != null) {
activeListener.onNativeToJsMessageAvailable();
}
}
}
}
}
/**
* Clears all messages and resets to the default bridge mode.
*/
public void reset() {
synchronized (this) {
queue.clear();
setBridgeMode(DEFAULT_BRIDGE_MODE);
}
}
/**
* Removes and returns the last statement in the queue.
* Returns null if the queue is empty.
*/
public String pop() {
synchronized (this) {
if (queue.isEmpty()) {
return null;
}
return queue.remove(0);
}
}
/**
* Combines and returns all statements. Clears the queue.
* Returns null if the queue is empty.
*/
public String popAll() {
synchronized (this) {
int length = queue.size();
if (length == 0) {
return null;
}
StringBuffer sb = new StringBuffer();
// Wrap each statement in a try/finally so that if one throws it does
// not affect the next.
int i = 0;
for (String message : queue) {
if (++i == length) {
sb.append(message);
} else {
sb.append("try{")
.append(message)
.append("}finally{");
}
}
for ( i = 1; i < length; ++i) {
sb.append('}');
}
queue.clear();
return sb.toString();
}
}
/**
* Add a JavaScript statement to the list.
*/
public void add(String statement) {
synchronized (this) {
queue.add(statement);
if (registeredListeners[activeListenerIndex] != null) {
registeredListeners[activeListenerIndex].onNativeToJsMessageAvailable();
}
}
}
private interface BridgeMode {
void onNativeToJsMessageAvailable();
}
/** Uses a local server to send messages to JS via an XHR */
private class CallbackBridgeMode implements BridgeMode {
public void onNativeToJsMessageAvailable() {
if (webView.callbackServer != null) {
webView.callbackServer.onNativeToJsMessageAvailable(NativeToJsMessageQueue.this);
}
}
}
/** Uses webView.loadUrl("javascript:") to execute messages. */
private class LoadUrlBridgeMode implements BridgeMode {
public void onNativeToJsMessageAvailable() {
webView.loadUrlNow("javascript:" + popAll());
}
}
/** Uses online/offline events to tell the JS when to poll for messages. */
private class OnlineEventsBridgeMode implements BridgeMode {
boolean online = true;
final Runnable runnable = new Runnable() {
@Override
public void run() {
if (!queue.isEmpty()) {
online = !online;
webView.setNetworkAvailable(online);
}
}
};
public void onNativeToJsMessageAvailable() {
cordova.getActivity().runOnUiThread(runnable);
}
}
/**
* Uses Java reflection to access an API that lets us eval JS.
* Requires Android 3.2.4 or above.
*/
private class PrivateApiBridgeMode implements BridgeMode {
// Message added in commit:
// http://omapzoom.org/?p=platform/frameworks/base.git;a=commitdiff;h=9497c5f8c4bc7c47789e5ccde01179abc31ffeb2
// Which first appeared in 3.2.4ish.
private static final int EXECUTE_JS = 194;
Method sendMessageMethod;
Object webViewCore;
boolean initFailed;
@SuppressWarnings("rawtypes")
private void initReflection() {
Object webViewObject = webView;
Class webViewClass = WebView.class;
try {
Field f = webViewClass.getDeclaredField("mProvider");
f.setAccessible(true);
webViewObject = f.get(webView);
webViewClass = webViewObject.getClass();
} catch (Throwable e) {
// mProvider is only required on newer Android releases.
}
try {
Field f = webViewClass.getDeclaredField("mWebViewCore");
f.setAccessible(true);
webViewCore = f.get(webViewObject);
if (webViewCore != null) {
sendMessageMethod = webViewCore.getClass().getDeclaredMethod("sendMessage", Message.class);
sendMessageMethod.setAccessible(true);
}
} catch (Throwable e) {
initFailed = true;
Log.e(LOG_TAG, "PrivateApiBridgeMode failed to find the expected APIs.", e);
}
}
public void onNativeToJsMessageAvailable() {
if (sendMessageMethod == null && !initFailed) {
initReflection();
}
// webViewCore is lazily initialized, and so may not be available right away.
if (sendMessageMethod != null) {
String js = popAll();
Message execJsMessage = Message.obtain(null, EXECUTE_JS, js);
try {
sendMessageMethod.invoke(webViewCore, execJsMessage);
} catch (Throwable e) {
Log.e(LOG_TAG, "Reflection message bridge failed.", e);
}
}
}
}
}

View File

@@ -49,7 +49,7 @@ public class Notification extends Plugin {
/**
* Executes the request and returns PluginResult.
*
*
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param callbackId The callback id used when calling back into JavaScript.
@@ -101,7 +101,7 @@ public class Notification extends Plugin {
/**
* Identifies if action to be executed returns a value and should be run synchronously.
*
*
* @param action The action to execute
* @return T=returns value
*/
@@ -138,7 +138,7 @@ public class Notification extends Plugin {
/**
* Beep plays the default notification ringtone.
*
*
* @param count Number of times to play notification
*/
public void beep(long count) {
@@ -163,7 +163,7 @@ public class Notification extends Plugin {
/**
* Vibrates the device for the specified amount of time.
*
*
* @param time Time to vibrate in ms.
*/
public void vibrate(long time) {
@@ -179,7 +179,7 @@ public class Notification extends Plugin {
* Builds and shows a native Android alert with given Strings
* @param message The message the alert should display
* @param title The title of the alert
* @param buttonLabel The label of the button
* @param buttonLabel The label of the button
* @param callbackId The callback id
*/
public synchronized void alert(final String message, final String title, final String buttonLabel, final String callbackId) {
@@ -193,7 +193,7 @@ public class Notification extends Plugin {
AlertDialog.Builder dlg = new AlertDialog.Builder(cordova.getActivity());
dlg.setMessage(message);
dlg.setTitle(title);
dlg.setCancelable(false);
dlg.setCancelable(true);
dlg.setPositiveButton(buttonLabel,
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
@@ -212,7 +212,7 @@ public class Notification extends Plugin {
* Builds and shows a native Android confirm dialog with given title, message, buttons.
* This dialog only shows up to 3 buttons. Any labels after that will be ignored.
* The index of the button pressed will be returned to the JavaScript callback identified by callbackId.
*
*
* @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)
@@ -229,7 +229,7 @@ public class Notification extends Plugin {
AlertDialog.Builder dlg = new AlertDialog.Builder(cordova.getActivity());
dlg.setMessage(message);
dlg.setTitle(title);
dlg.setCancelable(false);
dlg.setCancelable(true);
// First button
if (fButtons.length > 0) {
@@ -274,7 +274,7 @@ public class Notification extends Plugin {
/**
* Show the spinner.
*
*
* @param title Title of the dialog
* @param message The message of the dialog
*/
@@ -310,7 +310,7 @@ public class Notification extends Plugin {
/**
* Show the progress dialog.
*
*
* @param title Title of the dialog
* @param message The message of the dialog
*/
@@ -344,7 +344,7 @@ public class Notification extends Plugin {
/**
* Set value of progress bar.
*
*
* @param value 0-100
*/
public synchronized void progressValue(int value) {

View File

@@ -0,0 +1,131 @@
package org.apache.cordova.api;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.util.Log;
@Deprecated
public class LegacyContext implements CordovaInterface {
private static final String LOG_TAG = "Deprecation Notice";
private CordovaInterface cordova;
public LegacyContext(CordovaInterface cordova) {
this.cordova = cordova;
}
@Deprecated
public void cancelLoadUrl() {
Log.i(LOG_TAG, "Replace ctx.cancelLoadUrl() with cordova.cancelLoadUrl()");
this.cordova.cancelLoadUrl();
}
@Deprecated
public Activity getActivity() {
Log.i(LOG_TAG, "Replace ctx.getActivity() with cordova.getActivity()");
return this.cordova.getActivity();
}
@Deprecated
public Context getContext() {
Log.i(LOG_TAG, "Replace ctx.getContext() with cordova.getContext()");
return this.cordova.getContext();
}
@Deprecated
public Object onMessage(String arg0, Object arg1) {
Log.i(LOG_TAG, "Replace ctx.onMessage() with cordova.onMessage()");
return this.cordova.onMessage(arg0, arg1);
}
@Deprecated
public void setActivityResultCallback(IPlugin arg0) {
Log.i(LOG_TAG, "Replace ctx.setActivityResultCallback() with cordova.setActivityResultCallback()");
this.cordova.setActivityResultCallback(arg0);
}
@Deprecated
public void startActivityForResult(IPlugin arg0, Intent arg1, int arg2) {
Log.i(LOG_TAG, "Replace ctx.startActivityForResult() with cordova.startActivityForResult()");
this.cordova.startActivityForResult(arg0, arg1, arg2);
}
@Deprecated
public void startActivity(Intent intent) {
Log.i(LOG_TAG, "Replace ctx.startActivity() with cordova.getActivity().startActivity()");
this.cordova.getActivity().startActivity(intent);
}
@Deprecated
public Object getSystemService(String name) {
Log.i(LOG_TAG, "Replace ctx.getSystemService() with cordova.getActivity().getSystemService()");
return this.cordova.getActivity().getSystemService(name);
}
@Deprecated
public AssetManager getAssets() {
Log.i(LOG_TAG, "Replace ctx.getAssets() with cordova.getActivity().getAssets()");
return this.cordova.getActivity().getAssets();
}
@Deprecated
public void runOnUiThread(Runnable runnable) {
Log.i(LOG_TAG, "Replace ctx.runOnUiThread() with cordova.getActivity().runOnUiThread()");
this.cordova.getActivity().runOnUiThread(runnable);
}
@Deprecated
public Context getApplicationContext() {
Log.i(LOG_TAG, "Replace ctx.getApplicationContext() with cordova.getActivity().getApplicationContext()");
return this.cordova.getActivity().getApplicationContext();
}
@Deprecated
public PackageManager getPackageManager() {
Log.i(LOG_TAG, "Replace ctx.getPackageManager() with cordova.getActivity().getPackageManager()");
return this.cordova.getActivity().getPackageManager();
}
@Deprecated
public SharedPreferences getSharedPreferences(String name, int mode) {
Log.i(LOG_TAG, "Replace ctx.getSharedPreferences() with cordova.getActivity().getSharedPreferences()");
return this.cordova.getActivity().getSharedPreferences(name, mode);
}
@Deprecated
public void unregisterReceiver(BroadcastReceiver receiver) {
Log.i(LOG_TAG, "Replace ctx.unregisterReceiver() with cordova.getActivity().unregisterReceiver()");
this.cordova.getActivity().unregisterReceiver(receiver);
}
@Deprecated
public Resources getResources() {
Log.i(LOG_TAG, "Replace ctx.getResources() with cordova.getActivity().getResources()");
return this.cordova.getActivity().getResources();
}
@Deprecated
public ComponentName startService(Intent service) {
Log.i(LOG_TAG, "Replace ctx.startService() with cordova.getActivity().startService()");
return this.cordova.getActivity().startService(service);
}
@Deprecated
public boolean bindService(Intent service, ServiceConnection conn, int flags) {
Log.i(LOG_TAG, "Replace ctx.bindService() with cordova.getActivity().bindService()");
return this.cordova.getActivity().bindService(service, conn, flags);
}
@Deprecated
public void unbindService(ServiceConnection conn) {
Log.i(LOG_TAG, "Replace ctx.unbindService() with cordova.getActivity().unbindService()");
this.cordova.getActivity().unbindService(conn);
}
}

View File

@@ -32,12 +32,12 @@ public abstract class Plugin implements IPlugin {
public String id;
public CordovaWebView webView; // WebView object
public CordovaInterface ctx; // CordovaActivity object
public LegacyContext ctx; // LegacyContext object
public CordovaInterface cordova;
/**
* Executes the request and returns PluginResult.
*
*
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param callbackId The callback id used when calling back into JavaScript.
@@ -63,13 +63,13 @@ public abstract class Plugin implements IPlugin {
*/
public void setContext(CordovaInterface ctx) {
this.cordova = ctx;
this.ctx = cordova;
this.ctx = new LegacyContext(cordova);
}
/**
* Sets the main View of the application, this is the WebView within which
* 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) {
@@ -77,8 +77,8 @@ public abstract class Plugin implements IPlugin {
}
/**
* Called when the system is about to start resuming a previous activity.
*
* Called when the system is about to start resuming a previous activity.
*
* @param multitasking Flag indicating if multitasking is turned on for app
*/
public void onPause(boolean multitasking) {

View File

@@ -92,28 +92,74 @@ public class PluginManager {
* Load plugins from res/xml/plugins.xml
*/
public void loadPlugins() {
int id = this.ctx.getActivity().getResources().getIdentifier("plugins", "xml", this.ctx.getActivity().getPackageName());
int id = this.ctx.getActivity().getResources().getIdentifier("config", "xml", this.ctx.getActivity().getPackageName());
if(id == 0)
{
id = this.ctx.getActivity().getResources().getIdentifier("plugins", "xml", this.ctx.getActivity().getPackageName());
LOG.i(TAG, "Using plugins.xml instead of config.xml. plugins.xml will eventually be deprecated");
}
if (id == 0) {
this.pluginConfigurationMissing();
//We have the error, we need to exit without crashing!
return;
}
XmlResourceParser xml = this.ctx.getActivity().getResources().getXml(id);
int eventType = -1;
String service = "", pluginClass = "";
String service = "", pluginClass = "", paramType = "";
boolean onload = false;
PluginEntry entry = null;
boolean insideFeature = false;
while (eventType != XmlResourceParser.END_DOCUMENT) {
if (eventType == XmlResourceParser.START_TAG) {
String strNode = xml.getName();
//This is for the old scheme
if (strNode.equals("plugin")) {
service = xml.getAttributeValue(null, "name");
pluginClass = xml.getAttributeValue(null, "value");
// System.out.println("Plugin: "+name+" => "+value);
onload = "true".equals(xml.getAttributeValue(null, "onload"));
entry = new PluginEntry(service, pluginClass, onload);
this.addService(entry);
} else if (strNode.equals("url-filter")) {
this.addService(entry);
}
//What is this?
else if (strNode.equals("url-filter")) {
this.urlMap.put(xml.getAttributeValue(null, "value"), service);
}
else if (strNode.equals("feature")) {
insideFeature = true;
//Check for supported feature sets (Accelerometer, Geolocation, etc)
//Set the bit for reading params
String uri = xml.getAttributeValue(null,"name");
}
else if(strNode.equals("param")) {
if(insideFeature)
{
paramType = xml.getAttributeValue(null, "name");
if(paramType.equals("service"))
service = xml.getAttributeValue(null, "value");
else if(paramType.equals("package"))
pluginClass = xml.getAttributeValue(null, "value");
if(service.length() > 0 && pluginClass.length() > 0)
{
onload = "true".equals(xml.getAttributeValue(null, "onload"));
entry = new PluginEntry(service, pluginClass, onload);
this.addService(entry);
service = "";
pluginClass = "";
}
}
}
}
else if (eventType == XmlResourceParser.END_TAG)
{
String strNode = xml.getName();
if(strNode.equals("feature"))
{
//Empty the strings to prevent plugin loading bugs
service = "";
pluginClass = "";
insideFeature = false;
}
}
try {
eventType = xml.next();
@@ -165,9 +211,9 @@ public class PluginManager {
* immediate return value. If true, either Cordova.callbackSuccess(...) or
* Cordova.callbackError(...) is called once the plugin code has executed.
*
* @return JSON encoded string with a response message and status.
* @return PluginResult to send to the page, or null if no response is ready yet.
*/
public String exec(final String service, final String action, final String callbackId, final String jsonArgs, final boolean async) {
public PluginResult exec(final String service, final String action, final String callbackId, final String jsonArgs, final boolean async) {
PluginResult cr = null;
boolean runAsync = async;
try {
@@ -183,20 +229,9 @@ public class PluginManager {
try {
// Call execute on the plugin so that it can do it's thing
PluginResult cr = plugin.execute(action, args, callbackId);
int status = cr.getStatus();
// If no result to be sent and keeping callback, then no need to sent back to JavaScript
if ((status == PluginResult.Status.NO_RESULT.ordinal()) && cr.getKeepCallback()) {
}
// Check the success (OK, NO_RESULT & !KEEP_CALLBACK)
else if ((status == PluginResult.Status.OK.ordinal()) || (status == PluginResult.Status.NO_RESULT.ordinal())) {
app.sendJavascript(cr.toSuccessCallbackString(callbackId));
}
// If error
else {
app.sendJavascript(cr.toErrorCallbackString(callbackId));
String callbackString = cr.toCallbackString(callbackId);
if (callbackString != null) {
app.sendJavascript(callbackString);
}
} catch (Exception e) {
PluginResult cr = new PluginResult(PluginResult.Status.ERROR, e.getMessage());
@@ -205,14 +240,14 @@ public class PluginManager {
}
});
thread.start();
return "";
return null;
} else {
// Call execute on the plugin so that it can do it's thing
cr = plugin.execute(action, args, callbackId);
// If no result to be sent and keeping callback, then no need to sent back to JavaScript
if ((cr.getStatus() == PluginResult.Status.NO_RESULT.ordinal()) && cr.getKeepCallback()) {
return "";
return null;
}
}
}
@@ -227,7 +262,10 @@ public class PluginManager {
}
app.sendJavascript(cr.toErrorCallbackString(callbackId));
}
return (cr != null ? cr.getJSONString() : "{ status: 0, message: 'all good' }");
if (cr == null) {
cr = new PluginResult(PluginResult.Status.NO_RESULT);
}
return cr;
}
/**
@@ -361,9 +399,9 @@ public class PluginManager {
}
private void pluginConfigurationMissing() {
System.err.println("=====================================================================================");
System.err.println("ERROR: plugin.xml is missing. Add res/xml/plugins.xml to your project.");
System.err.println("https://git-wip-us.apache.org/repos/asf?p=incubator-cordova-android.git;a=blob;f=framework/res/xml/plugins.xml");
System.err.println("=====================================================================================");
LOG.e(TAG, "=====================================================================================");
LOG.e(TAG, "ERROR: plugin.xml is missing. Add res/xml/plugins.xml to your project.");
LOG.e(TAG, "https://git-wip-us.apache.org/repos/asf?p=incubator-cordova-android.git;a=blob;f=framework/res/xml/plugins.xml");
LOG.e(TAG, "=====================================================================================");
}
}

View File

@@ -25,13 +25,13 @@ public class PluginResult {
private final int status;
private final String message;
private boolean keepCallback = false;
public PluginResult(Status status) {
this.status = status.ordinal();
this.message = "'" + PluginResult.StatusMessages[this.status] + "'";
this.message = "\"" + PluginResult.StatusMessages[this.status] + "\"";
}
public PluginResult(Status status, String message) {
this.status = status.ordinal();
this.message = JSONObject.quote(message);
@@ -61,11 +61,11 @@ public class PluginResult {
this.status = status.ordinal();
this.message = ""+b;
}
public void setKeepCallback(boolean b) {
this.keepCallback = b;
}
public int getStatus() {
return status;
}
@@ -73,23 +73,36 @@ public class PluginResult {
public String getMessage() {
return message;
}
public boolean getKeepCallback() {
return this.keepCallback;
}
public String getJSONString() {
return "{\"status\":" + this.status + ",\"message\":" + this.message + ",\"keepCallback\":" + this.keepCallback + "}";
}
public String toCallbackString(String callbackId) {
// If no result to be sent and keeping callback, then no need to sent back to JavaScript
if ((status == PluginResult.Status.NO_RESULT.ordinal()) && keepCallback) {
return null;
}
// Check the success (OK, NO_RESULT & !KEEP_CALLBACK)
if ((status == PluginResult.Status.OK.ordinal()) || (status == PluginResult.Status.NO_RESULT.ordinal())) {
return toSuccessCallbackString(callbackId);
}
return toErrorCallbackString(callbackId);
}
public String toSuccessCallbackString(String callbackId) {
return "cordova.callbackSuccess('"+callbackId+"',"+this.getJSONString()+");";
}
public String toErrorCallbackString(String callbackId) {
return "cordova.callbackError('"+callbackId+"', " + this.getJSONString()+ ");";
}
public static String[] StatusMessages = new String[] {
"No result",
"OK",
@@ -102,7 +115,7 @@ public class PluginResult {
"JSON error",
"Error"
};
public enum Status {
NO_RESULT,
OK,

View File

@@ -1,29 +0,0 @@
# Cordova Upgrade Guide #
This document is for people who need to upgrade their Cordova versions from an older version to a current version of Cordova.
- To upgrade to 1.8.0, please go from 1.7.0
- To upgrade from 1.7.0, please go from 1.6.0
## Upgrade to 1.8.0 from 1.7.0 ##
1. Remove cordova-1.7.0.jar from the libs directory in your project
2. Add cordova-1.8.0.jar to the libs directory in your project
3. If you are using Eclipse, please refresh your eclipse project and do a clean
4. Copy the new cordova-1.7.0.js into your project
5. Update your HTML to sue the new cordova-1.7.0.js file
6. Update the res/xml/plugins.xml to be the same as the one found in framework/res/xml/plugins.xml
## Upgrade to 1.7.0 from 1.6.0 ##
1. Remove cordova-1.6.0.jar from the libs directory in your project
2. Add cordova-1.6.0.jar to the libs directory in your project
3. If you are using Eclipse, please refresh your eclipse project and do a clean
4. Copy the new cordova-1.6.0.js into your project
5. Update your HTML to sue the new cordova-1.6.0.js file
6. Update the res/xml/plugins.xml to be the same as the one found in framework/res/xml/plugins.xml

View File

@@ -1,42 +0,0 @@
Bryce Curtis (5):
[CB-352] Support initializing DroidGap with existing WebView, WebViewClient and webViewChrome. [CB-353] Create PluginEntry object to use by PluginManager.
[CB-367] Back button event should fire on key up not key down Also changed menu key and search key to be consistent.
Tests to verify Android native features.
[CB-423] Problem displaying patch-9 splash screen.
Update project template cordova.js reference and title.
Fil Maj (6):
switched from "require" syntax to "cordova.require"
cordova.require("cordova") is pretty funny. wish i didnt write it
updates to JS: removing require+define from global scope, tweaking geolocation code, online/offline events fire on document now
removed old javascript files and removed unused target + commented out lines in build.xml
spacing fixes, null check in getPhoneType in contacts, returning error integers instead of objects in contacts
updating network status plugin label and updating cordova-js to latest
Joe Bowser (11):
We show the default 404 on non-resolved domains
Fixing CB-210 with patch and adding fix for CB-210
Tweaked File Transfer to fix CB-74
Changing to the modern icon
Added temporary Cordova splash for now
Checking for the callback server before we call sendJavascript for the Kindle Fire, CB-247
Fixing CB-343: We need to respect the whitelist
Fixing a bug with File Upload on Android where Chunked mode isn't used by default
First stab at CB-21, I really need more info before I can close this
Tagged 1.6rc1
Fixing the template, since this doesn't have to be unit tested. :)
macdonst (12):
CB-383: Fixes issue with misspelled destinationType for Camera.getPicture()
Fix for CB-389: resolveLocalFileSystemURI does not work on a resized image captured from Camera.getPicture()
Fixing license header in com.phonegap.api.PluginManager
CB-321: Media API: 'mediaSuccess' callback param to new Media() is called soon after new obj created
CB-163: contactFindOptions.filter does not work as expected on Android
CB-426: camera.getPicture ignores mediaType in 1.5
Updating cordova.android.js for CB-421 and CB-426
CB-438: File metadata.modificationTime returns an invalid date
Return MediaError object and not error code from native side of Media API
CB-446: Enhance setting data source for local files in AudioPlayer
CB-453: FileWriter.append - Chinese characters are not appended to the file correctly
CB-446: Enhance setting data source for local files in AudioPlayer

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>PhoneGapViewTestActivity</name>
<name>CordovaViewTestActivity</name>
<comment></comment>
<projects>
</projects>

View File

@@ -35,7 +35,6 @@ $
<h4>Page 3</h4>
Press the 3 buttons below. You should stay on same page.<br>
Press "backbutton" 4 times. This will go back to #test3, #test2, #test1, then return to previous Page 2.<br>
(NOTE: IS THIS CORRECT BEHAVIOR?)
</div>
<a href="sample3.html#test1" class="btn large">page3#test1</a>
<a href="sample3.html#test2" class="btn large">page3#test2</a>

View File

@@ -17,5 +17,5 @@
under the License.
*/
document.write('<script type="text/javascript" charset="utf-8" src="../cordova-1.7.0.js"></script>');
document.write('<script type="text/javascript" charset="utf-8" src="cordova-1.7.0.js"></script>');
document.write('<script type="text/javascript" charset="utf-8" src="../cordova.android.js"></script>');
document.write('<script type="text/javascript" charset="utf-8" src="cordova.android.js"></script>');

View File

@@ -8,4 +8,4 @@
# project structure.
# Project target.
target=Google Inc.:Google APIs:15
target=android-16

View File

@@ -23,7 +23,7 @@
android:orientation="vertical" >
<org.apache.cordova.CordovaWebView
android:id="@+id/phoneGapView"
android:id="@+id/cordovaWebView"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />

View File

@@ -24,8 +24,8 @@ import org.json.JSONException;
import android.content.Intent;
import com.phonegap.api.Plugin;
import com.phonegap.api.PluginResult;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.PluginResult;
/**
* This class provides a service.

View File

@@ -0,0 +1,87 @@
package org.apache.cordova.test;
import org.apache.cordova.CordovaWebView;
import android.test.ActivityInstrumentationTestCase2;
import android.view.KeyEvent;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
public class BackButtonMultiPageTest extends ActivityInstrumentationTestCase2<backbuttonmultipage> {
private int TIMEOUT = 1000;
backbuttonmultipage testActivity;
private FrameLayout containerView;
private LinearLayout innerContainer;
private CordovaWebView testView;
public BackButtonMultiPageTest() {
super("org.apache.cordova.test", backbuttonmultipage.class);
}
protected void setUp() throws Exception {
super.setUp();
testActivity = this.getActivity();
containerView = (FrameLayout) testActivity.findViewById(android.R.id.content);
innerContainer = (LinearLayout) containerView.getChildAt(0);
testView = (CordovaWebView) innerContainer.getChildAt(0);
}
public void testPreconditions(){
assertNotNull(innerContainer);
assertNotNull(testView);
}
public void testViaHref() {
testView.sendJavascript("window.location = 'sample2.html';");
sleep();
String url = testView.getUrl();
assertTrue(url.endsWith("sample2.html"));
testView.sendJavascript("window.location = 'sample3.html';");
sleep();
url = testView.getUrl();
assertTrue(url.endsWith("sample3.html"));
boolean didGoBack = testView.backHistory();
sleep();
url = testView.getUrl();
assertTrue(url.endsWith("sample2.html"));
assertTrue(didGoBack);
didGoBack = testView.backHistory();
sleep();
url = testView.getUrl();
assertTrue(url.endsWith("index.html"));
assertTrue(didGoBack);
}
public void testViaLoadUrl() {
testView.loadUrl("file:///android_asset/www/backbuttonmultipage/sample2.html");
sleep();
String url = testView.getUrl();
assertTrue(url.endsWith("sample2.html"));
testView.loadUrl("file:///android_asset/www/backbuttonmultipage/sample3.html");
sleep();
url = testView.getUrl();
assertTrue(url.endsWith("sample3.html"));
boolean didGoBack = testView.backHistory();
sleep();
url = testView.getUrl();
assertTrue(url.endsWith("sample2.html"));
assertTrue(didGoBack);
didGoBack = testView.backHistory();
sleep();
url = testView.getUrl();
assertTrue(url.endsWith("index.html"));
assertTrue(didGoBack);
}
private void sleep() {
try {
Thread.sleep(TIMEOUT);
} catch (InterruptedException e) {
fail("Unexpected Timeout");
}
}
}

View File

@@ -20,8 +20,9 @@
package org.apache.cordova.test;
import org.apache.cordova.CordovaWebView;
import com.phonegap.api.PluginManager;
import org.apache.cordova.api.PluginManager;
import android.app.Instrumentation;
import android.test.ActivityInstrumentationTestCase2;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
@@ -32,6 +33,8 @@ public class CordovaActivityTest extends ActivityInstrumentationTestCase2<Cordov
private FrameLayout containerView;
private LinearLayout innerContainer;
private CordovaWebView testView;
private Instrumentation mInstr;
private int TIMEOUT = 1000;
@SuppressWarnings("deprecation")
public CordovaActivityTest()
@@ -41,6 +44,7 @@ public class CordovaActivityTest extends ActivityInstrumentationTestCase2<Cordov
protected void setUp() throws Exception {
super.setUp();
mInstr = this.getInstrumentation();
testActivity = this.getActivity();
containerView = (FrameLayout) testActivity.findViewById(android.R.id.content);
innerContainer = (LinearLayout) containerView.getChildAt(0);
@@ -63,6 +67,23 @@ public class CordovaActivityTest extends ActivityInstrumentationTestCase2<Cordov
String className = innerContainer.getClass().getSimpleName();
assertTrue(className.equals("LinearLayoutSoftKeyboardDetect"));
}
public void testPauseAndResume()
{
mInstr.callActivityOnPause(testActivity);
sleep();
assertTrue(testView.isPaused());
mInstr.callActivityOnResume(testActivity);
sleep();
assertFalse(testView.isPaused());
}
private void sleep() {
try {
Thread.sleep(TIMEOUT);
} catch (InterruptedException e) {
fail("Unexpected Timeout");
}
}
}

View File

@@ -23,6 +23,7 @@ import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.IPlugin;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
@@ -34,45 +35,42 @@ public class CordovaDriverAction extends Activity implements CordovaInterface {
super.onCreate(savedInstanceState);
}
@Override
public void bindBackButton(boolean arg0) {
// TODO Auto-generated method stub
}
@Override
public void cancelLoadUrl() {
// TODO Auto-generated method stub
}
@Override
public Activity getActivity() {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean isBackButtonBound() {
// TODO Auto-generated method stub
return false;
}
@Override
public Object onMessage(String arg0, Object arg1) {
// TODO Auto-generated method stub
return null;
}
@Override
public void setActivityResultCallback(IPlugin arg0) {
// TODO Auto-generated method stub
}
@Override
public void startActivityForResult(IPlugin arg0, Intent arg1, int arg2) {
// TODO Auto-generated method stub
}
public Context getContext() {
return this;
}
}

View File

@@ -19,8 +19,9 @@
package org.apache.cordova.test;
import org.apache.cordova.CordovaWebView;
import com.phonegap.api.PluginManager;
import org.apache.cordova.api.PluginManager;
import android.app.Instrumentation;
import android.test.ActivityInstrumentationTestCase2;
import android.view.View;
@@ -33,13 +34,13 @@ public class CordovaTest extends
private String rString;
public CordovaTest() {
super("com.phonegap.test.activities", CordovaWebViewTestActivity.class);
super("org.apache.cordova.test.activities", CordovaWebViewTestActivity.class);
}
protected void setUp() throws Exception {
super.setUp();
testActivity = this.getActivity();
testView = testActivity.findViewById(R.id.phoneGapView);
testView = testActivity.findViewById(R.id.cordovaWebView);
}
public void testPreconditions() {
@@ -98,6 +99,7 @@ public class CordovaTest extends
assertTrue(url.equals("file:///android_asset/www/index.html"));
}
*/
private void sleep() {
try {

View File

@@ -1,37 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova.test;
import org.openqa.selenium.android.library.ViewAdapter;
import org.openqa.selenium.android.library.ViewFactory;
import org.apache.cordova.CordovaWebView;
import android.app.Activity;
import android.webkit.WebView;
public class CordovaViewFactory implements ViewFactory {
public ViewAdapter createNewView(Activity arg0) {
// TODO Auto-generated method stub
return new ViewAdapter("org.apache.cordova.CordovaWebView", new CordovaWebView(arg0));
}
}

View File

@@ -24,12 +24,13 @@ import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.IPlugin;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
public class CordovaWebViewTestActivity extends Activity implements CordovaInterface {
CordovaWebView phoneGap;
CordovaWebView cordovaWebView;
/** Called when the activity is first created. */
@Override
@@ -38,59 +39,55 @@ public class CordovaWebViewTestActivity extends Activity implements CordovaInter
setContentView(R.layout.main);
phoneGap = (CordovaWebView) findViewById(R.id.phoneGapView);
cordovaWebView = (CordovaWebView) findViewById(R.id.cordovaWebView);
phoneGap.loadUrl("file:///android_asset/www/index.html");
cordovaWebView.loadUrl("file:///android_asset/www/index.html");
}
public void onDestroy()
{
super.onDestroy();
if (phoneGap.pluginManager != null) {
phoneGap.pluginManager.onDestroy();
if (cordovaWebView.pluginManager != null) {
cordovaWebView.pluginManager.onDestroy();
}
}
@Override
public void startActivityForResult(IPlugin command, Intent intent, int requestCode) {
// TODO Auto-generated method stub
}
@Override
public void setActivityResultCallback(IPlugin plugin) {
// TODO Auto-generated method stub
}
@Override
public void bindBackButton(boolean override) {
// TODO Auto-generated method stub
}
@Override
public boolean isBackButtonBound() {
// TODO Auto-generated method stub
return false;
}
@Override
public Activity getActivity() {
// TODO Auto-generated method stub
return this;
}
@Override
public void cancelLoadUrl() {
// TODO Auto-generated method stub
}
@Override
public Object onMessage(String id, Object data) {
// TODO Auto-generated method stub
return null;
}
public Context getContext() {
return this;
}
}

View File

@@ -44,11 +44,11 @@ public class ErrorUrlTest extends ActivityInstrumentationTestCase2<errorurl> {
private void sleep() {
try {
Thread.sleep(TIMEOUT);
Thread.sleep(TIMEOUT);
} catch (InterruptedException e) {
fail("Unexpected Timeout");
fail("Unexpected Timeout");
}
}
}
}

View File

@@ -41,7 +41,7 @@ public class GapClientTest extends ActivityInstrumentationTestCase2<CordovaWebVi
private CordovaChromeClient appCode;
public GapClientTest() {
super("com.phonegap.test.activities",CordovaWebViewTestActivity.class);
super("org.apache.cordova.test.activities",CordovaWebViewTestActivity.class);
}
protected void setUp() throws Exception{

View File

@@ -20,7 +20,7 @@
package org.apache.cordova.test;
import org.apache.cordova.CordovaWebView;
import com.phonegap.api.PluginManager;
import org.apache.cordova.api.PluginManager;
import android.test.ActivityInstrumentationTestCase2;
import android.view.View;
@@ -37,7 +37,7 @@ public class PluginManagerTest extends ActivityInstrumentationTestCase2<CordovaW
private PluginManager pMan;
public PluginManagerTest() {
super("com.phonegap.test.activities",CordovaWebViewTestActivity.class);
super("org.apache.cordova.test.activities",CordovaWebViewTestActivity.class);
}
protected void setUp() throws Exception{

View File

@@ -1,90 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova.test;
import org.apache.cordova.CordovaWebViewClient;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CordovaChromeClient;
import org.apache.cordova.test.CordovaViewFactory;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.android.library.AndroidWebDriver;
import org.openqa.selenium.android.library.ChromeClientWrapper;
import org.openqa.selenium.android.library.ViewClientWrapper;
import android.test.ActivityInstrumentationTestCase2;
public class WebDriverTest extends ActivityInstrumentationTestCase2<CordovaDriverAction> {
private static final long TIMEOUT = 5000;
private CordovaDriverAction testActivity;
private CordovaWebView testView;
private CordovaViewFactory viewFactory;
private CordovaChromeClient appCode;
private CordovaWebViewClient viewHandler;
private AndroidWebDriver testDriver;
private ViewClientWrapper viewClientWrapper;
private ChromeClientWrapper chromeClientWrapper;
public WebDriverTest() {
super("com.phonegap.test.activities",CordovaDriverAction.class);
}
protected void setUp() throws Exception{
super.setUp();
testActivity = this.getActivity();
viewFactory = new CordovaViewFactory();
appCode = new CordovaChromeClient(testActivity);
viewHandler = new CordovaWebViewClient(testActivity);
viewClientWrapper = new ViewClientWrapper("org.apache.cordova.CordovaWebViewClient", viewHandler);
chromeClientWrapper = new ChromeClientWrapper("org.apache.cordova.CordovaChromeClient", appCode);
testDriver = new AndroidWebDriver(testActivity, viewFactory, viewClientWrapper, chromeClientWrapper);
testView = (CordovaWebView) testDriver.getWebView();
viewHandler.setWebView(testView);
appCode.setWebView(testView);
}
public void testPreconditions(){
assertNotNull(testView);
}
public void testWebLoad() {
testDriver.get("file:///android_asset/www/index.html");
sleep();
String url = testView.getUrl();
//Check the sanity!
boolean result = url.equals("file:///android_asset/www/index.html");
assertTrue(result);
WebElement platformSpan = testDriver.findElement(By.id("platform"));
String text = platformSpan.getText();
assertTrue(text.equals("Android"));
}
private void sleep() {
try {
Thread.sleep(TIMEOUT);
} catch (InterruptedException e) {
fail("Unexpected Timeout");
}
}
}