Compare commits

..

558 Commits

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

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

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

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

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

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

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

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

Also replaced calls to `stripFileProtocol` with the more general-purpose `getRealPathFromURI`.  This helps support some operations on content URIs.
2013-02-11 22:45:14 -05:00
Andrew Grieve 8ab7278db2 Code clean-up of FileTransfer
- Fix warnings about toLowerCase()
- Don't assume connections are HTTP (fails for file://)
- Use StringBuilder
- Remove no-ops of disconnect() & keep-alive
2013-02-11 22:35:17 -05:00
Joe Bowser db099e7722 Preparing for 2.4.0 2013-02-04 11:12:52 -08:00
Joe Bowser fcc01bc37e Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/cordova-android 2013-01-31 10:45:02 -08:00
Joe Bowser a18dacf5f2 CB-2296 - Adding the screenshot configuration 2013-01-31 10:44:46 -08:00
Anis Kadri 77f9cae50b CB-1961 update to create script for android/windows 2013-01-30 14:57:49 -08:00
Joe Bowser 3610bbf21b CB-2296: Adding deprecation notices for removing the setProperties methods 2013-01-30 11:31:59 -08:00
Joe Bowser d5e3be9a55 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/cordova-android 2013-01-30 11:13:28 -08:00
Joe Bowser 80b369d6d5 CB-2296: Added parsing for Integer and Boolean parameters in config.xml 2013-01-30 11:13:07 -08:00
Andrew Grieve d29eb84010 Tagging 2.4.0rc2 2013-01-30 09:51:40 -05:00
Joe Bowser 381ce535bf Merge branch 'puritytool' 2013-01-29 15:20:21 -08:00
Andrew Grieve 2e20bb0639 [CB-2293] Fix typo bufferSize->bytesRead.
Also adds a log statement to print out amount uploaded when an
IOException is thrown.
2013-01-29 13:47:15 -05:00
Joe Bowser 56cd24797e Fix for CB-2284. 401s are appearing when we hit them 2013-01-25 16:39:02 -08:00
Joe Bowser 431ca99c23 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/cordova-android 2013-01-25 14:41:26 -08:00
Joe Bowser 6ced2ff293 CB-2292: Added a check for width and height. You can't scale nothing because you can't divide by zero. 2013-01-25 14:41:14 -08:00
Joe Bowser 31055bb303 Update to purity, adding better touch support 2013-01-25 14:39:44 -08:00
Fil Maj 24a53e39dd hey new line 2013-01-23 18:31:30 -08:00
Fil Maj 2ab113b695 Removing notice about incubation in the readme. 2013-01-23 12:21:49 -08:00
Fil Maj 9a0481a750 tweaking readme 2013-01-23 12:17:42 -08:00
Fil Maj 09035eb4c4 Setting template AndroidManifest values for version to 1.0 and code to 1. 2013-01-23 12:13:07 -08:00
Joe Bowser 1adf268e71 Updates to tests, including the use of Purity 2013-01-22 15:18:21 -08:00
Joe Bowser 23f57ad5a7 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/cordova-android into puritytool 2013-01-21 14:59:04 -08:00
Joe Bowser d9b15cf69e Updating with fixed JS from CB-2279 2013-01-21 13:47:26 -08:00
Joe Bowser dbfe12a993 Partial fix for CB-2269, we probably shoudln't call Config.init twice 2013-01-18 16:41:10 -08:00
Joe Bowser 2b32dfd99d Partial Fix for CB-2269
* Moved Config.init call into DroidGap for the most common use-case
  * The CordovaWebView docuemntation still has to be updated
  * The template has to be changed, since we don't want to have two different types of config
2013-01-18 16:37:55 -08:00
Joe Bowser 679de40780 2.4.0rc1 updating 2013-01-18 15:33:38 -08:00
Joe Bowser 66f15fdd37 Adding purity to the test suite. Purity is a test class that may be renamed later 2013-01-18 15:00:02 -08:00
Joe Bowser 038f0e45b1 Upgrading Test Project properties to 4.2 2013-01-18 11:44:09 -08:00
Joe Bowser 033bfcc804 This should be Android 4.2, not Google APIs 2013-01-18 11:36:12 -08:00
Braden Shepherdson fa87c08a29 Merge branch 'master' into arraybuffers 2013-01-18 12:33:25 -05:00
Braden Shepherdson dfb799739a Change binaryEcho to echoArrayBuffer. 2013-01-18 12:32:52 -05:00
Joe Bowser 1193f7ed22 Fixed Android 4.2 filepicker, time for master 2013-01-17 17:00:50 -08:00
Braden Shepherdson 7530c21a9f Full binary data support.
- Removed BinaryEcho; made Echo support a new binaryEcho action.
- Added CordovaArgs wrapper for JSONArray, and a new overload for
  execute that accepts a CordovaArgs. There is now a default
  implementation for the JSONArray version of execute that builds a
  CordovaArgs and calls that version of execute. The default
  implementation for the CordovaArgs execute is to return false.
- Added byte[] version of success() in CallbackContext.
2013-01-17 15:58:38 -05:00
Simon MacDonald a120614617 Initial input type=file support 2013-01-17 10:10:02 -05:00
Andrew Grieve 0311f0db38 CB-2208 Fix crash on File mobile-spec tests
Crash seems to not happen on every platform, but was showin up on the
x86 4.0.3 emulator.
2013-01-17 09:55:37 -05:00
Braden Shepherdson 547b683e61 Remove debugging. 2013-01-15 13:24:59 -05:00
Braden Shepherdson ff1d943a69 Add CordovaArguments helper to decode ArrayBuffers 2013-01-15 13:21:09 -05:00
Braden Shepherdson 15a5c89e86 Removed debugging output. 2013-01-15 11:52:06 -05:00
Braden Shepherdson 03b974ee3f Working Base64 encoding. 2013-01-15 11:48:09 -05:00
Braden Shepherdson f145605c63 Mostly working arraybuffer changes, needs Base64. 2013-01-15 11:16:32 -05:00
Joe Bowser 29230d0316 CB-2171: Patches are welcome. 2013-01-11 11:26:36 -08:00
Joe Bowser 57fc49ddc2 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/cordova-android 2013-01-11 10:24:50 -08:00
Simon MacDonald 5ac6853fed CB-2154: navigator.splashscreen.show() broken in Phonegap 2.2 and 2.3.0rc2
Fixed the splashscreen so it will show for a minimum of 3 seconds if the user has not called loadUrl with a timeout in their main activity.
2013-01-11 10:24:03 -08:00
Fil Maj b870214cca Fixes CB-2204: if bin/create fails, exit with code 1 2013-01-11 10:24:03 -08:00
Braden Shepherdson 55074b925f Added a comment to explain where the start page is getting set. 2013-01-11 10:24:03 -08:00
Braden Shepherdson 958424ce59 Add configurable start location to config.xml and template
Still possible to hardcode, there's a comment in the template showing
how that can be done.
2013-01-11 10:24:02 -08:00
Braden Shepherdson d04fc289ac Move config.xml parsing into its own Config class
Now the parsing happens very early in the bootstrap process, before
loadUrl() is called. This enables a future change to put the start page
in config.xml instead of hardcoding it.
2013-01-11 10:24:02 -08:00
Braden Shepherdson e14edf134d Merge branch 'master' into start_location 2013-01-11 12:13:43 -05:00
Simon MacDonald dbb127447f CB-2154: navigator.splashscreen.show() broken in Phonegap 2.2 and 2.3.0rc2
Fixed the splashscreen so it will show for a minimum of 3 seconds if the user has not called loadUrl with a timeout in their main activity.
2013-01-10 22:06:39 -05:00
Fil Maj dc94fc39ec Fixes CB-2204: if bin/create fails, exit with code 1 2013-01-10 18:29:36 -08:00
Joe Bowser 6db9a7cb12 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/cordova-android into CordovaActivity 2013-01-10 16:26:24 -08:00
Benn Mapes 1f39386616 Fixed broken functions that were deprecated 2013-01-10 15:07:26 -08:00
Benn Mapes 25aef945d1 Deleted depricated methods 2013-01-10 14:51:20 -08:00
Joe Bowser c9aa43afe0 CB-2185: Fixing getMimeType to get the mimetype of the file if it is upper-case 2013-01-10 11:32:37 -08:00
Braden Shepherdson 913e177f6f Added a comment to explain where the start page is getting set. 2013-01-09 17:36:12 -05:00
Braden Shepherdson ae431aec12 Add configurable start location to config.xml and template
Still possible to hardcode, there's a comment in the template showing
how that can be done.
2013-01-09 16:48:43 -05:00
Braden Shepherdson 8ac15048cd Move config.xml parsing into its own Config class
Now the parsing happens very early in the bootstrap process, before
loadUrl() is called. This enables a future change to put the start page
in config.xml instead of hardcoding it.
2013-01-09 14:22:23 -05:00
Simon MacDonald a1cfe87f1e CB-2093: NullPointerException when attaching image from Gallery that contains spaces in the path
Guarding against a null string being passed into FileUtils.getMimeType()
2013-01-08 21:10:50 -05:00
Simon MacDonald c130396d4e Merge branch 'master' of http://git-wip-us.apache.org/repos/asf/cordova-android 2013-01-08 18:48:44 -05:00
Joe Bowser bc2e7cf317 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/cordova-android 2013-01-08 13:54:44 -08:00
Joe Bowser 7ace1d652d Fixing CB-2171, 0 byte file in filesystem on 404 from server. Patches are welcome. 2013-01-08 13:54:38 -08:00
Simon MacDonald 26effd1def Test for correctOrientation not rotate=0
when determining if we are in the special case where the image should just be retureturned to the user without modification.
2013-01-08 15:21:55 -05:00
Braden Shepherdson 5f6824e5dd Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/cordova-android into file_slicing 2013-01-07 18:13:54 -05:00
Braden Shepherdson 4589bdd31f And support for slicing in readAsDataURL. 2013-01-04 15:00:55 -05:00
Joe Bowser 72e0b49e0b Fixed CB-2089 after I tagged for the release. This will have to sit in 2.4.0 2013-01-03 10:18:41 -08:00
Joe Bowser 3caa45d860 Typo. Missed index.html in incrementation 2013-01-02 17:10:00 -08:00
Joe Bowser 7c069f14f8 Incrementing version to 2.3.0 final 2013-01-02 17:07:21 -08:00
Braden Shepherdson 552885dbd3 Add support for reading slices of text files. 2013-01-02 15:34:39 -08:00
Joe Bowser 6efeb1471c Incremeting version to 2.3.0rc2 2012-12-10 14:26:38 -08:00
Simon MacDonald 01f062d2ba Saving a contact with an email type of work sets it to home 2012-12-08 22:02:34 -05:00
Joe Bowser 2a42c463d2 CB-1973: We don't need to log three damn times! If it wasn't for HTC, I'd remove this entirely. 2012-12-06 10:40:57 -08:00
Joe Bowser 182843edf6 CB-1850 change: Model is getModel, name is getProduct 2012-12-05 14:58:47 -08:00
Simon MacDonald 9a9d36e9d9 CB-1969: Searching for emails in Contacts throws an exception always errors out 2012-12-05 16:28:27 -05:00
Simon MacDonald 7d5249eea6 Clean up warnings in InAppBrowser 2012-12-05 12:09:17 -05:00
Joe Bowser f7910c41c3 Changing FILL_PARENT to MATCH_PARENT, removing Eclipse deprecation warnings 2012-12-04 14:14:19 -08:00
Joe Bowser 3973f4f952 More back button woes! The Fix for CB-1960 did weird things on both my end and Simon's end, sadly they're both different things. This should simply the code and resolve it. Sadly, all the unit tests pass as usual. 2012-12-04 12:06:05 -08:00
Joe Bowser 8a19769a47 Fix for CB-1960, we now check to see if any view is on the WebView, since they won't always be custom 2012-12-04 09:40:57 -08:00
Brian M Dube c0ee593c10 [CB-1959] Display usage and exit when no arguments given 2012-12-02 21:21:24 -05:00
Andrew Grieve c806451b8a Update Android SDK verions and commons-codec version in README.md. 2012-12-02 21:19:58 -05:00
Shazron Abdullah 00e5ff1964 Updated cordova.android.js for CB-1950 - InAppBrowser events 2012-11-30 05:47:37 -08:00
Shazron Abdullah 432aec62a9 [CB-1950] InAppBrowser - support events 2012-11-30 05:40:59 -08:00
Joe Bowser c8ec7e5191 Doing the merge and fixing DroidGap up a bit 2012-11-28 15:30:22 -08:00
Joe Bowser a0d2b96de6 Merge commit and hacking on CordovaWebView 2012-11-28 15:10:36 -08:00
Joe Bowser 2c202b82d7 Partial Fix/Workaround for CB-1856. Also removed old deprecated code 2012-11-28 14:42:55 -08:00
Simon MacDonald a42dc08756 Start adding events to InAppBrowser 2012-11-28 15:44:01 -05:00
Simon MacDonald 48f58110fe CB-1938: Regression, Android back button event is no longer fired 2012-11-27 12:18:49 -05:00
Simon MacDonald 7b3724972b Tagging to 2.3.0rc1 2012-11-26 16:09:52 -05:00
Simon MacDonald 9ca2a16218 Updating JS so that InAppBrowser will work out of the box 2012-11-23 09:38:49 -05:00
Simon MacDonald f1e8400abf Merge branch 'master' of http://git-wip-us.apache.org/repos/asf/cordova-android 2012-11-22 22:28:34 -05:00
Andrew Grieve 11e0ffa90a Add @JavascriptInterface annotations to ExposedJsApi.
And re-enable the JS bridge on 4.2.
https://issues.apache.org/jira/browse/CB-1879
2012-11-22 22:23:51 -05:00
Anis Kadri 2ee4326a4d updating create command 2012-11-22 22:23:51 -05:00
Anis Kadri 226e72ac18 adding release command 2012-11-22 22:23:51 -05:00
Anis Kadri 65c78b8f3f removing ApplicationInfo.class 2012-11-22 22:23:51 -05:00
Anis Kadri 6137c7ca06 removing appinfo.jar 2012-11-22 22:23:51 -05:00
Simon MacDonald 5bebf11b37 CB-1888: Can't add a Photo from a HTTPS address to Contact 2012-11-22 22:23:51 -05:00
Anis Kadri 68161d2714 refactoring windows scripts 2012-11-22 22:23:51 -05:00
Anis Kadri a6473cb826 adding install function 2012-11-22 22:23:51 -05:00
Anis Kadri 0084c6f96a refactoring android commands 2012-11-22 22:23:51 -05:00
Simon MacDonald a87825dbee CB-1508: Implement InAppBrowser feature
Initial checkin. Need to clean up the UI and add eventing.
2012-11-22 22:21:24 -05:00
Andrew Grieve 3566154cd0 Add @JavascriptInterface annotations to ExposedJsApi.
And re-enable the JS bridge on 4.2.
https://issues.apache.org/jira/browse/CB-1879
2012-11-22 12:39:18 -05:00
Anis Kadri 92d69e320f updating create command 2012-11-21 16:35:27 -08:00
Anis Kadri 08a190ef5b adding release command 2012-11-21 16:35:16 -08:00
Anis Kadri 98339ee5d8 removing ApplicationInfo.class 2012-11-21 13:37:19 -08:00
Anis Kadri fa387fd758 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android 2012-11-21 13:33:21 -08:00
Anis Kadri 54979f2fc4 removing appinfo.jar 2012-11-21 13:33:09 -08:00
Simon MacDonald 538e90f23a CB-1888: Can't add a Photo from a HTTPS address to Contact 2012-11-21 11:27:25 -05:00
Anis Kadri d9107bcac6 refactoring windows scripts 2012-11-20 18:49:16 -08:00
Anis Kadri 3f3a0b9140 adding install function 2012-11-20 14:49:49 -08:00
Anis Kadri e1347e434e refactoring android commands 2012-11-20 14:39:37 -08:00
Joe Bowser 7657faa9c3 CB-1852: Android version of model implemented, too bad it's all code names and not human readable 2012-11-19 13:26:22 -08:00
Joe Bowser 28ef765913 Upgrading App plugin to CordovaPlugin 2012-11-19 11:33:21 -08:00
Joe Bowser d2f59391a2 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android 2012-11-19 10:36:45 -08:00
Joe Bowser df90bdb350 Fixing up the tests so they crash less. 2012-11-19 10:36:26 -08:00
Joe Bowser c416c77d7a Fix for CB-1879 by Tom Clarkson. Hacked in due to lack of pull request 2012-11-19 10:35:47 -08:00
Andrew Grieve ce05a720d1 Update .gitignore 2012-11-16 15:41:38 -05:00
Joe Bowser 6c19a440f5 CB-1864: Figured out how to simulate back button, test both the CordovaWebView back button and the general DroidGap case using the default implementation 2012-11-15 16:04:09 -08:00
Joe Bowser f4612fdb5d Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android 2012-11-15 13:48:15 -08:00
Joe Bowser 04b9a0b09e Death to tabs while working on CB-1864 2012-11-15 13:47:52 -08:00
Simon MacDonald f93c438067 CB-1860: NPE in onReceivedError with non local errorUrl 2012-11-15 11:04:50 -05:00
Joe Bowser e1d608443a Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android 2012-11-14 13:23:35 -08:00
Joe Bowser 9233c3a898 Fixing error with the tests, backbuttonmultipage wasn't added 2012-11-14 13:22:58 -08:00
Simon MacDonald dfa514334b Bumping Android API version to 17 2012-11-14 16:05:50 -05:00
Joe Bowser 5810a96e62 Adding reflection so that this compiles, need to test against HTC Desire HD 2.3.6 device before resolving CB-1845 2012-11-14 11:15:22 -08:00
Joe Bowser 70473a80af Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android 2012-11-13 14:31:33 -08:00
Joe Bowser 525fd30cb2 Merge branch 'Android_2.3.4_camera_crash' of git://github.com/IuriiO/incubator-cordova-android into null_camera 2012-11-13 11:37:40 -08:00
Andrew Grieve 5212cd4dcd Disable JS Interface on Honeycomb
Fixes https://issues.apache.org/jira/browse/CB-1818
2012-11-13 12:50:15 -05:00
Simon MacDonald e95bde62a2 Correctly report the mime type of 3ga files 2012-11-12 10:22:35 -05:00
Simon MacDonald 4fe73cf6ad CB-1835: Camera.getPicture gives error when get a picture from photo library with spaces in its name on Android 2012-11-12 10:00:32 -05:00
Simon MacDonald 78b2835da4 Merge branch 'master' of https://github.com/ilbambino/incubator-cordova-android 2012-11-12 09:57:19 -05:00
Iurii Okhmat f9a49efae9 Removed unnecessary import. 2012-11-09 16:44:33 -08:00
Iurii Okhmat b9ddc9e678 Camera plugin (HTC Incredible) is crashing on 2.3.4 devices without SD card 2012-11-09 16:40:56 -08:00
Simon MacDonald dc459c84a3 CB-1829: Online/Offline events do not fire on subsequent pages of an app 2012-11-09 11:28:50 -05:00
Alvaro 1d26239809 not getting the path correctly if the URI contains a file://
Previous to 2.2 this function was crashing if the URI wasn't different
than a 'content://' but still if it is a 'file://' it fails getting the
correct path.
This happens for example picking a picture from dropbox instead of
local gallery.
2012-11-09 09:28:26 +02:00
Joe Bowser 5ca233779d This is as far as we can get fixing the Camera plugin by recovering state 2012-11-08 15:42:28 -08:00
Simon MacDonald e51b4897a3 Guard against null mimeType in MediaFile.getFormatData 2012-11-08 14:01:46 -05:00
Anis Kadri 81f283e56f CB-1794 fixing cordova commands for paths with spaces in them 2012-11-07 13:22:14 -08:00
Anis Kadri ccdd2fd2ca CB-1809 create script should print out meaningful error messages 2012-11-05 17:51:32 -08:00
Joe Bowser 69f11a29e1 Updating the project so that the activities are clearly separated from Test and Helper code 2012-11-02 16:15:51 -07:00
Joe Bowser cf494f3238 Fixing the tests so that they run as an Activity again 2012-11-02 13:48:05 -07:00
Joe Bowser d5895c635a Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android 2012-11-02 13:33:13 -07:00
Simon MacDonald 2ac9873613 CB-1808: FileEntry.moveTo across file systems incorrectly calls the success callback 2012-11-02 14:50:24 -04:00
Joe Bowser eb59e76cde Fixing CB-1801 2012-11-01 12:23:18 -07:00
Simon MacDonald d9db845b43 CB-1795: onCreateOptionsMenu in PhoneGap 2.2.0 Release Candidate 2 isn't working anymore 2012-11-01 10:30:34 -04:00
Joe Bowser e55327b064 Tagging the 2.2.0 release after this commit 2012-10-31 10:57:57 -07:00
Simon MacDonald bdd5a4e053 Merge branch 'master' of http://git-wip-us.apache.org/repos/asf/incubator-cordova-android
Somedays, I hate git.
2012-10-29 16:23:36 -04:00
Andrew Grieve ac2e2c9a42 Update JS to new tag (again). Includes latest fix to CB-1745. 2012-10-29 16:21:12 -04:00
Andrew Grieve 76f9d49e24 Disable limiting of payload size when sending data to JS.
Fixes https://issues.apache.org/jira/browse/CB-1745
2012-10-29 16:21:12 -04:00
Andrew Grieve 6ec8ab95fc Update JS to new 2.2.0rc2 tag. 2012-10-29 16:21:12 -04:00
Joe Bowser 9c98625610 Partial fix for CB-1742, still don't know what this should do for notification.confirm's cancel, so we return zero for now 2012-10-29 16:21:12 -04:00
Joe Bowser f270cde47d Changing DroidGap back and duplicating code so that we don't have a regression on CB-1568 2012-10-29 16:21:12 -04:00
Joe Bowser 9de7efd072 Added fix for webViewClient. CB-1568 2012-10-29 16:21:12 -04:00
Joe Bowser 7b81d317a0 Moved the initialization of the IceCreamWebViewClient to CordovaWebView, we weren't loading the fix in properly after the refactor - CB-1742 2012-10-29 16:21:12 -04:00
Simon MacDonald 876f975aa2 CB-1691: Android menu button event doesn't fire when textbox has focus 2012-10-29 16:20:39 -04:00
Andrew Grieve 3c5815ac0f Update JS to new tag (again). Includes latest fix to CB-1745. 2012-10-26 16:09:54 -04:00
Andrew Grieve 678ae2d684 Disable limiting of payload size when sending data to JS.
Fixes https://issues.apache.org/jira/browse/CB-1745
2012-10-26 16:08:35 -04:00
Andrew Grieve e4f8f44fb0 Update JS to new 2.2.0rc2 tag. 2012-10-26 10:41:08 -04:00
Joe Bowser 49566d29f8 Partial fix for CB-1742, still don't know what this should do for notification.confirm's cancel, so we return zero for now 2012-10-25 14:13:17 -07:00
Joe Bowser 7f4ee7b20a Changing DroidGap back and duplicating code so that we don't have a regression on CB-1568 2012-10-25 13:18:28 -07:00
Joe Bowser 32526a8c16 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android 2012-10-25 12:17:58 -07:00
Joe Bowser 71a7f72ab9 Added fix for webViewClient. CB-1568 2012-10-25 12:17:38 -07:00
Joe Bowser 4d0824f4a4 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android 2012-10-25 12:12:47 -07:00
Joe Bowser d56dd40d06 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android 2012-10-25 12:11:41 -07:00
Joe Bowser 6aafd6dc3a Moved the initialization of the IceCreamWebViewClient to CordovaWebView, we weren't loading the fix in properly after the refactor - CB-1742 2012-10-25 12:11:09 -07:00
Andrew Grieve 011b512f28 Update JS. 2012-10-25 15:02:26 -04:00
Andrew Grieve aa2d17e489 Disable JS_OBJECT bridge on pre-gingerbread devices.
It's the easiest way to avoid bugs with Java strings not being converted
to JS Strings.
2012-10-25 15:00:21 -04:00
Andrew Grieve 0eee2293dc Add support for null PluginResult payloads.
https://issues.apache.org/jira/browse/CB-1744
2012-10-25 12:05:39 -04:00
Simon MacDonald a2f35d2bda CB-1743: Globalization.getDateNames will crash Android 2.2 applications 2012-10-25 10:35:22 -04:00
Simon MacDonald 58f58d9ee8 Merge branch 'master' of http://git-wip-us.apache.org/repos/asf/incubator-cordova-android
Conflicts:
	framework/assets/js/cordova.android.js
2012-10-24 16:32:01 -04:00
Simon MacDonald 412bb349ac Pull in exec fix for Android 2.2 2012-10-24 16:29:26 -04:00
Simon MacDonald 652f15f893 Guard against NullPointerException in Compasslistenter 2012-10-24 16:29:02 -04:00
Joe Bowser 8512ebb923 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android 2012-10-24 12:07:20 -07:00
Joe Bowser f0ac173ec8 Adding the updated blank index 2012-10-24 12:06:37 -07:00
Joe Bowser bef0d47924 Starting incrementing the tag to RC2 2012-10-24 12:06:00 -07:00
Bryce Curtis cba0d59021 Fix exception when plugin returns a null string. 2012-10-24 12:36:30 -06:00
Andrew Grieve 7d3afcab94 Tweak the useBrowserHistory deprecation message to make the date is clear 2012-10-23 14:12:48 -04:00
Joe Bowser 5f1cda07e7 Cleaning up code, because negating string comparisons is confusing if the string itself is called false. Also clarified the deprecation message to match the Cordova policy that we agreed on for deprecation changes 2012-10-23 10:46:53 -07:00
Joe Bowser e11beade4b Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android 2012-10-23 10:25:33 -07:00
Andrew Grieve 6a1e089b73 Change useBrowserHistory to default to true (actually)
Also logs a deprecation mession on start-up when it is set to false.
Fixes issue: https://issues.apache.org/jira/browse/CB-1611
2012-10-23 13:15:44 -04:00
Simon MacDonald 0aa98ac2da CB-1697: openDatabase of Cordova for Android uses the wrong directory separator 2012-10-22 13:50:16 -04:00
Joe Bowser f9ef38cc7a Updating the config.xml default to be true. See discussion on CB-1611 2012-10-19 07:23:55 -07:00
Simon MacDonald a3a215a1ba Merge in video tag changes and fix back button issue 2012-10-17 10:14:30 -04:00
Joe Bowser d3ee322d7c Updating Hello World 2012-10-15 14:19:46 -07:00
Joe Bowser 7ec20e7752 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android 2012-10-15 13:52:35 -07:00
Joe Bowser 08dfb13dbf Updating Android to 2.2.0rc1 2012-10-15 13:52:19 -07:00
Andrew Grieve 6a5cddd907 Remove use of PluginResult.Status.NO_RESULT in GeoBroker.
It resolves to a no-op when KEEP_CALLBACK is set, and is therefore
confusing to use it with the new CordovaPlugin setup.
2012-10-15 14:49:47 -04:00
Andrew Grieve dc5078306d Remove manual catching of JSONException where possible.
Delegate the catching to caller instead. Related to refactoring of
Plugin->CordovaPlugin.
2012-10-15 14:24:03 -04:00
Andrew Grieve 1bc032853c Fix contact mobile-spec tests that were failing.
Also move contact operations back to background threads.
2012-10-15 14:21:15 -04:00
Joe Bowser e562e4e7b9 Removed overrides, fixed CB-1620 2012-10-12 14:43:46 -07:00
Joe Bowser 0ffffa9029 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android 2012-10-12 14:26:57 -07:00
Joe Bowser 0f2303e8d5 This time, we fixed the back button for real! CB-1658 2012-10-12 14:26:36 -07:00
Braden Shepherdson 31f7f8149e Merge branch 'master' into cordovaplugin_update 2012-10-12 17:05:47 -04:00
Braden Shepherdson fe1f57c23f Port Storage to use CordovaPlugin. 2012-10-12 17:05:35 -04:00
Braden Shepherdson 29a0b010da Port SplashScreen to CordovaPlugin. Untested, no tests for it. 2012-10-12 17:00:14 -04:00
Braden Shepherdson 621e1163f8 Port Notification to CordovaPlugin. 2012-10-12 16:58:15 -04:00
Braden Shepherdson 17d64cfcbe Port NetworkManager to CordovaPlugin. 2012-10-12 10:05:26 -04:00
Braden Shepherdson 7379d2135d Port Globalization to CordovaPlugin. 2012-10-11 18:32:35 -04:00
Braden Shepherdson c55fd06b99 Fix mountain of trailing whitespace. 2012-10-11 18:28:36 -04:00
Braden Shepherdson d81727a08c Port Location listeners and plugin to CordovaPlugin. 2012-10-11 18:26:19 -04:00
Braden Shepherdson b582e1592a Port FileUtils to CordovaPlugin. 2012-10-11 16:29:02 -04:00
Braden Shepherdson dd8533a320 Port Device to use CordovaPlugin. 2012-10-11 16:11:20 -04:00
Braden Shepherdson d72a8cbf89 Port Contacts to CordovaPlugin. 2012-10-11 16:03:49 -04:00
Braden Shepherdson fe0876ded6 Port CompassListener to CordovaPlugin. 2012-10-11 15:50:31 -04:00
Braden Shepherdson fa15763c5d Port Capture to use CordovaPlugin. Untested beyond compiling! 2012-10-11 15:50:04 -04:00
Braden Shepherdson 205215d409 Port CameraLauncher to CordovaPlugin. 2012-10-11 11:34:46 -04:00
Braden Shepherdson 076bfcde87 Port BatteryListener to CordovaPlugin. 2012-10-11 11:21:02 -04:00
Braden Shepherdson 10510484b5 Port AudioHandler and AudioPlayer to CordovaPlugin.
Had to adapt AudioPlayer because CordovaPlugin doesn't define
sendJavascript.
2012-10-11 11:09:12 -04:00
Braden Shepherdson e1dea5b4d3 Port AccelListener to CordovaPlugin.
Also using the MessageQueue to handle the sensor start timeout, instead
of a Thread.sleep() loop. This allows the listener to run synchronously.
2012-10-11 10:02:46 -04:00
Braden Shepherdson 891f8d00cf Cleanups to CordovaPlugin. 2012-10-10 14:11:13 -04:00
Joe Bowser 0d409f0fe3 Setting browser history as the default. This was supposed to be done earlier, sorry about that. CB-1611 2012-10-10 09:45:55 -07:00
alunny 4e0c8982c9 get commons-codec from apache archive, not osuosl
prevents the issue where, when a new release of commons-codec comes out,
`bin/create` stops working
2012-10-08 15:12:13 -07:00
Joe Bowser a741c66c97 Updating Native Tests to correspond with changes - CB-1580 2012-10-03 15:56:49 -07:00
Simon MacDonald 3e6a7cbdf5 CB-1574: On Android 4.1 events are only sent on every second menu button press 2012-10-03 14:41:10 -04:00
Simon MacDonald 5d34aa0afe Guard against null pointer exception in ES File Explorer being used to get a picture using DATA_URL 2012-10-03 11:31:58 -04:00
Simon MacDonald 979ae94698 Merge branch 'master' of http://git-wip-us.apache.org/repos/asf/incubator-cordova-android
Just brain dead today.
2012-10-02 14:40:48 -04:00
Simon MacDonald 8d7b85b26a CB-1573: Can we remove framework/src/org/apache/cordova/TempListener.java 2012-10-02 14:40:24 -04:00
Simon MacDonald 686977a986 CB-1573: Can we remove framework/src/org/apache/cordova/TempListener.java 2012-10-02 14:38:02 -04:00
Andrew Grieve 9c6c782146 Apply trustEveryone to the current connection and not globally.
Fixes https://issues.apache.org/jira/browse/CB-1565
2012-10-02 11:45:20 -04:00
Andrew Grieve ca9539b5b6 More tweaks to FileTransfer.
Some clean-up and moved IO out of critical sections.
2012-10-02 11:45:20 -04:00
Andrew Grieve ff25be8839 Fix warnings in FileTransfer.java 2012-10-02 11:45:20 -04:00
Simon MacDonald d1ab1b59be Remove @Override from interface methods
In Java 6 you will get an error in Eclipse if your put the @Override annotation on a method of an interface. You are not really over riding the methods you are implementing it. This is an undocumented change in Java 6. Just cleaning up our usage of this so some project problems got away.

Note: Things will build fine using 'ant jar' even with the @Override's.
2012-10-02 11:42:15 -04:00
Andrew Grieve 05bc1865a6 Change FileTransfer to use the new plugin signature.
Fixes slow abort(): https://issues.apache.org/jira/browse/CB-1516
Fixes abort() race condition: https://issues.apache.org/jira/browse/CB-1532
2012-10-02 10:14:52 -04:00
Andrew Grieve 6e6e0275ad Create CordovaPlugin.java and make Plugin.java a compatibility shim.
The intent of this is to encourage plugins to execute synchronously
when appropriate by not defaulting to using a background thread.

This will also encourage plugins that do run asynchronously to run some
of its logic synchronously before dispatching to a background thread.
This is required for tasks that can be abort()ed, such as
FileTransfer. (CB-1532)

This also makes it possible to lazily parse the JSON args, which is
important for large payloads. (CB-1530)
2012-10-02 10:14:30 -04:00
Andrew Grieve ec3c5b2ca2 Delete IPlugin interface. 2012-10-02 10:03:06 -04:00
Andrew Grieve 5289d569b0 Fix NPE caused by NetworkManager sending update before JS is ready.
This was happening for me when the device has been sleeping long
enough to turn its networking off, and I start an app via adb.
2012-10-02 10:02:42 -04:00
Andrew Grieve 6f873ff6b5 Fix default bridge mode being PRIVATE_API (should be ONLINE_EVENTS).
This was broken when HANGING_GET mode was removed.
2012-10-02 10:02:42 -04:00
Simon MacDonald 467cbe972c Fixing merge conflict 2012-10-02 09:54:36 -04:00
Simon MacDonald bfd1bfe9f0 CB-1564: DroidGap.loadUrl follows a path that never checks the white list
Whoops, I needed to do more testing. This looks like the trick but it could use a review.
2012-10-02 09:52:30 -04:00
Simon MacDonald 3404a6c699 CB-1564: DroidGap.loadUrl follows a path that never checks the white list 2012-10-01 16:03:28 -04:00
Anis Kadri 17a4b5155e CB-1359 set target SDK to the highest available 2012-09-28 18:18:11 -07:00
Anis Kadri d406e2ed22 merging conflict with create script 2012-09-28 17:43:26 -07:00
Anis Kadri 0bfc9935b2 CB-1359 simplifying API_LEVEL command 2012-09-28 17:41:12 -07:00
Andrew Grieve 64c6cbe303 Update JS snapshot after deleting callback server. 2012-09-28 14:37:08 -04:00
Andrew Grieve 2245db3e80 Delete CallbackServer.java 2012-09-28 14:37:08 -04:00
Andrew Grieve 6f19a50c98 Update .classpath file to use commons-codec-1.7.jar 2012-09-28 14:27:38 -04:00
Andrew Grieve c7ce9598a8 Remove unused async arg from PluginManager.exec(). 2012-09-28 14:10:19 -04:00
Andrew Grieve afcdccf783 Add an app-wide thead pool to CordovaInterface. 2012-09-28 14:10:13 -04:00
Joshua Granick 1bf12842ca Allow for predefined ANDROID_BIN value, fix for paths with spaces 2012-09-27 14:05:01 -04:00
Simon MacDonald da8fbee256 Merge branch 'master' of http://git-wip-us.apache.org/repos/asf/incubator-cordova-android 2012-09-27 11:17:04 -04:00
Simon MacDonald 4021f26e76 Globalization plugin should return an error object and not a code 2012-09-27 11:16:43 -04:00
Anis Kadri 8eab8438cf CB-1468 fixing paths with spaces 2012-09-26 16:22:35 -07:00
Simon MacDonald 1b4096b01d Guard against null pointer exception in ES File Explorer being used to get a picture 2012-09-26 15:52:37 -04:00
Marcel Kinard 54caa6e438 Fail the build gracefully with helpful error messages if the local.properties
file is missing, or if the commons-codec jar is missing, or if ant is not at
the minimum required version. Also add a little more detail to
README.md.
2012-09-25 13:09:40 -04:00
Braden Shepherdson 486eb149f2 Merge branch 'master' into plugin_reset 2012-09-24 14:38:17 -04:00
Braden Shepherdson faa034a205 Don't unregister the listener if it was never registered. 2012-09-24 14:37:04 -04:00
Braden Shepherdson 2cd3ebc7a8 Fix NPE on reset with undefined NetworkListener. 2012-09-24 14:36:29 -04:00
Braden Shepherdson 7e3af6c235 Add onReset() to TempListener. 2012-09-24 14:21:18 -04:00
Braden Shepherdson dd4de16d1d Add onReset to Storage. 2012-09-24 14:21:05 -04:00
Braden Shepherdson ba8577fa5f Add onReset() to NetworkManager. 2012-09-24 14:20:52 -04:00
Braden Shepherdson 6192319f8c Add onReset() to GeoBroker. 2012-09-24 14:20:36 -04:00
Andrew Grieve fed368d553 Set the total field for FileTransfer upload progress events.
This also removes an incorrect assumption that content: InputStreams
will be FileInputStreams.
2012-09-24 11:50:55 -04:00
Braden Shepherdson 20c885418e Add onReset to CompassListener. 2012-09-24 11:40:06 -04:00
Braden Shepherdson 9318ee30bd Add onReset to BatteryListener. 2012-09-24 11:35:35 -04:00
Braden Shepherdson 8b6c9574df Make AudioHandler stop and clean up on onReset() 2012-09-21 15:33:56 -04:00
Braden Shepherdson 313148136a Make AccelListener stop listening onReset() 2012-09-21 14:51:45 -04:00
Joe Bowser 6e1fdc77ae Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android 2012-09-21 11:51:12 -07:00
Joe Bowser 2a9582ebb1 Fixing CB-1521 - NullPointerException on Default Jellybean Emulator 2012-09-21 11:48:33 -07:00
Braden Shepherdson dd1cd46719 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android 2012-09-21 13:38:42 -04:00
Braden Shepherdson 9961d9e54d Add onReset to Plugin API, call on navigate. 2012-09-21 12:00:14 -04:00
Andrew Grieve 7eb12110d1 Add a work-around for a FileTransfer bug on 2.3 only.
Fixes https://issues.apache.org/jira/browse/CB-1413
2012-09-21 11:54:24 -04:00
Simon MacDonald 3d62744601 CB-1512: FileTransfer API and Mojolicious 2012-09-21 11:05:54 -04:00
Andrew Grieve 17af417235 Fix up some minor FileTransfer bugs / warnings.
- Catch abort error in download
- Fix up merge conflicts
- Fixed a couple of compiler warnings
2012-09-20 23:39:42 -04:00
Andrew Grieve df9d314361 Update JS to include FileProgress abort & progress support. 2012-09-20 23:39:09 -04:00
Brion Vibber 610e0c984a Add progress callbacks, abort for FileTransfer.upload and FileTransfer.download 2012-09-20 21:53:30 -04:00
Joe Bowser 3688fca126 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android 2012-09-20 16:27:49 -07:00
Joe Bowser 9bc89c784f Switching to ONLINE_EVENT 2012-09-20 16:27:44 -07:00
Anis Kadri 79682f5d52 updating windows create script test 2012-09-20 16:17:59 -07:00
Joe Bowser c206ac0335 Fixing CB-1504 2012-09-19 13:47:09 -07:00
Joe Bowser 34840175f3 Adding headers and converting tabs to spaces 2012-09-19 11:12:55 -07:00
Simon MacDonald 6312457425 CB-1469: Add Globalization Plug-in for Android 2012-09-19 14:08:52 -04:00
Anis Kadri f71e664952 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android 2012-09-18 14:45:20 -07:00
Anis Kadri 80d559f17e removing verbosity 2012-09-18 14:45:04 -07:00
Joe Bowser 772aedc263 Fixing CB-1462, there's a difference betwene Global and Local listeners: 2012-09-18 14:24:48 -07:00
Anis Kadri 45d7c124c8 fixing windows appinfo.jar 2012-09-18 14:11:22 -07:00
Joe Bowser 73abb20b3d Resolving CB-1496, this is faster than a pull request 2012-09-18 12:57:18 -07:00
Andrew Grieve 0baf104a75 Fix a deadlock in CallbackServer.java.
-Manifested when running mobile spec. Was caused by CallbackServer
obtaining lock #1 then #2, while other thread obtained #2 then #1.
2012-09-18 13:51:31 -04:00
Andrew Grieve 302d51cdfd Updated JS snapshot. 2012-09-18 13:25:18 -04:00
Andrew Grieve d3cbfd5467 Add a flag to disable exec() chaining for benchmarking.
- Also moved ENABLE_LOCATION_CHANGE_EXEC_MODE to NativeToJsMessageQueue
  so that all exec() related flags are in one place.
2012-09-18 13:24:38 -04:00
Andrew Grieve 9e3e7e1820 Remove TODO comment about calling webView from non-ui thread.
I tried it and it turned out to be a bit slower instead of faster.
2012-09-18 13:24:37 -04:00
Andrew Grieve 18893bf6cd Use a thread pool when executing async plugin operations. 2012-09-18 13:24:37 -04:00
Andrew Grieve f53161d6f5 Always send as many messages native->JS in one payload as possible. 2012-09-18 13:24:37 -04:00
Andrew Grieve 4c9a571106 Add constant to disable non-exec() messages in Native->JS bridge. 2012-09-18 13:24:37 -04:00
Andrew Grieve 365edcad16 Optimize encoding of PluginResults within NativeToJsMessageQueue.
Also included in this refactoring:
 -Better use of StringBuilder when encoding messages
 -Defers actual encoding of messages until they are popped.
 -Add a pause/unpause of message queue so that all pending messages will
 be sent to JS in order at the end of a PROMPT or JS_OBJECT context.
 Before they may not have been sent in order.
 -Tweaked LOAD_URL so that it always happens on the UI thread (avoids
 log warning).
2012-09-18 13:24:37 -04:00
Andrew Grieve ae9047a708 Refactor how PluginResults are sent to JS.
There is now a sendPluginResult() as well as a sendJavascript() on
CordovaWebview.
sendPluginResult() sends the result so that it can be parsed without
using eval(), when the active bridge allows it.
2012-09-18 13:24:37 -04:00
Andrew Grieve 9c0e58df8d Disable debug logging of loadUrl for javascript URLs.
When using LOAD_URL bridge mode, this is far too verbose.
2012-09-18 13:24:37 -04:00
Andrew Grieve ee34f11c29 Set the initial network-available state on start-up.
I've been assuming that it always starts as true, but this will ensure
that it does.
2012-09-18 13:24:37 -04:00
Andrew Grieve 6ca6d88bff Fix NPE when using LOCATION_CHANGE exec bridge. 2012-09-18 13:24:36 -04:00
Andrew Grieve 65a397fb63 Abstract JS->Native API calls into a class.
-setNativeToJsBridgeMode() and poll() can now be used via the JS interface
exported via addJavascriptInterface.
-prompt() now forwards calls to this class so that the logic will be the
same whether prompt() or the JS object is used.
2012-09-18 13:24:36 -04:00
Andrew Grieve 0a669077fb Fix warning about .close() not being called in FileUtils. 2012-09-18 13:23:26 -04:00
Simon MacDonald 451688a12e CB-1126: Splashscreen 2012-09-18 11:49:11 -04:00
Simon MacDonald d181d89dd2 CB-1411: Add trustAllHosts option to FileTransfer.download on Android 2012-09-17 22:09:52 -04:00
Simon MacDonald ac14b0d73b CB-1481: ContactName - name.formatted returns with a trailing white space 2012-09-17 22:09:51 -04:00
Simon MacDonald 0f42c65792 CB-1321: IMS types returning as other 2012-09-17 22:09:51 -04:00
Simon MacDonald 37b3e980dc CB-1362: We should add android:hardwareAccelerated=true to the template manifest.xml 2012-09-17 22:09:51 -04:00
Simon MacDonald eb49e011e2 CB-1405: navigator.language 2012-09-17 22:09:51 -04:00
Josh Soref e0a73f72ee Spelling: throw 2012-09-17 22:09:51 -04:00
Josh Soref e217ab28c5 Spelling: success 2012-09-17 22:09:51 -04:00
Josh Soref ca583865ea Spelling: substituted 2012-09-17 22:09:51 -04:00
Josh Soref 5e7efde311 Spelling: retrieve 2012-09-17 22:09:51 -04:00
Josh Soref 2c7c13420b Spelling: polyfill 2012-09-17 22:09:51 -04:00
Josh Soref ac4fc3e54e Spelling: occurred 2012-09-17 22:09:51 -04:00
Josh Soref 46db36a05e Spelling: necessary 2012-09-17 22:09:51 -04:00
Josh Soref 3d073be990 Spelling: milliseconds 2012-09-17 22:09:51 -04:00
Josh Soref 1bc49fe450 Spelling: explicitly 2012-09-17 22:09:51 -04:00
Josh Soref 1f7fe9abcc Spelling: definition 2012-09-17 22:09:51 -04:00
Josh Soref 5217abf57a Spelling: containing 2012-09-17 22:09:51 -04:00
Josh Soref 2ecbde891a Spelling: conjunction 2012-09-17 22:09:51 -04:00
Josh Soref bf7fc66646 Spelling: comparisons 2012-09-17 22:09:51 -04:00
Josh Soref 5a94b38e2f Spelling (en-us): behavior [slightly more instances of this spelling than the British] 2012-09-17 22:09:51 -04:00
Josh Soref 1bc55f5937 Typo stray paren 2012-09-17 22:09:51 -04:00
Josh Soref 04c9542f94 Spelling: application 2012-09-17 22:09:50 -04:00
Josh Soref 17e739f68a Spelling: aperture 2012-09-17 22:09:50 -04:00
Josh Soref 4f5515fde3 Spelling: after 2012-09-17 22:09:50 -04:00
Josh Soref ae3ba129ea Spelling: activities 2012-09-17 22:09:50 -04:00
Josh Soref 6b92a0fff7 Brand: cordova 2012-09-17 22:09:50 -04:00
Anis Kadri d859bb8e67 adding windows createAppInfoJar 2012-09-17 17:14:25 -07:00
Fil Maj f12bbf71ed Added license headers to test java files 2012-09-17 14:25:12 -07:00
Fil Maj b723beb545 [CB-1484] License headers audit 2012-09-17 14:19:02 -07:00
Joe Bowser 47daaaf14f Fixing up the commons-codec issue - CB-1483 2012-09-17 13:26:23 -07:00
Joe Bowser 9ba5bae34d Updating commons-codec 2012-09-17 09:44:47 -07:00
Philipp Klose dbfa2d7994 Change bin/create to use bash as shell
Fixes script on older Ubuntu versions.
2012-09-17 11:09:53 -04:00
Joe Bowser 8134f86d1f Fixing CB-1467 2012-09-14 11:42:16 -07:00
Joe Bowser 5c60b09bf4 Updating the manifest so that it is correct 2012-09-13 11:46:31 -07:00
Joe Bowser 20a19d67d0 Put this on the wrong branch 2012-09-13 11:44:09 -07:00
Joe Bowser 311a2f6023 Updating JS with feeling - See CB-1420 2012-09-12 12:58:30 -07:00
Simon MacDonald 59a3cf93e6 Tagging 2.1.0 2012-09-12 11:44:53 -04:00
Simon MacDonald a42f095cef CB-1409: Clicking the menu button crashes Jellybean 2012-09-11 09:04:42 -04:00
Simon MacDonald a29340523f CB-1293: Camera.getPicture crashes when selecting from a Picasa album on Android 2012-09-07 15:20:31 -04:00
Joe Bowser 5ad7a7c014 Forgot to put back echo 2012-09-06 17:12:36 -07:00
Joe Bowser c6fa7e4aad Reverting to the previously known to work config.xml, we'll introduce the new format in 2.2 2012-09-06 15:47:53 -07:00
Joe Bowser d4b248fbe3 Merge conflict on the plugins 2012-09-06 15:26:38 -07:00
Joe Bowser 48881d081a Adding support for data uri 2012-09-06 12:19:42 -07:00
Joe Bowser 331024414e Fixed the backbutton issues with the keyboard without causing issues with the menu button so far 2012-09-05 16:10:48 -07:00
Simon MacDonald 9d0c5349bb CB-1304: Update PhoneGap References to Apache Cordova 2012-09-03 14:31:35 -04:00
Simon MacDonald dc40d8afac CB-1335: Update www/ Application 2012-09-03 13:42:15 -04:00
Simon MacDonald 005877b4b8 CB-1303: Remove References to the Callback Project 2012-09-03 13:22:07 -04:00
Simon MacDonald 774d21747a Tagging 2.1.0rc2 2012-08-31 16:44:53 -04:00
Simon MacDonald 12e5b39c05 Fixing failing 'should return MediaError for bad filename' Media test case 2012-08-31 12:50:23 -04:00
Simon MacDonald 4d5e452ece CB-1358: Getting Force Close in incoming Cal while recording the Audio? 2012-08-31 12:45:01 -04:00
Michael Brooks 1ba3ecbef3 [#1301] Remove releasenotes.md
The file has not been updated in 5 months. If we choose to have
platform-level CHANGELOG's then it should be started across all
projects and named consistently as CHANGELOG.
2012-08-30 16:08:16 -07:00
Michael Brooks db6695cb02 [#1305] Remove guides/ directory
The guides are now documented in incubator-cordova-docs.
2012-08-30 15:34:57 -07:00
Simon MacDonald b3f5e039f2 Fixing 'FileTransfer.download() - should handle unknown host' failing mobile-spec test case 2012-08-30 17:01:16 -04:00
Andrew Grieve c3e17fb185 Fix broken file upload in mobile-spec tests (CB-1290).
The explicit transfer-encoding: chunk that was added breaks in
fixed-length streaming mode. It is, however, still required to
work-around an OOM bug in HTTPS mode. The new logic works for both the
mobile-spec and the HTTPS large-file test that I used before.

Commit adding the header: 999c548e6e
2012-08-30 13:37:19 -04:00
macdonst f7ae7fe43a CB=1293: Camera.getPicture crashes when selecting from a Picasa album on Android 2012-08-29 12:46:12 -04:00
macdonst e07822350e CB-1289: The menubutton event fires twice 2012-08-28 11:55:12 -04:00
Andrew Grieve 07439ff99c Refactor to avoid adb warnings of VFY: on start-up. 2012-08-27 15:05:20 -04:00
macdonst f111c245c1 Tagging 2.1.0rc1 2012-08-24 16:44:18 -04:00
Anis Kadri c3502da4a0 arg forgot dest file 2012-08-24 13:40:23 -07:00
Anis Kadri 4012108d48 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android 2012-08-24 12:15:02 -07:00
Anis Kadri 4a0605e09b CB-1235 Fixing Android create script on Windows 2012-08-24 12:14:58 -07:00
Andrew Grieve 250380d73e Implement LOAD_URL exec bridge.
Also refactors PluginManager.exec to return the PluginResult instead of
a string.
2012-08-24 14:19:41 -04:00
Andrew Grieve b30f5d782d Fix JS timers being disabled on pause and never re-enabled.
Was broken in this change:
https://github.com/apache/incubator-cordova-android/commit/b234b0bded73cf878fdc8521a337bef6b2f8fb62
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
Steren Giannini 06aafc96c9 Play <video> tags from the Webview in a Fullscreen video player.
Code from the Froyo Android Browser was adapted to support <video> elements in Cordova. The WebView creates a "CustomView" (a video player) that is displayed fullscreen.
It uses API level 7, work has to be done to support lower version.

Tested on Androdi 2.2: works
Tested on Android 4.1: doesn't work. It seems videos are handled differently (without the use of "Custom views"). To make video playing work on Android 4, add the android:hardwareAccelerated="true" attribute to the main activity of the AndroidManifest.
2012-08-14 18:13:35 +02:00
macdonst 2dc0727e36 CB-1151: FileTransfer.download decodes the URL, resulting in a 404 error 2012-08-13 15:43:55 -04:00
macdonst a219feaa60 Downgrade min sdk version of sample app 2012-08-13 13:30:27 -04:00
macdonst f3a09da340 Modify min sdk version in AndroidManifest.xml 2012-08-13 13:27:34 -04:00
macdonst 946e345a3f Add service methods to legacy ctx varialbe 2012-08-10 13:14:42 -04:00
macdonst 6cb8d11b22 CB-1196: No onSuccess callback after a complete play of the local mp3 file 2012-08-10 10:53:52 -04:00
macdonst fdcf9c5327 Reapply: CB-1211: Media record uses a .mp3 extension when it is a .3gp file 2012-08-10 09:27:37 -04:00
doggerelverse 45c714cbb5 reset of seekOnPrepared changed to properly reset only once playback has begun 2012-08-09 14:18:55 -07:00
macdonst 7352a309a0 CB-1217: Clicking back button does not dismiss navigator.notification.alert() dialog 2012-08-09 10:54:35 -04:00
Andrew Grieve b297fe6f59 Fix a NPE in GeoBroker when there is no previous location. 2012-08-08 23:39:02 -04:00
Evgeni Petrov e575212c49 GeoBroker checks if location service avialable for device first. 2012-08-08 23:23:01 -04:00
macdonst c52dc10c9e CB-1212: When camera is started, and then cancelled with no photo, attempt to read exif data results in fatal error 2012-08-08 15:53:48 -04:00
macdonst d35c913249 CB-1211: Media record uses a .mp3 extension when it is a .3gp file 2012-08-08 15:34:26 -04:00
macdonst 9bac59b952 CB-1206: file uri not handled correctly by Media Player 2012-08-08 15:33:01 -04:00
macdonst 5016253922 Update to use latest Android SDK 2012-08-07 15:40:05 -04:00
Joe Bowser 03893071fc Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android 2012-08-03 14:08:14 -07:00
Joe Bowser d3dc94c04b CB-578 - Adding a test of pause and resume to make sure that they're being called. Need to elaborate on this 2012-08-03 14:07:01 -07:00
Andrew Grieve af0feabb6a Prefer setFixedLengthStreamingMode over setChunkedStreamingMode in FileTransfer.
setFixedLengthStreamingMode causes the Content-Length header to be set,
which some servers require.

We now use setChunkedStreamingMode only on Eclair devices
since there is a bug with setFixedLengthStreamingMode in that version
of the OS.
2012-08-03 12:29:37 -04:00
Joe Bowser 81ab0a414f I forgot that Jellybean has some wacky changes on the onKeyUp and onKeyDown events, fixing CB-1181 2012-08-02 12:51:48 -07:00
Joe Bowser ecd6ca0172 Moving the fix for # and ? to a new class to fix CB-995 2012-07-31 16:16:57 -07:00
Joe Bowser db7ee192f7 This was a red herring. There was an XML error on the example when compared with master 2012-07-31 12:20:31 -07:00
Joe Bowser 2ec0b601fa Commenting out this past code, turns out this breaks more things than it fixes: CB-1101 2012-07-31 11:55:01 -07:00
Joe Bowser 79feb6d5d2 CB-1101: Specifying Jellybean means that we have to override the default CORS policy 2012-07-31 11:48:38 -07:00
Joe Bowser 8013b760e3 Adding functionality to read a config.xml file with plugin details implemented inside of it 2012-07-30 16:31:33 -07:00
Joe Bowser a29b8e5b36 Caught error with the back button again. backHistory actually goes back in history, used canGoBack() instead 2012-07-30 13:55:22 -07:00
Anis Kadri 9ef487a7a5 fixing replace for mountain lion 2012-07-28 08:13:31 -07:00
Anis Kadri 563fa46ba4 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android 2012-07-27 18:30:59 -07:00
Anis Kadri 7865c06863 CB-1148 fix for directories with spaces 2012-07-27 18:30:39 -07:00
Andrew Grieve 3d53b9244d Adds FileTransfer support for upload headers.
-Support previously existed via options.params.headers. This CL
deprecates this (undocumented) way of adding headers and adds support
for options.headers.
-This also adds support for multiple headers via:
    options.headers = {"Name": ["Value1", "Value2"]}.
2012-07-27 20:54:31 -04:00
Joe Bowser f2afa4dd50 Tweaking the Android Manfest to cope with new target changes: CB-1147 2012-07-27 13:31:25 -07:00
Joe Bowser 893ecec55e Minor fix to deal with weird keyboard focus issues and the back button. CB-1146 2012-07-27 10:33:38 -07:00
Joe Bowser 401584dbd8 Throwing code over the fence for CB-1128, We need a Samsung Galaxy S running 2.3.5 to confirm this fix 2012-07-25 12:54:03 -07:00
Joe Bowser b234b0bded Utility Methods based on Feedback 2012-07-24 15:54:01 -07:00
Joe Bowser b9b2c6a013 Updating sample project 2012-07-23 10:43:23 -07:00
Joe Bowser 1d2efa0d25 Fixing leaking sockets 2012-07-20 16:02:43 -07:00
Joe Bowser 93ec092eaf Forgot to add the example app. Works with the script 2012-07-20 09:57:26 -07:00
Joe Bowser 29ae492983 Upping the version to 2.0.0 2012-07-18 16:58:03 -07:00
Joe Bowser b9f6a59a20 Fix for CB-1085 2012-07-18 14:37:45 -07:00
Joe Bowser d74551216f Throwing code over the fence to fix CB-1087, I can't repro on my Galaxy Nexus 2012-07-18 14:30:45 -07:00
Andrew Grieve d4302ae51b Remove cordova.xml and plugins.xml from framework/res/xml.
They have been superseded by config.xml, and having them stick around
was confusing.
2012-07-16 15:06:12 -04:00
Joe Bowser 9d5fb0b201 Tagging 2.0.0rc1 2012-07-13 15:46:09 -07:00
Fil Maj e0a5fe4002 [CB-574] Added backbutton automated unit test for android. 2012-07-13 14:57:40 -07:00
macdonst f9d9a0a4bd Adding deprecation notice to LegacyContext 2012-07-13 15:36:56 -04:00
Joe Bowser 78f0c7b119 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android into audio 2012-07-12 14:41:56 -07:00
Fil Maj c6d8343de2 [CB-1035] Including newest JS built based on refactored common device module. 2012-07-12 13:37:08 -07:00
Anis Kadri 0ccd11e587 CB-1031 android create script fails 2012-07-11 14:00:42 -07:00
Joe Bowser b486711d68 Combining plugins.xml and cordova.xml to make config.xml 2012-07-11 11:23:31 -07:00
Fil Maj 2eb4c5e960 [CB-1022] Reverted nanoTime back to currentTimeMillis. Updated mobile-spec tests as well. This passes all accel tests. 2012-07-11 10:26:14 -07:00
Fil Maj 85aa740c98 [CB-481] Removed todo comment introduced by bryce, clarified what is going on 2012-07-11 09:35:29 -07:00
Joe Bowser 6415848383 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android into audio 2012-07-11 09:14:04 -07:00
macdonst beb9460538 CB-952: Android showSplashScreen crashes 2012-07-10 20:52:07 -04:00
Joe Bowser c030770be7 Working with Lorin's change 2012-07-10 14:37:26 -07:00
macdonst 0180342dff CB-993: Android plugin problems upgrading to 1.9.0 2012-07-10 16:26:52 -04:00
macdonst b97748d3dc CB-1005: Can not remove contact phonenumber values 2012-07-10 11:25:20 -04:00
Joe Bowser 9d4977db00 Fixing bug on ICS where the super.onKeyDown wasn't being called 2012-07-09 14:42:29 -07:00
macdonst f095284faa CB-1016: Zero width or height in getPicture throws java.lang.ArithmeticException 2012-07-07 22:19:55 -04:00
macdonst 401c2f42f9 Modify PluginResult(status) so it generates a JSON string that works with JSON.parse() 2012-07-06 17:39:04 -04:00
macdonst eb0348d47c CB-1014: Out of Memory error when getting image from photo library 2012-07-06 12:37:08 -04:00
macdonst 1f46240ba9 CB-999: When getting images from the PHOTOLIBRARY apply the correctOrientation fix 2012-07-05 16:04:47 -04:00
macdonst 14870726e0 CB-1008: Camera with targetHeight, targetWidth loses image aspect ratio 2012-07-05 15:32:55 -04:00
macdonst c7d6a2eecb CB-992: Camera tries to add temp photo to gallery 2012-07-05 14:02:20 -04:00
macdonst ce61eb2174 Implementing CordovaInterface.getContext in test folder classes 2012-07-03 11:36:04 -04:00
macdonst f3df21ef0a Fix mis-spelling in upgrade guide 2012-07-03 10:33:48 -04:00
macdonst 5eb554e008 CB-993: Android plugin problems upgrading to 1.9.0
The DroidGap.getContext() method causes an infinite loop and eventually a stack overflow error.
2012-07-02 16:37:14 -04:00
macdonst e5e7c3fad3 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android 2012-06-29 15:55:34 -04:00
macdonst 2a8b9ab75e Tagging to 1.9.0 2012-06-29 15:55:03 -04:00
Fil Maj c8f0ffb42f Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android 2012-06-29 09:37:59 -07:00
Fil Maj b3e68b96cf Removing CordovaWebView Guide; its going into the docs 2012-06-29 09:37:31 -07:00
macdonst ae7a550a09 Only load Exif information if necessary 2012-06-29 11:31:33 -04:00
Anis Kadri e069bbb800 CB-937 fixing debug for windows 2012-06-28 17:11:21 -07:00
Anis Kadri 17ff6be6a9 CB-937 fixing debug 2012-06-28 17:08:32 -07:00
Anis Kadri d42489c67a Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android 2012-06-28 16:47:18 -07: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
macdonst e2047afa42 Wire rotation fix to correctOrientation parameter 2012-06-28 12:00:19 -04:00
macdonst 231b39d2dc Reset orientation exif information when photo is rotated
When a photo is taken in portrait mode we rotate it so it shows up properly in the webview. The Exif orientation must be reset to normal orientation (0) or the image will not display properly on desktops.
2012-06-28 12:00:19 -04:00
macdonst dddce30368 Rotate image if taken in portrait mode 2012-06-28 12:00:19 -04:00
macdonst e0e4ba2bd7 Fix double image problem on Samsung phones
On Samsung phones even if you tell the camera not to save to the photo gallery it still does. This small fix deletes the original file as it is not needed.
2012-06-28 12:00:19 -04:00
macdonst e0eadb6b76 Using a better scaling algorithm to resize the image
Instead of reading the entire image into a bitmap then scaling we use the
inSampleSize option to get a close to the target width and height as possible
then we scale that smaller image.
2012-06-28 12:00:19 -04:00
macdonst 483e5dfbea Switch getPicture from Gallery to use file instead of content resolver 2012-06-28 12:00:18 -04:00
macdonst 8aa9d8213d Cache bust returned Image URI if saveToPhotoAlbum is false 2012-06-28 12:00:18 -04:00
macdonst a74f71c935 Decode image from File instead of content resolver 2012-06-28 12:00:18 -04:00
macdonst 87b81e53f0 CB-978: FileTransfer.upload from a directory with a space fails 2012-06-28 11:57:06 -04:00
Joe Bowser a2816e31c3 Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android 2012-06-28 08:00:59 -07:00
Joe Bowser f78af9f27b Forgot to add it renderscript.opt.level to the project. This will fix ant issues 2012-06-28 08:00:35 -07:00
Anis Kadri 98138a0a60 log was actually doing nothing...fixing it 2012-06-27 17:55:35 -07:00
Anis Kadri e639b6303e updating create script to work from distro and source 2012-06-27 17:54:57 -07:00
Anis Kadri 99fb3ebe00 creating project without source 2012-06-26 17:34:19 -07:00
Joe Bowser 5829840409 Re-adding getContext because yo dawg, I heard you like contexts in your contexts 2012-06-26 11:25:17 -07:00
Anis Kadri 4699ab5500 forgot to add +x on BOOM 2012-06-25 15:03:19 -07:00
Anis Kadri 69fc7f39b7 setting +x on script files 2012-06-25 14:59:35 -07:00
Anis Kadri 510a962a52 deleting old BOOM 2012-06-25 14:56:09 -07:00
Anis Kadri 570fc3cfb2 removing echoes 2012-06-25 14:46:10 -07:00
Joe Bowser 5d211f2fa6 Might as well keep isBackbuttonOverriden on the plugin 2012-06-22 10:38:42 -07:00
Joe Bowser dcb127c14d Weird merge error didn't account for isBackButtonBound 2012-06-22 10:37:33 -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
172 changed files with 21951 additions and 5872 deletions
+22 -7
View File
@@ -2,19 +2,34 @@
default.properties
gen
assets/www/cordova.js
framework/assets/www/.tmp*
local.properties
framework/proguard.cfg
framework/cordova.jar
framework/cordova-*.jar
framework/phonegap-*.jar
framework/lib
proguard.cfg
proguard.cfg
proguard-project.txt
framework/bin
framework/test/org/apache/cordova/*.class
framework/assets/www/.DS_Store
framework/assets/www/cordova-*.js
framework/assets/www/phonegap-*.js
framework/libs
test/libs
example
./test
tmp
*.tmp
test/libs/*.jar
test/bin
test/assets/www/.tmp*
tmp/**
bin/node_modules
.metadata
tmp/**/*
Thumbs.db
Desktop.ini
*.tmp
*.bak
*.swp
*.class
*.jar
# IntelliJ IDEA files
*.iml
.idea
+14 -23
View File
@@ -1,22 +1,18 @@
Cordova Android
===
Cordova Android is an Android application library that allows for Cordova based projects to be built for the Android Platform. Cordova based applications are, at the core, an application written with web technology: HTML, CSS and JavaScript.
Cordova Android is an Android application library that allows for Cordova-based
projects to be built for the Android Platform. Cordova based applications are,
at the core, applications written with web technology: HTML, CSS and JavaScript.
[Apache Cordova](http://cordova.io) is a project at The Apache Software Foundation (ASF).
Apache Cordova is an effort undergoing incubation at The Apache
Software Foundation (ASF), sponsored by the Apache Incubator project.
Incubation is required of all newly accepted projects until a further
review indicates that the infrastructure, communications, and decision
making process have stabilized in a manner consistent with other
successful ASF projects. While incubation status is not necessarily
a reflection of the completeness or stability of the code, it does
indicate that the project has yet to be fully endorsed by the ASF.
Requires
---
- Java JDK 1.5
- Apache ANT
- Java JDK 1.5 or greater
- Apache ANT 1.8.0 or greater
- Android SDK [http://developer.android.com](http://developer.android.com)
- Apache Commons Codec [http://commons.apache.org/codec/](http://commons.apache.org/codec/)
@@ -27,13 +23,13 @@ Test Requirements
Building
---
To create your cordova.jar, copy the commons codec:
To create your `cordova.jar` file, copy the commons codec:
mv commons-codec-1.6.jar framework/libs
mv commons-codec-1.7.jar framework/libs
then run in the framework directory:
android update project -p . -t android-15
android update project -p . -t android-17
ant jar
@@ -61,7 +57,7 @@ Project Commands
These commands live in a generated Cordova Android project.
./cordovap/debug [path] ..................... install to first device
./cordova/debug [path] ..................... install to first device
./cordova/emulate .......................... start avd (emulator) named default
./cordova/log .............................. starts logcat
@@ -82,15 +78,10 @@ Start adb logcat (console.log calls output here):
./cordova/log
Running the [callback/callback-test](http://github.com/callback/callback-test) tests:
---
./bin/test
Creating a new Cordova Android Project
---
./bin/create ~/Desktop/myapp com.phonegap.special MyApp
./bin/create ~/Desktop/myapp com.myapp.special MyApp
Importing a Cordova Android Project into Eclipse
----
@@ -110,5 +101,5 @@ Further Reading
---
- [http://developer.android.com](http://developer.android.com)
- [http://docs.phonegap.com](http://docs.phonegap.com)
- [http://wiki.phonegap.com](http://wiki.phonegap.com)
- [http://incubator.apache.org/cordova/](http://incubator.apache.org/cordova/)
- [http://wiki.apache.org/cordova/](http://wiki.apache.org/cordova/)
+1 -1
View File
@@ -1 +1 @@
1.9.0rc1
2.6.0
-21
View File
@@ -1,21 +0,0 @@
#! /bin/sh
# 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.
#
#
./bin/create
cd ./example && ./cordova/debug && ./cordova/log
+97 -54
View File
@@ -1,4 +1,4 @@
#! /bin/sh
#! /bin/bash
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
@@ -23,22 +23,22 @@
#
set -e
if [ -n "$1" ] && [ "$1" == "-h" ]
if [ -z "$1" ] || [ "$1" == "-h" ]
then
echo 'usage: create path package activity'
echo "Make sure the Android SDK tools folder is in your PATH!"
exit 0
fi
BUILD_PATH=$( cd "$( dirname "$0" )/.." && pwd )
VERSION=$(cat $BUILD_PATH/VERSION)
BUILD_PATH="$( cd "$( dirname "$0" )/.." && pwd )"
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,80 +46,123 @@ fi
# cleanup after exit and/or on error
function on_exit {
echo "Cleaning up ..."
# [ -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
[ -f $BUILD_PATH/framework/assets/www/cordova-$VERSION.js ] && rm $BUILD_PATH/framework/assets/www/cordova-$VERSION.js
[ -f $BUILD_PATH/framework/cordova-$VERSION.jar ] && rm $BUILD_PATH/framework/cordova-$VERSION.jar
# [ -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
fi
if [ -f "$BUILD_PATH"/framework/cordova-$VERSION.jar ]
then
rm "$BUILD_PATH"/framework/cordova-$VERSION.jar
fi
}
function createAppInfoJar {
pushd "$BUILD_PATH"/bin/templates/cordova/ApplicationInfo > /dev/null
javac ApplicationInfo.java
jar -cfe ../appinfo.jar ApplicationInfo ApplicationInfo.class
popd > /dev/null
}
function on_error {
echo "An error occured. Deleting project..."
[ -d $PROJECT_PATH ] && rm -rf $PROJECT_PATH
echo "An unexpected error occurred: $previous_command exited with $?"
echo "Deleting project..."
[ -d "$PROJECT_PATH" ] && rm -rf "$PROJECT_PATH"
exit 1
}
function replace {
local pattern=$1
local filename=$2
# Mac OS X requires -i argument
if [[ "$OSTYPE" =~ "darwin" ]]
then
/usr/bin/sed -i '' -e $pattern "$filename"
elif [[ "$OSTYPE" =~ "linux" ]]
then
/bin/sed -i -e $pattern "$filename"
fi
}
# we do not want the script to silently fail
trap 'previous_command=$this_command; this_command=$BASH_COMMAND' DEBUG
trap on_error ERR
trap on_exit EXIT
ANDROID_BIN=$( which android )
ANDROID_BIN="${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 ' ' )
TARGET=$("$ANDROID_BIN" list targets | grep id: | tail -1 | cut -f 2 -d ' ' )
API_LEVEL=$("$ANDROID_BIN" list target | grep "API level:" | tail -n 1 | cut -f 2 -d ':' | tr -d ' ')
# check that build targets exist
if [ -z "$TARGET" ] || [ -z "$API_LEVEL" ]
then
echo "No Android Targets are installed. Please install at least one via the android SDK"
exit 1
fi
# if this a distribution release no need to build a jar
if [ ! -e "$BUILD_PATH"/cordova-$VERSION.jar ] && [ -d "$BUILD_PATH"/framework ]
then
# update the cordova-android framework for the desired target
$ANDROID_BIN update project --target $TARGET --path $BUILD_PATH/framework &> /dev/null
"$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.7.jar ]; then
# Use curl to get the jar (TODO: Support Apache Mirrors)
echo "Downloading common-codecs-1.6 ..."
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
curl -OL http://archive.apache.org/dist/commons/codec/binaries/commons-codec-1.7-bin.zip &> /dev/null
unzip commons-codec-1.7-bin.zip &> /dev/null
mkdir -p "$BUILD_PATH"/framework/libs
cp commons-codec-1.7/commons-codec-1.7.jar "$BUILD_PATH"/framework/libs
# cleanup yo
rm commons-codec-1.6-bin.zip && rm -rf commons-codec-1.6
rm commons-codec-1.7-bin.zip && rm -rf commons-codec-1.7
fi
# compile cordova.js and cordova.jar
echo "Building cordova-$VERSION.jar and cordova-$VERSION.js ..."
(cd $BUILD_PATH/framework && ant jar &> /dev/null )
pushd "$BUILD_PATH"/framework > /dev/null
ant jar > /dev/null
popd > /dev/null
fi
# create new android project
echo "Creating a new cordova 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
echo "Copying assets and resources ..."
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
echo "Setting up config and plugins list ..."
cp -r $BUILD_PATH/framework/res/xml $PROJECT_PATH/res
echo "Adding cordova-$VERSION.js and cordova-$VERSION.jar ..."
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
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
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
fi
# interpolate the activity name and package
echo "Updating Activity and AndroidManifest ..."
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"
replace "s/__APILEVEL__/${API_LEVEL}/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
# creating cordova folder and copying run/build/log/launch scripts
mkdir "$PROJECT_PATH"/cordova
createAppInfoJar
cp "$BUILD_PATH"/bin/templates/cordova/appinfo.jar "$PROJECT_PATH"/cordova/appinfo.jar
cp "$BUILD_PATH"/bin/templates/cordova/cordova "$PROJECT_PATH"/cordova/cordova
cp "$BUILD_PATH"/bin/templates/cordova/build "$PROJECT_PATH"/cordova/build
cp "$BUILD_PATH"/bin/templates/cordova/release "$PROJECT_PATH"/cordova/release
cp "$BUILD_PATH"/bin/templates/cordova/clean "$PROJECT_PATH"/cordova/clean
cp "$BUILD_PATH"/bin/templates/cordova/log "$PROJECT_PATH"/cordova/log
cp "$BUILD_PATH"/bin/templates/cordova/run "$PROJECT_PATH"/cordova/run
+19 -2
View File
@@ -1,10 +1,27 @@
:: Licensed to the Apache Software Foundation (ASF) under one
:: or more contributor license agreements. See the NOTICE file
:: distributed with this work for additional information
:: regarding copyright ownership. The ASF licenses this file
:: to you under the Apache License, Version 2.0 (the
:: "License"); you may not use this file except in compliance
:: with the License. You may obtain a copy of the License at
::
:: http://www.apache.org/licenses/LICENSE-2.0
::
:: Unless required by applicable law or agreed to in writing,
:: software distributed under the License is distributed on an
:: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
:: KIND, either express or implied. See the License for the
:: specific language governing permissions and limitations
:: under the License.
@ECHO OFF
IF NOT DEFINED JAVA_HOME GOTO MISSING
FOR %%X in (java.exe ant.bat android.bat) do (
FOR %%X in (java.exe javac.exe ant.bat android.bat) do (
SET FOUND=%%~$PATH:X
IF NOT DEFINED FOUND GOTO MISSING
)
cscript %~dp0\create.js %*
cscript "%~dp0\create.js" %*
GOTO END
:MISSING
ECHO Missing one of the following:
+72 -27
View File
@@ -33,10 +33,24 @@ function read(filename) {
f.Close();
return s;
}
function checkTargets(targets) {
if(!targets) {
WScript.Echo("You do not have any android targets setup. Please create at least one target with the `android` command");
WScript.Quit(69);
}
}
function setTarget() {
var targets = shell.Exec('android.bat list targets').StdOut.ReadAll().match(/id:\s\d+/g);
checkTargets(targets);
return targets[targets.length - 1].replace(/id: /, ""); // TODO: give users the option to set their target
}
function setApiLevel() {
var targets = shell.Exec('android.bat list targets').StdOut.ReadAll().match(/API level:\s\d+/g);
checkTargets(targets);
return targets[targets.length - 1].replace(/API level: /, "");
}
function write(filename, contents) {
var fso=WScript.CreateObject("Scripting.FileSystemObject");
var f=fso.OpenTextFile(filename, 2, true);
@@ -49,10 +63,26 @@ function replaceInFile(filename, regexp, replacement) {
function exec(command) {
var oShell=shell.Exec(command);
while (oShell.Status == 0) {
if(!oShell.StdOut.AtEndOfStream) {
var line = oShell.StdOut.ReadLine();
// XXX: Change to verbose mode
// WScript.StdOut.WriteLine(line);
}
WScript.sleep(100);
}
}
function createAppInfoJar() {
if(!fso.FileExists(ROOT+"\\bin\\templates\\cordova\\appinfo.jar")) {
WScript.Echo("Creating appinfo.jar...");
var cur = shell.CurrentDirectory;
shell.CurrentDirectory = ROOT+"\\bin\\templates\\cordova\\ApplicationInfo";
exec("javac ApplicationInfo.java");
exec("jar -cfe ..\\appinfo.jar ApplicationInfo ApplicationInfo.class");
shell.CurrentDirectory = cur;
}
}
function cleanup() {
// Cleanup
// if(fso.FileExists(ROOT + '\\framework\\libs\\commons-codec-1.6.jar')) {
@@ -68,11 +98,11 @@ function cleanup() {
}
function downloadCommonsCodec() {
if (!fso.FileExists(ROOT + '\\framework\\libs\\commons-codec-1.6.jar')) {
if (!fso.FileExists(ROOT + '\\framework\\libs\\commons-codec-1.7.jar')) {
// We need the .jar
var url = 'http://mirror.symnds.com/software/Apache//commons/codec/binaries/commons-codec-1.6-bin.zip';
var url = 'http://archive.apache.org/dist/commons/codec/binaries/commons-codec-1.7-bin.zip';
var libsPath = ROOT + '\\framework\\libs';
var savePath = libsPath + '\\commons-codec-1.6-bin.zip';
var savePath = libsPath + '\\commons-codec-1.7-bin.zip';
if (!fso.FileExists(savePath)) {
if(!fso.FolderExists(ROOT + '\\framework\\libs')) {
fso.CreateFolder(libsPath);
@@ -99,11 +129,11 @@ function downloadCommonsCodec() {
target.CopyHere(source, 256);
// Move the jar into libs
fso.MoveFile(ROOT + '\\framework\\libs\\commons-codec-1.6\\commons-codec-1.6.jar', ROOT + '\\framework\\libs\\commons-codec-1.6.jar');
fso.MoveFile(ROOT + '\\framework\\libs\\commons-codec-1.7\\commons-codec-1.7.jar', ROOT + '\\framework\\libs\\commons-codec-1.7.jar');
// Clean up
fso.DeleteFile(ROOT + '\\framework\\libs\\commons-codec-1.6-bin.zip');
fso.DeleteFolder(ROOT + '\\framework\\libs\\commons-codec-1.6', true);
fso.DeleteFile(ROOT + '\\framework\\libs\\commons-codec-1.7-bin.zip');
fso.DeleteFolder(ROOT + '\\framework\\libs\\commons-codec-1.7', true);
}
}
@@ -129,51 +159,66 @@ var PACKAGE_AS_PATH=PACKAGE.replace(/\./g, '\\');
var ACTIVITY_PATH=PROJECT_PATH+'\\src\\'+PACKAGE_AS_PATH+'\\'+ACTIVITY+'.java';
var MANIFEST_PATH=PROJECT_PATH+'\\AndroidManifest.xml';
var TARGET=setTarget();
var API_LEVEL=setApiLevel();
var VERSION=read(ROOT+'\\VERSION').replace(/\r\n/,'').replace(/\n/,'');
// create the project
WScript.Echo("Creating new android project...");
exec('android.bat create project --target '+TARGET+' --path '+PROJECT_PATH+' --package '+PACKAGE+' --activity '+ACTIVITY);
// build from source. distro should have these files
if (!fso.FileExists(ROOT+'\\cordova-'+VERSION+'.jar') &&
!fso.FileExists(ROOT+'\\cordova-'+VERSION+'.js')) {
WScript.Echo("Building jar and js files...");
// update the cordova framework project to a target that exists on this machine
exec('android.bat update project --target '+TARGET+' --path '+ROOT+'\\framework');
// pull down commons codec if necessary
downloadCommonsCodec();
exec('ant.bat -f '+ ROOT +'\\framework\\build.xml jar');
exec('ant.bat -f \"'+ ROOT +'\\framework\\build.xml\" jar');
}
// copy in the project template
exec('%comspec% /c xcopy '+ ROOT + '\\bin\\templates\\project\\res '+PROJECT_PATH+'\\res\\ /E /Y');
exec('%comspec% /c xcopy '+ ROOT + '\\bin\\templates\\project\\assets '+PROJECT_PATH+'\\assets\\ /E /Y');
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\project\\AndroidManifest.xml ' + PROJECT_PATH + '\\AndroidManifest.xml /Y');
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\project\\Activity.java '+ ACTIVITY_PATH +' /Y');
WScript.Echo("Copying template files...");
exec('%comspec% /c xcopy "'+ ROOT + '"\\bin\\templates\\project\\res '+PROJECT_PATH+'\\res\\ /E /Y');
exec('%comspec% /c xcopy "'+ ROOT + '"\\bin\\templates\\project\\assets '+PROJECT_PATH+'\\assets\\ /E /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\project\\AndroidManifest.xml ' + PROJECT_PATH + '\\AndroidManifest.xml /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\project\\Activity.java '+ ACTIVITY_PATH +' /Y');
// check if we have the source or the distro files
WScript.Echo("Copying js, jar & config.xml files...");
if(fso.FolderExists(ROOT + '\\framework')) {
exec('%comspec% /c copy "'+ROOT+'"\\framework\\assets\\www\\cordova-'+VERSION+'.js '+PROJECT_PATH+'\\assets\\www\\cordova-'+VERSION+'.js /Y');
exec('%comspec% /c copy "'+ROOT+'"\\framework\\cordova-'+VERSION+'.jar '+PROJECT_PATH+'\\libs\\cordova-'+VERSION+'.jar /Y');
fso.CreateFolder(PROJECT_PATH + '\\res\\xml');
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+'\\framework\\assets\\www\\cordova-'+VERSION+'.js '+PROJECT_PATH+'\\assets\\www\\cordova-'+VERSION+'.js /Y');
exec('%comspec% /c copy "'+ROOT+'"\\cordova-'+VERSION+'.js '+PROJECT_PATH+'\\assets\\www\\cordova-'+VERSION+'.js /Y');
// copy in cordova.jar
exec('%comspec% /c copy '+ROOT+'\\framework\\cordova-'+VERSION+'.jar '+PROJECT_PATH+'\\libs\\cordova-'+VERSION+'.jar /Y');
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+'\\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+'"\\xml\\config.xml ' + PROJECT_PATH + '\\res\\xml\\config.xml /Y');
}
// copy cordova scripts
fso.CreateFolder(PROJECT_PATH + '\\cordova');
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\appinfo.jar ' + PROJECT_PATH + '\\cordova\\appinfo.jar /Y');
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\cordova.js ' + PROJECT_PATH + '\\cordova\\cordova.js /Y');
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\cordova.bat ' + PROJECT_PATH + '\\cordova\\cordova.bat /Y');
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\clean.bat ' + PROJECT_PATH + '\\cordova\\clean.bat /Y');
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\debug.bat ' + PROJECT_PATH + '\\cordova\\debug.bat /Y');
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\log.bat ' + PROJECT_PATH + '\\cordova\\log.bat /Y');
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\emulate.bat ' + PROJECT_PATH + '\\cordova\\emulate.bat /Y');
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\BOOM.bat ' + PROJECT_PATH + '\\cordova\\BOOM.bat /Y');
createAppInfoJar();
WScript.Echo("Copying cordova command tools...");
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\appinfo.jar ' + PROJECT_PATH + '\\cordova\\appinfo.jar /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\cordova.js ' + PROJECT_PATH + '\\cordova\\cordova.js /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\cordova.bat ' + PROJECT_PATH + '\\cordova\\cordova.bat /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\clean.bat ' + PROJECT_PATH + '\\cordova\\clean.bat /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\build.bat ' + PROJECT_PATH + '\\cordova\\build.bat /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\log.bat ' + PROJECT_PATH + '\\cordova\\log.bat /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\run.bat ' + PROJECT_PATH + '\\cordova\\run.bat /Y');
// interpolate the activity name and package
WScript.Echo("Updating AndroidManifest.xml and Main Activity...");
replaceInFile(ACTIVITY_PATH, /__ACTIVITY__/, ACTIVITY);
replaceInFile(ACTIVITY_PATH, /__ID__/, PACKAGE);
replaceInFile(MANIFEST_PATH, /__ACTIVITY__/, ACTIVITY);
replaceInFile(MANIFEST_PATH, /__PACKAGE__/, PACKAGE);
replaceInFile(MANIFEST_PATH, /__APILEVEL__/, API_LEVEL);
cleanup();
@@ -1,3 +1,20 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import javax.xml.parsers.DocumentBuilder;
-7
View File
@@ -1,7 +0,0 @@
#!/bin/bash
set -e
CORDOVA_PATH=$( cd "$( dirname "$0" )" && pwd )
bash $CORDOVA_PATH/cordova BOOM
-1
View File
@@ -1 +0,0 @@
%~dp0\cordova.bat BOOM
Binary file not shown.
+24
View File
@@ -0,0 +1,24 @@
# 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.
#!/bin/bash
set -e
CORDOVA_PATH=$( cd "$( dirname "$0" )" && pwd )
bash "$CORDOVA_PATH"/cordova build
+18
View File
@@ -0,0 +1,18 @@
:: 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.
%~dp0\cordova.bat build
Regular → Executable
+18 -1
View File
@@ -1,7 +1,24 @@
# 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.
#!/bin/bash
set -e
CORDOVA_PATH=$( cd "$( dirname "$0" )" && pwd )
bash $CORDOVA_PATH/cordova clean
bash "$CORDOVA_PATH"/cordova clean
+17
View File
@@ -1 +1,18 @@
:: 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.
%~dp0\cordova.bat clean
Regular → Executable
+89 -21
View File
@@ -1,11 +1,28 @@
# 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.
#!/bin/bash
set -e
PROJECT_PATH=$( cd "$( dirname "$0" )/.." && pwd )
function check_devices {
local devices=`adb devices | awk '/List of devices attached/ { while(getline > 0) { print }}'`
# FIXME
local devices=`adb devices | awk '/List of devices attached/ { while(getline > 0) { print }}' | grep device`
if [ -z "$devices" ] ; then
echo "1"
else
@@ -20,7 +37,6 @@ function emulate {
# Do not launch an emulator if there is already one running or if a device is attached
if [ $(check_devices) == 0 ] ; then
echo "Device attached or emulator already running"
return
fi
@@ -45,8 +61,9 @@ function emulate {
do
echo "$i) ${avd_list[$i]}"
done
echo -n "> "
read avd_id
read -t 5 -p "> " avd_id
# default value if input timeout
if [ $avd_id -eq 1000 ] ; then avd_id=0 ; fi
done
emulator -cpu-delay 0 -no-boot-anim -cache /tmp/cache -avd ${avd_list[$avd_id]} 1> /dev/null 2>&1 &
fi
@@ -56,36 +73,87 @@ function emulate {
function clean {
ant clean
}
# has to be used independently and not in conjuction with other commands
# has to be used independently and not in conjunction with other commands
function log {
adb logcat
}
function debug_install {
function run {
clean && emulate && wait_for_device && install && launch
}
function install {
declare -a devices=($(adb devices | awk '/List of devices attached/ { while(getline > 0) { print }}' | grep device | cut -f 1))
local device_id="1000" #FIXME: hopefully user does not have 1000 AVDs
if [ ${#devices[@]} == 0 ]
then
# should not reach here. Emulator should launch or device should be attached
echo "Emulator not running or device not attached. Could not install debug package"
exit 70
fi
if [ ${#devices[@]} == 1 ]
then
export ANDROID_SERIAL=${devices[0]}
# User has more than 1 AVD
elif [ ${#devices[@]} -gt 1 ]
then
while [ -z ${devices[$device_id]} ]
do
echo "Choose from one of the following devices/emulators [0 to $((${#devices[@]}-1))]:"
for(( i = 0 ; i < ${#devices[@]} ; i++ ))
do
echo "$i) ${devices[$i]}"
done
read -t 5 -p "> " device_id
# default value if input timeout
if [ $device_id -eq 1000 ] ; then device_id=0 ; fi
done
export ANDROID_SERIAL=${devices[$device_id]}
fi
ant debug install
}
function debug {
function build {
ant debug
}
function launch {
local launch_str=$(java -jar $PROJECT_PATH/cordova/appinfo.jar $PROJECT_PATH/AndroidManifest.xml)
adb shell am start -n $launch_str
function release {
ant release
}
function BOOM {
clean
if [ $(check_devices) == 0 ] ; then
debug_install && launch
return
function wait_for_device {
local i="0"
echo -n "Waiting for device..."
while [ $i -lt 300 ]
do
if [ $(check_devices) -eq 0 ]
then
break
else
debug
echo "##################################################################"
echo "# Plug in your device or launch an emulator with cordova/emulate #"
echo "##################################################################"
sleep 1
i=$[i+1]
echo -n "."
fi
done
# Device timeout: emulator has not started in time or device not attached
if [ $i -eq 300 ]
then
echo "device timeout!"
exit 69
else
echo "connected!"
fi
}
function launch {
local launch_str=$(java -jar "$PROJECT_PATH"/cordova/appinfo.jar "$PROJECT_PATH"/AndroidManifest.xml)
adb shell am start -n $launch_str
}
# TODO parse arguments
(cd $PROJECT_PATH && $1)
(cd "$PROJECT_PATH" && $1)
+17
View File
@@ -1,3 +1,20 @@
:: Licensed to the Apache Software Foundation (ASF) under one
:: or more contributor license agreements. See the NOTICE file
:: distributed with this work for additional information
:: regarding copyright ownership. The ASF licenses this file
:: to you under the Apache License, Version 2.0 (the
:: "License"); you may not use this file except in compliance
:: with the License. You may obtain a copy of the License at
::
:: http://www.apache.org/licenses/LICENSE-2.0
::
:: Unless required by applicable law or agreed to in writing,
:: software distributed under the License is distributed on an
:: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
:: KIND, either express or implied. See the License for the
:: specific language governing permissions and limitations
:: under the License.
@ECHO OFF
IF NOT DEFINED JAVA_HOME GOTO MISSING
FOR %%X in (java.exe ant.bat android.bat) do (
+45 -16
View File
@@ -1,3 +1,20 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
var ROOT = WScript.ScriptFullName.split('\\cordova\\cordova.js').join(''),
shell=WScript.CreateObject("WScript.Shell");
@@ -17,17 +34,19 @@ function exec(command) {
return output;
}
function emulator_running() {
function device_running() {
var local_devices = shell.Exec("%comspec% /c adb devices").StdOut.ReadAll();
if(local_devices.match(/emulator/)) {
if(local_devices.match(/\w+\tdevice/)) {
WScript.Echo("Yes");
return true;
}
WScript.Echo("No");
return false;
}
function emulate() {
// don't run emulator if a device is plugged in or if emulator is already running
if(emulator_running()) {
WScript.Echo("Device or Emulator already running!");
if(device_running()) {
//WScript.Echo("Device or Emulator already running!");
return;
}
var oExec = shell.Exec("%comspec% /c android.bat list avd");
@@ -67,38 +86,48 @@ function emulate() {
}
function clean() {
WScript.Echo("Cleaning project...");
exec("%comspec% /c ant.bat clean -f "+ROOT+"\\build.xml 2>&1");
}
function debug() {
function build() {
WScript.Echo("Building project...");
exec("%comspec% /c ant.bat debug -f "+ROOT+"\\build.xml 2>&1");
}
function debug_install() {
function install() {
WScript.Echo("Building/Installing project...");
exec("%comspec% /c ant.bat debug install -f "+ROOT+"\\build.xml 2>&1");
}
function log() {
WScript.Echo(exec("%comspec% /c adb.bat logcat"));
shell.Run("%comspec% /c adb logcat");
}
function launch() {
WScript.Echo("Launching app...");
var launch_str=exec("%comspec% /c java -jar "+ROOT+"\\cordova\\appinfo.jar "+ROOT+"\\AndroidManifest.xml");
//WScript.Echo(launch_str);
exec("%comspec% /c adb shell am start -n "+launch_str+" 2>&1");
}
function BOOM() {
function run() {
var i=0;
clean();
if(emulator_running()) {
debug_install();
launch();
} else {
debug();
WScript.Echo("##################################################################");
WScript.Echo("# Plug in your device or launch an emulator with cordova/emulate #");
WScript.Echo("##################################################################");
emulate();
WScript.Stdout.Write('Waiting for device...');
while(!device_running() && i < 300) {
WScript.Stdout.Write('.');
WScript.sleep(1000);
i += 1;
}
if(i == 300) {
WScript.Stderr.WriteLine("device/emulator timeout!");
} else {
WScript.Stdout.WriteLine("connected!");
}
install();
launch();
}
var args = WScript.Arguments;
if(args.count() != 1) {
-7
View File
@@ -1,7 +0,0 @@
#!/bin/bash
set -e
CORDOVA_PATH=$( cd "$( dirname "$0" )" && pwd )
bash $CORDOVA_PATH/cordova debug
-1
View File
@@ -1 +0,0 @@
%~dp0\cordova.bat debug
-7
View File
@@ -1,7 +0,0 @@
#!/bin/bash
set -e
CORDOVA_PATH=$( cd "$( dirname "$0" )" && pwd )
bash $CORDOVA_PATH/cordova emulate
-1
View File
@@ -1 +0,0 @@
%~dp0\cordova.bat emulate
Regular → Executable
+19 -2
View File
@@ -1,7 +1,24 @@
# 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.
#!/bin/bash
set -e
PROJECT_PATH=$( cd "$( dirname "$0" )/.." && pwd )
CORDOVA_PATH=$( cd "$( dirname "$0" )/.." && pwd )
bash $PROJECT_PATH/cordova/cordova log
bash "$CORDOVA_PATH"/cordova/cordova log
+18
View File
@@ -0,0 +1,18 @@
:: 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.
%~dp0\cordova.bat log
+24
View File
@@ -0,0 +1,24 @@
# 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.
#!/bin/bash
set -e
CORDOVA_PATH=$( cd "$( dirname "$0" )" && pwd )
bash "$CORDOVA_PATH"/cordova release
+24
View File
@@ -0,0 +1,24 @@
# 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.
#!/bin/bash
set -e
CORDOVA_PATH=$( cd "$( dirname "$0" )" && pwd )
bash "$CORDOVA_PATH"/cordova run
+1
View File
@@ -0,0 +1 @@
%~dp0\cordova.bat run
+3 -2
View File
@@ -19,7 +19,6 @@
package __ID__;
import android.app.Activity;
import android.os.Bundle;
import org.apache.cordova.*;
@@ -29,7 +28,9 @@ public class __ACTIVITY__ extends DroidGap
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
super.loadUrl("file:///android_asset/www/index.html");
// Set by <content src="index.html" /> in config.xml
super.loadUrl(Config.getStartUrl());
//super.loadUrl("file:///android_asset/www/index.html")
}
}
+5 -3
View File
@@ -18,7 +18,7 @@
under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:windowSoftInputMode="adjustPan"
package="__PACKAGE__" android:versionName="1.1" android:versionCode="5">
package="__PACKAGE__" android:versionName="1.0" android:versionCode="1" android:hardwareAccelerated="true">
<supports-screens
android:largeScreens="true"
android:normalScreens="true"
@@ -47,9 +47,11 @@
<application android:icon="@drawable/icon" android:label="@string/app_name"
android:hardwareAccelerated="true"
android:debuggable="true">
<activity android:name="__ACTIVITY__" android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden">
android:theme="@android:style/Theme.Black.NoTitleBar"
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 +59,5 @@
</activity>
</application>
<uses-sdk android:minSdkVersion="5" />
<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="__APILEVEL__"/>
</manifest>
@@ -0,0 +1,115 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
* {
-webkit-tap-highlight-color: rgba(0,0,0,0); /* make transparent link selection, adjust last value opacity 0 to 1.0 */
}
body {
-webkit-touch-callout: none; /* prevent callout to copy image, etc when tap to hold */
-webkit-text-size-adjust: none; /* prevent webkit from resizing text to fit */
-webkit-user-select: none; /* prevent copy paste, to allow, change 'none' to 'text' */
background-color:#E4E4E4;
background-image:linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%);
background-image:-webkit-linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%);
background-image:-ms-linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%);
background-image:-webkit-gradient(
linear,
left top,
left bottom,
color-stop(0, #A7A7A7),
color-stop(0.51, #E4E4E4)
);
background-attachment:fixed;
font-family:'HelveticaNeue-Light', 'HelveticaNeue', Helvetica, Arial, sans-serif;
font-size:12px;
height:100%;
margin:0px;
padding:0px;
text-transform:uppercase;
width:100%;
}
/* Portrait layout (default) */
.app {
background:url(../img/logo.png) no-repeat center top; /* 170px x 200px */
position:absolute; /* position in the center of the screen */
left:50%;
top:50%;
height:50px; /* text area height */
width:225px; /* text area width */
text-align:center;
padding:180px 0px 0px 0px; /* image height is 200px (bottom 20px are overlapped with text) */
margin:-115px 0px 0px -112px; /* offset vertical: half of image height and text area height */
/* offset horizontal: half of text area width */
}
/* Landscape layout (with min-width) */
@media screen and (min-aspect-ratio: 1/1) and (min-width:400px) {
.app {
background-position:left center;
padding:75px 0px 75px 170px; /* padding-top + padding-bottom + text area = image height */
margin:-90px 0px 0px -198px; /* offset vertical: half of image height */
/* offset horizontal: half of image width and text area width */
}
}
h1 {
font-size:24px;
font-weight:normal;
margin:0px;
overflow:visible;
padding:0px;
text-align:center;
}
.event {
border-radius:4px;
-webkit-border-radius:4px;
color:#FFFFFF;
font-size:12px;
margin:0px 30px;
padding:2px 0px;
}
.event.listening {
background-color:#333333;
display:block;
}
.event.received {
background-color:#4B946A;
display:none;
}
@keyframes fade {
from { opacity: 1.0; }
50% { opacity: 0.4; }
to { opacity: 1.0; }
}
@-webkit-keyframes fade {
from { opacity: 1.0; }
50% { opacity: 0.4; }
to { opacity: 1.0; }
}
.blink {
animation:fade 3000ms infinite;
-webkit-animation:fade 3000ms infinite;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

+17 -35
View File
@@ -1,4 +1,4 @@
<!DOCTYPE HTML>
<!DOCTYPE html>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
@@ -19,42 +19,24 @@
-->
<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.0rc1.js"></script>
<script type="text/javascript" charset="utf-8" src="main.js"></script>
<meta charset="utf-8" />
<meta name="format-detection" content="telephone=no" />
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
<link rel="stylesheet" type="text/css" href="css/index.css" />
<title>Hello World</title>
</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>
<body>
<div class="app">
<h1>Apache Cordova</h1>
<div id="deviceready" class="blink">
<p class="event listening">Connecting to Device</p>
<p class="event received">Device is Ready</p>
</div>
<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>
<script type="text/javascript" src="cordova-2.6.0.js"></script>
<script type="text/javascript" src="js/index.js"></script>
<script type="text/javascript">
app.initialize();
</script>
</body>
</html>
@@ -0,0 +1,49 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
var app = {
// Application Constructor
initialize: function() {
this.bindEvents();
},
// Bind Event Listeners
//
// Bind any events that are required on startup. Common events are:
// 'load', 'deviceready', 'offline', and 'online'.
bindEvents: function() {
document.addEventListener('deviceready', this.onDeviceReady, false);
},
// deviceready Event Handler
//
// The scope of 'this' is the event. In order to call the 'receivedEvent'
// function, we must explicity call 'app.receivedEvent(...);'
onDeviceReady: function() {
app.receivedEvent('deviceready');
},
// Update DOM on a Received Event
receivedEvent: function(id) {
var parentElement = document.getElementById(id);
var listeningElement = parentElement.querySelector('.listening');
var receivedElement = parentElement.querySelector('.received');
listeningElement.setAttribute('style', 'display:none;');
receivedElement.setAttribute('style', 'display:block;');
console.log('Received Event: ' + id);
}
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 KiB

@@ -0,0 +1,68 @@
<!DOCTYPE html>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<html>
<head>
<title>Jasmine Spec Runner</title>
<!-- jasmine source -->
<link rel="shortcut icon" type="image/png" href="spec/lib/jasmine-1.2.0/jasmine_favicon.png">
<link rel="stylesheet" type="text/css" href="spec/lib/jasmine-1.2.0/jasmine.css">
<script type="text/javascript" src="spec/lib/jasmine-1.2.0/jasmine.js"></script>
<script type="text/javascript" src="spec/lib/jasmine-1.2.0/jasmine-html.js"></script>
<!-- include source files here... -->
<script type="text/javascript" src="js/index.js"></script>
<!-- include spec files here... -->
<script type="text/javascript" src="spec/helper.js"></script>
<script type="text/javascript" src="spec/index.js"></script>
<script type="text/javascript">
(function() {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000;
var htmlReporter = new jasmine.HtmlReporter();
jasmineEnv.addReporter(htmlReporter);
jasmineEnv.specFilter = function(spec) {
return htmlReporter.specFilter(spec);
};
var currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
execJasmine();
};
function execJasmine() {
jasmineEnv.execute();
}
})();
</script>
</head>
<body>
<div id="stage" style="display:none;"></div>
</body>
</html>
@@ -0,0 +1,33 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
afterEach(function() {
document.getElementById('stage').innerHTML = '';
});
var helper = {
trigger: function(obj, name) {
var e = document.createEvent('Event');
e.initEvent(name, true, true);
obj.dispatchEvent(e);
},
getComputedStyle: function(querySelector, property) {
var element = document.querySelector(querySelector);
return window.getComputedStyle(element).getPropertyValue(property);
}
};
@@ -0,0 +1,67 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
describe('app', function() {
describe('initialize', function() {
it('should bind deviceready', function() {
runs(function() {
spyOn(app, 'onDeviceReady');
app.initialize();
helper.trigger(window.document, 'deviceready');
});
waitsFor(function() {
return (app.onDeviceReady.calls.length > 0);
}, 'onDeviceReady should be called once', 500);
runs(function() {
expect(app.onDeviceReady).toHaveBeenCalled();
});
});
});
describe('onDeviceReady', function() {
it('should report that it fired', function() {
spyOn(app, 'receivedEvent');
app.onDeviceReady();
expect(app.receivedEvent).toHaveBeenCalledWith('deviceready');
});
});
describe('receivedEvent', function() {
beforeEach(function() {
var el = document.getElementById('stage');
el.innerHTML = ['<div id="deviceready">',
' <p class="event listening">Listening</p>',
' <p class="event received">Received</p>',
'</div>'].join('\n');
});
it('should hide the listening element', function() {
app.receivedEvent('deviceready');
var displayStyle = helper.getComputedStyle('#deviceready .listening', 'display');
expect(displayStyle).toEqual('none');
});
it('should show the received element', function() {
app.receivedEvent('deviceready');
var displayStyle = helper.getComputedStyle('#deviceready .received', 'display');
expect(displayStyle).toEqual('block');
});
});
});
@@ -0,0 +1,20 @@
Copyright (c) 2008-2011 Pivotal Labs
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,616 @@
jasmine.HtmlReporterHelpers = {};
jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) {
var el = document.createElement(type);
for (var i = 2; i < arguments.length; i++) {
var child = arguments[i];
if (typeof child === 'string') {
el.appendChild(document.createTextNode(child));
} else {
if (child) {
el.appendChild(child);
}
}
}
for (var attr in attrs) {
if (attr == "className") {
el[attr] = attrs[attr];
} else {
el.setAttribute(attr, attrs[attr]);
}
}
return el;
};
jasmine.HtmlReporterHelpers.getSpecStatus = function(child) {
var results = child.results();
var status = results.passed() ? 'passed' : 'failed';
if (results.skipped) {
status = 'skipped';
}
return status;
};
jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) {
var parentDiv = this.dom.summary;
var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite';
var parent = child[parentSuite];
if (parent) {
if (typeof this.views.suites[parent.id] == 'undefined') {
this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views);
}
parentDiv = this.views.suites[parent.id].element;
}
parentDiv.appendChild(childElement);
};
jasmine.HtmlReporterHelpers.addHelpers = function(ctor) {
for(var fn in jasmine.HtmlReporterHelpers) {
ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn];
}
};
jasmine.HtmlReporter = function(_doc) {
var self = this;
var doc = _doc || window.document;
var reporterView;
var dom = {};
// Jasmine Reporter Public Interface
self.logRunningSpecs = false;
self.reportRunnerStarting = function(runner) {
var specs = runner.specs() || [];
if (specs.length == 0) {
return;
}
createReporterDom(runner.env.versionString());
doc.body.appendChild(dom.reporter);
reporterView = new jasmine.HtmlReporter.ReporterView(dom);
reporterView.addSpecs(specs, self.specFilter);
};
self.reportRunnerResults = function(runner) {
reporterView && reporterView.complete();
};
self.reportSuiteResults = function(suite) {
reporterView.suiteComplete(suite);
};
self.reportSpecStarting = function(spec) {
if (self.logRunningSpecs) {
self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
}
};
self.reportSpecResults = function(spec) {
reporterView.specComplete(spec);
};
self.log = function() {
var console = jasmine.getGlobal().console;
if (console && console.log) {
if (console.log.apply) {
console.log.apply(console, arguments);
} else {
console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
}
}
};
self.specFilter = function(spec) {
if (!focusedSpecName()) {
return true;
}
return spec.getFullName().indexOf(focusedSpecName()) === 0;
};
return self;
function focusedSpecName() {
var specName;
(function memoizeFocusedSpec() {
if (specName) {
return;
}
var paramMap = [];
var params = doc.location.search.substring(1).split('&');
for (var i = 0; i < params.length; i++) {
var p = params[i].split('=');
paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
}
specName = paramMap.spec;
})();
return specName;
}
function createReporterDom(version) {
dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' },
dom.banner = self.createDom('div', { className: 'banner' },
self.createDom('span', { className: 'title' }, "Jasmine "),
self.createDom('span', { className: 'version' }, version)),
dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}),
dom.alert = self.createDom('div', {className: 'alert'}),
dom.results = self.createDom('div', {className: 'results'},
dom.summary = self.createDom('div', { className: 'summary' }),
dom.details = self.createDom('div', { id: 'details' }))
);
}
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);jasmine.HtmlReporter.ReporterView = function(dom) {
this.startedAt = new Date();
this.runningSpecCount = 0;
this.completeSpecCount = 0;
this.passedCount = 0;
this.failedCount = 0;
this.skippedCount = 0;
this.createResultsMenu = function() {
this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'},
this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'),
' | ',
this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing'));
this.summaryMenuItem.onclick = function() {
dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, '');
};
this.detailsMenuItem.onclick = function() {
showDetails();
};
};
this.addSpecs = function(specs, specFilter) {
this.totalSpecCount = specs.length;
this.views = {
specs: {},
suites: {}
};
for (var i = 0; i < specs.length; i++) {
var spec = specs[i];
this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views);
if (specFilter(spec)) {
this.runningSpecCount++;
}
}
};
this.specComplete = function(spec) {
this.completeSpecCount++;
if (isUndefined(this.views.specs[spec.id])) {
this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom);
}
var specView = this.views.specs[spec.id];
switch (specView.status()) {
case 'passed':
this.passedCount++;
break;
case 'failed':
this.failedCount++;
break;
case 'skipped':
this.skippedCount++;
break;
}
specView.refresh();
this.refresh();
};
this.suiteComplete = function(suite) {
var suiteView = this.views.suites[suite.id];
if (isUndefined(suiteView)) {
return;
}
suiteView.refresh();
};
this.refresh = function() {
if (isUndefined(this.resultsMenu)) {
this.createResultsMenu();
}
// currently running UI
if (isUndefined(this.runningAlert)) {
this.runningAlert = this.createDom('a', {href: "?", className: "runningAlert bar"});
dom.alert.appendChild(this.runningAlert);
}
this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount);
// skipped specs UI
if (isUndefined(this.skippedAlert)) {
this.skippedAlert = this.createDom('a', {href: "?", className: "skippedAlert bar"});
}
this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
if (this.skippedCount === 1 && isDefined(dom.alert)) {
dom.alert.appendChild(this.skippedAlert);
}
// passing specs UI
if (isUndefined(this.passedAlert)) {
this.passedAlert = this.createDom('span', {href: "?", className: "passingAlert bar"});
}
this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount);
// failing specs UI
if (isUndefined(this.failedAlert)) {
this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"});
}
this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount);
if (this.failedCount === 1 && isDefined(dom.alert)) {
dom.alert.appendChild(this.failedAlert);
dom.alert.appendChild(this.resultsMenu);
}
// summary info
this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount);
this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing";
};
this.complete = function() {
dom.alert.removeChild(this.runningAlert);
this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
if (this.failedCount === 0) {
dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount)));
} else {
showDetails();
}
dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"));
};
return this;
function showDetails() {
if (dom.reporter.className.search(/showDetails/) === -1) {
dom.reporter.className += " showDetails";
}
}
function isUndefined(obj) {
return typeof obj === 'undefined';
}
function isDefined(obj) {
return !isUndefined(obj);
}
function specPluralizedFor(count) {
var str = count + " spec";
if (count > 1) {
str += "s"
}
return str;
}
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView);
jasmine.HtmlReporter.SpecView = function(spec, dom, views) {
this.spec = spec;
this.dom = dom;
this.views = views;
this.symbol = this.createDom('li', { className: 'pending' });
this.dom.symbolSummary.appendChild(this.symbol);
this.summary = this.createDom('div', { className: 'specSummary' },
this.createDom('a', {
className: 'description',
href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
title: this.spec.getFullName()
}, this.spec.description)
);
this.detail = this.createDom('div', { className: 'specDetail' },
this.createDom('a', {
className: 'description',
href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
title: this.spec.getFullName()
}, this.spec.getFullName())
);
};
jasmine.HtmlReporter.SpecView.prototype.status = function() {
return this.getSpecStatus(this.spec);
};
jasmine.HtmlReporter.SpecView.prototype.refresh = function() {
this.symbol.className = this.status();
switch (this.status()) {
case 'skipped':
break;
case 'passed':
this.appendSummaryToSuiteDiv();
break;
case 'failed':
this.appendSummaryToSuiteDiv();
this.appendFailureDetail();
break;
}
};
jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() {
this.summary.className += ' ' + this.status();
this.appendToSummary(this.spec, this.summary);
};
jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() {
this.detail.className += ' ' + this.status();
var resultItems = this.spec.results().getItems();
var messagesDiv = this.createDom('div', { className: 'messages' });
for (var i = 0; i < resultItems.length; i++) {
var result = resultItems[i];
if (result.type == 'log') {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
} else if (result.type == 'expect' && result.passed && !result.passed()) {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
if (result.trace.stack) {
messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
}
}
}
if (messagesDiv.childNodes.length > 0) {
this.detail.appendChild(messagesDiv);
this.dom.details.appendChild(this.detail);
}
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) {
this.suite = suite;
this.dom = dom;
this.views = views;
this.element = this.createDom('div', { className: 'suite' },
this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(this.suite.getFullName()) }, this.suite.description)
);
this.appendToSummary(this.suite, this.element);
};
jasmine.HtmlReporter.SuiteView.prototype.status = function() {
return this.getSpecStatus(this.suite);
};
jasmine.HtmlReporter.SuiteView.prototype.refresh = function() {
this.element.className += " " + this.status();
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView);
/* @deprecated Use jasmine.HtmlReporter instead
*/
jasmine.TrivialReporter = function(doc) {
this.document = doc || document;
this.suiteDivs = {};
this.logRunningSpecs = false;
};
jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
var el = document.createElement(type);
for (var i = 2; i < arguments.length; i++) {
var child = arguments[i];
if (typeof child === 'string') {
el.appendChild(document.createTextNode(child));
} else {
if (child) { el.appendChild(child); }
}
}
for (var attr in attrs) {
if (attr == "className") {
el[attr] = attrs[attr];
} else {
el.setAttribute(attr, attrs[attr]);
}
}
return el;
};
jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
var showPassed, showSkipped;
this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' },
this.createDom('div', { className: 'banner' },
this.createDom('div', { className: 'logo' },
this.createDom('span', { className: 'title' }, "Jasmine"),
this.createDom('span', { className: 'version' }, runner.env.versionString())),
this.createDom('div', { className: 'options' },
"Show ",
showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
)
),
this.runnerDiv = this.createDom('div', { className: 'runner running' },
this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
);
this.document.body.appendChild(this.outerDiv);
var suites = runner.suites();
for (var i = 0; i < suites.length; i++) {
var suite = suites[i];
var suiteDiv = this.createDom('div', { className: 'suite' },
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
this.suiteDivs[suite.id] = suiteDiv;
var parentDiv = this.outerDiv;
if (suite.parentSuite) {
parentDiv = this.suiteDivs[suite.parentSuite.id];
}
parentDiv.appendChild(suiteDiv);
}
this.startedAt = new Date();
var self = this;
showPassed.onclick = function(evt) {
if (showPassed.checked) {
self.outerDiv.className += ' show-passed';
} else {
self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
}
};
showSkipped.onclick = function(evt) {
if (showSkipped.checked) {
self.outerDiv.className += ' show-skipped';
} else {
self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
}
};
};
jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
var results = runner.results();
var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
this.runnerDiv.setAttribute("class", className);
//do it twice for IE
this.runnerDiv.setAttribute("className", className);
var specs = runner.specs();
var specCount = 0;
for (var i = 0; i < specs.length; i++) {
if (this.specFilter(specs[i])) {
specCount++;
}
}
var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
};
jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
var results = suite.results();
var status = results.passed() ? 'passed' : 'failed';
if (results.totalCount === 0) { // todo: change this to check results.skipped
status = 'skipped';
}
this.suiteDivs[suite.id].className += " " + status;
};
jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
if (this.logRunningSpecs) {
this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
}
};
jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
var results = spec.results();
var status = results.passed() ? 'passed' : 'failed';
if (results.skipped) {
status = 'skipped';
}
var specDiv = this.createDom('div', { className: 'spec ' + status },
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
this.createDom('a', {
className: 'description',
href: '?spec=' + encodeURIComponent(spec.getFullName()),
title: spec.getFullName()
}, spec.description));
var resultItems = results.getItems();
var messagesDiv = this.createDom('div', { className: 'messages' });
for (var i = 0; i < resultItems.length; i++) {
var result = resultItems[i];
if (result.type == 'log') {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
} else if (result.type == 'expect' && result.passed && !result.passed()) {
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
if (result.trace.stack) {
messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
}
}
}
if (messagesDiv.childNodes.length > 0) {
specDiv.appendChild(messagesDiv);
}
this.suiteDivs[spec.suite.id].appendChild(specDiv);
};
jasmine.TrivialReporter.prototype.log = function() {
var console = jasmine.getGlobal().console;
if (console && console.log) {
if (console.log.apply) {
console.log.apply(console, arguments);
} else {
console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
}
}
};
jasmine.TrivialReporter.prototype.getLocation = function() {
return this.document.location;
};
jasmine.TrivialReporter.prototype.specFilter = function(spec) {
var paramMap = {};
var params = this.getLocation().search.substring(1).split('&');
for (var i = 0; i < params.length; i++) {
var p = params[i].split('=');
paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
}
if (!paramMap.spec) {
return true;
}
return spec.getFullName().indexOf(paramMap.spec) === 0;
};
@@ -0,0 +1,81 @@
body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; }
#HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; }
#HTMLReporter a { text-decoration: none; }
#HTMLReporter a:hover { text-decoration: underline; }
#HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; }
#HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; }
#HTMLReporter #jasmine_content { position: fixed; right: 100%; }
#HTMLReporter .version { color: #aaaaaa; }
#HTMLReporter .banner { margin-top: 14px; }
#HTMLReporter .duration { color: #aaaaaa; float: right; }
#HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; }
#HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; }
#HTMLReporter .symbolSummary li.passed { font-size: 14px; }
#HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; }
#HTMLReporter .symbolSummary li.failed { line-height: 9px; }
#HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; }
#HTMLReporter .symbolSummary li.skipped { font-size: 14px; }
#HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; }
#HTMLReporter .symbolSummary li.pending { line-height: 11px; }
#HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; }
#HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
#HTMLReporter .runningAlert { background-color: #666666; }
#HTMLReporter .skippedAlert { background-color: #aaaaaa; }
#HTMLReporter .skippedAlert:first-child { background-color: #333333; }
#HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; }
#HTMLReporter .passingAlert { background-color: #a6b779; }
#HTMLReporter .passingAlert:first-child { background-color: #5e7d00; }
#HTMLReporter .failingAlert { background-color: #cf867e; }
#HTMLReporter .failingAlert:first-child { background-color: #b03911; }
#HTMLReporter .results { margin-top: 14px; }
#HTMLReporter #details { display: none; }
#HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; }
#HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
#HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
#HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
#HTMLReporter.showDetails .summary { display: none; }
#HTMLReporter.showDetails #details { display: block; }
#HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
#HTMLReporter .summary { margin-top: 14px; }
#HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; }
#HTMLReporter .summary .specSummary.passed a { color: #5e7d00; }
#HTMLReporter .summary .specSummary.failed a { color: #b03911; }
#HTMLReporter .description + .suite { margin-top: 0; }
#HTMLReporter .suite { margin-top: 14px; }
#HTMLReporter .suite a { color: #333333; }
#HTMLReporter #details .specDetail { margin-bottom: 28px; }
#HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; }
#HTMLReporter .resultMessage { padding-top: 14px; color: #333333; }
#HTMLReporter .resultMessage span.result { display: block; }
#HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; }
#TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ }
#TrivialReporter a:visited, #TrivialReporter a { color: #303; }
#TrivialReporter a:hover, #TrivialReporter a:active { color: blue; }
#TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; }
#TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; }
#TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; }
#TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; }
#TrivialReporter .runner.running { background-color: yellow; }
#TrivialReporter .options { text-align: right; font-size: .8em; }
#TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; }
#TrivialReporter .suite .suite { margin: 5px; }
#TrivialReporter .suite.passed { background-color: #dfd; }
#TrivialReporter .suite.failed { background-color: #fdd; }
#TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; }
#TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; }
#TrivialReporter .spec.failed { background-color: #fbb; border-color: red; }
#TrivialReporter .spec.passed { background-color: #bfb; border-color: green; }
#TrivialReporter .spec.skipped { background-color: #bbb; }
#TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; }
#TrivialReporter .passed { background-color: #cfc; display: none; }
#TrivialReporter .failed { background-color: #fbb; }
#TrivialReporter .skipped { color: #777; background-color: #eee; display: none; }
#TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; }
#TrivialReporter .resultMessage .mismatch { color: black; }
#TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; }
#TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; }
#TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; }
#TrivialReporter #jasmine_content { position: fixed; right: 100%; }
#TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; }
File diff suppressed because it is too large Load Diff
+18 -1
View File
@@ -1,3 +1,20 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
var build_path = __dirname + '/../..',
project_path = '/tmp/example',
package_name = 'org.apache.cordova.example',
@@ -58,7 +75,7 @@ create_project.on('exit', function(code) {
// make sure main Activity was added
path.exists(util.format('%s/src/%s/%s.java', project_path, package_as_path, project_name), function(exists) {
assert(exists, 'Activity did not get created');
// TODO check that package name and activity name were substitued properly
// TODO check that package name and activity name were substituted properly
});
// make sure plugins.xml was added
+21 -9
View File
@@ -1,3 +1,20 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
var build_path = __dirname + '/../..'
project_path = process.env.Temp + '\\example',
package_name = 'org.apache.cordova.example',
@@ -68,17 +85,12 @@ create_project.on('exit', function(code) {
// make sure main Activity was added
path.exists(util.format('%s/src/%s/%s.java', project_path, package_as_path, project_name), function(exists) {
assert(exists, 'Activity did not get created');
// TODO check that package name and activity name were substitued properly
// TODO check that package name and activity name were substituted properly
});
// make sure plugins.xml was added
path.exists(util.format('%s/res/xml/plugins.xml', project_path), function(exists) {
assert(exists, 'plugins.xml did not get created');
});
// make sure cordova.xml was added
path.exists(util.format('%s/res/xml/cordova.xml', project_path), function(exists) {
assert(exists, 'plugins.xml did not get created');
// make sure config.xml was added
path.exists(util.format('%s/res/xml/config.xml', project_path), function(exists) {
assert(exists, 'config.xml did not get created');
});
// make sure cordova.jar was added
Executable
+139
View File
@@ -0,0 +1,139 @@
#! /bin/bash
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
# update a cordova/android project's command line tools
#
# USAGE
# ./update [path]
#
set -e
if [ -z "$1" ] || [ "$1" == "-h" ]
then
echo 'usage: update path'
echo "Make sure the Android SDK tools folder is in your PATH!"
exit 0
fi
BUILD_PATH="$( cd "$( dirname "$0" )/.." && pwd )"
VERSION=$(cat "$BUILD_PATH"/VERSION)
PROJECT_PATH="${1:-'./example'}"
if [ ! -d "$PROJECT_PATH" ]
then
echo "The project path has to exist for it to be updated"
exit 0
fi
# cleanup after exit and/or on error
function on_exit {
if [ -f "$BUILD_PATH"/framework/assets/www/cordova-$VERSION.js ]
then
rm "$BUILD_PATH"/framework/assets/www/cordova-$VERSION.js
fi
if [ -f "$BUILD_PATH"/framework/cordova-$VERSION.jar ]
then
rm "$BUILD_PATH"/framework/cordova-$VERSION.jar
fi
}
function createAppInfoJar {
(cd "$BUILD_PATH"/bin/templates/cordova/ApplicationInfo &&
javac ApplicationInfo.java &&
jar -cfe ../appinfo.jar ApplicationInfo ApplicationInfo.class
)
}
function on_error {
echo "An unexpected error occurred: $previous_command exited with $?"
echo "Deleting project..."
[ -d "$PROJECT_PATH" ] && rm -rf "$PROJECT_PATH"
exit 1
}
function replace {
local pattern=$1
local filename=$2
# Mac OS X requires -i argument
if [[ "$OSTYPE" =~ "darwin" ]]
then
/usr/bin/sed -i '' -e $pattern "$filename"
elif [[ "$OSTYPE" =~ "linux" ]]
then
/bin/sed -i -e $pattern "$filename"
fi
}
# we do not want the script to silently fail
trap 'previous_command=$this_command; this_command=$BASH_COMMAND' DEBUG
trap on_error ERR
trap on_exit EXIT
ANDROID_BIN="${ANDROID_BIN:=$( which android )}"
TARGET=$("$ANDROID_BIN" list targets | grep id: | tail -1 | cut -f 2 -d ' ' )
API_LEVEL=$("$ANDROID_BIN" list target | grep "API level:" | tail -n 1 | cut -f 2 -d ':' | tr -d ' ')
# check that build targets exist
if [ -z "$TARGET" ] || [ -z "$API_LEVEL" ]
then
echo "No Android Targets are installed. Please install at least one via the android SDK"
exit 1
fi
# if this a distribution release no need to build a jar
if [ ! -e "$BUILD_PATH"/cordova-$VERSION.jar ] && [ -d "$BUILD_PATH"/framework ]
then
# update the cordova-android framework for the desired target
"$ANDROID_BIN" update project --target $TARGET --path "$BUILD_PATH"/framework &> /dev/null
if [ ! -e "$BUILD_PATH"/framework/libs/commons-codec-1.7.jar ]; then
# Use curl to get the jar (TODO: Support Apache Mirrors)
curl -OL http://archive.apache.org/dist/commons/codec/binaries/commons-codec-1.7-bin.zip &> /dev/null
unzip commons-codec-1.7-bin.zip &> /dev/null
mkdir -p "$BUILD_PATH"/framework/libs
cp commons-codec-1.7/commons-codec-1.7.jar "$BUILD_PATH"/framework/libs
# cleanup yo
rm commons-codec-1.7-bin.zip && rm -rf commons-codec-1.7
fi
# compile cordova.js and cordova.jar
(cd "$BUILD_PATH"/framework && ant jar &> /dev/null )
fi
# copy cordova.js, cordova.jar and res/xml
if [ -d "$BUILD_PATH"/framework ]
then
cp "$BUILD_PATH"/framework/assets/www/cordova-$VERSION.js "$PROJECT_PATH"/assets/www/cordova-$VERSION.js
cp "$BUILD_PATH"/framework/cordova-$VERSION.jar "$PROJECT_PATH"/libs/cordova-$VERSION.jar
else
cp "$BUILD_PATH"/cordova-$VERSION.js "$PROJECT_PATH"/assets/www/cordova-$VERSION.js
cp "$BUILD_PATH"/cordova-$VERSION.jar "$PROJECT_PATH"/libs/cordova-$VERSION.jar
fi
# creating cordova folder and copying run/build/log/launch scripts
cp "$BUILD_PATH"/bin/templates/cordova/appinfo.jar "$PROJECT_PATH"/cordova/appinfo.jar
cp "$BUILD_PATH"/bin/templates/cordova/cordova "$PROJECT_PATH"/cordova/cordova
cp "$BUILD_PATH"/bin/templates/cordova/build "$PROJECT_PATH"/cordova/build
cp "$BUILD_PATH"/bin/templates/cordova/release "$PROJECT_PATH"/cordova/release
cp "$BUILD_PATH"/bin/templates/cordova/clean "$PROJECT_PATH"/cordova/clean
cp "$BUILD_PATH"/bin/templates/cordova/log "$PROJECT_PATH"/cordova/log
cp "$BUILD_PATH"/bin/templates/cordova/run "$PROJECT_PATH"/cordova/run
+32
View File
@@ -0,0 +1,32 @@
:: Licensed to the Apache Software Foundation (ASF) under one
:: or more contributor license agreements. See the NOTICE file
:: distributed with this work for additional information
:: regarding copyright ownership. The ASF licenses this file
:: to you under the Apache License, Version 2.0 (the
:: "License"); you may not use this file except in compliance
:: with the License. You may obtain a copy of the License at
::
:: http://www.apache.org/licenses/LICENSE-2.0
::
:: Unless required by applicable law or agreed to in writing,
:: software distributed under the License is distributed on an
:: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
:: KIND, either express or implied. See the License for the
:: specific language governing permissions and limitations
:: under the License.
@ECHO OFF
IF NOT DEFINED JAVA_HOME GOTO MISSING
FOR %%X in (java.exe javac.exe ant.bat android.bat) do (
SET FOUND=%%~$PATH:X
IF NOT DEFINED FOUND GOTO MISSING
)
cscript "%~dp0\update.js" %*
GOTO END
:MISSING
ECHO Missing one of the following:
ECHO JDK: http://java.oracle.com
ECHO Android SDK: http://developer.android.com
ECHO Apache ant: http://ant.apache.org
EXIT /B 1
:END
+193
View File
@@ -0,0 +1,193 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
/*
* create a cordova/android project
*
* USAGE
* ./update [path]
*/
var fso = WScript.CreateObject('Scripting.FileSystemObject');
function read(filename) {
var fso=WScript.CreateObject("Scripting.FileSystemObject");
var f=fso.OpenTextFile(filename, 1);
var s=f.ReadAll();
f.Close();
return s;
}
function checkTargets(targets) {
if(!targets) {
WScript.Echo("You do not have any android targets setup. Please create at least one target with the `android` command");
WScript.Quit(69);
}
}
function setTarget() {
var targets = shell.Exec('android.bat list targets').StdOut.ReadAll().match(/id:\s\d+/g);
checkTargets(targets);
return targets[targets.length - 1].replace(/id: /, ""); // TODO: give users the option to set their target
}
function setApiLevel() {
var targets = shell.Exec('android.bat list targets').StdOut.ReadAll().match(/API level:\s\d+/g);
checkTargets(targets);
return targets[targets.length - 1].replace(/API level: /, "");
}
function write(filename, contents) {
var fso=WScript.CreateObject("Scripting.FileSystemObject");
var f=fso.OpenTextFile(filename, 2, true);
f.Write(contents);
f.Close();
}
function replaceInFile(filename, regexp, replacement) {
write(filename, read(filename).replace(regexp, replacement));
}
function exec(command) {
var oShell=shell.Exec(command);
while (oShell.Status == 0) {
if(!oShell.StdOut.AtEndOfStream) {
var line = oShell.StdOut.ReadLine();
// XXX: Change to verbose mode
// WScript.StdOut.WriteLine(line);
}
WScript.sleep(100);
}
}
function createAppInfoJar() {
if(!fso.FileExists(ROOT+"\\bin\\templates\\cordova\\appinfo.jar")) {
WScript.Echo("Creating appinfo.jar...");
var cur = shell.CurrentDirectory;
shell.CurrentDirectory = ROOT+"\\bin\\templates\\cordova\\ApplicationInfo";
exec("javac ApplicationInfo.java");
exec("jar -cfe ..\\appinfo.jar ApplicationInfo ApplicationInfo.class");
shell.CurrentDirectory = cur;
}
}
function cleanup() {
if(fso.FileExists(ROOT + '\\framework\\cordova-'+VERSION+'.jar')) {
fso.DeleteFile(ROOT + '\\framework\\cordova-'+VERSION+'.jar');
}
if(fso.FileExists(ROOT + '\\framework\\assets\\www\\cordova-'+VERSION+'.js')) {
fso.DeleteFile(ROOT + '\\framework\\assets\\www\\cordova-'+VERSION+'.js');
}
}
function downloadCommonsCodec() {
if (!fso.FileExists(ROOT + '\\framework\\libs\\commons-codec-1.7.jar')) {
// We need the .jar
var url = 'http://archive.apache.org/dist/commons/codec/binaries/commons-codec-1.7-bin.zip';
var libsPath = ROOT + '\\framework\\libs';
var savePath = libsPath + '\\commons-codec-1.7-bin.zip';
if (!fso.FileExists(savePath)) {
if(!fso.FolderExists(ROOT + '\\framework\\libs')) {
fso.CreateFolder(libsPath);
}
// We need the zip to get the jar
var xhr = WScript.CreateObject('MSXML2.XMLHTTP');
xhr.open('GET', url, false);
xhr.send();
if (xhr.status == 200) {
var stream = WScript.CreateObject('ADODB.Stream');
stream.Open();
stream.Type = 1;
stream.Write(xhr.ResponseBody);
stream.Position = 0;
stream.SaveToFile(savePath);
stream.Close();
} else {
WScript.Echo('Could not retrieve the commons-codec. Please download it yourself and put into the framework/libs directory. This process may fail now. Sorry.');
}
}
var app = WScript.CreateObject('Shell.Application');
var source = app.NameSpace(savePath).Items();
var target = app.NameSpace(ROOT + '\\framework\\libs');
target.CopyHere(source, 256);
// Move the jar into libs
fso.MoveFile(ROOT + '\\framework\\libs\\commons-codec-1.7\\commons-codec-1.7.jar', ROOT + '\\framework\\libs\\commons-codec-1.7.jar');
// Clean up
fso.DeleteFile(ROOT + '\\framework\\libs\\commons-codec-1.7-bin.zip');
fso.DeleteFolder(ROOT + '\\framework\\libs\\commons-codec-1.7', true);
}
}
var args = WScript.Arguments, PROJECT_PATH="example",
shell=WScript.CreateObject("WScript.Shell");
// working dir
var ROOT = WScript.ScriptFullName.split('\\bin\\update.js').join('');
if (args.Count() == 1) {
PROJECT_PATH=args(0);
}
if(!fso.FolderExists(PROJECT_PATH)) {
WScript.Echo("Project doesn't exist!");
WScript.Quit(1);
}
var TARGET=setTarget();
var API_LEVEL=setApiLevel();
var VERSION=read(ROOT+'\\VERSION').replace(/\r\n/,'').replace(/\n/,'');
// build from source. distro should have these files
if (!fso.FileExists(ROOT+'\\cordova-'+VERSION+'.jar') &&
!fso.FileExists(ROOT+'\\cordova-'+VERSION+'.js')) {
WScript.Echo("Building jar and js files...");
// update the cordova framework project to a target that exists on this machine
exec('android.bat update project --target '+TARGET+' --path '+ROOT+'\\framework');
// pull down commons codec if necessary
downloadCommonsCodec();
exec('ant.bat -f \"'+ ROOT +'\\framework\\build.xml\" jar');
}
// check if we have the source or the distro files
WScript.Echo("Copying js, jar & config.xml files...");
if(fso.FolderExists(ROOT + '\\framework')) {
exec('%comspec% /c copy "'+ROOT+'"\\framework\\assets\\www\\cordova-'+VERSION+'.js '+PROJECT_PATH+'\\assets\\www\\cordova-'+VERSION+'.js /Y');
exec('%comspec% /c copy "'+ROOT+'"\\framework\\cordova-'+VERSION+'.jar '+PROJECT_PATH+'\\libs\\cordova-'+VERSION+'.jar /Y');
} else {
// copy in cordova.js
exec('%comspec% /c copy "'+ROOT+'"\\cordova-'+VERSION+'.js '+PROJECT_PATH+'\\assets\\www\\cordova-'+VERSION+'.js /Y');
// copy in cordova.jar
exec('%comspec% /c copy "'+ROOT+'"\\cordova-'+VERSION+'.jar '+PROJECT_PATH+'\\libs\\cordova-'+VERSION+'.jar /Y');
// copy in xml
}
// update cordova scripts
createAppInfoJar();
WScript.Echo("Copying cordova command tools...");
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\appinfo.jar ' + PROJECT_PATH + '\\cordova\\appinfo.jar /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\cordova.js ' + PROJECT_PATH + '\\cordova\\cordova.js /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\cordova.bat ' + PROJECT_PATH + '\\cordova\\cordova.bat /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\clean.bat ' + PROJECT_PATH + '\\cordova\\clean.bat /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\build.bat ' + PROJECT_PATH + '\\cordova\\build.bat /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\log.bat ' + PROJECT_PATH + '\\cordova\\log.bat /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\run.bat ' + PROJECT_PATH + '\\cordova\\run.bat /Y');
cleanup();
+2 -1
View File
@@ -3,6 +3,7 @@
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry kind="lib" path="libs/commons-codec-1.6.jar"/>
<classpathentry kind="lib" path="libs/commons-codec-1.7.jar"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry kind="output" path="bin/classes"/>
</classpath>
+3 -3
View File
@@ -18,7 +18,7 @@
under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:windowSoftInputMode="adjustPan"
package="org.apache.cordova" android:versionName="1.1" android:versionCode="5">
package="org.apache.cordova" android:versionName="1.0" android:versionCode="1">
<supports-screens
android:largeScreens="true"
android:normalScreens="true"
@@ -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
+1 -1
View File
@@ -19,7 +19,7 @@
<html>
<head>
<title></title>
<script src="cordova-1.9.0rc1.js"></script>
<script src="cordova-2.6.0.js"></script>
</head>
<body>
+31 -3
View File
@@ -26,9 +26,37 @@
</filterchain>
</loadfile>
<!-- The local.properties file is created and updated by the 'android' tool.
It contains the path to the SDK. It should *NOT* be checked into
Version Control Systems. -->
<!-- check that the version of ant is at least 1.8.0, as is needed
for the dblQuote property -->
<antversion property="thisantversion" atleast="1.8.0" />
<fail message="The required minimum version of ant is 1.8.0, you have ${ant.version}"
unless="thisantversion" />
<!-- check that commons codec is available. You should copy the codec jar to
framework/libs, as it is not included in the Cordova distribution.
The name of the jar file in framework/libs does not matter. -->
<available classname="org.apache.commons.codec.binary.Base64"
property="exists.base64"
ignoresystemclasses="true">
<classpath>
<pathelement path="${classpath}" />
<fileset dir="libs">
<include name="*.jar" />
</fileset>
</classpath>
</available>
<fail message="You need to put a copy of Apache Commons Codec jar in the framework/libs directory"
unless="exists.base64" />
<!-- The local.properties file is created and updated by the 'android'
tool. (For example "sdkdir/tools/android update project -p ." inside
of this directory where the AndroidManifest.xml file exists. This
properties file that gets built contains the path to the SDK. It
should *NOT* be checked into Version Control Systems since it holds
data about the local machine. -->
<available file="local.properties" property="exists.local.properties" />
<fail message="You need to create the file 'local.properties' by running 'android update project -p .' here."
unless="exists.local.properties" />
<loadproperties srcFile="local.properties" />
<!-- The ant.properties file can be created by you. It is only edited by the
+3 -1
View File
@@ -10,5 +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:17
apk-configurations=
renderscript.opt.level=O0
android.library=true
+23 -1
View File
@@ -17,6 +17,24 @@
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=".*"/>
<!-- <content src="http://mysite.com/myapp.html" /> for external pages -->
<content src="index.html" />
<log level="DEBUG"/>
<preference name="useBrowserHistory" value="true" />
<preference name="exit-on-suspend" value="false" />
<plugins>
<plugin name="App" value="org.apache.cordova.App"/>
<plugin name="Geolocation" value="org.apache.cordova.GeoBroker"/>
@@ -30,9 +48,13 @@
<plugin name="NetworkStatus" value="org.apache.cordova.NetworkManager"/>
<plugin name="Notification" value="org.apache.cordova.Notification"/>
<plugin name="Storage" value="org.apache.cordova.Storage"/>
<plugin name="Temperature" value="org.apache.cordova.TempListener"/>
<plugin name="FileTransfer" value="org.apache.cordova.FileTransfer"/>
<plugin name="Capture" value="org.apache.cordova.Capture"/>
<plugin name="Battery" value="org.apache.cordova.BatteryListener"/>
<plugin name="SplashScreen" value="org.apache.cordova.SplashScreen"/>
<plugin name="Echo" value="org.apache.cordova.Echo" />
<plugin name="Globalization" value="org.apache.cordova.Globalization"/>
<plugin name="InAppBrowser" value="org.apache.cordova.InAppBrowser"/>
</plugins>
</cordova>
-37
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>
@@ -1,27 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package com.phonegap.api;
/**
* Plugin interface must be implemented by any plugin classes.
*
* The execute method is called by the PluginManager.
*/
public interface IPlugin extends org.apache.cordova.api.IPlugin {
}
-28
View File
@@ -1,28 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package com.phonegap.api;
/**
* Log to Android logging system.
*
* Log message can be a string or a printf formatted string with arguments.
* See http://developer.android.com/reference/java/util/Formatter.html
*/
public class LOG extends org.apache.cordova.api.LOG {
}
@@ -1,28 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package com.phonegap.api;
import android.app.Activity;
/**
* The Cordova activity abstract class that is extended by DroidGap.
* It is used to isolate plugin development, and remove dependency on entire Cordova library.
*/
public abstract class PhonegapActivity extends Activity implements org.apache.cordova.api.CordovaInterface {
}
@@ -1,27 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package com.phonegap.api;
/**
* Plugin interface must be implemented by any plugin classes.
*
* The execute method is called by the PluginManager.
*/
public abstract class Plugin extends org.apache.cordova.api.Plugin {
}
@@ -1,37 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package com.phonegap.api;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.api.CordovaInterface;
import android.webkit.WebView;
/**
* PluginManager is exposed to JavaScript in the Cordova WebView.
*
* Calling native plugin code can be done by calling PluginManager.exec(...)
* from JavaScript.
*/
public class PluginManager extends org.apache.cordova.api.PluginManager {
public PluginManager(WebView app, CordovaInterface ctx) throws Exception {
super((CordovaWebView) app, ctx);
}
}
@@ -1,53 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package com.phonegap.api;
import org.json.JSONArray;
import org.json.JSONObject;
public class PluginResult extends org.apache.cordova.api.PluginResult {
public PluginResult(Status status) {
super(status);
}
public PluginResult(Status status, String message) {
super(status, message);
}
public PluginResult(Status status, JSONArray message) {
super(status, message);
}
public PluginResult(Status status, JSONObject message) {
super(status, message);
}
public PluginResult(Status status, int i) {
super(status, i);
}
public PluginResult(Status status, float f) {
super(status, f);
}
public PluginResult(Status status, boolean b) {
super(status, b);
}
}
@@ -19,24 +19,29 @@
package org.apache.cordova;
import java.util.List;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
/**
* This class listens to the accelerometer sensor and stores the latest
* acceleration values x,y,z.
*/
public class AccelListener extends Plugin implements SensorEventListener {
public class AccelListener extends CordovaPlugin implements SensorEventListener {
public static int STOPPED = 0;
public static int STARTING = 1;
@@ -51,7 +56,7 @@ public class AccelListener extends Plugin implements SensorEventListener {
private SensorManager sensorManager; // Sensor manager
private Sensor mSensor; // Acceleration sensor returned by sensor manager
private String callbackId; // Keeps track of the single "start" callback ID passed in from JS
private CallbackContext callbackContext; // Keeps track of the JS callback context.
/**
* Create an accelerometer listener.
@@ -69,29 +74,25 @@ public class AccelListener extends Plugin implements SensorEventListener {
* get file paths associated with the Activity.
*
* @param cordova The context of the main Activity.
* @param webView The associated CordovaWebView.
*/
public void setContext(CordovaInterface cordova) {
super.setContext(cordova);
@Override
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
super.initialize(cordova, webView);
this.sensorManager = (SensorManager) cordova.getActivity().getSystemService(Context.SENSOR_SERVICE);
}
/**
* Executes the request and returns PluginResult.
* Executes the request.
*
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param args The exec() arguments.
* @param callbackId The callback id used when calling back into JavaScript.
* @return A PluginResult object with a status and message.
* @return Whether the action was valid.
*/
public PluginResult execute(String action, JSONArray args, String callbackId) {
PluginResult.Status status = PluginResult.Status.NO_RESULT;
String message = "";
PluginResult result = new PluginResult(status, message);
result.setKeepCallback(true);
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) {
if (action.equals("start")) {
this.callbackId = callbackId;
this.callbackContext = callbackContext;
if (this.status != AccelListener.RUNNING) {
// If not running, then this is an async call, so don't worry about waiting
// We drop the callback onto our stack, call start, and let start and the sensor callback fire off the callback down the road
@@ -104,9 +105,13 @@ public class AccelListener extends Plugin implements SensorEventListener {
}
} else {
// Unsupported action
return new PluginResult(PluginResult.Status.INVALID_ACTION);
return false;
}
return result;
PluginResult result = new PluginResult(PluginResult.Status.NO_RESULT, "");
result.setKeepCallback(true);
callbackContext.sendPluginResult(result);
return true;
}
/**
@@ -148,20 +153,14 @@ public class AccelListener extends Plugin implements SensorEventListener {
return this.status;
}
// Wait until running
long timeout = 2000;
while ((this.status == STARTING) && (timeout > 0)) {
timeout = timeout - 100;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (timeout == 0) {
this.setStatus(AccelListener.ERROR_FAILED_TO_START);
this.fail(AccelListener.ERROR_FAILED_TO_START, "Accelerometer could not be started.");
// Set a timeout callback on the main thread.
Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(new Runnable() {
public void run() {
AccelListener.this.timeout();
}
}, 2000);
return this.status;
}
@@ -176,6 +175,18 @@ public class AccelListener extends Plugin implements SensorEventListener {
this.accuracy = SensorManager.SENSOR_STATUS_UNRELIABLE;
}
/**
* Returns an error if the sensor hasn't started.
*
* Called two seconds after starting the listener.
*/
private void timeout() {
if (this.status == AccelListener.STARTING) {
this.setStatus(AccelListener.ERROR_FAILED_TO_START);
this.fail(AccelListener.ERROR_FAILED_TO_START, "Accelerometer could not be started.");
}
}
/**
* Called when the accuracy of the sensor has changed.
*
@@ -215,7 +226,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];
@@ -224,6 +235,16 @@ public class AccelListener extends Plugin implements SensorEventListener {
}
}
/**
* Called when the view navigates.
*/
@Override
public void onReset() {
if (this.status == AccelListener.RUNNING) {
this.stop();
}
}
// Sends an error back to JS
private void fail(int code, String message) {
// Error object
@@ -236,16 +257,14 @@ public class AccelListener extends Plugin implements SensorEventListener {
}
PluginResult err = new PluginResult(PluginResult.Status.ERROR, errorObj);
err.setKeepCallback(true);
this.error(err, this.callbackId);
callbackContext.sendPluginResult(err);
}
private void win() {
// Success return object
PluginResult result = new PluginResult(PluginResult.Status.OK, this.getAccelerationJSON());
result.setKeepCallback(true);
this.success(result, this.callbackId);
callbackContext.sendPluginResult(result);
}
private void setStatus(int status) {
+29 -17
View File
@@ -19,28 +19,30 @@
package org.apache.cordova;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.LOG;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
/**
* This class exposes methods in DroidGap that can be called from JavaScript.
*/
public class App extends Plugin {
public class App extends CordovaPlugin {
/**
* 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.
* @param callbackContext The callback context from which we were invoked.
* @return A PluginResult object with a status and message.
*/
public PluginResult execute(String action, JSONArray args, String callbackId) {
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
PluginResult.Status status = PluginResult.Status.OK;
String result = "";
@@ -48,7 +50,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");
@@ -59,7 +64,7 @@ public class App extends Plugin {
this.loadUrl(args.getString(0), args.optJSONObject(1));
}
else if (action.equals("cancelLoadUrl")) {
this.cancelLoadUrl();
//this.cancelLoadUrl();
}
else if (action.equals("clearHistory")) {
this.clearHistory();
@@ -76,9 +81,11 @@ public class App extends Plugin {
else if (action.equals("exitApp")) {
this.exitApp();
}
return new PluginResult(status, result);
callbackContext.sendPluginResult(new PluginResult(status, result));
return true;
} catch (JSONException e) {
return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
return false;
}
}
@@ -153,14 +160,6 @@ public class App extends Plugin {
this.webView.showWebPage(url, openExternal, clearHistory, params);
}
/**
* Cancel loadUrl before it has been loaded (Only works on a CordovaInterface class)
*/
@Deprecated
public void cancelLoadUrl() {
this.cordova.cancelLoadUrl();
}
/**
* Clear page history for the app.
*/
@@ -173,7 +172,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();
}
});
}
/**
@@ -199,6 +202,15 @@ public class App extends Plugin {
webView.bindButton(button, override);
}
/**
* Return whether the Android back button is overridden by the user.
*
* @return boolean
*/
public boolean isBackbuttonOverridden() {
return webView.isBackButtonBound();
}
/**
* Exit the Android application.
*/
+41 -37
View File
@@ -18,11 +18,14 @@
*/
package org.apache.cordova;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import android.content.Context;
import android.media.AudioManager;
import java.util.ArrayList;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
@@ -39,7 +42,7 @@ import java.util.HashMap;
* android_asset: file name must start with /android_asset/sound.mp3
* sdcard: file name is just sound.mp3
*/
public class AudioHandler extends Plugin {
public class AudioHandler extends CordovaPlugin {
public static String TAG = "AudioHandler";
HashMap<String, AudioPlayer> players; // Audio player object
@@ -57,22 +60,21 @@ public class AudioHandler 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.
* @param callbackContext The callback context used when calling back into JavaScript.
* @return A PluginResult object with a status and message.
*/
public PluginResult execute(String action, JSONArray args, String callbackId) {
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
PluginResult.Status status = PluginResult.Status.OK;
String result = "";
try {
if (action.equals("startRecordingAudio")) {
this.startRecordingAudio(args.getString(0), args.getString(1));
this.startRecordingAudio(args.getString(0), FileHelper.stripFileProtocol(args.getString(1)));
}
else if (action.equals("stopRecordingAudio")) {
this.stopRecordingAudio(args.getString(0));
}
else if (action.equals("startPlayingAudio")) {
this.startPlayingAudio(args.getString(0), args.getString(1));
this.startPlayingAudio(args.getString(0), FileHelper.stripFileProtocol(args.getString(1)));
}
else if (action.equals("seekToAudio")) {
this.seekToAudio(args.getString(0), args.getInt(1));
@@ -90,37 +92,33 @@ public class AudioHandler extends Plugin {
}
} else if (action.equals("getCurrentPositionAudio")) {
float f = this.getCurrentPositionAudio(args.getString(0));
return new PluginResult(status, f);
callbackContext.sendPluginResult(new PluginResult(status, f));
return true;
}
else if (action.equals("getDurationAudio")) {
float f = this.getDurationAudio(args.getString(0), args.getString(1));
return new PluginResult(status, f);
callbackContext.sendPluginResult(new PluginResult(status, f));
return true;
}
else if (action.equals("create")) {
String id = args.getString(0);
String src = FileHelper.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);
}
return new PluginResult(status, result);
} catch (JSONException e) {
e.printStackTrace();
return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
callbackContext.sendPluginResult(new PluginResult(status, b));
return true;
}
else { // Unrecognized action.
return false;
}
/**
* Identifies if action to be executed returns a value and should be run synchronously.
* @param action The action to execute
* @return T=returns value
*/
public boolean isSynch(String action) {
if (action.equals("getCurrentPositionAudio")) {
callbackContext.sendPluginResult(new PluginResult(status, result));
return true;
}
else if (action.equals("getDurationAudio")) {
return true;
}
return false;
}
/**
* Stop all audio players and recorders.
@@ -132,6 +130,14 @@ public class AudioHandler extends Plugin {
this.players.clear();
}
/**
* Stop all audio players and recorders on navigate.
*/
@Override
public void onReset() {
onDestroy();
}
/**
* Called when a message is sent to plugin.
*
@@ -149,7 +155,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 +198,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 = new AudioPlayer(this, id);
AudioPlayer audio = this.players.get(id);
if ( audio == null) {
audio = new AudioPlayer(this, id, file);
this.players.put(id, audio);
}
audio.startRecording(file);
}
@@ -209,7 +214,6 @@ public class AudioHandler extends Plugin {
AudioPlayer audio = this.players.get(id);
if (audio != null) {
audio.stopRecording();
this.players.remove(id);
}
}
@@ -221,7 +225,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);
@@ -230,7 +234,7 @@ public class AudioHandler extends Plugin {
/**
* Seek to a location.
* @param id The id of the audio player
* @param miliseconds int: number of milliseconds to skip 1000 = 1 second
* @param milliseconds int: number of milliseconds to skip 1000 = 1 second
*/
public void seekToAudio(String id, int milliseconds) {
AudioPlayer audio = this.players.get(id);
@@ -292,7 +296,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));
}
+237 -148
View File
@@ -42,14 +42,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;
@@ -66,15 +71,18 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
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 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 mPlayer = null; // Audio player object
private boolean prepareOnly = false;
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.
@@ -82,14 +90,18 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
* @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 +109,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();
@@ -118,14 +130,13 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
* @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) {
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});");
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,18 +144,18 @@ 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();
} catch (IOException e) {
e.printStackTrace();
}
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});");
}
else {
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});");
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+"});");
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});");
}
}
@@ -156,13 +167,18 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
public void moveFile(String file) {
/* this is a hack to save the file as the specified name */
File f = new File(this.tempFile);
if (!file.startsWith("/")) {
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
f.renameTo(new File(Environment.getExternalStorageDirectory().getAbsolutePath()
+ File.separator + file));
file = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + file;
} else {
f.renameTo(new File("/data/data/" + handler.cordova.getActivity().getPackageName() + "/cache/" + file));
file = "/data/data/" + handler.cordova.getActivity().getPackageName() + "/cache/" + file;
}
}
String logMsg = "renaming " + this.tempFile + " to " + file;
Log.d(LOG_TAG, logMsg);
if (!f.renameTo(new File(file))) Log.e(LOG_TAG, "FAILED " + logMsg);
}
/**
@@ -171,10 +187,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 +200,22 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
}
}
//==========================================================================
// Playback
//==========================================================================
/**
* Start or resume playing audio file.
*
* @param file The name of the audio file.
*/
public void startPlaying(String file) {
if (this.recorder != null) {
Log.d(LOG_TAG, "AudioPlayer Error: Can't play in record mode.");
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});");
}
// If this is a new request to play audio, or stopped
else if ((this.mPlayer == null) || (this.state == MEDIA_STOPPED)) {
try {
// If stopped, then reset player
if (this.mPlayer != null) {
this.mPlayer.reset();
}
// Otherwise, create a new one
else {
this.mPlayer = new MediaPlayer();
}
this.audioFile = file;
// If streaming file
if (this.isStreaming(file)) {
this.mPlayer.setDataSource(file);
this.mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
this.setState(MEDIA_STARTING);
this.mPlayer.setOnPreparedListener(this);
this.mPlayer.prepareAsync();
}
// If local file
else {
if (file.startsWith("/android_asset/")) {
String f = file.substring(15);
android.content.res.AssetFileDescriptor fd = this.handler.cordova.getActivity().getAssets().openFd(f);
this.mPlayer.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
}
else {
File fp = new File(file);
if (fp.exists()) {
FileInputStream fileInputStream = new FileInputStream(file);
this.mPlayer.setDataSource(fileInputStream.getFD());
}
else {
this.mPlayer.setDataSource("/sdcard/" + file);
}
}
this.setState(MEDIA_STARTING);
this.mPlayer.setOnPreparedListener(this);
this.mPlayer.prepare();
// Get duration
this.duration = getDurationInSeconds();
}
} catch (Exception e) {
e.printStackTrace();
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});");
}
}
// If we have already have created an audio player
else {
// If player has been paused, then resume playback
if ((this.state == MEDIA_PAUSED) || (this.state == MEDIA_STARTING)) {
this.mPlayer.start();
this.setState(MEDIA_RUNNING);
}
else {
Log.d(LOG_TAG, "AudioPlayer Error: startPlaying() called during invalid state: " + this.state);
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});");
}
if (this.readyPlayer(file) && this.player != null) {
this.player.start();
this.setState(STATE.MEDIA_RUNNING);
this.seekOnPrepared = 0; //insures this is always reset
} else {
this.prepareOnly = false;
}
}
@@ -265,10 +223,13 @@ 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 + ");");
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_POSITION + ", " + milliseconds / 1000.0f + ");");
}
else {
this.seekOnPrepared = milliseconds;
}
}
@@ -278,13 +239,13 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
public void pausePlaying() {
// If playing, then pause
if (this.state == MEDIA_RUNNING) {
this.mPlayer.pause();
this.setState(MEDIA_PAUSED);
if (this.state == STATE.MEDIA_RUNNING && this.player != null) {
this.player.pause();
this.setState(STATE.MEDIA_PAUSED);
}
else {
Log.d(LOG_TAG, "AudioPlayer Error: pausePlaying() called during invalid state: " + this.state);
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_NONE_ACTIVE + "});");
Log.d(LOG_TAG, "AudioPlayer Error: pausePlaying() called during invalid state: " + this.state.ordinal());
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_NONE_ACTIVE + "});");
}
}
@@ -292,23 +253,26 @@ 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);
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_NONE_ACTIVE + "});");
Log.d(LOG_TAG, "AudioPlayer Error: stopPlaying() called during invalid state: " + this.state.ordinal());
this.handler.webView.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);
}
/**
@@ -317,9 +281,9 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
* @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();
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_POSITION + ", " + curPos / 1000.0f + ");");
if ((this.state == STATE.MEDIA_RUNNING) || (this.state == STATE.MEDIA_PAUSED)) {
int curPos = this.player.getCurrentPosition();
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_POSITION + ", " + curPos / 1000.0f + ");");
return curPos;
}
else {
@@ -359,7 +323,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;
}
@@ -377,29 +341,28 @@ 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
* @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 + ");");
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_DURATION + "," + this.duration + ");");
}
/**
@@ -408,26 +371,26 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
* @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 + "});");
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', { \"code\":" + arg1 + "});");
return false;
}
@@ -436,12 +399,24 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
*
* @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.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_STATE + ", " + state.ordinal() + ");");
}
this.state = state;
}
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 behavior, so no notification
//this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_STATE + ", " + mode + ");");
}
this.mode = mode;
}
/**
@@ -450,7 +425,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
* @return int
*/
public int getState() {
return this.state;
return this.state.ordinal();
}
/**
@@ -459,6 +434,120 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
* @param volume
*/
public void setVolume(float volume) {
this.mPlayer.setVolume(volume, volume);
this.player.setVolume(volume, volume);
}
/**
* attempts to put the player in play mode
* @return true if in playmode, false otherwise
*/
private boolean playMode() {
switch(this.mode) {
case NONE:
this.setMode(MODE.PLAY);
break;
case PLAY:
break;
case RECORD:
Log.d(LOG_TAG, "AudioPlayer Error: Can't play in record mode.");
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});");
return false; //player is not ready
}
return true;
}
/**
* attempts to initialize the media player for playback
* @param file the file to play
* @return false if player not ready, reports if in wrong mode or state
*/
private boolean readyPlayer(String file) {
if (playMode()) {
switch (this.state) {
case MEDIA_NONE:
if (this.player == null) {
this.player = new MediaPlayer();
}
try {
this.loadAudioFile(file);
} catch (Exception e) {
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});");
}
return false;
case MEDIA_LOADING:
//cordova js is not aware of MEDIA_LOADING, so we send MEDIA_STARTING instead
Log.d(LOG_TAG, "AudioPlayer Loading: startPlaying() called during media preparation: " + STATE.MEDIA_STARTING.ordinal());
this.prepareOnly = false;
return false;
case MEDIA_STARTING:
case MEDIA_RUNNING:
case MEDIA_PAUSED:
return true;
case MEDIA_STOPPED:
//if we are readying the same file
if (this.audioFile.compareTo(file) == 0) {
//reset the audio file
player.seekTo(0);
player.pause();
return true;
} else {
//reset the player
this.player.reset();
try {
this.loadAudioFile(file);
} catch (Exception e) {
this.handler.webView.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.webView.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.cordova.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();
}
}
}
@@ -18,7 +18,8 @@
*/
package org.apache.cordova;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
@@ -30,13 +31,13 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.util.Log;
public class BatteryListener extends Plugin {
public class BatteryListener extends CordovaPlugin {
private static final String LOG_TAG = "BatteryManager";
BroadcastReceiver receiver;
private String batteryCallbackId = null;
private CallbackContext batteryCallbackContext = null;
/**
* Constructor.
@@ -46,22 +47,20 @@ public class BatteryListener extends Plugin {
}
/**
* Executes the request and returns PluginResult.
* Executes the request.
*
* @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.
* @param callbackContext The callback context used when calling back into JavaScript.
* @return True if the action was valid, false if not.
*/
public PluginResult execute(String action, JSONArray args, String callbackId) {
PluginResult.Status status = PluginResult.Status.INVALID_ACTION;
String result = "Unsupported Operation: " + action;
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) {
if (action.equals("start")) {
if (this.batteryCallbackId != null) {
return new PluginResult(PluginResult.Status.ERROR, "Battery listener already running.");
if (this.batteryCallbackContext != null) {
callbackContext.error( "Battery listener already running.");
return true;
}
this.batteryCallbackId = callbackId;
this.batteryCallbackContext = callbackContext;
// We need to listen to power events to update battery status
IntentFilter intentFilter = new IntentFilter();
@@ -79,17 +78,19 @@ public class BatteryListener extends Plugin {
// Don't return any result now, since status results will be sent when events come in from broadcast receiver
PluginResult pluginResult = new PluginResult(PluginResult.Status.NO_RESULT);
pluginResult.setKeepCallback(true);
return pluginResult;
callbackContext.sendPluginResult(pluginResult);
return true;
}
else if (action.equals("stop")) {
removeBatteryListener();
this.sendUpdate(new JSONObject(), false); // release status callback in JS side
this.batteryCallbackId = null;
return new PluginResult(PluginResult.Status.OK);
this.batteryCallbackContext = null;
callbackContext.success();
return true;
}
return new PluginResult(status, result);
return false;
}
/**
@@ -99,6 +100,13 @@ public class BatteryListener extends Plugin {
removeBatteryListener();
}
/**
* Stop battery receiver.
*/
public void onReset() {
removeBatteryListener();
}
/**
* Stop the battery receiver and set it to null.
*/
@@ -146,10 +154,10 @@ public class BatteryListener extends Plugin {
* @param connection the network info to set as navigator.connection
*/
private void sendUpdate(JSONObject info, boolean keepCallback) {
if (this.batteryCallbackId != null) {
if (this.batteryCallbackContext != null) {
PluginResult result = new PluginResult(PluginResult.Status.OK, info);
result.setKeepCallback(keepCallback);
this.success(result, this.batteryCallbackId);
this.batteryCallbackContext.sendPluginResult(result);
}
}
}
@@ -1,426 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.LinkedList;
/**
* This class provides a way for Java to run JavaScript in the web page that has loaded Cordova.
* The CallbackServer class implements an XHR server and a polling server with a list of JavaScript
* statements that are to be executed on the web page.
*
* The process flow for XHR is:
* 1. JavaScript makes an async XHR call.
* 2. The server holds the connection open until data is available.
* 3. The server writes the data to the client and closes the connection.
* 4. The server immediately starts listening for the next XHR call.
* 5. The client receives this XHR response, processes it.
* 6. The client sends a new async XHR request.
*
* The CallbackServer class requires the following permission in Android manifest file
* <uses-permission android:name="android.permission.INTERNET" />
*
* If the device has a proxy set, then XHR cannot be used, so polling must be used instead.
* This can be determined by the client by calling CallbackServer.usePolling().
*
* The process flow for polling is:
* 1. The client calls CallbackServer.getJavascript() to retrieve next statement.
* 2. If statement available, then client processes it.
* 3. The client repeats #1 in loop.
*/
public class CallbackServer implements Runnable {
@SuppressWarnings("unused")
private static final String LOG_TAG = "CallbackServer";
/**
* The list of JavaScript statements to be sent to JavaScript.
*/
private LinkedList<String> javascript;
/**
* The port to listen on.
*/
private int port;
/**
* The server thread.
*/
private Thread serverThread;
/**
* Indicates the server is running.
*/
private boolean active;
/**
* Indicates that the JavaScript statements list is empty
*/
private boolean empty;
/**
* Indicates that polling should be used instead of XHR.
*/
private boolean usePolling = true;
/**
* Security token to prevent other apps from accessing this callback server via XHR
*/
private String token;
/**
* Constructor.
*/
public CallbackServer() {
//Log.d(LOG_TAG, "CallbackServer()");
this.active = false;
this.empty = true;
this.port = 0;
this.javascript = new LinkedList<String>();
}
/**
* Init callback server and start XHR if running local app.
*
* If Cordova app is loaded from file://, then we can use XHR
* otherwise we have to use polling due to cross-domain security restrictions.
*
* @param url The URL of the Cordova app being loaded
*/
public void init(String url) {
//System.out.println("CallbackServer.start("+url+")");
this.active = false;
this.empty = true;
this.port = 0;
this.javascript = new LinkedList<String>();
// Determine if XHR or polling is to be used
if ((url != null) && !url.startsWith("file://")) {
this.usePolling = true;
this.stopServer();
}
else if (android.net.Proxy.getDefaultHost() != null) {
this.usePolling = true;
this.stopServer();
}
else {
this.usePolling = false;
this.startServer();
}
}
/**
* 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
*/
public boolean usePolling() {
return this.usePolling;
}
/**
* Get the port that this server is running on.
* @return
*/
public int getPort() {
return this.port;
}
/**
* Get the security token that this server requires when calling getJavascript().
* @return
*/
public String getToken() {
return this.token;
}
/**
* Start the server on a new thread.
*/
public void startServer() {
//Log.d(LOG_TAG, "CallbackServer.startServer()");
this.active = false;
// Start server on new thread
this.serverThread = new Thread(this);
this.serverThread.start();
}
/**
* Restart the server on a new thread.
*/
public void restartServer() {
// Stop server
this.stopServer();
// Start server again
this.startServer();
}
/**
* Start running the server.
* This is called automatically when the server thread is started.
*/
public void run() {
// Start server
try {
this.active = true;
String request;
ServerSocket waitSocket = new ServerSocket(0);
this.port = waitSocket.getLocalPort();
//Log.d(LOG_TAG, "CallbackServer -- using port " +this.port);
this.token = java.util.UUID.randomUUID().toString();
//Log.d(LOG_TAG, "CallbackServer -- using token "+this.token);
while (this.active) {
//Log.d(LOG_TAG, "CallbackServer: Waiting for data on socket");
Socket connection = waitSocket.accept();
BufferedReader xhrReader = new BufferedReader(new InputStreamReader(connection.getInputStream()), 40);
DataOutputStream output = new DataOutputStream(connection.getOutputStream());
request = xhrReader.readLine();
String response = "";
//Log.d(LOG_TAG, "CallbackServerRequest="+request);
if (this.active && (request != null)) {
if (request.contains("GET")) {
// Get requested file
String[] requestParts = request.split(" ");
// Must have security token
if ((requestParts.length == 3) && (requestParts[1].substring(1).equals(this.token))) {
//Log.d(LOG_TAG, "CallbackServer -- Processing GET request");
// 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) {
try {
this.wait(10000); // prevent timeout from happening
//Log.d(LOG_TAG, "CallbackServer>>> break <<<");
break;
} catch (Exception e) {
}
}
}
// If server is still running
if (this.active) {
// If no data, then send 404 back to client before it times out
if (this.empty) {
//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");
}
}
}
else {
response = "HTTP/1.1 503 Service Unavailable\r\n\r\n ";
}
}
else {
response = "HTTP/1.1 403 Forbidden\r\n\r\n ";
}
}
else {
response = "HTTP/1.1 400 Bad Request\r\n\r\n ";
}
//Log.d(LOG_TAG, "CallbackServer: response="+response);
//Log.d(LOG_TAG, "CallbackServer: closing output");
output.writeBytes(response);
output.flush();
}
output.close();
xhrReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
this.active = false;
//Log.d(LOG_TAG, "CallbackServer.startServer() - EXIT");
}
/**
* Stop server.
* This stops the thread that the server is running on.
*/
public void stopServer() {
//Log.d(LOG_TAG, "CallbackServer.stopServer()");
if (this.active) {
this.active = false;
// Break out of server wait
synchronized (this) {
this.notify();
}
}
}
/**
* Destroy
*/
public void destroy() {
this.stopServer();
}
/**
* Get the number of JavaScript statements.
*
* @return int
*/
public int getSize() {
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.notify();
}
}
/* The Following code has been modified from original implementation of URLEncoder */
/* start */
/*
* 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.
*/
static final String digits = "0123456789ABCDEF";
/**
* This will encode the return value to JavaScript. We revert the encoding for
* common characters that don't require encoding to reduce the size of the string
* being passed to JavaScript.
*
* @param s to be encoded
* @param enc encoding type
* @return encoded string
*/
public static String encode(String s, String enc) throws UnsupportedEncodingException {
if (s == null || enc == null) {
throw new NullPointerException();
}
// check for UnsupportedEncodingException
"".getBytes(enc);
// Guess a bit bigger for encoded form
StringBuilder buf = new StringBuilder(s.length() + 16);
int start = -1;
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
|| (ch >= '0' && ch <= '9')
|| " .-*_'(),<>=?@[]{}:~\"\\/;!".indexOf(ch) > -1) {
if (start >= 0) {
convert(s.substring(start, i), buf, enc);
start = -1;
}
if (ch != ' ') {
buf.append(ch);
} else {
buf.append(' ');
}
} else {
if (start < 0) {
start = i;
}
}
}
if (start >= 0) {
convert(s.substring(start, s.length()), buf, enc);
}
return buf.toString();
}
private static void convert(String s, StringBuilder buf, String enc) throws UnsupportedEncodingException {
byte[] bytes = s.getBytes(enc);
for (int j = 0; j < bytes.length; j++) {
buf.append('%');
buf.append(digits.charAt((bytes[j] & 0xf0) >> 4));
buf.append(digits.charAt(bytes[j] & 0xf));
}
}
/* end */
}
@@ -27,19 +27,19 @@ 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.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.LOG;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
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;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.Bitmap.CompressFormat;
import android.media.MediaScannerConnection;
@@ -54,10 +54,11 @@ import android.util.Log;
* and returns the captured image. When the camera view is closed, the screen displayed before
* the camera view was shown is redisplayed.
*/
public class CameraLauncher extends Plugin implements MediaScannerConnectionClient {
public class CameraLauncher extends CordovaPlugin implements MediaScannerConnectionClient {
private static final int DATA_URL = 0; // Return base64 encoded string
private static final int FILE_URI = 1; // Return file uri (content://media/external/images/media/2 for Android)
private static final int NATIVE_URI = 2; // On Android, this is the same as FILE_URI
private static final int PHOTOLIBRARY = 0; // Choose image from picture library (same as SAVEDPHOTOALBUM for Android)
private static final int CAMERA = 1; // Take picture from camera
@@ -82,11 +83,14 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
private int encodingType; // Type of encoding to use
private int mediaType; // What type of media to retrieve
private boolean saveToPhotoAlbum; // Should the picture be saved to the device's photo album
private boolean correctOrientation; // Should the pictures orientation be corrected
//private boolean allowEdit; // Should we allow the user to crop the image. UNUSED.
public String callbackId;
public CallbackContext callbackContext;
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;
@@ -110,15 +114,12 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
*
* @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.
* @param callbackContext 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) {
PluginResult.Status status = PluginResult.Status.OK;
String result = "";
this.callbackId = callbackId;
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
this.callbackContext = callbackContext;
try {
if (action.equals("takePicture")) {
int srcType = CAMERA;
int destType = FILE_URI;
@@ -136,8 +137,19 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
this.targetHeight = args.getInt(4);
this.encodingType = args.getInt(5);
this.mediaType = args.getInt(6);
//this.allowEdit = args.getBoolean(7); // This field is unused.
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 comparisons succeed
if (this.targetWidth < 1) {
this.targetWidth = -1;
}
if (this.targetHeight < 1) {
this.targetHeight = -1;
}
if (srcType == CAMERA) {
this.takePicture(destType, encodingType);
}
@@ -146,13 +158,10 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
}
PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT);
r.setKeepCallback(true);
return r;
}
return new PluginResult(status, result);
} catch (JSONException e) {
e.printStackTrace();
return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
callbackContext.sendPluginResult(r);
return true;
}
return false;
}
//--------------------------------------------------------------------------
@@ -181,13 +190,12 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
// Specify file so that large image is captured and returned
// TODO: What if there isn't any external storage?
File photo = createCaptureFile(encodingType);
intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(photo));
this.imageUri = Uri.fromFile(photo);
if (this.cordova != null) {
this.cordova.startActivityForResult((Plugin) this, intent, (CAMERA + 1) * 16 + returnType + 1);
this.cordova.startActivityForResult((CordovaPlugin) this, intent, (CAMERA + 1) * 16 + returnType + 1);
}
// else
// LOG.d(LOG_TAG, "ERROR: You must use the CordovaInterface for this to work correctly. Please implement it in your activity");
@@ -239,26 +247,369 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
intent.setAction(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
if (this.cordova != null) {
this.cordova.startActivityForResult((Plugin) this, Intent.createChooser(intent,
this.cordova.startActivityForResult((CordovaPlugin) this, Intent.createChooser(intent,
new String(title)), (srcType + 1) * 16 + returnType + 1);
}
}
/**
* Scales the bitmap according to the requested size.
* Called when the camera view exits.
*
* @param bitmap The bitmap to scale.
* @return Bitmap A new Bitmap object of the same bitmap after scaling.
* @param requestCode The request code originally supplied to startActivityForResult(),
* allowing you to identify who this result came from.
* @param resultCode The integer result code returned by the child activity through its setResult().
* @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
*/
public Bitmap scaleBitmap(Bitmap bitmap) {
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
// Get src and dest types from request code
int srcType = (requestCode / 16) - 1;
int destType = (requestCode % 16) - 1;
int rotate = 0;
// If CAMERA
if (srcType == CAMERA) {
// 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) {
bitmap = getScaledBitmap(FileHelper.stripFileProtocol(imageUri.toString()));
if (bitmap == null) {
// Try to get the bitmap from intent.
bitmap = (Bitmap)intent.getExtras().get("data");
}
// Double-check the bitmap.
if (bitmap == null) {
Log.d(LOG_TAG, "I either have a null image path or bitmap");
this.failPicture("Unable to create bitmap!");
return;
}
if (rotate != 0 && this.correctOrientation) {
bitmap = getRotatedBitmap(rotate, bitmap, exif);
}
this.processPicture(bitmap);
checkForDuplicateImage(DATA_URL);
}
// If sending filename back
else if (destType == FILE_URI || destType == NATIVE_URI) {
if (this.saveToPhotoAlbum) {
Uri inputUri = getUriFromMediaStore();
//Just because we have a media URI doesn't mean we have a real file, we need to make it
uri = Uri.fromFile(new File(FileHelper.getRealPath(inputUri, this.cordova)));
} else {
uri = Uri.fromFile(new File(DirectoryManager.getTempDirectoryPath(this.cordova.getActivity()), System.currentTimeMillis() + ".jpg"));
}
if (uri == null) {
this.failPicture("Error capturing image - no media storage found.");
}
// If all this is true we shouldn't compress the image.
if (this.targetHeight == -1 && this.targetWidth == -1 && this.mQuality == 100 &&
!this.correctOrientation) {
writeUncompressedImage(uri);
this.callbackContext.success(uri.toString());
} else {
bitmap = getScaledBitmap(FileHelper.stripFileProtocol(imageUri.toString()));
if (rotate != 0 && this.correctOrientation) {
bitmap = getRotatedBitmap(rotate, bitmap, exif);
}
// Add compressed version of captured image to returned media store Uri
OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os);
os.close();
// Restore exif data to file
if (this.encodingType == JPEG) {
String exifPath;
if (this.saveToPhotoAlbum) {
exifPath = FileHelper.getRealPath(uri, this.cordova);
} else {
exifPath = uri.getPath();
}
exif.createOutFile(exifPath);
exif.writeExifData();
}
}
// Send Uri back to JavaScript for viewing image
this.callbackContext.success(uri.toString());
}
this.cleanup(FILE_URI, this.imageUri, uri, bitmap);
bitmap = null;
} catch (IOException e) {
e.printStackTrace();
this.failPicture("Error capturing image.");
}
}
// If cancelled
else if (resultCode == Activity.RESULT_CANCELED) {
this.failPicture("Camera cancelled.");
}
// If something else
else {
this.failPicture("Did not complete!");
}
}
// If retrieving photo from library
else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) {
if (resultCode == Activity.RESULT_OK) {
Uri uri = intent.getData();
// If you ask for video or all media type you will automatically get back a file URI
// and there will be no attempt to resize any returned data
if (this.mediaType != PICTURE) {
this.callbackContext.success(uri.toString());
}
else {
// This is a special case to just return the path as no scaling,
// rotating, nor compressing needs to be done
if (this.targetHeight == -1 && this.targetWidth == -1 &&
(destType == FILE_URI || destType == NATIVE_URI) && !this.correctOrientation) {
this.callbackContext.success(uri.toString());
} else {
// Get the path to the image. Makes loading so much easier.
String imagePath = FileHelper.getRealPath(uri, this.cordova);
String mimeType = FileHelper.getMimeType(imagePath, this.cordova);
// Log.d(LOG_TAG, "Real path = " + imagePath);
// Log.d(LOG_TAG, "mime type = " + mimeType);
// If we don't have a valid image so quit.
if (imagePath == null || mimeType == null ||
!(mimeType.equalsIgnoreCase("image/jpeg") || mimeType.equalsIgnoreCase("image/png"))) {
Log.d(LOG_TAG, "I either have a null image path or bitmap");
this.failPicture("Unable to retrieve path to picture!");
return;
}
Bitmap bitmap = getScaledBitmap(imagePath);
if (bitmap == null) {
Log.d(LOG_TAG, "I either have a null image path or bitmap");
this.failPicture("Unable to create bitmap!");
return;
}
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);
}
}
// If sending base64 image back
if (destType == DATA_URL) {
this.processPicture(bitmap);
}
// If sending filename back
else if (destType == FILE_URI || destType == NATIVE_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(FileHelper.getRealPath(uri, this.cordova));
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(resizePath);
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.callbackContext.success("file://" + resizePath + "?" + System.currentTimeMillis());
} catch (Exception e) {
e.printStackTrace();
this.failPicture("Error retrieving image.");
}
}
else {
this.callbackContext.success(uri.toString());
}
}
if (bitmap != null) {
bitmap.recycle();
bitmap = null;
}
System.gc();
}
}
}
else if (resultCode == Activity.RESULT_CANCELED) {
this.failPicture("Selection cancelled.");
}
else {
this.failPicture("Selection did not complete!");
}
}
}
/**
* Figure out if the bitmap should be rotated. For instance if the picture was taken in
* portrait mode
*
* @param rotate
* @param bitmap
* @return rotated bitmap
*/
private Bitmap getRotatedBitmap(int rotate, Bitmap bitmap, ExifHelper exif) {
Matrix matrix = new Matrix();
if (rotate == 180) {
matrix.setRotate(rotate);
} else {
matrix.setRotate(rotate, (float) bitmap.getWidth() / 2, (float) bitmap.getHeight() / 2);
}
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
exif.resetOrientation();
return bitmap;
}
/**
* In the special case where the default width, height and quality are unchanged
* we just write the file out to disk saving the expensive Bitmap.compress function.
*
* @param uri
* @throws FileNotFoundException
* @throws IOException
*/
private void writeUncompressedImage(Uri uri) throws FileNotFoundException,
IOException {
FileInputStream fis = new FileInputStream(FileHelper.stripFileProtocol(imageUri.toString()));
OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
byte[] buffer = new byte[4096];
int len;
while ((len = fis.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
os.flush();
os.close();
fis.close();
}
/**
* Create entry in media store for image
*
* @return uri
*/
private Uri getUriFromMediaStore() {
ContentValues values = new ContentValues();
values.put(android.provider.MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
Uri uri;
try {
uri = this.cordova.getActivity().getContentResolver().insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
} catch (UnsupportedOperationException e) {
LOG.d(LOG_TAG, "Can't write to external media storage.");
try {
uri = this.cordova.getActivity().getContentResolver().insert(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, values);
} catch (UnsupportedOperationException ex) {
LOG.d(LOG_TAG, "Can't write to internal media storage.");
return null;
}
}
return uri;
}
/**
* Return a scaled bitmap based on the target width and height
*
* @param imagePath
* @return
*/
private Bitmap getScaledBitmap(String imagePath) {
// If no new width or height were specified return the original bitmap
if (this.targetWidth <= 0 && this.targetHeight <= 0) {
return BitmapFactory.decodeFile(imagePath);
}
// figure out the original width and height of the image
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imagePath, options);
//CB-2292: WTF? Why is the width null?
if(options.outWidth == 0 || options.outHeight == 0)
{
return null;
}
// 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);
if (unscaledBitmap == null) {
return null;
}
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;
int origWidth = bitmap.getWidth();
int origHeight = bitmap.getHeight();
// If no new width or height were specified return the original bitmap
if (newWidth <= 0 && newHeight <= 0) {
return bitmap;
newWidth = origWidth;
newHeight = origHeight;
}
// Only the width was specified
else if (newWidth > 0 && newHeight <= 0) {
@@ -285,237 +636,32 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
}
}
Bitmap retval = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true);
bitmap.recycle();
System.gc();
int[] retval = new int[2];
retval[0] = newWidth;
retval[1] = newHeight;
return retval;
}
/**
* Called when the camera view exits.
* Figure out what ratio we can load our image into memory at while still being bigger than
* our desired width and height
*
* @param requestCode The request code originally supplied to startActivityForResult(),
* allowing you to identify who this result came from.
* @param resultCode The integer result code returned by the child activity through its setResult().
* @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
* @param srcWidth
* @param srcHeight
* @param dstWidth
* @param dstHeight
* @return
*/
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
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;
// Get src and dest types from request code
int srcType = (requestCode / 16) - 1;
int destType = (requestCode % 16) - 1;
int rotate = 0;
// 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();
}
} catch (IOException e) {
e.printStackTrace();
}
// If CAMERA
if (srcType == CAMERA) {
// If image available
if (resultCode == Activity.RESULT_OK) {
try {
Bitmap bitmap = null;
// If sending base64 image back
if (destType == DATA_URL) {
bitmap = scaleBitmap(getBitmapFromResult(intent));
this.processPicture(bitmap);
checkForDuplicateImage(DATA_URL);
}
// 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()));
if (srcAspect > dstAspect) {
return srcWidth / dstWidth;
} else {
// Create entry in media store for image
// (Don't use insertImage() because it uses default compression setting of 50 - no way to change it)
ContentValues values = new ContentValues();
values.put(android.provider.MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
try {
uri = this.cordova.getActivity().getContentResolver().insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
} catch (UnsupportedOperationException e) {
LOG.d(LOG_TAG, "Can't write to external media storage.");
try {
uri = this.cordova.getActivity().getContentResolver().insert(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, values);
} catch (UnsupportedOperationException ex) {
LOG.d(LOG_TAG, "Can't write to internal media storage.");
this.failPicture("Error capturing image - no media storage found.");
return;
return srcHeight / dstHeight;
}
}
}
// If all this is true we shouldn't compress the image.
if (this.targetHeight == -1 && this.targetWidth == -1 && this.mQuality == 100) {
FileInputStream fis = new FileInputStream(FileUtils.stripFileProtocol(imageUri.toString()));
OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
byte[] buffer = new byte[4096];
int len;
while ((len = fis.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
os.flush();
os.close();
fis.close();
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
} else {
bitmap = scaleBitmap(getBitmapFromResult(intent));
// Add compressed version of captured image to returned media store Uri
OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os);
os.close();
// Restore exif data to file
if (this.encodingType == JPEG) {
String exifPath;
if (this.saveToPhotoAlbum) {
exifPath = FileUtils.getRealPathFromURI(uri, this.cordova);
} else {
exifPath = uri.getPath();
}
exif.createOutFile(exifPath);
exif.writeExifData();
}
}
// Send Uri back to JavaScript for viewing image
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
}
this.cleanup(FILE_URI, this.imageUri, bitmap);
bitmap = null;
} catch (IOException e) {
e.printStackTrace();
this.failPicture("Error capturing image.");
}
}
// If cancelled
else if (resultCode == Activity.RESULT_CANCELED) {
this.failPicture("Camera cancelled.");
}
// If something else
else {
this.failPicture("Did not complete!");
}
}
// If retrieving photo from library
else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) {
if (resultCode == Activity.RESULT_OK) {
Uri uri = intent.getData();
android.content.ContentResolver resolver = this.cordova.getActivity().getContentResolver();
// If you ask for video or all media type you will automatically get back a file URI
// and there will be no attempt to resize any returned data
if (this.mediaType != PICTURE) {
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
}
else {
// If sending base64 image back
if (destType == DATA_URL) {
try {
Bitmap bitmap = android.graphics.BitmapFactory.decodeStream(resolver.openInputStream(uri));
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);
}
bitmap = scaleBitmap(bitmap);
this.processPicture(bitmap);
bitmap.recycle();
bitmap = null;
System.gc();
} catch (FileNotFoundException e) {
e.printStackTrace();
this.failPicture("Error retrieving image.");
}
}
// 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 {
Bitmap bitmap = android.graphics.BitmapFactory.decodeStream(resolver.openInputStream(uri));
bitmap = scaleBitmap(bitmap);
String fileName = DirectoryManager.getTempDirectoryPath(this.cordova.getActivity()) + "/resize.jpg";
OutputStream os = new FileOutputStream(fileName);
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();
}
bitmap.recycle();
bitmap = null;
// 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://" + fileName + "?" + System.currentTimeMillis())), this.callbackId);
System.gc();
} catch (Exception e) {
e.printStackTrace();
this.failPicture("Error retrieving image.");
}
}
else {
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
}
}
}
}
else if (resultCode == Activity.RESULT_CANCELED) {
this.failPicture("Selection cancelled.");
}
else {
this.failPicture("Selection did not complete!");
}
}
}
private Bitmap getBitmapFromResult(Intent intent)
throws IOException, FileNotFoundException {
Bitmap bitmap = null;
try {
bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getActivity().getContentResolver(), imageUri);
} catch (FileNotFoundException e) {
Uri uri = intent.getData();
android.content.ContentResolver resolver = this.ctx.getActivity().getContentResolver();
bitmap = android.graphics.BitmapFactory.decodeStream(resolver.openInputStream(uri));
}
return bitmap;
}
/**
* Creates a cursor that can be used to determine how many images we have.
@@ -533,16 +679,21 @@ 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();
}
// Clean up initial camera-written image file.
(new File(FileUtils.stripFileProtocol(oldImage.toString()))).delete();
(new File(FileHelper.stripFileProtocol(oldImage.toString()))).delete();
checkForDuplicateImage(imageType);
// Scan for the gallery to update pic refs in gallery
this.scanForGallery();
if (this.saveToPhotoAlbum && newImage != null) {
this.scanForGallery(newImage);
}
System.gc();
}
@@ -560,14 +711,17 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
Cursor cursor = queryImgDB(contentStore);
int currentNumOfImages = cursor.getCount();
if (type == FILE_URI) {
if (type == FILE_URI && this.saveToPhotoAlbum) {
diff = 2;
}
// 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);
}
@@ -597,7 +751,7 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
byte[] code = jpeg_data.toByteArray();
byte[] output = Base64.encodeBase64(code);
String js_out = new String(output);
this.success(new PluginResult(PluginResult.Status.OK, js_out), this.callbackId);
this.callbackContext.success(js_out);
js_out = null;
output = null;
code = null;
@@ -614,21 +768,23 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
* @param err
*/
public void failPicture(String err) {
this.error(new PluginResult(PluginResult.Status.ERROR, err), this.callbackId);
this.callbackContext.error(err);
}
private void scanForGallery() {
if(this.conn!=null) this.conn.disconnect();
this.conn = new MediaScannerConnection(this.ctx.getActivity().getApplicationContext(), this);
private void scanForGallery(Uri newImage) {
this.scanMe = newImage;
if(this.conn != null) {
this.conn.disconnect();
}
this.conn = new MediaScannerConnection(this.cordova.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 after taking picture");
}
}
+31 -37
View File
@@ -23,8 +23,10 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import android.os.Build;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.LOG;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
@@ -41,7 +43,7 @@ import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
public class Capture extends Plugin {
public class Capture extends CordovaPlugin {
private static final String VIDEO_3GPP = "video/3gpp";
private static final String VIDEO_MP4 = "video/mp4";
@@ -57,13 +59,11 @@ public class Capture extends Plugin {
// private static final int CAPTURE_APPLICATION_BUSY = 1;
// private static final int CAPTURE_INVALID_ARGUMENT = 2;
private static final int CAPTURE_NO_MEDIA_FILES = 3;
private static final int CAPTURE_NOT_SUPPORTED = 20;
private String callbackId; // The ID of the callback to be invoked with our result
private CallbackContext callbackContext; // The callback context from which we were invoked.
private long limit; // the number of pics/vids/clips to take
private double duration; // optional duration parameter for video recording
private JSONArray results; // The array of results to be returned to the user
private Uri imageUri; // Uri of captured image
private int numPics; // Number of pictures before capture activity
//private CordovaInterface cordova;
@@ -77,8 +77,8 @@ public class Capture extends Plugin {
// }
@Override
public PluginResult execute(String action, JSONArray args, String callbackId) {
this.callbackId = callbackId;
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
this.callbackContext = callbackContext;
this.limit = 1;
this.duration = 0.0f;
this.results = new JSONArray();
@@ -90,12 +90,9 @@ public class Capture extends Plugin {
}
if (action.equals("getFormatData")) {
try {
JSONObject obj = getFormatData(args.getString(0), args.getString(1));
return new PluginResult(PluginResult.Status.OK, obj);
} catch (JSONException e) {
return new PluginResult(PluginResult.Status.ERROR);
}
callbackContext.success(obj);
return true;
}
else if (action.equals("captureAudio")) {
this.captureAudio();
@@ -106,10 +103,11 @@ public class Capture extends Plugin {
else if (action.equals("captureVideo")) {
this.captureVideo(duration);
}
else {
return false;
}
PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT);
r.setKeepCallback(true);
return r;
return true;
}
/**
@@ -119,9 +117,8 @@ public class Capture extends Plugin {
* @param mimeType of the file
* @return a MediaFileData object
*/
private JSONObject getFormatData(String filePath, String mimeType) {
private JSONObject getFormatData(String filePath, String mimeType) throws JSONException {
JSONObject obj = new JSONObject();
try {
// setup defaults
obj.put("height", 0);
obj.put("width", 0);
@@ -131,8 +128,8 @@ public class Capture extends Plugin {
// If the mimeType isn't set the rest will fail
// so let's see if we can determine it.
if (mimeType == null || mimeType.equals("")) {
mimeType = FileUtils.getMimeType(filePath);
if (mimeType == null || mimeType.equals("") || "null".equals(mimeType)) {
mimeType = FileHelper.getMimeType(filePath, cordova);
}
Log.d(LOG_TAG, "Mime type = " + mimeType);
@@ -145,9 +142,6 @@ public class Capture extends Plugin {
else if (mimeType.equals(VIDEO_3GPP) || mimeType.equals(VIDEO_MP4)) {
obj = getAudioVideoData(filePath, obj, true);
}
} catch (JSONException e) {
Log.d(LOG_TAG, "Error: setting media file data object");
}
return obj;
}
@@ -162,7 +156,7 @@ public class Capture extends Plugin {
private JSONObject getImageData(String filePath, JSONObject obj) throws JSONException {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(FileUtils.stripFileProtocol(filePath), options);
BitmapFactory.decodeFile(FileHelper.stripFileProtocol(filePath), options);
obj.put("height", options.outHeight);
obj.put("width", options.outWidth);
return obj;
@@ -199,7 +193,7 @@ public class Capture extends Plugin {
private void captureAudio() {
Intent intent = new Intent(android.provider.MediaStore.Audio.Media.RECORD_SOUND_ACTION);
this.cordova.startActivityForResult((Plugin) this, intent, CAPTURE_AUDIO);
this.cordova.startActivityForResult((CordovaPlugin) this, intent, CAPTURE_AUDIO);
}
/**
@@ -214,9 +208,8 @@ public class Capture extends Plugin {
// Specify file so that large image is captured and returned
File photo = new File(DirectoryManager.getTempDirectoryPath(this.cordova.getActivity()), "Capture.jpg");
intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(photo));
this.imageUri = Uri.fromFile(photo);
this.cordova.startActivityForResult((Plugin) this, intent, CAPTURE_IMAGE);
this.cordova.startActivityForResult((CordovaPlugin) this, intent, CAPTURE_IMAGE);
}
/**
@@ -224,10 +217,11 @@ public class Capture extends Plugin {
*/
private void captureVideo(double duration) {
Intent intent = new Intent(android.provider.MediaStore.ACTION_VIDEO_CAPTURE);
// Introduced in API 8
//intent.putExtra(android.provider.MediaStore.EXTRA_DURATION_LIMIT, duration);
this.cordova.startActivityForResult((Plugin) this, intent, CAPTURE_VIDEO);
if(Build.VERSION.SDK_INT > 8){
intent.putExtra("android.intent.extra.durationLimit", duration);
}
this.cordova.startActivityForResult((CordovaPlugin) this, intent, CAPTURE_VIDEO);
}
/**
@@ -252,7 +246,7 @@ public class Capture extends Plugin {
if (results.length() >= limit) {
// Send Uri back to JavaScript for listening to audio
this.success(new PluginResult(PluginResult.Status.OK, results), this.callbackId);
this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, results));
} else {
// still need to capture more audio clips
captureAudio();
@@ -298,7 +292,7 @@ public class Capture extends Plugin {
if (results.length() >= limit) {
// Send Uri back to JavaScript for viewing image
this.success(new PluginResult(PluginResult.Status.OK, results), this.callbackId);
this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, results));
} else {
// still need to capture more images
captureImage();
@@ -315,7 +309,7 @@ public class Capture extends Plugin {
if (results.length() >= limit) {
// Send Uri back to JavaScript for viewing video
this.success(new PluginResult(PluginResult.Status.OK, results), this.callbackId);
this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, results));
} else {
// still need to capture more video clips
captureVideo(duration);
@@ -326,7 +320,7 @@ public class Capture extends Plugin {
else if (resultCode == Activity.RESULT_CANCELED) {
// If we have partial results send them back to the user
if (results.length() > 0) {
this.success(new PluginResult(PluginResult.Status.OK, results), this.callbackId);
this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, results));
}
// user canceled the action
else {
@@ -337,7 +331,7 @@ public class Capture extends Plugin {
else {
// If we have partial results send them back to the user
if (results.length() > 0) {
this.success(new PluginResult(PluginResult.Status.OK, results), this.callbackId);
this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, results));
}
// something bad happened
else {
@@ -354,7 +348,7 @@ public class Capture extends Plugin {
* @throws IOException
*/
private JSONObject createMediaFile(Uri data) {
File fp = new File(FileUtils.getRealPathFromURI(data, this.cordova));
File fp = new File(FileHelper.getRealPath(data, this.cordova));
JSONObject obj = new JSONObject();
try {
@@ -371,7 +365,7 @@ public class Capture extends Plugin {
obj.put("type", VIDEO_3GPP);
}
} else {
obj.put("type", FileUtils.getMimeType(fp.getAbsolutePath()));
obj.put("type", FileHelper.getMimeType(fp.getAbsolutePath(), cordova));
}
obj.put("lastModifiedDate", fp.lastModified());
@@ -401,7 +395,7 @@ public class Capture extends Plugin {
* @param err
*/
public void fail(JSONObject err) {
this.error(new PluginResult(PluginResult.Status.ERROR, err), this.callbackId);
this.callbackContext.error(err);
}
@@ -20,8 +20,9 @@ package org.apache.cordova;
import java.util.List;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
@@ -33,10 +34,13 @@ import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
/**
* This class listens to the compass sensor and stores the latest heading value.
*/
public class CompassListener extends Plugin implements SensorEventListener {
public class CompassListener extends CordovaPlugin implements SensorEventListener {
public static int STOPPED = 0;
public static int STARTING = 1;
@@ -54,6 +58,8 @@ public class CompassListener extends Plugin implements SensorEventListener {
private SensorManager sensorManager;// Sensor manager
Sensor mSensor; // Compass sensor returned by sensor manager
private CallbackContext callbackContext;
/**
* Constructor.
*/
@@ -68,9 +74,10 @@ public class CompassListener extends Plugin implements SensorEventListener {
* get file paths associated with the Activity.
*
* @param cordova The context of the main Activity.
* @param webView The CordovaWebView Cordova is running in.
*/
public void setContext(CordovaInterface cordova) {
super.setContext(cordova);
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
super.initialize(cordova, webView);
this.sensorManager = (SensorManager) cordova.getActivity().getSystemService(Context.SENSOR_SERVICE);
}
@@ -79,14 +86,11 @@ public class CompassListener extends Plugin implements SensorEventListener {
*
* @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.
* @param callbackS=Context The callback id used when calling back into JavaScript.
* @return True if the action was valid.
* @throws JSONException
*/
public PluginResult execute(String action, JSONArray args, String callbackId) {
PluginResult.Status status = PluginResult.Status.OK;
String result = "";
try {
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
if (action.equals("start")) {
this.start();
}
@@ -95,69 +99,38 @@ public class CompassListener extends Plugin implements SensorEventListener {
}
else if (action.equals("getStatus")) {
int i = this.getStatus();
return new PluginResult(status, i);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, i));
}
else if (action.equals("getHeading")) {
// If not running, then this is an async call, so don't worry about waiting
if (this.status != CompassListener.RUNNING) {
int r = this.start();
if (r == CompassListener.ERROR_FAILED_TO_START) {
return new PluginResult(PluginResult.Status.IO_EXCEPTION, CompassListener.ERROR_FAILED_TO_START);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, CompassListener.ERROR_FAILED_TO_START));
return true;
}
// Wait until running
long timeout = 2000;
while ((this.status == STARTING) && (timeout > 0)) {
timeout = timeout - 100;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
// Set a timeout callback on the main thread.
Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(new Runnable() {
public void run() {
CompassListener.this.timeout();
}
}, 2000);
}
if (timeout == 0) {
return new PluginResult(PluginResult.Status.IO_EXCEPTION, CompassListener.ERROR_FAILED_TO_START);
}
}
return new PluginResult(status, getCompassHeading());
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, getCompassHeading()));
}
else if (action.equals("setTimeout")) {
this.setTimeout(args.getLong(0));
}
else if (action.equals("getTimeout")) {
long l = this.getTimeout();
return new PluginResult(status, l);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, l));
} else {
// Unsupported action
return new PluginResult(PluginResult.Status.INVALID_ACTION);
}
return new PluginResult(status, result);
} catch (JSONException e) {
e.printStackTrace();
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) {
if (action.equals("getStatus")) {
return true;
}
else if (action.equals("getHeading")) {
// Can only return value if RUNNING
if (this.status == CompassListener.RUNNING) {
return true;
}
}
else if (action.equals("getTimeout")) {
return true;
}
return false;
}
return true;
}
/**
* Called when listener is to be shut down and object is being destroyed.
@@ -166,6 +139,13 @@ public class CompassListener extends Plugin implements SensorEventListener {
this.stop();
}
/**
* Called when app has navigated and JS listeners have been destroyed.
*/
public void onReset() {
this.stop();
}
//--------------------------------------------------------------------------
// LOCAL METHODS
//--------------------------------------------------------------------------
@@ -216,6 +196,18 @@ public class CompassListener extends Plugin implements SensorEventListener {
// TODO Auto-generated method stub
}
/**
* Called after a delay to time out if the listener has not attached fast enough.
*/
private void timeout() {
if (this.status == CompassListener.STARTING) {
this.setStatus(CompassListener.ERROR_FAILED_TO_START);
if (this.callbackContext != null) {
this.callbackContext.error("Compass listener failed to start.");
}
}
}
/**
* Sensor listener event.
*
@@ -287,19 +279,15 @@ public class CompassListener extends Plugin implements SensorEventListener {
*
* @return a compass heading
*/
private JSONObject getCompassHeading() {
private JSONObject getCompassHeading() throws JSONException {
JSONObject obj = new JSONObject();
try {
obj.put("magneticHeading", this.getHeading());
obj.put("trueHeading", this.getHeading());
// Since the magnetic and true heading are always the same our and accuracy
// is defined as the difference between true and magnetic always return zero
obj.put("headingAccuracy", 0);
obj.put("timestamp", this.timeStamp);
} catch (JSONException e) {
// Should never happen
}
return obj;
}
@@ -0,0 +1,283 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.cordova.api.LOG;
import org.xmlpull.v1.XmlPullParserException;
import android.app.Activity;
import android.content.res.XmlResourceParser;
import android.graphics.Color;
import android.util.Log;
public class Config {
public static final String TAG = "Config";
private ArrayList<Pattern> whiteList = new ArrayList<Pattern>();
private HashMap<String, Boolean> whiteListCache = new HashMap<String, Boolean>();
private String startUrl;
private static Config self = null;
public static void init(Activity action) {
if (self == null) {
self = new Config(action);
}
}
// Intended to be used for testing only; creates an empty configuration.
public static void init() {
if (self == null) {
self = new Config();
}
}
// Intended to be used for testing only; creates an empty configuration.
private Config() {
}
private Config(Activity action) {
if (action == null) {
LOG.i("CordovaLog", "There is no activity. Is this on the lock screen?");
return;
}
int id = action.getResources().getIdentifier("config", "xml", action.getPackageName());
if (id == 0) {
id = action.getResources().getIdentifier("cordova", "xml", action.getPackageName());
LOG.i("CordovaLog", "config.xml missing, reverting to cordova.xml");
}
if (id == 0) {
LOG.i("CordovaLog", "cordova.xml missing. Ignoring...");
return;
}
XmlResourceParser xml = action.getResources().getXml(id);
int eventType = -1;
while (eventType != XmlResourceParser.END_DOCUMENT) {
if (eventType == XmlResourceParser.START_TAG) {
String strNode = xml.getName();
if (strNode.equals("access")) {
String origin = xml.getAttributeValue(null, "origin");
String subdomains = xml.getAttributeValue(null, "subdomains");
if (origin != null) {
this._addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0));
}
}
else if (strNode.equals("log")) {
String level = xml.getAttributeValue(null, "level");
LOG.i("CordovaLog", "Found log level %s", level);
if (level != null) {
LOG.setLogLevel(level);
}
}
else if (strNode.equals("preference")) {
String name = xml.getAttributeValue(null, "name");
/* Java 1.6 does not support switch-based strings
Java 7 does, but we're using Dalvik, which is apparently not Java.
Since we're reading XML, this has to be an ugly if/else.
Also, due to cast issues, each of them has to call their separate putExtra!
Wheee!!! Isn't Java FUN!?!?!?
Note: We should probably pass in the classname for the variable splash on splashscreen!
*/
if(name.equals("splashscreen")) {
String value = xml.getAttributeValue(null, "value");
int resource = 0;
if (value != null)
{
value = "splash";
}
resource = action.getResources().getIdentifier(value, "drawable", action.getPackageName());
action.getIntent().putExtra(name, resource);
LOG.i("CordovaLog", "Found preference for %s=%s", name, value);
Log.d("CordovaLog", "Found preference for " + name + "=" + value);
}
else if(name.equals("backgroundColor")) {
int value = xml.getAttributeIntValue(null, "value", Color.BLACK);
action.getIntent().putExtra(name, value);
LOG.i("CordovaLog", "Found preference for %s=%d", name, value);
Log.d("CordovaLog", "Found preference for " + name + "=" + Integer.toString(value));
}
else if(name.equals("loadUrlTimeoutValue")) {
int value = xml.getAttributeIntValue(null, "value", 20000);
action.getIntent().putExtra(name, value);
LOG.i("CordovaLog", "Found preference for %s=%d", name, value);
Log.d("CordovaLog", "Found preference for " + name + "=" + Integer.toString(value));
}
else if(name.equals("keepRunning"))
{
boolean value = xml.getAttributeValue(null, "value").equals("true");
action.getIntent().putExtra(name, value);
}
else if(name.equals("InAppBrowserStorageEnabled"))
{
boolean value = xml.getAttributeValue(null, "value").equals("true");
action.getIntent().putExtra(name, value);
}
else if(name.equals("disallowOverscroll"))
{
boolean value = xml.getAttributeValue(null, "value").equals("true");
action.getIntent().putExtra(name, value);
}
else
{
String value = xml.getAttributeValue(null, "value");
action.getIntent().putExtra(name, value);
LOG.i("CordovaLog", "Found preference for %s=%s", name, value);
Log.d("CordovaLog", "Found preference for " + name + "=" + value);
}
/*
LOG.i("CordovaLog", "Found preference for %s=%s", name, value);
Log.d("CordovaLog", "Found preference for " + name + "=" + value);
*/
}
else if (strNode.equals("content")) {
String src = xml.getAttributeValue(null, "src");
LOG.i("CordovaLog", "Found start page location: %s", src);
if (src != null) {
Pattern schemeRegex = Pattern.compile("^[a-z]+://");
Matcher matcher = schemeRegex.matcher(src);
if (matcher.find()) {
startUrl = src;
} else {
if (src.charAt(0) == '/') {
src = src.substring(1);
}
startUrl = "file:///android_asset/www/" + src;
}
}
}
}
try {
eventType = xml.next();
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* Add entry to approved list of URLs (whitelist)
*
* @param origin URL regular expression to allow
* @param subdomains T=include all subdomains under origin
*/
public static void addWhiteListEntry(String origin, boolean subdomains) {
if (self == null) {
return;
}
self._addWhiteListEntry(origin, subdomains);
}
private void _addWhiteListEntry(String origin, boolean subdomains) {
try {
// Unlimited access to network resources
if (origin.compareTo("*") == 0) {
LOG.d(TAG, "Unlimited access to network resources");
this.whiteList.add(Pattern.compile(".*"));
} else { // specific access
// check if subdomains should be included
// TODO: we should not add more domains if * has already been added
if (subdomains) {
// XXX making it stupid friendly for people who forget to include protocol/SSL
if (origin.startsWith("http")) {
this.whiteList.add(Pattern.compile(origin.replaceFirst("https?://", "^https?://(.*\\.)?")));
} else {
this.whiteList.add(Pattern.compile("^https?://(.*\\.)?" + origin));
}
LOG.d(TAG, "Origin to allow with subdomains: %s", origin);
} else {
// XXX making it stupid friendly for people who forget to include protocol/SSL
if (origin.startsWith("http")) {
this.whiteList.add(Pattern.compile(origin.replaceFirst("https?://", "^https?://")));
} else {
this.whiteList.add(Pattern.compile("^https?://" + origin));
}
LOG.d(TAG, "Origin to allow: %s", origin);
}
}
} catch (Exception e) {
LOG.d(TAG, "Failed to add origin %s", origin);
}
}
/**
* Determine if URL is in approved list of URLs to load.
*
* @param url
* @return
*/
public static boolean isUrlWhiteListed(String url) {
if (self == null) {
return false;
}
// Check to see if we have matched url previously
if (self.whiteListCache.get(url) != null) {
return true;
}
// Look for match in white list
Iterator<Pattern> pit = self.whiteList.iterator();
while (pit.hasNext()) {
Pattern p = pit.next();
Matcher m = p.matcher(url);
// If match found, then cache it to speed up subsequent comparisons
if (m.find()) {
self.whiteListCache.put(url, true);
return true;
}
}
return false;
}
public static String getStartUrl() {
if (self == null || self.startUrl == null) {
return "file:///android_asset/www/index.html";
}
return self.startUrl;
}
}
@@ -18,7 +18,6 @@ package org.apache.cordova;
import java.util.HashMap;
import android.content.Context;
import android.util.Log;
import android.webkit.WebView;
@@ -19,6 +19,25 @@
package org.apache.cordova;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.util.Log;
import android.webkit.WebView;
import org.apache.cordova.api.CordovaInterface;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -31,27 +50,8 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.cordova.api.CordovaInterface;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.accounts.Account;
import android.accounts.AccountManager;
//import android.app.Activity;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentUris;
import android.content.ContentValues;
//import android.content.Context;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.util.Log;
import android.webkit.WebView;
/**
* An implementation of {@link ContactAccessor} that uses current Contacts API.
@@ -71,6 +71,7 @@ import android.webkit.WebView;
* social status updates (see {@link android.provider.ContactsContract.StatusUpdates}).
* </ul>
*/
public class ContactAccessorSdk5 extends ContactAccessor {
/**
@@ -150,6 +151,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
else {
searchTerm = "%" + searchTerm + "%";
}
try {
multiple = options.getBoolean("multiple");
if (!multiple) {
@@ -163,6 +165,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
searchTerm = "%";
}
//Log.d(LOG_TAG, "Search Term = " + searchTerm);
//Log.d(LOG_TAG, "Field Length = " + fields.length());
//Log.d(LOG_TAG, "Fields = " + fields.toString());
@@ -181,19 +184,89 @@ public class ContactAccessorSdk5 extends ContactAccessor {
ContactsContract.Data.CONTACT_ID + " ASC");
// Create a set of unique ids
//Log.d(LOG_TAG, "ID cursor query returns = " + idCursor.getCount());
Set<String> contactIds = new HashSet<String>();
int idColumn = -1;
while (idCursor.moveToNext()) {
contactIds.add(idCursor.getString(idCursor.getColumnIndex(ContactsContract.Data.CONTACT_ID)));
if (idColumn < 0) {
idColumn = idCursor.getColumnIndex(ContactsContract.Data.CONTACT_ID);
}
contactIds.add(idCursor.getString(idColumn));
}
idCursor.close();
// Build a query that only looks at ids
WhereOptions idOptions = buildIdClause(contactIds, searchTerm);
// Determine which columns we should be fetching.
HashSet<String> columnsToFetch = new HashSet<String>();
columnsToFetch.add(ContactsContract.Data.CONTACT_ID);
columnsToFetch.add(ContactsContract.Data.RAW_CONTACT_ID);
columnsToFetch.add(ContactsContract.Data.MIMETYPE);
if (isRequired("displayName", populate)) {
columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME);
}
if (isRequired("name", populate)) {
columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME);
columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME);
columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME);
columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredName.PREFIX);
columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredName.SUFFIX);
}
if (isRequired("phoneNumbers", populate)) {
columnsToFetch.add(ContactsContract.CommonDataKinds.Phone._ID);
columnsToFetch.add(ContactsContract.CommonDataKinds.Phone.NUMBER);
columnsToFetch.add(ContactsContract.CommonDataKinds.Phone.TYPE);
}
if (isRequired("emails", populate)) {
columnsToFetch.add(ContactsContract.CommonDataKinds.Email._ID);
columnsToFetch.add(ContactsContract.CommonDataKinds.Email.DATA);
columnsToFetch.add(ContactsContract.CommonDataKinds.Email.TYPE);
}
if (isRequired("addresses", populate)) {
columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredPostal._ID);
columnsToFetch.add(ContactsContract.CommonDataKinds.Organization.TYPE);
columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS);
columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredPostal.STREET);
columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredPostal.CITY);
columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredPostal.REGION);
columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE);
columnsToFetch.add(ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY);
}
if (isRequired("organizations", populate)) {
columnsToFetch.add(ContactsContract.CommonDataKinds.Organization._ID);
columnsToFetch.add(ContactsContract.CommonDataKinds.Organization.TYPE);
columnsToFetch.add(ContactsContract.CommonDataKinds.Organization.DEPARTMENT);
columnsToFetch.add(ContactsContract.CommonDataKinds.Organization.COMPANY);
columnsToFetch.add(ContactsContract.CommonDataKinds.Organization.TITLE);
}
if (isRequired("ims", populate)) {
columnsToFetch.add(ContactsContract.CommonDataKinds.Im._ID);
columnsToFetch.add(ContactsContract.CommonDataKinds.Im.DATA);
columnsToFetch.add(ContactsContract.CommonDataKinds.Im.TYPE);
}
if (isRequired("note", populate)) {
columnsToFetch.add(ContactsContract.CommonDataKinds.Note.NOTE);
}
if (isRequired("nickname", populate)) {
columnsToFetch.add(ContactsContract.CommonDataKinds.Nickname.NAME);
}
if (isRequired("urls", populate)) {
columnsToFetch.add(ContactsContract.CommonDataKinds.Website._ID);
columnsToFetch.add(ContactsContract.CommonDataKinds.Website.URL);
columnsToFetch.add(ContactsContract.CommonDataKinds.Website.TYPE);
}
if (isRequired("birthday", populate)) {
columnsToFetch.add(ContactsContract.CommonDataKinds.Event.START_DATE);
columnsToFetch.add(ContactsContract.CommonDataKinds.Event.TYPE);
}
if (isRequired("photos", populate)) {
columnsToFetch.add(ContactsContract.CommonDataKinds.Photo._ID);
}
// Do the id query
Cursor c = mApp.getActivity().getContentResolver().query(ContactsContract.Data.CONTENT_URI,
null,
columnsToFetch.toArray(new String[] {}),
idOptions.getWhere(),
idOptions.getWhereArgs(),
ContactsContract.Data.CONTACT_ID + " ASC");
@@ -258,11 +331,21 @@ public class ContactAccessorSdk5 extends ContactAccessor {
JSONArray websites = new JSONArray();
JSONArray photos = new JSONArray();
// Column indices
int colContactId = c.getColumnIndex(ContactsContract.Data.CONTACT_ID);
int colRawContactId = c.getColumnIndex(ContactsContract.Data.RAW_CONTACT_ID);
int colMimetype = c.getColumnIndex(ContactsContract.Data.MIMETYPE);
int colDisplayName = c.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME);
int colNote = c.getColumnIndex(ContactsContract.CommonDataKinds.Note.NOTE);
int colNickname = c.getColumnIndex(ContactsContract.CommonDataKinds.Nickname.NAME);
int colBirthday = c.getColumnIndex(ContactsContract.CommonDataKinds.Event.START_DATE);
int colEventType = c.getColumnIndex(ContactsContract.CommonDataKinds.Event.TYPE);
if (c.getCount() > 0) {
while (c.moveToNext() && (contacts.length() <= (limit - 1))) {
try {
contactId = c.getString(c.getColumnIndex(ContactsContract.Data.CONTACT_ID));
rawId = c.getString(c.getColumnIndex(ContactsContract.Data.RAW_CONTACT_ID));
contactId = c.getString(colContactId);
rawId = c.getString(colRawContactId);
// If we are in the first row set the oldContactId
if (c.getPosition() == 0) {
@@ -300,11 +383,12 @@ public class ContactAccessorSdk5 extends ContactAccessor {
}
// Grab the mimetype of the current row as it will be used in a lot of comparisons
mimetype = c.getString(c.getColumnIndex(ContactsContract.Data.MIMETYPE));
mimetype = c.getString(colMimetype);
if (mimetype.equals(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)) {
contact.put("displayName", c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME)));
contact.put("displayName", c.getString(colDisplayName));
}
if (mimetype.equals(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
&& isRequired("name", populate)) {
contact.put("name", nameQuery(c));
@@ -331,20 +415,20 @@ public class ContactAccessorSdk5 extends ContactAccessor {
}
else if (mimetype.equals(ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE)
&& isRequired("note", populate)) {
contact.put("note", c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.Note.NOTE)));
contact.put("note", c.getString(colNote));
}
else if (mimetype.equals(ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE)
&& isRequired("nickname", populate)) {
contact.put("nickname", c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.Nickname.NAME)));
contact.put("nickname", c.getString(colNickname));
}
else if (mimetype.equals(ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE)
&& isRequired("urls", populate)) {
websites.put(websiteQuery(c));
}
else if (mimetype.equals(ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE)) {
if (ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY == c.getInt(c.getColumnIndex(ContactsContract.CommonDataKinds.Event.TYPE))
&& isRequired("birthday", populate)) {
contact.put("birthday", c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.Event.START_DATE)));
if (isRequired("birthday", populate) &&
ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY == c.getInt(colEventType)) {
contact.put("birthday", c.getString(colBirthday));
}
}
else if (mimetype.equals(ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE)
@@ -357,6 +441,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
// Set the old contact ID
oldContactId = contactId;
}
// Push the last contact into the contacts array
@@ -708,10 +793,10 @@ public class ContactAccessorSdk5 extends ContactAccessor {
formatted.append(middleName + " ");
}
if (familyName != null) {
formatted.append(familyName + " ");
formatted.append(familyName);
}
if (honorificSuffix != null) {
formatted.append(honorificSuffix + " ");
formatted.append(" " + honorificSuffix);
}
contactName.put("familyName", familyName);
@@ -775,7 +860,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
im.put("id", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Im._ID)));
im.put("pref", false); // Android does not store pref attribute
im.put("value", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA)));
im.put("type", getContactType(cursor.getInt(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Im.TYPE))));
im.put("type", getImType(cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Im.PROTOCOL))));
} catch (JSONException e) {
Log.e(LOG_TAG, e.getMessage(), e);
}
@@ -944,6 +1029,16 @@ public class ContactAccessorSdk5 extends ContactAccessor {
try {
phones = contact.getJSONArray("phoneNumbers");
if (phones != null) {
// Delete all the phones
if (phones.length() == 0) {
ops.add(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI)
.withSelection(ContactsContract.Data.RAW_CONTACT_ID + "=? AND " +
ContactsContract.Data.MIMETYPE + "=?",
new String[] { "" + rawId, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE })
.build());
}
// Modify or add a phone
else {
for (int i = 0; i < phones.length(); i++) {
JSONObject phone = (JSONObject) phones.get(i);
String phoneId = getJsonString(phone, "id");
@@ -970,6 +1065,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
}
}
}
}
} catch (JSONException e) {
Log.d(LOG_TAG, "Could not get phone numbers");
}
@@ -979,6 +1075,16 @@ public class ContactAccessorSdk5 extends ContactAccessor {
try {
emails = contact.getJSONArray("emails");
if (emails != null) {
// Delete all the emails
if (emails.length() == 0) {
ops.add(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI)
.withSelection(ContactsContract.Data.RAW_CONTACT_ID + "=? AND " +
ContactsContract.Data.MIMETYPE + "=?",
new String[] { "" + rawId, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE })
.build());
}
// Modify or add a email
else {
for (int i = 0; i < emails.length(); i++) {
JSONObject email = (JSONObject) emails.get(i);
String emailId = getJsonString(email, "id");
@@ -1005,6 +1111,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
}
}
}
}
} catch (JSONException e) {
Log.d(LOG_TAG, "Could not get emails");
}
@@ -1014,6 +1121,16 @@ public class ContactAccessorSdk5 extends ContactAccessor {
try {
addresses = contact.getJSONArray("addresses");
if (addresses != null) {
// Delete all the addresses
if (addresses.length() == 0) {
ops.add(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI)
.withSelection(ContactsContract.Data.RAW_CONTACT_ID + "=? AND " +
ContactsContract.Data.MIMETYPE + "=?",
new String[] { "" + rawId, ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE })
.build());
}
// Modify or add a address
else {
for (int i = 0; i < addresses.length(); i++) {
JSONObject address = (JSONObject) addresses.get(i);
String addressId = getJsonString(address, "id");
@@ -1050,6 +1167,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
}
}
}
}
} catch (JSONException e) {
Log.d(LOG_TAG, "Could not get addresses");
}
@@ -1059,6 +1177,16 @@ public class ContactAccessorSdk5 extends ContactAccessor {
try {
organizations = contact.getJSONArray("organizations");
if (organizations != null) {
// Delete all the organizations
if (organizations.length() == 0) {
ops.add(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI)
.withSelection(ContactsContract.Data.RAW_CONTACT_ID + "=? AND " +
ContactsContract.Data.MIMETYPE + "=?",
new String[] { "" + rawId, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE })
.build());
}
// Modify or add a organization
else {
for (int i = 0; i < organizations.length(); i++) {
JSONObject org = (JSONObject) organizations.get(i);
String orgId = getJsonString(org, "id");
@@ -1089,6 +1217,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
}
}
}
}
} catch (JSONException e) {
Log.d(LOG_TAG, "Could not get organizations");
}
@@ -1098,6 +1227,16 @@ public class ContactAccessorSdk5 extends ContactAccessor {
try {
ims = contact.getJSONArray("ims");
if (ims != null) {
// Delete all the ims
if (ims.length() == 0) {
ops.add(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI)
.withSelection(ContactsContract.Data.RAW_CONTACT_ID + "=? AND " +
ContactsContract.Data.MIMETYPE + "=?",
new String[] { "" + rawId, ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE })
.build());
}
// Modify or add a im
else {
for (int i = 0; i < ims.length(); i++) {
JSONObject im = (JSONObject) ims.get(i);
String imId = getJsonString(im, "id");
@@ -1107,7 +1246,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
contentValues.put(ContactsContract.Data.RAW_CONTACT_ID, rawId);
contentValues.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE);
contentValues.put(ContactsContract.CommonDataKinds.Im.DATA, getJsonString(im, "value"));
contentValues.put(ContactsContract.CommonDataKinds.Im.TYPE, getContactType(getJsonString(im, "type")));
contentValues.put(ContactsContract.CommonDataKinds.Im.TYPE, getImType(getJsonString(im, "type")));
ops.add(ContentProviderOperation.newInsert(
ContactsContract.Data.CONTENT_URI).withValues(contentValues).build());
@@ -1124,6 +1263,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
}
}
}
}
} catch (JSONException e) {
Log.d(LOG_TAG, "Could not get emails");
}
@@ -1151,8 +1291,19 @@ public class ContactAccessorSdk5 extends ContactAccessor {
// Modify urls
JSONArray websites = null;
try {
websites = contact.getJSONArray("websites");
websites = contact.getJSONArray("urls");
if (websites != null) {
// Delete all the websites
if (websites.length() == 0) {
Log.d(LOG_TAG, "This means we should be deleting all the phone numbers.");
ops.add(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI)
.withSelection(ContactsContract.Data.RAW_CONTACT_ID + "=? AND " +
ContactsContract.Data.MIMETYPE + "=?",
new String[] { "" + rawId, ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE })
.build());
}
// Modify or add a website
else {
for (int i = 0; i < websites.length(); i++) {
JSONObject website = (JSONObject) websites.get(i);
String websiteId = getJsonString(website, "id");
@@ -1179,6 +1330,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
}
}
}
}
} catch (JSONException e) {
Log.d(LOG_TAG, "Could not get websites");
}
@@ -1201,6 +1353,16 @@ public class ContactAccessorSdk5 extends ContactAccessor {
try {
photos = contact.getJSONArray("photos");
if (photos != null) {
// Delete all the photos
if (photos.length() == 0) {
ops.add(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI)
.withSelection(ContactsContract.Data.RAW_CONTACT_ID + "=? AND " +
ContactsContract.Data.MIMETYPE + "=?",
new String[] { "" + rawId, ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE })
.build());
}
// Modify or add a photo
else {
for (int i = 0; i < photos.length(); i++) {
JSONObject photo = (JSONObject) photos.get(i);
String photoId = getJsonString(photo, "id");
@@ -1228,6 +1390,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
}
}
}
}
} catch (JSONException e) {
Log.d(LOG_TAG, "Could not get photos");
}
@@ -1247,7 +1410,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
retVal = false;
}
// if the save was a succes return the contact ID
// if the save was a success return the contact ID
if (retVal) {
return id;
} else {
@@ -1282,7 +1445,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Im.DATA, getJsonString(im, "value"))
.withValue(ContactsContract.CommonDataKinds.Im.TYPE, getContactType(getJsonString(im, "type")))
.withValue(ContactsContract.CommonDataKinds.Im.TYPE, getImType(getJsonString(im, "type")))
.build());
}
@@ -1337,7 +1500,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Email.DATA, getJsonString(email, "value"))
.withValue(ContactsContract.CommonDataKinds.Email.TYPE, getPhoneType(getJsonString(email, "type")))
.withValue(ContactsContract.CommonDataKinds.Email.TYPE, getContactType(getJsonString(email, "type")))
.build());
}
@@ -1416,7 +1579,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
Uri uri = Uri.parse(path);
return mApp.getActivity().getContentResolver().openInputStream(uri);
}
if (path.startsWith("http:") || path.startsWith("file:")) {
if (path.startsWith("http:") || path.startsWith("https:") || path.startsWith("file:")) {
URL url = new URL(path);
return url.openStream();
}
@@ -1554,7 +1717,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
// Add urls
JSONArray websites = null;
try {
websites = contact.getJSONArray("websites");
websites = contact.getJSONArray("urls");
if (websites != null) {
for (int i = 0; i < websites.length(); i++) {
JSONObject website = (JSONObject) websites.get(i);
@@ -1926,5 +2089,86 @@ public class ContactAccessorSdk5 extends ContactAccessor {
}
return stringType;
}
/**
* Converts a string from the W3C Contact API to it's Android int value.
* @param string
* @return Android int value
*/
private int getImType(String string) {
int type = ContactsContract.CommonDataKinds.Im.PROTOCOL_CUSTOM;
if (string != null) {
if ("aim".equals(string.toLowerCase())) {
return ContactsContract.CommonDataKinds.Im.PROTOCOL_AIM;
}
else if ("google talk".equals(string.toLowerCase())) {
return ContactsContract.CommonDataKinds.Im.PROTOCOL_GOOGLE_TALK;
}
else if ("icq".equals(string.toLowerCase())) {
return ContactsContract.CommonDataKinds.Im.PROTOCOL_ICQ;
}
else if ("jabber".equals(string.toLowerCase())) {
return ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER;
}
else if ("msn".equals(string.toLowerCase())) {
return ContactsContract.CommonDataKinds.Im.PROTOCOL_MSN;
}
else if ("netmeeting".equals(string.toLowerCase())) {
return ContactsContract.CommonDataKinds.Im.PROTOCOL_NETMEETING;
}
else if ("qq".equals(string.toLowerCase())) {
return ContactsContract.CommonDataKinds.Im.PROTOCOL_QQ;
}
else if ("skype".equals(string.toLowerCase())) {
return ContactsContract.CommonDataKinds.Im.PROTOCOL_SKYPE;
}
else if ("yahoo".equals(string.toLowerCase())) {
return ContactsContract.CommonDataKinds.Im.PROTOCOL_YAHOO;
}
}
return type;
}
/**
* getPhoneType converts an Android phone type into a string
* @param type
* @return phone type as string.
*/
private String getImType(int type) {
String stringType;
switch (type) {
case ContactsContract.CommonDataKinds.Im.PROTOCOL_AIM:
stringType = "AIM";
break;
case ContactsContract.CommonDataKinds.Im.PROTOCOL_GOOGLE_TALK:
stringType = "Google Talk";
break;
case ContactsContract.CommonDataKinds.Im.PROTOCOL_ICQ:
stringType = "ICQ";
break;
case ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER:
stringType = "Jabber";
break;
case ContactsContract.CommonDataKinds.Im.PROTOCOL_MSN:
stringType = "MSN";
break;
case ContactsContract.CommonDataKinds.Im.PROTOCOL_NETMEETING:
stringType = "NetMeeting";
break;
case ContactsContract.CommonDataKinds.Im.PROTOCOL_QQ:
stringType = "QQ";
break;
case ContactsContract.CommonDataKinds.Im.PROTOCOL_SKYPE:
stringType = "Skype";
break;
case ContactsContract.CommonDataKinds.Im.PROTOCOL_YAHOO:
stringType = "Yahoo";
break;
default:
stringType = "custom";
break;
}
return stringType;
}
}
@@ -18,14 +18,15 @@
*/
package org.apache.cordova;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.util.Log;
public class ContactManager extends Plugin {
public class ContactManager extends CordovaPlugin {
private ContactAccessor contactAccessor;
private static final String LOG_TAG = "Contact Query";
@@ -48,20 +49,18 @@ public class ContactManager 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.
* @param args JSONArray of arguments for the plugin.
* @param callbackContext The callback context used when calling back into JavaScript.
* @return True if the action was valid, false otherwise.
*/
public PluginResult execute(String action, JSONArray args, String callbackId) {
PluginResult.Status status = PluginResult.Status.OK;
String result = "";
public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException {
/**
* Check to see if we are on an Android 1.X device. If we are return an error as we
* do not support this as of Cordova 1.0.
*/
if (android.os.Build.VERSION.RELEASE.startsWith("1.")) {
return new PluginResult(PluginResult.Status.ERROR, ContactManager.NOT_SUPPORTED_ERROR);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, ContactManager.NOT_SUPPORTED_ERROR));
return true;
}
/**
@@ -72,30 +71,52 @@ public class ContactManager extends Plugin {
this.contactAccessor = new ContactAccessorSdk5(this.webView, this.cordova);
}
try {
if (action.equals("search")) {
JSONArray res = contactAccessor.search(args.getJSONArray(0), args.optJSONObject(1));
return new PluginResult(status, res);
final JSONArray filter = args.getJSONArray(0);
final JSONObject options = args.getJSONObject(1);
this.cordova.getThreadPool().execute(new Runnable() {
public void run() {
JSONArray res = contactAccessor.search(filter, options);
callbackContext.success(res);
}
});
}
else if (action.equals("save")) {
String id = contactAccessor.save(args.getJSONObject(0));
final JSONObject contact = args.getJSONObject(0);
this.cordova.getThreadPool().execute(new Runnable() {
public void run() {
JSONObject res = null;
String id = contactAccessor.save(contact);
if (id != null) {
JSONObject res = contactAccessor.getContactById(id);
try {
res = contactAccessor.getContactById(id);
} catch (JSONException e) {
Log.e(LOG_TAG, "JSON fail.", e);
}
}
if (res != null) {
return new PluginResult(status, res);
callbackContext.success(res);
} else {
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, UNKNOWN_ERROR));
}
}
});
}
else if (action.equals("remove")) {
if (contactAccessor.remove(args.getString(0))) {
return new PluginResult(status, result);
final String contactId = args.getString(0);
this.cordova.getThreadPool().execute(new Runnable() {
public void run() {
if (contactAccessor.remove(contactId)) {
callbackContext.success();
} else {
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, UNKNOWN_ERROR));
}
}
// If we get to this point an error has occurred
return new PluginResult(PluginResult.Status.ERROR, ContactManager.UNKNOWN_ERROR);
} catch (JSONException e) {
Log.e(LOG_TAG, e.getMessage(), e);
return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
}
});
}
else {
return false;
}
return true;
}
}
@@ -0,0 +1,113 @@
/*
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.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.util.Base64;
public class CordovaArgs {
private JSONArray baseArgs;
public CordovaArgs(JSONArray args) {
this.baseArgs = args;
}
// Pass through the basics to the base args.
public Object get(int index) throws JSONException {
return baseArgs.get(index);
}
public boolean getBoolean(int index) throws JSONException {
return baseArgs.getBoolean(index);
}
public double getDouble(int index) throws JSONException {
return baseArgs.getDouble(index);
}
public int getInt(int index) throws JSONException {
return baseArgs.getInt(index);
}
public JSONArray getJSONArray(int index) throws JSONException {
return baseArgs.getJSONArray(index);
}
public JSONObject getJSONObject(int index) throws JSONException {
return baseArgs.getJSONObject(index);
}
public long getLong(int index) throws JSONException {
return baseArgs.getLong(index);
}
public String getString(int index) throws JSONException {
return baseArgs.getString(index);
}
public Object opt(int index) {
return baseArgs.opt(index);
}
public boolean optBoolean(int index) {
return baseArgs.optBoolean(index);
}
public double optDouble(int index) {
return baseArgs.optDouble(index);
}
public int optInt(int index) {
return baseArgs.optInt(index);
}
public JSONArray optJSONArray(int index) {
return baseArgs.optJSONArray(index);
}
public JSONObject optJSONObject(int index) {
return baseArgs.optJSONObject(index);
}
public long optLong(int index) {
return baseArgs.optLong(index);
}
public String optString(int index) {
return baseArgs.optString(index);
}
public boolean isNull(int index) {
return baseArgs.isNull(index);
}
// The interesting custom helpers.
public byte[] getArrayBuffer(int index) throws JSONException {
String encoded = baseArgs.getString(index);
return Base64.decode(encoded, Base64.DEFAULT);
}
}
@@ -23,31 +23,46 @@ import org.apache.cordova.api.LOG;
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.content.Intent;
import android.net.Uri;
import android.view.Gravity;
import android.view.KeyEvent;
//import android.view.View;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.webkit.ConsoleMessage;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebStorage;
import android.webkit.WebView;
import android.webkit.GeolocationPermissions.Callback;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
/**
* This class is the WebChromeClient that implements callbacks for our web view.
*/
public class CordovaChromeClient extends WebChromeClient {
public static final int FILECHOOSER_RESULTCODE = 5173;
private static final String LOG_TAG = "CordovaChromeClient";
private String TAG = "CordovaLog";
private long MAX_QUOTA = 100 * 1024 * 1024;
private CordovaInterface cordova;
private CordovaWebView appView;
// the video progress view
private View mVideoProgressView;
// File Chooser
public ValueCallback<Uri> mUploadMessage;
/**
* Constructor.
*
@@ -101,7 +116,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() {
@@ -190,7 +205,7 @@ public class CordovaChromeClient extends WebChromeClient {
// Security check to make sure any requests are coming from the page initially
// loaded in webview and not another loaded in an iframe.
boolean reqOk = false;
if (url.startsWith("file://") || url.indexOf(this.appView.baseUrl) == 0 || this.appView.isUrlWhiteListed(url)) {
if (url.startsWith("file://") || Config.isUrlWhiteListed(url)) {
reqOk = true;
}
@@ -203,18 +218,23 @@ public class CordovaChromeClient extends WebChromeClient {
String service = array.getString(0);
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);
String r = this.appView.exposedJsApi.exec(service, action, callbackId, message);
result.confirm(r == null ? "" : r);
} catch (JSONException e) {
e.printStackTrace();
}
}
// Sets the native->JS bridge mode.
else if (reqOk && defaultValue != null && defaultValue.equals("gap_bridge_mode:")) {
this.appView.exposedJsApi.setNativeToJsBridgeMode(Integer.parseInt(message));
result.confirm("");
}
// Polling for JavaScript messages
else if (reqOk && defaultValue != null && defaultValue.equals("gap_poll:")) {
String r = this.appView.callbackServer.getJavascript();
result.confirm(r);
String r = this.appView.exposedJsApi.retrieveJsMessages();
result.confirm(r == null ? "" : r);
}
// Do NO-OP so older code doesn't display dialog
@@ -222,24 +242,6 @@ public class CordovaChromeClient extends WebChromeClient {
result.confirm("OK");
}
// Calling into CallbackServer
else if (reqOk && defaultValue != null && defaultValue.equals("gap_callbackServer:")) {
String r = "";
if (message.equals("usePolling")) {
r = "" + this.appView.callbackServer.usePolling();
}
else if (message.equals("restartServer")) {
this.appView.callbackServer.restartServer();
}
else if (message.equals("getPort")) {
r = Integer.toString(this.appView.callbackServer.getPort());
}
else if (message.equals("getToken")) {
r = this.appView.callbackServer.getToken();
}
result.confirm(r);
}
// Show dialog
else {
final JsPromptResult res = result;
@@ -302,14 +304,20 @@ public class CordovaChromeClient extends WebChromeClient {
}
// console.log in api level 7: http://developer.android.com/guide/developing/debug-tasks.html
// Expect this to not compile in a future Android release!
@SuppressWarnings("deprecation")
@Override
public void onConsoleMessage(String message, int lineNumber, String sourceID)
{
//This is only for Android 2.1
if(android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.ECLAIR_MR1)
{
LOG.d(TAG, "%s: Line %d : %s", sourceID, lineNumber, message);
super.onConsoleMessage(message, lineNumber, sourceID);
}
}
@TargetApi(8)
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage)
{
@@ -329,4 +337,66 @@ public class CordovaChromeClient extends WebChromeClient {
super.onGeolocationPermissionsShowPrompt(origin, callback);
callback.invoke(origin, true, false);
}
// API level 7 is required for this, see if we could lower this using something else
@Override
public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
this.appView.showCustomView(view, callback);
}
@Override
public void onHideCustomView() {
this.appView.hideCustomView();
}
@Override
/**
* Ask the host application for a custom progress view to show while
* a <video> is loading.
* @return View The progress view.
*/
public View getVideoLoadingProgressView() {
if (mVideoProgressView == null) {
// Create a new Loading view programmatically.
// create the linear layout
LinearLayout layout = new LinearLayout(this.appView.getContext());
layout.setOrientation(LinearLayout.VERTICAL);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
layout.setLayoutParams(layoutParams);
// the proress bar
ProgressBar bar = new ProgressBar(this.appView.getContext());
LinearLayout.LayoutParams barLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
barLayoutParams.gravity = Gravity.CENTER;
bar.setLayoutParams(barLayoutParams);
layout.addView(bar);
mVideoProgressView = layout;
}
return mVideoProgressView;
}
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
this.openFileChooser(uploadMsg, "*/*");
}
public void openFileChooser( ValueCallback<Uri> uploadMsg, String acceptType ) {
this.openFileChooser(uploadMsg, acceptType, null);
}
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture)
{
mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("*/*");
this.cordova.getActivity().startActivityForResult(Intent.createChooser(i, "File Browser"),
FILECHOOSER_RESULTCODE);
}
public ValueCallback<Uri> getValueCallback() {
return this.mUploadMessage;
}
}
@@ -22,7 +22,8 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.cordova.api.CallbackContext;
import android.location.Location;
import android.location.LocationListener;
@@ -39,8 +40,8 @@ public class CordovaLocationListener implements LocationListener {
private GeoBroker owner;
protected boolean running = false;
public HashMap<String, String> watches = new HashMap<String, String>();
private List<String> callbacks = new ArrayList<String>();
public HashMap<String, CallbackContext> watches = new HashMap<String, CallbackContext>();
private List<CallbackContext> callbacks = new ArrayList<CallbackContext>();
private String TAG = "[Cordova Location Listener]";
@@ -51,30 +52,38 @@ public class CordovaLocationListener implements LocationListener {
}
protected void fail(int code, String message) {
for (String callbackId: this.callbacks)
for (CallbackContext callbackContext: this.callbacks)
{
this.owner.fail(code, message, callbackId);
this.owner.fail(code, message, callbackContext);
}
if(this.owner.isGlobalListener(this))
{
Log.d(TAG, "Stopping global listener");
this.stop();
}
this.callbacks.clear();
Iterator it = this.watches.entrySet().iterator();
Iterator<CallbackContext> it = this.watches.values().iterator();
while (it.hasNext()) {
Map.Entry pairs = (Map.Entry)it.next();
this.owner.fail(code, message, (String)pairs.getValue());
this.owner.fail(code, message, it.next());
}
}
private void win(Location loc) {
for (String callbackId: this.callbacks)
for (CallbackContext callbackContext: this.callbacks)
{
this.owner.win(loc, callbackId);
this.owner.win(loc, callbackContext);
}
if(this.owner.isGlobalListener(this))
{
Log.d(TAG, "Stopping global listener");
this.stop();
}
this.callbacks.clear();
Iterator it = this.watches.entrySet().iterator();
Iterator<CallbackContext> it = this.watches.values().iterator();
while (it.hasNext()) {
Map.Entry pairs = (Map.Entry)it.next();
this.owner.win(loc, (String)pairs.getValue());
this.owner.win(loc, it.next());
}
}
@@ -140,14 +149,14 @@ public class CordovaLocationListener implements LocationListener {
return this.watches.size() + this.callbacks.size();
}
public void addWatch(String timerId, String callbackId) {
this.watches.put(timerId, callbackId);
public void addWatch(String timerId, CallbackContext callbackContext) {
this.watches.put(timerId, callbackContext);
if (this.size() == 1) {
this.start();
}
}
public void addCallback(String callbackId) {
this.callbacks.add(callbackId);
public void addCallback(CallbackContext callbackContext) {
this.callbacks.add(callbackContext);
if (this.size() == 1) {
this.start();
}
@@ -19,67 +19,102 @@
package org.apache.cordova;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.cordova.Config;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.LOG;
import org.apache.cordova.api.PluginManager;
import org.xmlpull.v1.XmlPullParserException;
import org.apache.cordova.api.PluginResult;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.res.XmlResourceParser;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.webkit.WebBackForwardList;
import android.webkit.WebHistoryItem;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebSettings.LayoutAlgorithm;
import android.widget.FrameLayout;
public class CordovaWebView extends WebView {
public static final String TAG = "CordovaWebView";
/** The whitelist **/
private ArrayList<Pattern> whiteList = new ArrayList<Pattern>();
private HashMap<String, Boolean> whiteListCache = new HashMap<String, Boolean>();
private ArrayList<Integer> keyDownCodes = new ArrayList<Integer>();
private ArrayList<Integer> keyUpCodes = new ArrayList<Integer>();
public PluginManager pluginManager;
public CallbackServer callbackServer;
private boolean paused;
private BroadcastReceiver receiver;
/** Actvities and other important classes **/
/** Activities and other important classes **/
private CordovaInterface cordova;
CordovaWebViewClient viewClient;
@SuppressWarnings("unused")
private CordovaChromeClient chromeClient;
//This is for the polyfil history
private String url;
String baseUrl;
private Stack<String> urls = new Stack<String>();
boolean useBrowserHistory = false;
// Flag to track that a loadUrl timeout occurred
int loadUrlTimeout = 0;
private boolean bound;
private boolean volumedownBound;
private boolean handleButton = false;
private boolean volumeupBound;
private long lastMenuEventTime = 0;
NativeToJsMessageQueue jsMessageQueue;
ExposedJsApi exposedJsApi;
/** custom view created by the browser (a video player for example) */
private View mCustomView;
private WebChromeClient.CustomViewCallback mCustomViewCallback;
private ActivityResult mResult = null;
class ActivityResult {
int request;
int result;
Intent incoming;
public ActivityResult(int req, int res, Intent intent) {
request = req;
result = res;
incoming = intent;
}
}
static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
Gravity.CENTER);
/**
* Constructor.
@@ -117,7 +152,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 +176,6 @@ 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.loadConfiguration();
this.setup();
}
@@ -154,6 +188,7 @@ public class CordovaWebView extends WebView {
* @param defStyle
* @param 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 +200,30 @@ 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 ||
android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.JELLY_BEAN_MR1)
{
this.setWebViewClient(new CordovaWebViewClient(this.cordova, this));
}
else
{
this.setWebViewClient(new IceCreamCordovaWebViewClient(this.cordova, this));
}
}
/**
* Initialize webview.
*/
@SuppressWarnings("deprecation")
@SuppressLint("NewApi")
private void setup() {
this.setInitialScale(0);
this.setVerticalScrollBarEnabled(false);
this.requestFocusFromTouch();
@@ -186,13 +234,41 @@ 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, deprecated entirely for Jellybean 4.2)
try {
Method gingerbread_getMethod = WebSettings.class.getMethod("setNavDump", new Class[] { boolean.class });
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB)
{
gingerbread_getMethod.invoke(settings, true);
}
} catch (NoSuchMethodException e) {
Log.d(TAG, "We are on a modern version of Android, we will deprecate HTC 2.3 devices in 2.8");
} catch (IllegalArgumentException e) {
Log.d(TAG, "Doing the NavDump failed with bad arguments");
} catch (IllegalAccessException e) {
Log.d(TAG, "This should never happen: IllegalAccessException means this isn't Android anymore");
} catch (InvocationTargetException e) {
Log.d(TAG, "This should never happen: InvocationTargetException means this isn't Android anymore.");
}
//We don't save any form data in the application
settings.setSaveFormData(false);
settings.setSavePassword(false);
// Jellybean rightfully tried to lock this down. Too bad they didn't give us a whitelist
// while we do this
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
Level16Apis.enableUniversalAccess(settings);
// Enable database
settings.setDatabaseEnabled(true);
// We keep this disabled because we use or shim to get around DOM_EXCEPTION_ERROR_16
String databasePath = this.cordova.getActivity().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB)
{
settings.setDatabaseEnabled(true);
settings.setDatabasePath(databasePath);
}
settings.setGeolocationDatabasePath(databasePath);
// Enable DOM storage
settings.setDomStorageEnabled(true);
@@ -200,13 +276,55 @@ public class CordovaWebView extends WebView {
// Enable built-in geolocation
settings.setGeolocationEnabled(true);
//Start up the plugin manager
try {
this.pluginManager = new PluginManager(this, this.cordova);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
// Enable AppCache
// Fix for CB-2282
settings.setAppCacheMaxSize(5 * 1048576);
String pathToCache = this.cordova.getActivity().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
settings.setAppCachePath(pathToCache);
settings.setAppCacheEnabled(true);
// Fix for CB-1405
// Google issue 4641
this.updateUserAgentString();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
if (this.receiver == null) {
this.receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
updateUserAgentString();
}
};
this.cordova.getActivity().registerReceiver(this.receiver, intentFilter);
}
// end CB-1405
pluginManager = new PluginManager(this, this.cordova);
jsMessageQueue = new NativeToJsMessageQueue(this, cordova);
exposedJsApi = new ExposedJsApi(pluginManager, jsMessageQueue);
exposeJsInterface();
}
private void updateUserAgentString() {
this.getSettings().getUserAgentString();
}
private void exposeJsInterface() {
int SDK_INT = Build.VERSION.SDK_INT;
boolean isHoneycomb = (SDK_INT >= Build.VERSION_CODES.HONEYCOMB && SDK_INT <= Build.VERSION_CODES.HONEYCOMB_MR2);
if (isHoneycomb || (SDK_INT < Build.VERSION_CODES.GINGERBREAD)) {
Log.i(TAG, "Disabled addJavascriptInterface() bridge since Android version is old.");
// Bug being that Java Strings do not get converted to JS strings automatically.
// This isn't hard to work-around on the JS side, but it's easier to just
// use the prompt bridge instead.
return;
} else if (SDK_INT < Build.VERSION_CODES.HONEYCOMB && Build.MANUFACTURER.equals("unknown")) {
// addJavascriptInterface crashes on the 2.3 emulator.
Log.i(TAG, "Disabled addJavascriptInterface() bridge callback due to a bug on the 2.3 emulator");
return;
}
this.addJavascriptInterface(exposedJsApi, "_cordovaNative");
}
/**
@@ -229,70 +347,8 @@ public class CordovaWebView extends WebView {
super.setWebChromeClient(client);
}
/**
* Add entry to approved list of URLs (whitelist)
*
* @param origin URL regular expression to allow
* @param subdomains T=include all subdomains under origin
*/
public void addWhiteListEntry(String origin, boolean subdomains) {
try {
// Unlimited access to network resources
if (origin.compareTo("*") == 0) {
LOG.d(TAG, "Unlimited access to network resources");
this.whiteList.add(Pattern.compile(".*"));
} else { // specific access
// check if subdomains should be included
// TODO: we should not add more domains if * has already been added
if (subdomains) {
// XXX making it stupid friendly for people who forget to include protocol/SSL
if (origin.startsWith("http")) {
this.whiteList.add(Pattern.compile(origin.replaceFirst("https?://", "^https?://(.*\\.)?")));
} else {
this.whiteList.add(Pattern.compile("^https?://(.*\\.)?" + origin));
}
LOG.d(TAG, "Origin to allow with subdomains: %s", origin);
} else {
// XXX making it stupid friendly for people who forget to include protocol/SSL
if (origin.startsWith("http")) {
this.whiteList.add(Pattern.compile(origin.replaceFirst("https?://", "^https?://")));
} else {
this.whiteList.add(Pattern.compile("^https?://" + origin));
}
LOG.d(TAG, "Origin to allow: %s", origin);
}
}
} catch (Exception e) {
LOG.d(TAG, "Failed to add origin %s", origin);
}
}
/**
* Determine if URL is in approved list of URLs to load.
*
* @param url
* @return
*/
public boolean isUrlWhiteListed(String url) {
// Check to see if we have matched url previously
if (this.whiteListCache.get(url) != null) {
return true;
}
// Look for match in white list
Iterator<Pattern> pit = this.whiteList.iterator();
while (pit.hasNext()) {
Pattern p = pit.next();
Matcher m = p.matcher(url);
// If match found, then cache it to speed up subsequent comparisons
if (m.find()) {
this.whiteListCache.put(url, true);
return true;
}
}
return false;
public CordovaChromeClient getWebChromeClient() {
return this.chromeClient;
}
/**
@@ -310,7 +366,7 @@ public class CordovaWebView extends WebView {
String initUrl = this.getProperty("url", null);
// If first page of app, then set URL to load to be the one passed in
if (initUrl == null || (this.urls.size() > 0)) {
if (initUrl == null) {
this.loadUrlIntoView(url);
}
// Otherwise use the URL specified in the activity's extras bundle
@@ -331,7 +387,7 @@ public class CordovaWebView extends WebView {
String initUrl = this.getProperty("url", null);
// If first page of app, then set URL to load to be the one passed in
if (initUrl == null || (this.urls.size() > 0)) {
if (initUrl == null) {
this.loadUrlIntoView(url, time);
}
// Otherwise use the URL specified in the activity's extras bundle
@@ -349,21 +405,8 @@ public class CordovaWebView extends WebView {
LOG.d(TAG, ">>> loadUrl(" + url + ")");
this.url = url;
if (this.baseUrl == null) {
int i = url.lastIndexOf('/');
if (i > 0) {
this.baseUrl = url.substring(0, i + 1);
}
else {
this.baseUrl = this.url + "/";
}
this.pluginManager.init();
if (!this.useBrowserHistory) {
this.urls.push(url);
}
}
// Create a timeout timer for loadUrl
final CordovaWebView me = this;
@@ -414,10 +457,14 @@ public class CordovaWebView extends WebView {
*
* @param url
*/
private void loadUrlNow(String url) {
void loadUrlNow(String url) {
if (LOG.isLoggable(LOG.DEBUG) && !url.startsWith("javascript:")) {
LOG.d(TAG, ">>> loadUrlNow()");
}
if (url.startsWith("file://") || url.startsWith("javascript:") || Config.isUrlWhiteListed(url)) {
super.loadUrl(url);
}
}
/**
* Load the url into the webview after waiting for period of time.
@@ -430,7 +477,7 @@ public class CordovaWebView extends WebView {
// If not first page of app, then load immediately
// Add support for browser history if we use it.
if ((url.startsWith("javascript:")) || this.urls.size() > 0 || this.canGoBack()) {
if ((url.startsWith("javascript:")) || this.canGoBack()) {
}
// If first page, then show splashscreen
@@ -453,9 +500,17 @@ public class CordovaWebView extends WebView {
* @param message
*/
public void sendJavascript(String statement) {
if (this.callbackServer != null) {
this.callbackServer.sendJavascript(statement);
this.jsMessageQueue.addJavaScript(statement);
}
/**
* Send a plugin result back to JavaScript.
* (This is a convenience method)
*
* @param message
*/
public void sendPluginResult(PluginResult result, String callbackId) {
this.jsMessageQueue.addPluginResult(result, callbackId);
}
/**
@@ -470,25 +525,6 @@ public class CordovaWebView extends WebView {
}
}
/**
* Returns the top url on the stack without removing it from
* the stack.
*/
public String peekAtUrlStack() {
if (this.urls.size() > 0) {
return this.urls.peek();
}
return "";
}
/**
* Add a url to the stack
*
* @param url
*/
public void pushUrl(String url) {
this.urls.push(url);
}
/**
* Go to previous page in history. (We manage our own history)
@@ -500,36 +536,15 @@ public class CordovaWebView extends WebView {
// Check webview first to see if there is a history
// This is needed to support curPage#diffLink, since they are added to appView's history, but not our history url array (JQMobile behavior)
if (super.canGoBack()) {
printBackForwardList();
super.goBack();
return true;
}
// If our managed history has prev url
if (this.urls.size() > 1) {
this.urls.pop(); // Pop current url
String url = this.urls.pop(); // Pop prev url that we want to load, since it will be added back by loadUrl()
this.loadUrl(url);
return true;
}
return false;
}
/**
* Return true if there is a history item.
*
* @return
*/
public boolean canGoBack() {
if (super.canGoBack()) {
return true;
}
if (this.urls.size() > 1) {
return true;
}
return false;
}
/**
* Load the specified URL in the Cordova webview or a new browser instance.
*
@@ -552,14 +567,8 @@ public class CordovaWebView extends WebView {
if (!openExternal) {
// Make sure url is in whitelist
if (url.startsWith("file://") || url.indexOf(this.baseUrl) == 0 || isUrlWhiteListed(url)) {
if (url.startsWith("file://") || Config.isUrlWhiteListed(url)) {
// TODO: What about params?
// Clear out current url from history, since it will be replacing it
if (clearHistory) {
this.urls.clear();
}
// Load new URL
this.loadUrl(url);
}
@@ -589,63 +598,13 @@ public class CordovaWebView extends WebView {
}
/**
* Load Cordova configuration from res/xml/cordova.xml.
* Check configuration parameters from Config.
* Approved list of URLs that can be loaded into DroidGap
* <access origin="http://server regexp" subdomains="true" />
* Log level: ERROR, WARN, INFO, DEBUG, VERBOSE (default=ERROR)
* <log level="DEBUG" />
*/
private void loadConfiguration() {
int id = getResources().getIdentifier("cordova", "xml", this.cordova.getActivity().getPackageName());
if (id == 0) {
LOG.i("CordovaLog", "cordova.xml missing. Ignoring...");
return;
}
XmlResourceParser xml = getResources().getXml(id);
int eventType = -1;
while (eventType != XmlResourceParser.END_DOCUMENT) {
if (eventType == XmlResourceParser.START_TAG) {
String strNode = xml.getName();
if (strNode.equals("access")) {
String origin = xml.getAttributeValue(null, "origin");
String subdomains = xml.getAttributeValue(null, "subdomains");
if (origin != null) {
this.addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0));
}
}
else if (strNode.equals("log")) {
String level = xml.getAttributeValue(null, "level");
LOG.i("CordovaLog", "Found log level %s", level);
if (level != null) {
LOG.setLogLevel(level);
}
}
else if (strNode.equals("preference")) {
String name = xml.getAttributeValue(null, "name");
String value = xml.getAttributeValue(null, "value");
LOG.i("CordovaLog", "Found preference for %s=%s", name, value);
// Save preferences in Intent
this.cordova.getActivity().getIntent().putExtra(name, value);
}
}
try {
eventType = xml.next();
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
// Init preferences
if ("true".equals(this.getProperty("useBrowserHistory", "true"))) {
this.useBrowserHistory = true;
}
else {
this.useBrowserHistory = false;
}
if ("true".equals(this.getProperty("fullscreen", "false"))) {
this.cordova.getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
@@ -675,14 +634,13 @@ public class CordovaWebView extends WebView {
/*
* onKeyDown
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
if(keyDownCodes.contains(keyCode))
{
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
// only override default behaviour is event bound
// only override default behavior is event bound
LOG.d(TAG, "Down Key Hit");
this.loadUrl("javascript:cordova.fireDocumentEvent('volumedownbutton');");
return true;
@@ -695,20 +653,41 @@ public class CordovaWebView extends WebView {
}
else
{
//Do some other stuff!
return super.onKeyDown(keyCode, event);
}
}
return false;
else if(keyCode == KeyEvent.KEYCODE_BACK)
{
return !(this.startOfHistory()) || this.bound;
}
else if(keyCode == KeyEvent.KEYCODE_MENU)
{
//How did we get here? Is there a childView?
View childView = this.getFocusedChild();
if(childView != null)
{
//Make sure we close the keyboard if it's present
InputMethodManager imm = (InputMethodManager) cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(childView.getWindowToken(), 0);
cordova.getActivity().openOptionsMenu();
}
return true;
}
return super.onKeyDown(keyCode, event);
}
@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) {
// A custom view is currently displayed (e.g. playing a video)
if(mCustomView != null) {
this.hideCustomView();
} else {
// The webview is currently displayed
// If back key is bound, then send event to JavaScript
if (this.bound) {
this.loadUrl("javascript:cordova.fireDocumentEvent('backbutton');");
@@ -722,13 +701,19 @@ public class CordovaWebView extends WebView {
// If not, then invoke default behaviour
else {
//this.activityState = ACTIVITY_EXITING;
return false;
//return false;
// If they hit back button when app is initializing, app should exit instead of hang until initilazation (CB2-458)
this.cordova.getActivity().finish();
}
}
}
}
// Legacy
else if (keyCode == KeyEvent.KEYCODE_MENU) {
if (this.lastMenuEventTime < event.getEventTime()) {
this.loadUrl("javascript:cordova.fireDocumentEvent('menubutton');");
}
this.lastMenuEventTime = event.getEventTime();
return super.onKeyUp(keyCode, event);
}
// If search key
@@ -739,13 +724,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 behavior?
return super.onKeyUp(keyCode, event);
}
public void bindButton(boolean override)
{
this.bound = override;
@@ -771,4 +757,187 @@ public class CordovaWebView extends WebView {
keyUpCodes.add(keyCode);
}
}
public boolean isBackButtonBound()
{
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)
{
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
// Since baseUrl is set in loadUrlIntoView, if user hit Back button before loadUrl was called, we'll get an NPE on baseUrl (CB-2458)
this.loadUrlIntoView("javascript:try{cordova.require('cordova/channel').onDestroy.fire();}catch(e){console.log('exception firing destroy event from native');};");
// Load blank page so that JavaScript onunload is called
this.loadUrl("about:blank");
// Forward to plugins
if (this.pluginManager != null) {
this.pluginManager.onDestroy();
}
// unregister the receiver
if (this.receiver != null) {
try {
this.cordova.getActivity().unregisterReceiver(this.receiver);
} catch (Exception e) {
Log.e(TAG, "Error unregistering configuration receiver: " + e.getMessage(), e);
}
}
}
public void onNewIntent(Intent intent)
{
//Forward to plugins
if (this.pluginManager != null) {
this.pluginManager.onNewIntent(intent);
}
}
public boolean isPaused()
{
return paused;
}
public boolean hadKeyEvent() {
return handleButton;
}
// Wrapping these functions in their own class prevents warnings in adb like:
// VFY: unable to resolve virtual method 285: Landroid/webkit/WebSettings;.setAllowUniversalAccessFromFileURLs
@TargetApi(16)
private static class Level16Apis {
static void enableUniversalAccess(WebSettings settings) {
settings.setAllowUniversalAccessFromFileURLs(true);
}
}
public void printBackForwardList() {
WebBackForwardList currentList = this.copyBackForwardList();
int currentSize = currentList.getSize();
for(int i = 0; i < currentSize; ++i)
{
WebHistoryItem item = currentList.getItemAtIndex(i);
String url = item.getUrl();
LOG.d(TAG, "The URL at index: " + Integer.toString(i) + "is " + url );
}
}
//Can Go Back is BROKEN!
public boolean startOfHistory()
{
WebBackForwardList currentList = this.copyBackForwardList();
WebHistoryItem item = currentList.getItemAtIndex(0);
if( item!=null){ // Null-fence in case they haven't called loadUrl yet (CB-2458)
String url = item.getUrl();
String currentUrl = this.getUrl();
LOG.d(TAG, "The current URL is: " + currentUrl);
LOG.d(TAG, "The URL at item 0 is:" + url);
return currentUrl.equals(url);
}
return false;
}
public void showCustomView(View view, WebChromeClient.CustomViewCallback callback) {
// This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
Log.d(TAG, "showing Custom View");
// if a view already exists then immediately terminate the new one
if (mCustomView != null) {
callback.onCustomViewHidden();
return;
}
// Store the view and its callback for later (to kill it properly)
mCustomView = view;
mCustomViewCallback = callback;
// Add the custom view to its container.
ViewGroup parent = (ViewGroup) this.getParent();
parent.addView(view, COVER_SCREEN_GRAVITY_CENTER);
// Hide the content view.
this.setVisibility(View.GONE);
// Finally show the custom view container.
parent.setVisibility(View.VISIBLE);
parent.bringToFront();
}
public void hideCustomView() {
// This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
Log.d(TAG, "Hidding Custom View");
if (mCustomView == null) return;
// Hide the custom view.
mCustomView.setVisibility(View.GONE);
// Remove the custom view from its container.
ViewGroup parent = (ViewGroup) this.getParent();
parent.removeView(mCustomView);
mCustomView = null;
mCustomViewCallback.onCustomViewHidden();
// Show the content view.
this.setVisibility(View.VISIBLE);
}
/**
* if the video overlay is showing then we need to know
* as it effects back button handling
*
* @return
*/
public boolean isCustomViewShowing() {
return mCustomView != null;
}
public WebBackForwardList restoreState(Bundle savedInstanceState)
{
WebBackForwardList myList = super.restoreState(savedInstanceState);
Log.d(TAG, "WebView restoration crew now restoring!");
//Initialize the plugin manager once more
this.pluginManager.init();
return myList;
}
public void storeResult(int requestCode, int resultCode, Intent intent) {
mResult = new ActivityResult(requestCode, resultCode, intent);
}
}
@@ -18,25 +18,24 @@
*/
package org.apache.cordova;
import java.io.ByteArrayInputStream;
import java.util.Hashtable;
import org.apache.cordova.api.CordovaInterface;
import java.io.IOException;
import java.io.InputStream;
import org.apache.cordova.api.LOG;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.annotation.TargetApi;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.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;
@@ -50,6 +49,7 @@ import android.webkit.WebViewClient;
public class CordovaWebViewClient extends WebViewClient {
private static final String TAG = "Cordova";
private static final String CORDOVA_EXEC_URL_PREFIX = "http://cdv_exec/";
CordovaInterface cordova;
CordovaWebView appView;
private boolean doClearHistory = false;
@@ -86,6 +86,25 @@ 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);
appView.pluginManager.exec(service, action, callbackId, jsonArgs);
}
/**
* Give the host application a chance to take over the control when a new url
* is about to be loaded in the current WebView.
@@ -96,9 +115,13 @@ public class CordovaWebViewClient extends WebViewClient {
*/
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// Check if it's an exec() bridge command message.
if (NativeToJsMessageQueue.ENABLE_LOCATION_CHANGE_EXEC_MODE && url.startsWith(CORDOVA_EXEC_URL_PREFIX)) {
handleExecUrl(url);
}
// First give any plugins the chance to handle the url themselves
if ((this.appView.pluginManager != null) && this.appView.pluginManager.onOverrideUrlLoading(url)) {
// Give plugins the chance to handle the url
else if ((this.appView.pluginManager != null) && this.appView.pluginManager.onOverrideUrlLoading(url)) {
}
// If dialing phone (tel:5551212)
@@ -171,12 +194,8 @@ public class CordovaWebViewClient extends WebViewClient {
// If our app or file:, then load into a new Cordova webview container by starting a new instance of our activity.
// Our app continues to run. When BACK is pressed, our app is redisplayed.
if (url.startsWith("file://") || url.indexOf(this.appView.baseUrl) == 0 || this.appView.isUrlWhiteListed(url)) {
//This will fix iFrames
if (appView.useBrowserHistory)
if (url.startsWith("file://") || url.startsWith("data:") || Config.isUrlWhiteListed(url)) {
return false;
else
this.appView.loadUrl(url);
}
// If not our application, let default viewer handle
@@ -193,6 +212,34 @@ public class CordovaWebViewClient extends WebViewClient {
return true;
}
/**
* Check for intercepting any requests for resources.
* This includes images and scripts and so on, not just top-level pages.
* @param view The WebView.
* @param url The URL to be loaded.
* @return Either null to proceed as normal, or a WebResourceResponse.
*/
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
//If something isn't whitelisted, just send a blank response
if(!Config.isUrlWhiteListed(url) && (url.startsWith("http://") || url.startsWith("https://")))
{
return getWhitelistResponse();
}
if (this.appView.pluginManager != null) {
return this.appView.pluginManager.shouldInterceptRequest(url);
}
return null;
}
private WebResourceResponse getWhitelistResponse()
{
WebResourceResponse emptyResponse;
String empty = "";
ByteArrayInputStream data = new ByteArrayInputStream(empty.getBytes());
return new WebResourceResponse("text/plain", "UTF-8", data);
}
/**
* On received http auth request.
* The method reacts on all registered authentication tokens. There is one and only one authentication token for any host + realm combination
@@ -210,6 +257,10 @@ public class CordovaWebViewClient extends WebViewClient {
if (token != null) {
handler.proceed(token.getUserName(), token.getPassword());
}
else {
// Handle 401 like we'd normally do!
super.onReceivedHttpAuthRequest(view, handler, host, realm);
}
}
/**
@@ -223,24 +274,17 @@ public class CordovaWebViewClient extends WebViewClient {
*/
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
// Clear history so history.back() doesn't do anything.
// So we can reinit() native side CallbackServer & PluginManager.
if (!this.appView.useBrowserHistory) {
view.clearHistory();
this.doClearHistory = true;
}
// Create callback server and plugin manager
if (this.appView.callbackServer == null) {
this.appView.callbackServer = new CallbackServer();
this.appView.callbackServer.init(url);
}
else {
this.appView.callbackServer.reinit(url);
}
// Flush stale messages.
this.appView.jsMessageQueue.reset();
// Broadcast message that page has loaded
this.appView.postMessage("onPageStarted", url);
// Notify all plugins of the navigation, so they can clean up if necessary.
if (this.appView.pluginManager != null) {
this.appView.pluginManager.onReset();
}
}
/**
@@ -274,6 +318,7 @@ public class CordovaWebViewClient extends WebViewClient {
// not loaded yet then just set a flag so that the onNativeReady can be fired
// from the JS side when the JS gets to that code.
if (!url.equals("about:blank")) {
LOG.d(TAG, "Trying to fire onNativeReady");
this.appView.loadUrl("javascript:try{ cordova.require('cordova/channel').onNativeReady.fire();}catch(e){_nativeReady = true;}");
this.appView.postMessage("onNativeReady", null);
}
@@ -301,9 +346,6 @@ public class CordovaWebViewClient extends WebViewClient {
// Shutdown if blank loaded
if (url.equals("about:blank")) {
if (this.appView.callbackServer != null) {
this.appView.callbackServer.destroy();
}
appView.postMessage("exit", null);
}
}
@@ -346,6 +388,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) {
@@ -369,23 +412,6 @@ public class CordovaWebViewClient extends WebViewClient {
}
}
/**
* Notify the host application to update its visited links database.
*
* @param view The WebView that is initiating the callback.
* @param url The url being visited.
* @param isReload True if this url is being reloaded.
*/
@Override
public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
/*
* If you do a document.location.href the url does not get pushed on the stack
* so we do a check here to see if the url should be pushed.
*/
if (!this.appView.peekAtUrlStack().equals(url)) {
this.appView.pushUrl(url);
}
}
/**
* Sets the authentication token.
@@ -459,43 +485,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;
}
}
+15 -33
View File
@@ -20,10 +20,10 @@ package org.apache.cordova;
import java.util.TimeZone;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.LOG;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -35,10 +35,10 @@ import android.content.IntentFilter;
import android.provider.Settings;
import android.telephony.TelephonyManager;
public class Device extends Plugin {
public class Device extends CordovaPlugin {
public static final String TAG = "Device";
public static String cordovaVersion = "1.9.0rc1"; // Cordova version
public static String cordovaVersion = "2.6.0"; // Cordova version
public static String platform = "Android"; // Device OS
public static String uuid; // Device UUID
@@ -55,9 +55,10 @@ public class Device extends Plugin {
* get file paths associated with the Activity.
*
* @param cordova The context of the main Activity.
* @param webView The CordovaWebView Cordova is running in.
*/
public void setContext(CordovaInterface cordova) {
super.setContext(cordova);
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
super.initialize(cordova, webView);
Device.uuid = getUuid();
this.initTelephonyReceiver();
}
@@ -67,14 +68,10 @@ public class Device extends Plugin {
*
* @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.
* @param callbackContext The callback id used when calling back into JavaScript.
* @return True if the action was valid, false if not.
*/
public PluginResult execute(String action, JSONArray args, String callbackId) {
PluginResult.Status status = PluginResult.Status.OK;
String result = "";
try {
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
if (action.equals("getDeviceInfo")) {
JSONObject r = new JSONObject();
r.put("uuid", Device.uuid);
@@ -82,29 +79,14 @@ public class Device extends Plugin {
r.put("platform", Device.platform);
r.put("name", this.getProductName());
r.put("cordova", Device.cordovaVersion);
//JSONObject pg = new JSONObject();
//pg.put("version", Device.CordovaVersion);
//r.put("cordova", pg);
return new PluginResult(status, r);
}
return new PluginResult(status, result);
} 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) {
if (action.equals("getDeviceInfo")) {
return true;
r.put("model", this.getModel());
callbackContext.success(r);
}
else {
return false;
}
return true;
}
/**
* Unregister receiver.
+163 -72
View File
@@ -19,13 +19,16 @@
package org.apache.cordova;
import java.util.HashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.cordova.api.IPlugin;
import org.apache.cordova.api.LOG;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.LOG;
import org.json.JSONException;
import org.json.JSONObject;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -36,8 +39,10 @@ import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Color;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.Display;
import android.view.KeyEvent;
import android.view.Menu;
@@ -46,6 +51,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.webkit.ValueCallback;
import android.webkit.WebViewClient;
import android.widget.LinearLayout;
@@ -140,6 +146,8 @@ public class DroidGap extends Activity implements CordovaInterface {
protected LinearLayout root;
protected boolean cancelLoadUrl = false;
protected ProgressDialog spinnerDialog = null;
private final ExecutorService threadPool = Executors.newCachedThreadPool();
// The initial URL for our app
// ie http://server/path/index.html#abc?query
@@ -150,13 +158,8 @@ public class DroidGap extends Activity implements CordovaInterface {
private static int ACTIVITY_EXITING = 2;
private int activityState = 0; // 0=starting, 1=running (after 1st resume), 2=shutting down
// The base of the initial URL for our app.
// Does not include file name. Ends with /
// ie http://server/path/
String baseUrl = null;
// Plugin to call when activity result is received
protected IPlugin activityResultCallback = null;
protected CordovaPlugin activityResultCallback = null;
protected boolean activityResultKeepRunning;
// Default background color for activity
@@ -170,7 +173,7 @@ public class DroidGap extends Activity implements CordovaInterface {
// Draw a splash screen using an image located in the drawable resource directory.
// This is not the same as calling super.loadSplashscreen(url)
protected int splashscreen = 0;
protected int splashscreenTime = 0;
protected int splashscreenTime = 3000;
// LoadUrl timeout value in msec (default of 20 sec)
protected int loadUrlTimeoutValue = 20000;
@@ -180,6 +183,18 @@ public class DroidGap extends Activity implements CordovaInterface {
// when another application (activity) is started.
protected boolean keepRunning = true;
private int lastRequestCode;
private Object responseCode;
private Intent lastIntent;
private Object lastResponseCode;
private String initCallbackClass;
private Object LOG_TAG;
/**
* Sets the authentication token.
*
@@ -246,11 +261,15 @@ public class DroidGap extends Activity implements CordovaInterface {
@SuppressWarnings("deprecation")
@Override
public void onCreate(Bundle savedInstanceState) {
//preferences = new PreferenceSet();
Config.init(this);
LOG.d(TAG, "DroidGap.onCreate()");
super.onCreate(savedInstanceState);
if(savedInstanceState != null)
{
initCallbackClass = savedInstanceState.getString("callbackClass");
}
if(!this.getBooleanProperty("showTitle", false))
{
getWindow().requestFeature(Window.FEATURE_NO_TITLE);
@@ -295,7 +314,16 @@ public class DroidGap extends Activity implements CordovaInterface {
*/
public void init() {
CordovaWebView webView = new CordovaWebView(DroidGap.this);
this.init(webView, new CordovaWebViewClient(this, webView), new CordovaChromeClient(this, webView));
CordovaWebViewClient webViewClient;
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB)
{
webViewClient = new CordovaWebViewClient(this, webView);
}
else
{
webViewClient = new IceCreamCordovaWebViewClient(this, webView);
}
this.init(webView, webViewClient, new CordovaChromeClient(this, webView));
}
/**
@@ -305,6 +333,7 @@ public class DroidGap extends Activity implements CordovaInterface {
* @param webViewClient
* @param webChromeClient
*/
@SuppressLint("NewApi")
public void init(CordovaWebView webView, CordovaWebViewClient webViewClient, CordovaChromeClient webChromeClient) {
LOG.d(TAG, "DroidGap.init()");
@@ -322,6 +351,12 @@ public class DroidGap extends Activity implements CordovaInterface {
ViewGroup.LayoutParams.MATCH_PARENT,
1.0F));
if (this.getBooleanProperty("disallowOverscroll", false)) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD) {
this.appView.setOverScrollMode(CordovaWebView.OVER_SCROLL_NEVER);
}
}
// Add web view but make it invisible while loading URL
this.appView.setVisibility(View.INVISIBLE);
this.root.addView(this.appView);
@@ -329,6 +364,7 @@ public class DroidGap extends Activity implements CordovaInterface {
// Clear cancel flag
this.cancelLoadUrl = false;
}
/**
@@ -404,6 +440,8 @@ public class DroidGap extends Activity implements CordovaInterface {
}
this.splashscreenTime = time;
this.splashscreen = this.getIntegerProperty("splashscreen", 0);
this.showSplashScreen(this.splashscreenTime);
this.appView.loadUrl(url, time);
}
@@ -560,6 +598,7 @@ public class DroidGap extends Activity implements CordovaInterface {
* @param value
*/
public void setBooleanProperty(String name, boolean value) {
Log.d(TAG, "Setting boolean properties in DroidGap will be deprecated in 3.0 on July 2013, please use config.xml");
this.getIntent().putExtra(name, value);
}
@@ -570,6 +609,7 @@ public class DroidGap extends Activity implements CordovaInterface {
* @param value
*/
public void setIntegerProperty(String name, int value) {
Log.d(TAG, "Setting integer properties in DroidGap will be deprecated in 3.1 on August 2013, please use config.xml");
this.getIntent().putExtra(name, value);
}
@@ -580,6 +620,7 @@ public class DroidGap extends Activity implements CordovaInterface {
* @param value
*/
public void setStringProperty(String name, String value) {
Log.d(TAG, "Setting string properties in DroidGap will be deprecated in 3.0 on July 2013, please use config.xml");
this.getIntent().putExtra(name, value);
}
@@ -590,6 +631,7 @@ public class DroidGap extends Activity implements CordovaInterface {
* @param value
*/
public void setDoubleProperty(String name, double value) {
Log.d(TAG, "Setting double properties in DroidGap will be deprecated in 3.0 on July 2013, please use config.xml");
this.getIntent().putExtra(name, value);
}
@@ -600,6 +642,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 +652,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 +667,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 +679,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 +689,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 +699,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 +710,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 +755,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.addJavaScript(statement);
}
}
@@ -762,10 +781,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;
}
@@ -788,7 +807,7 @@ public class DroidGap extends Activity implements CordovaInterface {
* @param intent The intent to start
* @param requestCode The request code that is passed to callback to identify the activity
*/
public void startActivityForResult(IPlugin command, Intent intent, int requestCode) {
public void startActivityForResult(CordovaPlugin command, Intent intent, int requestCode) {
this.activityResultCallback = command;
this.activityResultKeepRunning = this.keepRunning;
@@ -812,14 +831,40 @@ public class DroidGap extends Activity implements CordovaInterface {
* @param data An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
*/
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
LOG.d(TAG, "Incoming Result");
super.onActivityResult(requestCode, resultCode, intent);
IPlugin callback = this.activityResultCallback;
if (callback != null) {
Log.d(TAG, "Request code = " + requestCode);
ValueCallback<Uri> mUploadMessage = this.appView.getWebChromeClient().getValueCallback();
if (requestCode == CordovaChromeClient.FILECHOOSER_RESULTCODE) {
Log.d(TAG, "did we get here?");
if (null == mUploadMessage)
return;
Uri result = intent == null || resultCode != Activity.RESULT_OK ? null : intent.getData();
Log.d(TAG, "result = " + result);
// Uri filepath = Uri.parse("file://" + FileUtils.getRealPathFromURI(result, this));
// Log.d(TAG, "result = " + filepath);
mUploadMessage.onReceiveValue(result);
mUploadMessage = null;
}
CordovaPlugin callback = this.activityResultCallback;
if(callback == null)
{
if(initCallbackClass != null)
{
this.activityResultCallback = appView.pluginManager.getPlugin(initCallbackClass);
callback = activityResultCallback;
LOG.d(TAG, "We have a callback to send this result to");
callback.onActivityResult(requestCode, resultCode, intent);
}
}
else
{
LOG.d(TAG, "We have a callback to send this result to");
callback.onActivityResult(requestCode, resultCode, intent);
}
}
public void setActivityResultCallback(IPlugin plugin) {
public void setActivityResultCallback(CordovaPlugin plugin) {
this.activityResultCallback = plugin;
}
@@ -834,16 +879,15 @@ 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))) {
if ((errorUrl != null) && (errorUrl.startsWith("file://") || Config.isUrlWhiteListed(errorUrl)) && (!failingUrl.equals(errorUrl))) {
// 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);
}
});
@@ -853,8 +897,7 @@ public class DroidGap extends Activity implements CordovaInterface {
final boolean exit = !(errorCode == WebViewClient.ERROR_HOST_LOOKUP);
me.runOnUiThread(new Runnable() {
public void run() {
if (exit)
{
if (exit) {
me.appView.setVisibility(View.GONE);
me.displayError("Application Error", description + " (" + failingUrl + ")", "OK", exit);
}
@@ -905,11 +948,7 @@ public class DroidGap extends Activity implements CordovaInterface {
* @return
*/
public boolean isUrlWhiteListed(String url) {
// Check to see if we have matched url previously
if (this.appView != null) {
return this.appView.isUrlWhiteListed(url);
}
return false;
return Config.isUrlWhiteListed(url);
}
/*
@@ -925,7 +964,7 @@ public class DroidGap extends Activity implements CordovaInterface {
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
this.postMessage("onPrepareOptionsMenu", menu);
return super.onPrepareOptionsMenu(menu);
return true;
}
@Override
@@ -940,7 +979,8 @@ public class DroidGap extends Activity implements CordovaInterface {
* @return
*/
public Context getContext() {
return this.getContext();
LOG.d(TAG, "This will be deprecated December 2012");
return this;
}
/**
@@ -965,7 +1005,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;
}
@@ -1017,6 +1057,40 @@ public class DroidGap extends Activity implements CordovaInterface {
this.runOnUiThread(runnable);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event)
{
//Get whatever has focus!
View childView = appView.getFocusedChild();
if ((appView.isCustomViewShowing() || childView != null ) &&
(keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU)) {
return appView.onKeyUp(keyCode, event);
} else {
return super.onKeyUp(keyCode, event);
}
}
/*
* Android 2.x needs to be able to check where the cursor is. Android 4.x does not
*
* (non-Javadoc)
* @see android.app.Activity#onKeyDown(int, android.view.KeyEvent)
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
//Get whatever has focus!
View childView = appView.getFocusedChild();
//Determine if the focus is on the current view or not
if (childView != null && (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU)) {
return appView.onKeyDown(keyCode, event);
}
else
return super.onKeyDown(keyCode, event);
}
/**
* Called when a message is sent to plugin.
*
@@ -1031,10 +1105,13 @@ public class DroidGap extends Activity implements CordovaInterface {
this.removeSplashScreen();
}
else {
// If the splash dialog is showing don't try to show it again
if (this.splashDialog == null || !this.splashDialog.isShowing()) {
this.splashscreen = this.getIntegerProperty("splashscreen", 0);
this.showSplashScreen(this.splashscreenTime);
}
}
}
else if ("spinner".equals(id)) {
if ("stop".equals(data.toString())) {
this.spinnerStop();
@@ -1055,4 +1132,18 @@ public class DroidGap extends Activity implements CordovaInterface {
return null;
}
public ExecutorService getThreadPool() {
return threadPool;
}
protected void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
if(this.activityResultCallback != null)
{
String cClass = this.activityResultCallback.getClass().getName();
outState.putString("callbackClass", cClass);
}
}
}
@@ -0,0 +1,48 @@
/*
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.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.json.JSONException;
public class Echo extends CordovaPlugin {
@Override
public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext) throws JSONException {
if ("echo".equals(action)) {
final String result = args.isNull(0) ? null : args.getString(0);
callbackContext.success(result);
return true;
} else if ("echoAsync".equals(action)) {
final String result = args.isNull(0) ? null : args.getString(0);
cordova.getThreadPool().execute(new Runnable() {
public void run() {
callbackContext.success(result);
}
});
return true;
} else if ("echoArrayBuffer".equals(action)) {
final byte[] result = args.getArrayBuffer(0);
callbackContext.success(result);
return true;
}
return false;
}
}
@@ -23,7 +23,7 @@ import java.io.IOException;
import android.media.ExifInterface;
public class ExifHelper {
private String aperature = null;
private String aperture = null;
private String datetime = null;
private String exposureTime = null;
private String flash = null;
@@ -70,7 +70,7 @@ public class ExifHelper {
* Reads all the EXIF data from the input file.
*/
public void readExifData() {
this.aperature = inFile.getAttribute(ExifInterface.TAG_APERTURE);
this.aperture = inFile.getAttribute(ExifInterface.TAG_APERTURE);
this.datetime = inFile.getAttribute(ExifInterface.TAG_DATETIME);
this.exposureTime = inFile.getAttribute(ExifInterface.TAG_EXPOSURE_TIME);
this.flash = inFile.getAttribute(ExifInterface.TAG_FLASH);
@@ -102,8 +102,8 @@ public class ExifHelper {
return;
}
if (this.aperature != null) {
this.outFile.setAttribute(ExifInterface.TAG_APERTURE, this.aperature);
if (this.aperture != null) {
this.outFile.setAttribute(ExifInterface.TAG_APERTURE, this.aperture);
}
if (this.datetime != null) {
this.outFile.setAttribute(ExifInterface.TAG_DATETIME, this.datetime);
@@ -162,4 +162,24 @@ public class ExifHelper {
this.outFile.saveAttributes();
}
public int getOrientation() {
int o = Integer.parseInt(this.orientation);
if (o == ExifInterface.ORIENTATION_NORMAL) {
return 0;
} else if (o == ExifInterface.ORIENTATION_ROTATE_90) {
return 90;
} else if (o == ExifInterface.ORIENTATION_ROTATE_180) {
return 180;
} else if (o == ExifInterface.ORIENTATION_ROTATE_270) {
return 270;
} else {
return 0;
}
}
public void resetOrientation() {
this.orientation = "" + ExifInterface.ORIENTATION_NORMAL;
}
}
+64
View File
@@ -0,0 +1,64 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import android.webkit.JavascriptInterface;
import org.apache.cordova.api.PluginManager;
import org.json.JSONException;
/**
* Contains APIs that the JS can call. All functions in here should also have
* an equivalent entry in CordovaChromeClient.java, and be added to
* cordova-js/lib/android/plugin/android/promptbasednativeapi.js
*/
/* package */ class ExposedJsApi {
private PluginManager pluginManager;
private NativeToJsMessageQueue jsMessageQueue;
public ExposedJsApi(PluginManager pluginManager, NativeToJsMessageQueue jsMessageQueue) {
this.pluginManager = pluginManager;
this.jsMessageQueue = jsMessageQueue;
}
@JavascriptInterface
public String exec(String service, String action, String callbackId, String arguments) throws JSONException {
jsMessageQueue.setPaused(true);
try {
boolean wasSync = pluginManager.exec(service, action, callbackId, arguments);
String ret = "";
if (!NativeToJsMessageQueue.DISABLE_EXEC_CHAINING || wasSync) {
ret = jsMessageQueue.popAndEncode();
}
return ret;
} finally {
jsMessageQueue.setPaused(false);
}
}
@JavascriptInterface
public void setNativeToJsBridgeMode(int value) {
jsMessageQueue.setBridgeMode(value);
}
@JavascriptInterface
public String retrieveJsMessages() {
return jsMessageQueue.popAndEncode();
}
}
@@ -0,0 +1,142 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import android.database.Cursor;
import android.net.Uri;
import android.webkit.MimeTypeMap;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.LOG;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class FileHelper {
private static final String LOG_TAG = "FileUtils";
private static final String _DATA = "_data";
/**
* Returns the real path of the given URI string.
* If the given URI string represents a content:// URI, the real path is retrieved from the media store.
*
* @param uriString the URI string of the audio/image/video
* @param cordova the current application context
* @return the full path to the file
*/
@SuppressWarnings("deprecation")
public static String getRealPath(String uriString, CordovaInterface cordova) {
String realPath = null;
if (uriString.startsWith("content://")) {
String[] proj = { _DATA };
Cursor cursor = cordova.getActivity().managedQuery(Uri.parse(uriString), proj, null, null, null);
int column_index = cursor.getColumnIndexOrThrow(_DATA);
cursor.moveToFirst();
realPath = cursor.getString(column_index);
if (realPath == null) {
LOG.e(LOG_TAG, "Could get real path for URI string %s", uriString);
}
} else if (uriString.startsWith("file://")) {
realPath = uriString.substring(7);
if (realPath.startsWith("/android_asset/")) {
LOG.e(LOG_TAG, "Cannot get real path for URI string %s because it is a file:///android_asset/ URI.", uriString);
realPath = null;
}
} else {
realPath = uriString;
}
return realPath;
}
/**
* Returns the real path of the given URI.
* If the given URI is a content:// URI, the real path is retrieved from the media store.
*
* @param uri the URI of the audio/image/video
* @param cordova the current application context
* @return the full path to the file
*/
public static String getRealPath(Uri uri, CordovaInterface cordova) {
return FileHelper.getRealPath(uri.toString(), cordova);
}
/**
* Returns an input stream based on given URI string.
*
* @param uriString the URI string from which to obtain the input stream
* @param cordova the current application context
* @return an input stream into the data at the given URI or null if given an invalid URI string
* @throws IOException
*/
public static InputStream getInputStreamFromUriString(String uriString, CordovaInterface cordova) throws IOException {
if (uriString.startsWith("content")) {
Uri uri = Uri.parse(uriString);
return cordova.getActivity().getContentResolver().openInputStream(uri);
} else if (uriString.startsWith("file:///android_asset/")) {
String relativePath = uriString.substring(22);
return cordova.getActivity().getAssets().open(relativePath);
} else {
return new FileInputStream(getRealPath(uriString, cordova));
}
}
/**
* Removes the "file://" prefix from the given URI string, if applicable.
* If the given URI string doesn't have a "file://" prefix, it is returned unchanged.
*
* @param uriString the URI string to operate on
* @return a path without the "file://" prefix
*/
public static String stripFileProtocol(String uriString) {
if (uriString.startsWith("file://")) {
uriString = uriString.substring(7);
}
return uriString;
}
/**
* Returns the mime type of the data specified by the given URI string.
*
* @param uriString the URI string of the data
* @return the mime type of the specified data
*/
public static String getMimeType(String uriString, CordovaInterface cordova) {
String mimeType = null;
if (uriString.startsWith("content://")) {
Uri uri = Uri.parse(uriString);
mimeType = cordova.getActivity().getContentResolver().getType(uri);
} else {
// MimeTypeMap.getFileExtensionFromUrl has a bug that occurs when the filename has a space, so we encode it.
// We also convert the URI string to lower case to ensure compatibility with MimeTypeMap (see CB-2185).
String encodedUriString = uriString.replace(" ", "%20").toLowerCase();
String extension = MimeTypeMap.getFileExtensionFromUrl(encodedUriString);
if (extension.equals("3ga")) {
mimeType = "audio/3gpp";
} else {
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
}
}
return mimeType;
}
}
@@ -0,0 +1,63 @@
/*
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.json.JSONException;
import org.json.JSONObject;
/**
* Encapsulates in-progress status of uploading or downloading a file to a remote server.
*/
public class FileProgressResult {
private boolean lengthComputable = false; // declares whether total is known
private long loaded = 0; // bytes sent so far
private long total = 0; // bytes total, if known
public boolean getLengthComputable() {
return lengthComputable;
}
public void setLengthComputable(boolean computable) {
this.lengthComputable = computable;
}
public long getLoaded() {
return loaded;
}
public void setLoaded(long bytes) {
this.loaded = bytes;
}
public long getTotal() {
return total;
}
public void setTotal(long bytes) {
this.total = bytes;
}
public JSONObject toJSONObject() throws JSONException {
return new JSONObject(
"{loaded:" + loaded +
",total:" + total +
",lengthComputable:" + (lengthComputable ? "true" : "false") + "}");
}
}
+519 -207
View File
@@ -18,19 +18,28 @@
*/
package org.apache.cordova;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Iterator;
import javax.net.ssl.HostnameVerifier;
@@ -41,51 +50,130 @@ import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import android.webkit.CookieManager;
public class FileTransfer extends Plugin {
public class FileTransfer extends CordovaPlugin {
private static final String LOG_TAG = "FileTransfer";
private static final String LINE_START = "--";
private static final String LINE_END = "\r\n";
private static final String BOUNDARY = "*****";
private static final String BOUNDARY = "+++++";
public static int FILE_NOT_FOUND_ERR = 1;
public static int INVALID_URL_ERR = 2;
public static int CONNECTION_ERR = 3;
public static int ABORTED_ERR = 4;
private SSLSocketFactory defaultSSLSocketFactory = null;
private HostnameVerifier defaultHostnameVerifier = null;
private static HashMap<String, RequestContext> activeRequests = new HashMap<String, RequestContext>();
private static final int MAX_BUFFER_SIZE = 16 * 1024;
/* (non-Javadoc)
* @see org.apache.cordova.api.Plugin#execute(java.lang.String, org.json.JSONArray, java.lang.String)
*/
@Override
public PluginResult execute(String action, JSONArray args, String callbackId) {
String source = null;
String target = null;
try {
source = args.getString(0);
target = args.getString(1);
} catch (JSONException e) {
Log.d(LOG_TAG, "Missing source or target");
return new PluginResult(PluginResult.Status.JSON_EXCEPTION, "Missing source or target");
private static final class RequestContext {
String source;
String target;
File targetFile;
CallbackContext callbackContext;
InputStream currentInputStream;
OutputStream currentOutputStream;
boolean aborted;
RequestContext(String source, String target, CallbackContext callbackContext) {
this.source = source;
this.target = target;
this.callbackContext = callbackContext;
}
void sendPluginResult(PluginResult pluginResult) {
synchronized (this) {
if (!aborted) {
callbackContext.sendPluginResult(pluginResult);
}
}
}
}
/**
* Works around a bug on Android 2.3.
* http://code.google.com/p/android/issues/detail?id=14562
*/
private static final class DoneHandlerInputStream extends FilterInputStream {
private boolean done;
public DoneHandlerInputStream(InputStream stream) {
super(stream);
}
@Override
public int read() throws IOException {
int result = done ? -1 : super.read();
done = (result == -1);
return result;
}
@Override
public int read(byte[] buffer) throws IOException {
int result = done ? -1 : super.read(buffer);
done = (result == -1);
return result;
}
@Override
public int read(byte[] bytes, int offset, int count) throws IOException {
int result = done ? -1 : super.read(bytes, offset, count);
done = (result == -1);
return result;
}
}
@Override
public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException {
if (action.equals("upload") || action.equals("download")) {
String source = args.getString(0);
String target = args.getString(1);
if (action.equals("upload")) {
return upload(source, target, args);
} else if (action.equals("download")) {
return download(source, target);
try {
source = URLDecoder.decode(source, "UTF-8");
upload(source, target, args, callbackContext);
} catch (UnsupportedEncodingException e) {
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.MALFORMED_URL_EXCEPTION, "UTF-8 error."));
}
} else {
return new PluginResult(PluginResult.Status.INVALID_ACTION);
download(source, target, args, callbackContext);
}
return true;
} else if (action.equals("abort")) {
String objectId = args.getString(0);
abort(objectId);
callbackContext.success();
return true;
}
return false;
}
private static void addHeadersToRequest(URLConnection connection, JSONObject headers) {
try {
for (Iterator<?> iter = headers.keys(); iter.hasNext(); ) {
String headerKey = iter.next().toString();
JSONArray headerValues = headers.optJSONArray(headerKey);
if (headerValues == null) {
headerValues = new JSONArray();
headerValues.put(headers.getString(headerKey));
}
connection.setRequestProperty(headerKey, headerValues.getString(0));
for (int i = 1; i < headerValues.length(); ++i) {
connection.addRequestProperty(headerKey, headerValues.getString(i));
}
}
} catch (JSONException e1) {
// No headers to be manipulated!
}
}
@@ -94,6 +182,7 @@ public class FileTransfer extends Plugin {
* @param source Full path of the file on the file system
* @param target URL of the server to receive the file
* @param args JSON Array of args
* @param callbackContext callback id for optional progress reports
*
* args[2] fileKey Name of file request parameter
* args[3] fileName File name to be used on server
@@ -101,19 +190,20 @@ public class FileTransfer extends Plugin {
* args[5] params key:value pairs of user-defined parameters
* @return FileUploadResult containing result of upload request
*/
private PluginResult upload(String source, String target, JSONArray args) {
private void upload(final String source, final String target, JSONArray args, CallbackContext callbackContext) throws JSONException {
Log.d(LOG_TAG, "upload " + source + " to " + target);
HttpURLConnection conn = null;
try {
// Setup the options
String fileKey = getArgument(args, 2, "file");
String fileName = getArgument(args, 3, "image.jpg");
String mimeType = getArgument(args, 4, "image/jpeg");
JSONObject params = args.optJSONObject(5);
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
final String fileKey = getArgument(args, 2, "file");
final String fileName = getArgument(args, 3, "image.jpg");
final String mimeType = getArgument(args, 4, "image/jpeg");
final JSONObject params = args.optJSONObject(5) == null ? new JSONObject() : args.optJSONObject(5);
final boolean trustEveryone = args.optBoolean(6);
// Always use chunked mode unless set to false as per API
final boolean chunkedMode = args.optBoolean(7) || args.isNull(7);
// Look for headers on the params map for backwards compatibility with older Cordova versions.
final JSONObject headers = args.optJSONObject(8) == null ? params.optJSONObject("headers") : args.optJSONObject(8);
final String objectId = args.getString(9);
Log.d(LOG_TAG, "fileKey: " + fileKey);
Log.d(LOG_TAG, "fileName: " + fileName);
@@ -121,26 +211,43 @@ 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);
Log.d(LOG_TAG, "objectId: " + objectId);
final URL url;
try {
url = new URL(target);
} catch (MalformedURLException e) {
JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target, null, 0);
Log.e(LOG_TAG, error.toString(), e);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error));
return;
}
final boolean useHttps = url.getProtocol().equals("https");
final RequestContext context = new RequestContext(source, target, callbackContext);
synchronized (activeRequests) {
activeRequests.put(objectId, context);
}
cordova.getThreadPool().execute(new Runnable() {
public void run() {
if (context.aborted) {
return;
}
HttpURLConnection conn = null;
HostnameVerifier oldHostnameVerifier = null;
SSLSocketFactory oldSocketFactory = null;
int totalBytes = 0;
int fixedLength = -1;
try {
// Create return object
FileUploadResult result = new FileUploadResult();
// Get a input stream of the file on the phone
FileInputStream fileInputStream = (FileInputStream) getPathFromUri(source);
DataOutputStream dos = null;
int bytesRead, bytesAvailable, bufferSize;
long totalBytes;
byte[] buffer;
int maxBufferSize = 8096;
FileProgressResult progress = new FileProgressResult();
//------------------ CLIENT REQUEST
// open a URL connection to the server
URL url = new URL(target);
// Open a HTTP connection to the URL based on protocol
if (url.getProtocol().toLowerCase().equals("https")) {
if (useHttps) {
// Using standard HTTPS connection. Will not allow self signed certificate
if (!trustEveryone) {
conn = (HttpsURLConnection) url.openConnection();
@@ -149,10 +256,10 @@ public class FileTransfer extends Plugin {
// This should only be used in debug environments
else {
// Setup the HTTPS connection class to trust everyone
trustAllHosts();
HttpsURLConnection https = (HttpsURLConnection) url.openConnection();
oldSocketFactory = trustAllHosts(https);
// Save the current hostnameVerifier
defaultHostnameVerifier = https.getHostnameVerifier();
oldHostnameVerifier = https.getHostnameVerifier();
// Setup the connection not to verify hostnames
https.setHostnameVerifier(DO_NOT_VERIFY);
conn = https;
@@ -174,180 +281,223 @@ public class FileTransfer extends Plugin {
// Use a post method.
conn.setRequestMethod("POST");
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) {
addHeadersToRequest(conn, headers);
}
/*
* Store the non-file portions of the multipart data as a string, so that we can add it
* to the contentSize, since it is part of the body of the HTTP request.
*/
String extraParams = "";
StringBuilder beforeData = new StringBuilder();
try {
for (Iterator iter = params.keys(); iter.hasNext();) {
for (Iterator<?> iter = params.keys(); iter.hasNext();) {
Object key = iter.next();
if(!String.valueOf(key).equals("headers"))
{
extraParams += LINE_START + BOUNDARY + LINE_END;
extraParams += "Content-Disposition: form-data; name=\"" + key.toString() + "\";";
extraParams += LINE_END + LINE_END;
extraParams += params.getString(key.toString());
extraParams += LINE_END;
beforeData.append(LINE_START).append(BOUNDARY).append(LINE_END);
beforeData.append("Content-Disposition: form-data; name=\"").append(key.toString()).append('"');
beforeData.append(LINE_END).append(LINE_END);
beforeData.append(params.getString(key.toString()));
beforeData.append(LINE_END);
}
}
} catch (JSONException e) {
Log.e(LOG_TAG, e.getMessage(), e);
}
extraParams += LINE_START + BOUNDARY + LINE_END;
extraParams += "Content-Disposition: form-data; name=\"" + fileKey + "\";" + " filename=\"";
byte[] extraBytes = extraParams.getBytes("UTF-8");
beforeData.append(LINE_START).append(BOUNDARY).append(LINE_END);
beforeData.append("Content-Disposition: form-data; name=\"").append(fileKey).append("\";");
beforeData.append(" filename=\"").append(fileName).append('"').append(LINE_END);
beforeData.append("Content-Type: ").append(mimeType).append(LINE_END).append(LINE_END);
byte[] beforeDataBytes = beforeData.toString().getBytes("UTF-8");
byte[] tailParamsBytes = (LINE_END + LINE_START + BOUNDARY + LINE_START + LINE_END).getBytes("UTF-8");
String midParams = "\"" + LINE_END + "Content-Type: " + mimeType + LINE_END + LINE_END;
String tailParams = LINE_END + LINE_START + BOUNDARY + LINE_START + LINE_END;
byte[] fileNameBytes = fileName.getBytes("UTF-8");
// Should set this up as an option
if (chunkedMode) {
conn.setChunkedStreamingMode(maxBufferSize);
// Get a input stream of the file on the phone
InputStream sourceInputStream = getPathFromUri(source);
int stringLength = beforeDataBytes.length + tailParamsBytes.length;
if (sourceInputStream instanceof FileInputStream) {
fixedLength = (int) ((FileInputStream)sourceInputStream).getChannel().size() + stringLength;
progress.setLengthComputable(true);
progress.setTotal(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);
// setFixedLengthStreamingMode causes and OutOfMemoryException on pre-Froyo devices.
// http://code.google.com/p/android/issues/detail?id=3164
// It also causes OOM if HTTPS is used, even on newer devices.
boolean useChunkedMode = chunkedMode && (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO || useHttps);
useChunkedMode = useChunkedMode || (fixedLength == -1);
if (useChunkedMode) {
conn.setChunkedStreamingMode(MAX_BUFFER_SIZE);
// Although setChunkedStreamingMode sets this header, setting it explicitly here works
// around an OutOfMemoryException when using https.
conn.setRequestProperty("Transfer-Encoding", "chunked");
} else {
conn.setFixedLengthStreamingMode(fixedLength);
}
conn.connect();
dos = new DataOutputStream( conn.getOutputStream() );
OutputStream sendStream = null;
try {
sendStream = conn.getOutputStream();
synchronized (context) {
if (context.aborted) {
return;
}
context.currentOutputStream = sendStream;
}
//We don't want to change encoding, we just want this to write for all Unicode.
dos.write(extraBytes);
dos.write(fileNameBytes);
dos.writeBytes(midParams);
sendStream.write(beforeDataBytes);
totalBytes += beforeDataBytes.length;
// create a buffer of maximum size
bytesAvailable = fileInputStream.available();
bufferSize = Math.min(bytesAvailable, maxBufferSize);
buffer = new byte[bufferSize];
int bytesAvailable = sourceInputStream.available();
int bufferSize = Math.min(bytesAvailable, MAX_BUFFER_SIZE);
byte[] buffer = new byte[bufferSize];
// read file and write it into form...
bytesRead = fileInputStream.read(buffer, 0, bufferSize);
totalBytes = 0;
int bytesRead = sourceInputStream.read(buffer, 0, bufferSize);
long prevBytesRead = 0;
while (bytesRead > 0) {
totalBytes += bytesRead;
result.setBytesSent(totalBytes);
dos.write(buffer, 0, bufferSize);
bytesAvailable = fileInputStream.available();
bufferSize = Math.min(bytesAvailable, maxBufferSize);
bytesRead = fileInputStream.read(buffer, 0, bufferSize);
sendStream.write(buffer, 0, bytesRead);
totalBytes += bytesRead;
if (totalBytes > prevBytesRead + 102400) {
prevBytesRead = totalBytes;
Log.d(LOG_TAG, "Uploaded " + totalBytes + " of " + fixedLength + " bytes");
}
bytesAvailable = sourceInputStream.available();
bufferSize = Math.min(bytesAvailable, MAX_BUFFER_SIZE);
bytesRead = sourceInputStream.read(buffer, 0, bufferSize);
// Send a progress event.
progress.setLoaded(totalBytes);
PluginResult progressResult = new PluginResult(PluginResult.Status.OK, progress.toJSONObject());
progressResult.setKeepCallback(true);
context.sendPluginResult(progressResult);
}
// send multipart form data necesssary after file data...
dos.writeBytes(tailParams);
// close streams
fileInputStream.close();
dos.flush();
dos.close();
// send multipart form data necessary after file data...
sendStream.write(tailParamsBytes);
totalBytes += tailParamsBytes.length;
sendStream.flush();
} finally {
safeClose(sourceInputStream);
safeClose(sendStream);
}
context.currentOutputStream = null;
Log.d(LOG_TAG, "Sent " + totalBytes + " of " + fixedLength);
//------------------ read the SERVER RESPONSE
StringBuffer responseString = new StringBuffer("");
DataInputStream inStream;
String responseString;
int responseCode = conn.getResponseCode();
Log.d(LOG_TAG, "response code: " + responseCode);
Log.d(LOG_TAG, "response headers: " + conn.getHeaderFields());
InputStream inStream = null;
try {
inStream = new DataInputStream ( conn.getInputStream() );
} catch(FileNotFoundException e) {
Log.e(LOG_TAG, e.toString(), e);
throw new IOException("Received error from server");
inStream = getInputStream(conn);
synchronized (context) {
if (context.aborted) {
return;
}
context.currentInputStream = inStream;
}
String line;
while (( line = inStream.readLine()) != null) {
responseString.append(line);
ByteArrayOutputStream out = new ByteArrayOutputStream(Math.max(1024, conn.getContentLength()));
byte[] buffer = new byte[1024];
int bytesRead = 0;
// write bytes to file
while ((bytesRead = inStream.read(buffer)) > 0) {
out.write(buffer, 0, bytesRead);
}
responseString = out.toString("UTF-8");
} finally {
context.currentInputStream = null;
safeClose(inStream);
}
Log.d(LOG_TAG, "got response from server");
Log.d(LOG_TAG, responseString.toString());
Log.d(LOG_TAG, responseString.substring(0, Math.min(256, responseString.length())));
// send request and retrieve response
result.setResponseCode(conn.getResponseCode());
result.setResponse(responseString.toString());
inStream.close();
// Revert back to the proper verifier and socket factories
if (trustEveryone && url.getProtocol().toLowerCase().equals("https")) {
((HttpsURLConnection) conn).setHostnameVerifier(defaultHostnameVerifier);
HttpsURLConnection.setDefaultSSLSocketFactory(defaultSSLSocketFactory);
}
Log.d(LOG_TAG, "****** About to return a result from upload");
return new PluginResult(PluginResult.Status.OK, result.toJSONObject());
result.setResponseCode(responseCode);
result.setResponse(responseString);
context.sendPluginResult(new PluginResult(PluginResult.Status.OK, result.toJSONObject()));
} catch (FileNotFoundException e) {
JSONObject error = createFileTransferError(FILE_NOT_FOUND_ERR, source, target, conn);
Log.e(LOG_TAG, error.toString(), e);
return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
} catch (MalformedURLException e) {
JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target, conn);
Log.e(LOG_TAG, error.toString(), e);
return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
context.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error));
} catch (IOException e) {
JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, conn);
Log.e(LOG_TAG, error.toString(), e);
return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
Log.e(LOG_TAG, "Failed after uploading " + totalBytes + " of " + fixedLength + " bytes.");
context.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error));
} catch (JSONException e) {
Log.e(LOG_TAG, e.getMessage(), e);
return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
context.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
} catch (Throwable t) {
// Shouldn't happen, but will
JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, conn);
Log.wtf(LOG_TAG, error.toString(), t);
return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
Log.e(LOG_TAG, error.toString(), t);
context.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error));
} finally {
synchronized (activeRequests) {
activeRequests.remove(objectId);
}
if (conn != null) {
conn.disconnect();
// Revert back to the proper verifier and socket factories
// Revert back to the proper verifier and socket factories
if (trustEveryone && useHttps) {
HttpsURLConnection https = (HttpsURLConnection) conn;
https.setHostnameVerifier(oldHostnameVerifier);
https.setSSLSocketFactory(oldSocketFactory);
}
}
}
}
});
}
private static void safeClose(Closeable stream) {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
}
}
}
private static InputStream getInputStream(URLConnection conn) throws IOException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
return new DoneHandlerInputStream(conn.getInputStream());
}
return conn.getInputStream();
}
// always verify the host - don't check for certificate
final static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
private static final HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
/**
* This function will install a trust manager that will blindly trust all SSL
* certificates. The reason this code is being added is to enable developers
* to do development using self signed SSL certificates on their web server.
*
* The standard HttpsURLConnection class will throw an exception on self
* signed certificates if this code is not run.
*/
private void trustAllHosts() {
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
private static final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[] {};
}
@@ -361,32 +511,59 @@ public class FileTransfer extends Plugin {
}
} };
/**
* This function will install a trust manager that will blindly trust all SSL
* certificates. The reason this code is being added is to enable developers
* to do development using self signed SSL certificates on their web server.
*
* The standard HttpsURLConnection class will throw an exception on self
* signed certificates if this code is not run.
*/
private static SSLSocketFactory trustAllHosts(HttpsURLConnection connection) {
// Install the all-trusting trust manager
SSLSocketFactory oldFactory = connection.getSSLSocketFactory();
try {
// Backup the current SSL socket factory
defaultSSLSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
// Install our all trusting manager
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
SSLSocketFactory newFactory = sc.getSocketFactory();
connection.setSSLSocketFactory(newFactory);
} catch (Exception e) {
Log.e(LOG_TAG, e.getMessage(), e);
}
return oldFactory;
}
private JSONObject createFileTransferError(int errorCode, String source, String target, HttpURLConnection connection) {
Integer httpStatus = null;
private static JSONObject createFileTransferError(int errorCode, String source, String target, URLConnection connection) {
int httpStatus = 0;
StringBuilder bodyBuilder = new StringBuilder();
String body = null;
if (connection != null) {
try {
httpStatus = connection.getResponseCode();
if (connection instanceof HttpURLConnection) {
httpStatus = ((HttpURLConnection)connection).getResponseCode();
InputStream err = ((HttpURLConnection) connection).getErrorStream();
if(err != null)
{
BufferedReader reader = new BufferedReader(new InputStreamReader(err, "UTF-8"));
String line = reader.readLine();
while(line != null)
{
bodyBuilder.append(line);
line = reader.readLine();
if(line != null)
bodyBuilder.append('\n');
}
body = bodyBuilder.toString();
}
}
} catch (IOException e) {
Log.w(LOG_TAG, "Error getting HTTP status code from connection.", e);
}
}
return createFileTransferError(errorCode, source, target, httpStatus);
return createFileTransferError(errorCode, source, target, body, httpStatus);
}
/**
@@ -394,13 +571,17 @@ public class FileTransfer extends Plugin {
* @param errorCode the error
* @return JSONObject containing the error
*/
private JSONObject createFileTransferError(int errorCode, String source, String target, Integer httpStatus) {
private static JSONObject createFileTransferError(int errorCode, String source, String target, String body, Integer httpStatus) {
JSONObject error = null;
try {
error = new JSONObject();
error.put("code", errorCode);
error.put("source", source);
error.put("target", target);
if(body != null)
{
error.put("body", body);
}
if (httpStatus != null) {
error.put("http_status", httpStatus);
}
@@ -417,7 +598,7 @@ public class FileTransfer extends Plugin {
* @param defaultString the default to be used if the arg does not exist
* @return String with the retrieved value
*/
private String getArgument(JSONArray args, int position, String defaultString) {
private static String getArgument(JSONArray args, int position, String defaultString) {
String arg = defaultString;
if (args.length() >= position) {
arg = args.optString(position);
@@ -433,24 +614,83 @@ public class FileTransfer extends Plugin {
*
* @param source URL of the server to receive the file
* @param target Full path of the file on the file system
* @return JSONObject the downloaded file
*/
private PluginResult download(String source, String target) {
private void download(final String source, final String target, JSONArray args, CallbackContext callbackContext) throws JSONException {
Log.d(LOG_TAG, "download " + source + " to " + target);
HttpURLConnection connection = null;
try {
File file = getFileFromPath(target);
final boolean trustEveryone = args.optBoolean(2);
final String objectId = args.getString(3);
final JSONObject headers = args.optJSONObject(4);
final URL url;
try {
url = new URL(source);
} catch (MalformedURLException e) {
JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target, null, 0);
Log.e(LOG_TAG, error.toString(), e);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error));
return;
}
final boolean useHttps = url.getProtocol().equals("https");
if (!Config.isUrlWhiteListed(source)) {
Log.w(LOG_TAG, "Source URL is not in white list: '" + source + "'");
JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, null, 401);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error));
return;
}
final RequestContext context = new RequestContext(source, target, callbackContext);
synchronized (activeRequests) {
activeRequests.put(objectId, context);
}
cordova.getThreadPool().execute(new Runnable() {
public void run() {
if (context.aborted) {
return;
}
URLConnection connection = null;
HostnameVerifier oldHostnameVerifier = null;
SSLSocketFactory oldSocketFactory = null;
File file = null;
PluginResult result = null;
try {
file = getFileFromPath(target);
context.targetFile = file;
// create needed directories
file.getParentFile().mkdirs();
// connect to server
if (webView.isUrlWhiteListed(source))
{
URL url = new URL(source);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
// Open a HTTP connection to the URL based on protocol
if (useHttps) {
// Using standard HTTPS connection. Will not allow self signed certificate
if (!trustEveryone) {
connection = (HttpsURLConnection) url.openConnection();
}
// Use our HTTPS connection that blindly trusts everyone.
// This should only be used in debug environments
else {
// Setup the HTTPS connection class to trust everyone
HttpsURLConnection https = (HttpsURLConnection) url.openConnection();
oldSocketFactory = trustAllHosts(https);
// Save the current hostnameVerifier
oldHostnameVerifier = https.getHostnameVerifier();
// Setup the connection not to verify hostnames
https.setHostnameVerifier(DO_NOT_VERIFY);
connection = https;
}
}
// Return a standard HTTP connection
else {
connection = url.openConnection();
}
if (connection instanceof HttpURLConnection) {
((HttpURLConnection)connection).setRequestMethod("GET");
}
//Add cookie support
String cookie = CookieManager.getInstance().getCookie(source);
@@ -459,59 +699,100 @@ public class FileTransfer extends Plugin {
connection.setRequestProperty("cookie", cookie);
}
connection.connect();
Log.d(LOG_TAG, "Download file: " + url);
connection.connect();
Log.d(LOG_TAG, "Download file:" + url);
InputStream inputStream = connection.getInputStream();
byte[] buffer = new byte[1024];
int bytesRead = 0;
FileOutputStream outputStream = new FileOutputStream(file);
// write bytes to file
while ((bytesRead = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, bytesRead);
// Handle the other headers
if (headers != null) {
addHeadersToRequest(connection, headers);
}
outputStream.close();
connection.connect();
Log.d(LOG_TAG, "Download file:" + url);
FileProgressResult progress = new FileProgressResult();
if (connection.getContentEncoding() == null) {
// Only trust content-length header if no gzip etc
progress.setLengthComputable(true);
progress.setTotal(connection.getContentLength());
}
FileOutputStream outputStream = null;
InputStream inputStream = null;
try {
inputStream = getInputStream(connection);
outputStream = new FileOutputStream(file);
synchronized (context) {
if (context.aborted) {
return;
}
context.currentInputStream = inputStream;
}
// write bytes to file
byte[] buffer = new byte[MAX_BUFFER_SIZE];
int bytesRead = 0;
long totalBytes = 0;
while ((bytesRead = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, bytesRead);
totalBytes += bytesRead;
// Send a progress event.
progress.setLoaded(totalBytes);
PluginResult progressResult = new PluginResult(PluginResult.Status.OK, progress.toJSONObject());
progressResult.setKeepCallback(true);
context.sendPluginResult(progressResult);
}
} finally {
context.currentInputStream = null;
safeClose(inputStream);
safeClose(outputStream);
}
Log.d(LOG_TAG, "Saved file: " + target);
// create FileEntry object
FileUtils fileUtil = new FileUtils();
JSONObject fileEntry = fileUtil.getEntry(file);
return new PluginResult(PluginResult.Status.OK, fileEntry);
}
else
{
Log.w(LOG_TAG, "Source URL is not in white list: '" + source + "'");
JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, 401);
return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
}
JSONObject fileEntry = FileUtils.getEntry(file);
result = new PluginResult(PluginResult.Status.OK, fileEntry);
} catch (FileNotFoundException e) {
JSONObject error = createFileTransferError(FILE_NOT_FOUND_ERR, source, target, connection);
Log.e(LOG_TAG, error.toString(), e);
return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
} catch (MalformedURLException e) {
JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target, connection);
Log.e(LOG_TAG, error.toString(), e);
return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
} catch (Exception e) { // IOException, JSONException, NullPointer
result = new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
} catch (IOException e) {
JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, connection);
Log.e(LOG_TAG, error.toString(), e);
return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
result = new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
} catch (JSONException e) {
Log.e(LOG_TAG, e.getMessage(), e);
result = new PluginResult(PluginResult.Status.JSON_EXCEPTION);
} catch (Throwable e) {
JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, connection);
Log.e(LOG_TAG, error.toString(), e);
result = new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
} finally {
synchronized (activeRequests) {
activeRequests.remove(objectId);
}
if (connection != null) {
connection.disconnect();
// Revert back to the proper verifier and socket factories
if (trustEveryone && useHttps) {
HttpsURLConnection https = (HttpsURLConnection) connection;
https.setHostnameVerifier(oldHostnameVerifier);
https.setSSLSocketFactory(oldSocketFactory);
}
}
if (result == null) {
result = new PluginResult(PluginResult.Status.ERROR, createFileTransferError(CONNECTION_ERR, source, target, connection));
}
// Remove incomplete download.
if (result.getStatus() != PluginResult.Status.OK.ordinal() && file != null) {
file.delete();
}
context.sendPluginResult(result);
}
}
});
}
/**
@@ -561,4 +842,35 @@ public class FileTransfer extends Plugin {
return file;
}
/**
* Abort an ongoing upload or download.
*/
private void abort(String objectId) {
final RequestContext context;
synchronized (activeRequests) {
context = activeRequests.remove(objectId);
}
if (context != null) {
File file = context.targetFile;
if (file != null) {
file.delete();
}
// Trigger the abort callback immediately to minimize latency between it and abort() being called.
JSONObject error = createFileTransferError(ABORTED_ERR, context.source, context.target, null, -1);
synchronized (context) {
context.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, error));
context.aborted = true;
}
// Closing the streams can block, so execute on a background thread.
cordova.getThreadPool().execute(new Runnable() {
public void run() {
synchronized (context) {
safeClose(context.currentInputStream);
safeClose(context.currentOutputStream);
}
}
});
}
}
}
@@ -29,6 +29,7 @@ public class FileUploadResult {
private long bytesSent = 0; // bytes sent
private int responseCode = -1; // HTTP response code
private String response = null; // HTTP response
private String objectId = null; // FileTransfer object id
public long getBytesSent() {
return bytesSent;
@@ -54,10 +55,19 @@ public class FileUploadResult {
this.response = response;
}
public String getObjectId() {
return objectId;
}
public void setObjectId(String objectId) {
this.objectId = objectId;
}
public JSONObject toJSONObject() throws JSONException {
return new JSONObject(
"{bytesSent:" + bytesSent +
",responseCode:" + responseCode +
",response:" + JSONObject.quote(response) + "}");
",response:" + JSONObject.quote(response) +
",objectId:" + JSONObject.quote(objectId) + "}");
}
}

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