Compare commits

...

99 Commits

Author SHA1 Message Date
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
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
51 changed files with 2632 additions and 1544 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

@@ -1 +1 @@
1.9.0
2.1.0rc1

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,100 @@
html,
body {
height:100%;
font-size:12px;
width:100%;
}
html {
display:table;
}
body {
background-color:#A7A7A7;
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)
);
display:table-cell;
font-family:'HelveticaNeue-Light', 'HelveticaNeue', Helvetica, Arial, sans-serif;
text-transform:uppercase;
vertical-align:middle;
}
.app {
background-image:url(../img/cordova.png);
background-repeat:no-repeat;
margin:0px auto;
width:275px;
}
h1 {
font-size:2em;
font-weight:300;
margin:0px;
overflow:visible;
padding:0px;
text-align:center;
}
.status {
background-color:#333333;
border-radius:4px;
-webkit-border-radius:4px;
color:#FFFFFF;
font-size:1em;
margin:0px auto;
padding:2px 10px;
text-align:center;
width:100%;
max-width:175px;
}
.status.complete {
background-color:#4B946A;
}
.hide {
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;
}
/* portrait */
/* @media screen and (max-aspect-ratio: 1/1) */
.app {
background-position:center top;
height:100px; /* adds enough room for text */
padding:180px 0px 0px 0px; /* background height - shadow offset */
}
/* lanscape (when wide enough) */
@media screen and (min-aspect-ratio: 1/1) and (min-width:445px) {
.app {
background-position:left center;
height:140px; /* height + padding = background image size */
padding-left:170px; /* background width */
padding-top:60px; /* center the text */
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -1,60 +1,24 @@
<!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.
-->
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=320; user-scalable=no" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>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;" />
<link rel="stylesheet" type="text/css" href="css/index.css" />
<title>Hello Cordova</title>
</head>
<body>
<div class="app">
<h1>Apache Cordova</h1>
<div id="deviceready">
<p class="status pending blink">Connecting to Device</p>
<p class="status complete blink hide">Device is Ready</p>
</div>
</div>
<script type="text/javascript" src="cordova-2.1.0rc1.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,20 @@
var app = {
initialize: function() {
this.bind();
},
bind: function() {
document.addEventListener('deviceready', this.deviceready, false);
},
deviceready: function() {
// note that this is an event handler so the scope is that of the event
// so we need to call app.report(), and not this.report()
app.report('deviceready');
},
report: function(id) {
console.log("report:" + id);
// hide the .pending <p> and show the .complete <p>
document.querySelector('#' + id + ' .pending').className += ' hide';
var completeElem = document.querySelector('#' + id + ' .complete');
completeElem.className = completeElem.className.split('hide').join('');
}
};

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.0rc1.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,20 @@
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" />
<plugins>
<plugin name="App" value="org.apache.cordova.App"/>
<plugin name="Geolocation" value="org.apache.cordova.GeoBroker"/>
@@ -35,4 +49,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

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

401
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.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.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,121 @@ 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) {
e.printStackTrace();
}
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) {
e.printStackTrace();
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,81 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
}
else {
// 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);
Bitmap bitmap = getScaledBitmap(imagePath);
// 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();
}
if (rotate != 0) {
Matrix matrix = new Matrix();
matrix.setRotate(rotate);
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}
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);
}
}
bitmap.recycle();
bitmap = null;
System.gc();
}
bitmap.recycle();
bitmap = null;
System.gc();
}
}
else if (resultCode == Activity.RESULT_CANCELED) {
@@ -533,20 +543,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 +643,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 +655,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 +682,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 +735,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)
settings.setAllowUniversalAccessFromFileURLs(true);
// 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,71 @@ 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;
}
}

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)
@@ -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.0rc1"; // 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;
@@ -600,6 +601,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 +611,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 +626,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 +638,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 +648,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 +658,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 +669,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 +714,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 +740,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 +757,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.
@@ -833,10 +811,7 @@ 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 +819,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 +918,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 +943,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;
}
@@ -1055,5 +1032,20 @@ public class DroidGap extends Activity implements CordovaInterface {
}
return null;
}
/*
* (non-Javadoc)
* @see android.app.Activity#onKeyUp(int, android.view.KeyEvent)
*/
@Override
public boolean onKeyUp(int keyCode, KeyEvent event)
{
if (appView.canGoBack() || keyCode != KeyEvent.KEYCODE_BACK)
return appView.onKeyUp(keyCode, event);
else
return super.onKeyUp(keyCode, event);
}
}

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();
@@ -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,18 @@ 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
if (chunkedMode) {
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
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO && chunkedMode) {
conn.setChunkedStreamingMode(maxBufferSize);
} 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);
}
conn.setRequestProperty("Transfer-Encoding", "chunked");
dos = new DataOutputStream( conn.getOutputStream() );
//We don't want to change encoding, we just want this to write for all Unicode.
@@ -255,10 +268,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);

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,9 +92,16 @@ 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;
@@ -165,9 +172,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 +190,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 +201,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 +223,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 +360,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

@@ -11,7 +11,7 @@ This document is for people who need to upgrade their Cordova versions from an o
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
5. Update your HTML to use 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
@@ -21,7 +21,7 @@ This document is for people who need to upgrade their Cordova versions from an o
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
5. Update your HTML to use 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

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

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

@@ -22,6 +22,7 @@ package org.apache.cordova.test;
import org.apache.cordova.CordovaWebView;
import com.phonegap.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

@@ -21,6 +21,7 @@ package org.apache.cordova.test;
import org.apache.cordova.CordovaWebView;
import com.phonegap.api.PluginManager;
import android.app.Instrumentation;
import android.test.ActivityInstrumentationTestCase2;
import android.view.View;
@@ -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,6 +24,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;
@@ -52,45 +53,41 @@ public class CordovaWebViewTestActivity extends Activity implements CordovaInter
}
}
@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

@@ -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");
}
}
}