Compare commits

..

154 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
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
5ca233779d This is as far as we can get fixing the Camera plugin by recovering state 2012-11-08 15:42:28 -08:00
58 changed files with 4016 additions and 2084 deletions

3
.gitignore vendored
View File

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

View File

@@ -1,16 +1,12 @@
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
---
@@ -27,7 +23,7 @@ Test Requirements
Building
---
To create your cordova.jar, copy the commons codec:
To create your `cordova.jar` file, copy the commons codec:
mv commons-codec-1.7.jar framework/libs

View File

@@ -1 +1 @@
2.3.0rc2
2.6.0

View File

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

View File

@@ -33,12 +33,22 @@ 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) {

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

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" android:hardwareAccelerated="true">
package="__PACKAGE__" android:versionName="1.0" android:versionCode="1" android:hardwareAccelerated="true">
<supports-screens
android:largeScreens="true"
android:normalScreens="true"

View File

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

139
bin/update Executable file
View File

@@ -0,0 +1,139 @@
#! /bin/bash
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
# update a cordova/android project's command line tools
#
# USAGE
# ./update [path]
#
set -e
if [ -z "$1" ] || [ "$1" == "-h" ]
then
echo 'usage: update path'
echo "Make sure the Android SDK tools folder is in your PATH!"
exit 0
fi
BUILD_PATH="$( cd "$( dirname "$0" )/.." && pwd )"
VERSION=$(cat "$BUILD_PATH"/VERSION)
PROJECT_PATH="${1:-'./example'}"
if [ ! -d "$PROJECT_PATH" ]
then
echo "The project path has to exist for it to be updated"
exit 0
fi
# cleanup after exit and/or on error
function on_exit {
if [ -f "$BUILD_PATH"/framework/assets/www/cordova-$VERSION.js ]
then
rm "$BUILD_PATH"/framework/assets/www/cordova-$VERSION.js
fi
if [ -f "$BUILD_PATH"/framework/cordova-$VERSION.jar ]
then
rm "$BUILD_PATH"/framework/cordova-$VERSION.jar
fi
}
function createAppInfoJar {
(cd "$BUILD_PATH"/bin/templates/cordova/ApplicationInfo &&
javac ApplicationInfo.java &&
jar -cfe ../appinfo.jar ApplicationInfo ApplicationInfo.class
)
}
function on_error {
echo "An unexpected error occurred: $previous_command exited with $?"
echo "Deleting project..."
[ -d "$PROJECT_PATH" ] && rm -rf "$PROJECT_PATH"
exit 1
}
function replace {
local pattern=$1
local filename=$2
# Mac OS X requires -i argument
if [[ "$OSTYPE" =~ "darwin" ]]
then
/usr/bin/sed -i '' -e $pattern "$filename"
elif [[ "$OSTYPE" =~ "linux" ]]
then
/bin/sed -i -e $pattern "$filename"
fi
}
# we do not want the script to silently fail
trap 'previous_command=$this_command; this_command=$BASH_COMMAND' DEBUG
trap on_error ERR
trap on_exit EXIT
ANDROID_BIN="${ANDROID_BIN:=$( which android )}"
TARGET=$("$ANDROID_BIN" list targets | grep id: | tail -1 | cut -f 2 -d ' ' )
API_LEVEL=$("$ANDROID_BIN" list target | grep "API level:" | tail -n 1 | cut -f 2 -d ':' | tr -d ' ')
# check that build targets exist
if [ -z "$TARGET" ] || [ -z "$API_LEVEL" ]
then
echo "No Android Targets are installed. Please install at least one via the android SDK"
exit 1
fi
# if this a distribution release no need to build a jar
if [ ! -e "$BUILD_PATH"/cordova-$VERSION.jar ] && [ -d "$BUILD_PATH"/framework ]
then
# update the cordova-android framework for the desired target
"$ANDROID_BIN" update project --target $TARGET --path "$BUILD_PATH"/framework &> /dev/null
if [ ! -e "$BUILD_PATH"/framework/libs/commons-codec-1.7.jar ]; then
# Use curl to get the jar (TODO: Support Apache Mirrors)
curl -OL http://archive.apache.org/dist/commons/codec/binaries/commons-codec-1.7-bin.zip &> /dev/null
unzip commons-codec-1.7-bin.zip &> /dev/null
mkdir -p "$BUILD_PATH"/framework/libs
cp commons-codec-1.7/commons-codec-1.7.jar "$BUILD_PATH"/framework/libs
# cleanup yo
rm commons-codec-1.7-bin.zip && rm -rf commons-codec-1.7
fi
# compile cordova.js and cordova.jar
(cd "$BUILD_PATH"/framework && ant jar &> /dev/null )
fi
# copy cordova.js, cordova.jar and res/xml
if [ -d "$BUILD_PATH"/framework ]
then
cp "$BUILD_PATH"/framework/assets/www/cordova-$VERSION.js "$PROJECT_PATH"/assets/www/cordova-$VERSION.js
cp "$BUILD_PATH"/framework/cordova-$VERSION.jar "$PROJECT_PATH"/libs/cordova-$VERSION.jar
else
cp "$BUILD_PATH"/cordova-$VERSION.js "$PROJECT_PATH"/assets/www/cordova-$VERSION.js
cp "$BUILD_PATH"/cordova-$VERSION.jar "$PROJECT_PATH"/libs/cordova-$VERSION.jar
fi
# creating cordova folder and copying run/build/log/launch scripts
cp "$BUILD_PATH"/bin/templates/cordova/appinfo.jar "$PROJECT_PATH"/cordova/appinfo.jar
cp "$BUILD_PATH"/bin/templates/cordova/cordova "$PROJECT_PATH"/cordova/cordova
cp "$BUILD_PATH"/bin/templates/cordova/build "$PROJECT_PATH"/cordova/build
cp "$BUILD_PATH"/bin/templates/cordova/release "$PROJECT_PATH"/cordova/release
cp "$BUILD_PATH"/bin/templates/cordova/clean "$PROJECT_PATH"/cordova/clean
cp "$BUILD_PATH"/bin/templates/cordova/log "$PROJECT_PATH"/cordova/log
cp "$BUILD_PATH"/bin/templates/cordova/run "$PROJECT_PATH"/cordova/run

32
bin/update.bat Normal file
View File

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

193
bin/update.js Normal file
View File

@@ -0,0 +1,193 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
/*
* create a cordova/android project
*
* USAGE
* ./update [path]
*/
var fso = WScript.CreateObject('Scripting.FileSystemObject');
function read(filename) {
var fso=WScript.CreateObject("Scripting.FileSystemObject");
var f=fso.OpenTextFile(filename, 1);
var s=f.ReadAll();
f.Close();
return s;
}
function checkTargets(targets) {
if(!targets) {
WScript.Echo("You do not have any android targets setup. Please create at least one target with the `android` command");
WScript.Quit(69);
}
}
function setTarget() {
var targets = shell.Exec('android.bat list targets').StdOut.ReadAll().match(/id:\s\d+/g);
checkTargets(targets);
return targets[targets.length - 1].replace(/id: /, ""); // TODO: give users the option to set their target
}
function setApiLevel() {
var targets = shell.Exec('android.bat list targets').StdOut.ReadAll().match(/API level:\s\d+/g);
checkTargets(targets);
return targets[targets.length - 1].replace(/API level: /, "");
}
function write(filename, contents) {
var fso=WScript.CreateObject("Scripting.FileSystemObject");
var f=fso.OpenTextFile(filename, 2, true);
f.Write(contents);
f.Close();
}
function replaceInFile(filename, regexp, replacement) {
write(filename, read(filename).replace(regexp, replacement));
}
function exec(command) {
var oShell=shell.Exec(command);
while (oShell.Status == 0) {
if(!oShell.StdOut.AtEndOfStream) {
var line = oShell.StdOut.ReadLine();
// XXX: Change to verbose mode
// WScript.StdOut.WriteLine(line);
}
WScript.sleep(100);
}
}
function createAppInfoJar() {
if(!fso.FileExists(ROOT+"\\bin\\templates\\cordova\\appinfo.jar")) {
WScript.Echo("Creating appinfo.jar...");
var cur = shell.CurrentDirectory;
shell.CurrentDirectory = ROOT+"\\bin\\templates\\cordova\\ApplicationInfo";
exec("javac ApplicationInfo.java");
exec("jar -cfe ..\\appinfo.jar ApplicationInfo ApplicationInfo.class");
shell.CurrentDirectory = cur;
}
}
function cleanup() {
if(fso.FileExists(ROOT + '\\framework\\cordova-'+VERSION+'.jar')) {
fso.DeleteFile(ROOT + '\\framework\\cordova-'+VERSION+'.jar');
}
if(fso.FileExists(ROOT + '\\framework\\assets\\www\\cordova-'+VERSION+'.js')) {
fso.DeleteFile(ROOT + '\\framework\\assets\\www\\cordova-'+VERSION+'.js');
}
}
function downloadCommonsCodec() {
if (!fso.FileExists(ROOT + '\\framework\\libs\\commons-codec-1.7.jar')) {
// We need the .jar
var url = 'http://archive.apache.org/dist/commons/codec/binaries/commons-codec-1.7-bin.zip';
var libsPath = ROOT + '\\framework\\libs';
var savePath = libsPath + '\\commons-codec-1.7-bin.zip';
if (!fso.FileExists(savePath)) {
if(!fso.FolderExists(ROOT + '\\framework\\libs')) {
fso.CreateFolder(libsPath);
}
// We need the zip to get the jar
var xhr = WScript.CreateObject('MSXML2.XMLHTTP');
xhr.open('GET', url, false);
xhr.send();
if (xhr.status == 200) {
var stream = WScript.CreateObject('ADODB.Stream');
stream.Open();
stream.Type = 1;
stream.Write(xhr.ResponseBody);
stream.Position = 0;
stream.SaveToFile(savePath);
stream.Close();
} else {
WScript.Echo('Could not retrieve the commons-codec. Please download it yourself and put into the framework/libs directory. This process may fail now. Sorry.');
}
}
var app = WScript.CreateObject('Shell.Application');
var source = app.NameSpace(savePath).Items();
var target = app.NameSpace(ROOT + '\\framework\\libs');
target.CopyHere(source, 256);
// Move the jar into libs
fso.MoveFile(ROOT + '\\framework\\libs\\commons-codec-1.7\\commons-codec-1.7.jar', ROOT + '\\framework\\libs\\commons-codec-1.7.jar');
// Clean up
fso.DeleteFile(ROOT + '\\framework\\libs\\commons-codec-1.7-bin.zip');
fso.DeleteFolder(ROOT + '\\framework\\libs\\commons-codec-1.7', true);
}
}
var args = WScript.Arguments, PROJECT_PATH="example",
shell=WScript.CreateObject("WScript.Shell");
// working dir
var ROOT = WScript.ScriptFullName.split('\\bin\\update.js').join('');
if (args.Count() == 1) {
PROJECT_PATH=args(0);
}
if(!fso.FolderExists(PROJECT_PATH)) {
WScript.Echo("Project doesn't exist!");
WScript.Quit(1);
}
var TARGET=setTarget();
var API_LEVEL=setApiLevel();
var VERSION=read(ROOT+'\\VERSION').replace(/\r\n/,'').replace(/\n/,'');
// build from source. distro should have these files
if (!fso.FileExists(ROOT+'\\cordova-'+VERSION+'.jar') &&
!fso.FileExists(ROOT+'\\cordova-'+VERSION+'.js')) {
WScript.Echo("Building jar and js files...");
// update the cordova framework project to a target that exists on this machine
exec('android.bat update project --target '+TARGET+' --path '+ROOT+'\\framework');
// pull down commons codec if necessary
downloadCommonsCodec();
exec('ant.bat -f \"'+ ROOT +'\\framework\\build.xml\" jar');
}
// check if we have the source or the distro files
WScript.Echo("Copying js, jar & config.xml files...");
if(fso.FolderExists(ROOT + '\\framework')) {
exec('%comspec% /c copy "'+ROOT+'"\\framework\\assets\\www\\cordova-'+VERSION+'.js '+PROJECT_PATH+'\\assets\\www\\cordova-'+VERSION+'.js /Y');
exec('%comspec% /c copy "'+ROOT+'"\\framework\\cordova-'+VERSION+'.jar '+PROJECT_PATH+'\\libs\\cordova-'+VERSION+'.jar /Y');
} else {
// copy in cordova.js
exec('%comspec% /c copy "'+ROOT+'"\\cordova-'+VERSION+'.js '+PROJECT_PATH+'\\assets\\www\\cordova-'+VERSION+'.js /Y');
// copy in cordova.jar
exec('%comspec% /c copy "'+ROOT+'"\\cordova-'+VERSION+'.jar '+PROJECT_PATH+'\\libs\\cordova-'+VERSION+'.jar /Y');
// copy in xml
}
// update cordova scripts
createAppInfoJar();
WScript.Echo("Copying cordova command tools...");
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\appinfo.jar ' + PROJECT_PATH + '\\cordova\\appinfo.jar /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\cordova.js ' + PROJECT_PATH + '\\cordova\\cordova.js /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\cordova.bat ' + PROJECT_PATH + '\\cordova\\cordova.bat /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\clean.bat ' + PROJECT_PATH + '\\cordova\\clean.bat /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\build.bat ' + PROJECT_PATH + '\\cordova\\build.bat /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\log.bat ' + PROJECT_PATH + '\\cordova\\log.bat /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\run.bat ' + PROJECT_PATH + '\\cordova\\run.bat /Y');
cleanup();

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"

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -29,6 +29,9 @@
<!-- <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" />

View File

@@ -64,7 +64,7 @@ public class App extends CordovaPlugin {
this.loadUrl(args.getString(0), args.optJSONObject(1));
}
else if (action.equals("cancelLoadUrl")) {
this.cancelLoadUrl();
//this.cancelLoadUrl();
}
else if (action.equals("clearHistory")) {
this.clearHistory();
@@ -160,14 +160,6 @@ public class App extends CordovaPlugin {
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.
*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -26,6 +26,8 @@ import org.json.JSONException;
import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
@@ -33,6 +35,7 @@ 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;
@@ -47,6 +50,8 @@ import android.widget.RelativeLayout;
*/
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;
@@ -55,6 +60,9 @@ public class CordovaChromeClient extends WebChromeClient {
// the video progress view
private View mVideoProgressView;
// File Chooser
public ValueCallback<Uri> mUploadMessage;
/**
* Constructor.
*
@@ -197,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;
}
@@ -369,5 +377,26 @@ public class CordovaChromeClient extends WebChromeClient {
}
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;
}
}

View File

@@ -19,30 +19,24 @@
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.apache.cordova.api.PluginResult;
import org.xmlpull.v1.XmlPullParserException;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.XmlResourceParser;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -53,6 +47,7 @@ import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.webkit.WebBackForwardList;
import android.webkit.WebHistoryItem;
import android.webkit.WebChromeClient;
@@ -65,15 +60,12 @@ 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;
private boolean paused;
private BroadcastReceiver receiver;
@@ -83,12 +75,7 @@ public class CordovaWebView extends WebView {
@SuppressWarnings("unused")
private CordovaChromeClient chromeClient;
//This is for the polyfill history
private String url;
String baseUrl;
private Stack<String> urls = new Stack<String>();
boolean useBrowserHistory = true;
// Flag to track that a loadUrl timeout occurred
int loadUrlTimeout = 0;
@@ -105,6 +92,23 @@ public class CordovaWebView extends WebView {
/** 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(
@@ -203,7 +207,8 @@ public class CordovaWebView extends WebView {
private void initWebViewClient(CordovaInterface cordova) {
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB)
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB ||
android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.JELLY_BEAN_MR1)
{
this.setWebViewClient(new CordovaWebViewClient(this.cordova, this));
}
@@ -246,14 +251,24 @@ public class CordovaWebView extends WebView {
Log.d(TAG, "This should never happen: InvocationTargetException means this isn't Android anymore.");
}
//We don't save any form data in the application
settings.setSaveFormData(false);
settings.setSavePassword(false);
// Jellybean rightfully tried to lock this down. Too bad they didn't give us a whitelist
// while we do this
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
Level16Apis.enableUniversalAccess(settings);
// Enable database
settings.setDatabaseEnabled(true);
// We keep this disabled because we use or shim to get around DOM_EXCEPTION_ERROR_16
String databasePath = this.cordova.getActivity().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
settings.setDatabasePath(databasePath);
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB)
{
settings.setDatabaseEnabled(true);
settings.setDatabasePath(databasePath);
}
settings.setGeolocationDatabasePath(databasePath);
// Enable DOM storage
settings.setDomStorageEnabled(true);
@@ -261,6 +276,13 @@ public class CordovaWebView extends WebView {
// Enable built-in geolocation
settings.setGeolocationEnabled(true);
// Enable AppCache
// Fix for CB-2282
settings.setAppCacheMaxSize(5 * 1048576);
String pathToCache = this.cordova.getActivity().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
settings.setAppCachePath(pathToCache);
settings.setAppCacheEnabled(true);
// Fix for CB-1405
// Google issue 4641
this.updateUserAgentString();
@@ -324,71 +346,9 @@ public class CordovaWebView extends WebView {
this.chromeClient = client;
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;
}
/**
@@ -406,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
@@ -427,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
@@ -445,21 +405,8 @@ public class CordovaWebView extends WebView {
LOG.d(TAG, ">>> loadUrl(" + url + ")");
this.url = url;
if (this.baseUrl == null) {
int i = url.lastIndexOf('/');
if (i > 0) {
this.baseUrl = url.substring(0, i + 1);
}
else {
this.baseUrl = this.url + "/";
}
this.pluginManager.init();
this.pluginManager.init();
if (!this.useBrowserHistory) {
this.urls.push(url);
}
}
// Create a timeout timer for loadUrl
final CordovaWebView me = this;
@@ -514,8 +461,8 @@ public class CordovaWebView extends WebView {
if (LOG.isLoggable(LOG.DEBUG) && !url.startsWith("javascript:")) {
LOG.d(TAG, ">>> loadUrlNow()");
}
if (url.startsWith("file://") || url.indexOf(this.baseUrl) == 0 || url.startsWith("javascript:") || this.isUrlWhiteListed(url)) {
super.loadUrl(url);
if (url.startsWith("file://") || url.startsWith("javascript:") || Config.isUrlWhiteListed(url)) {
super.loadUrl(url);
}
}
@@ -530,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
@@ -578,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)
@@ -613,32 +541,9 @@ public class CordovaWebView extends WebView {
return true;
}
// If our managed history has prev url
if (this.urls.size() > 1 && !this.useBrowserHistory) {
this.urls.pop(); // Pop current url
String url = this.urls.pop(); // Pop prev url that we want to load, since it will be added back by loadUrl()
this.loadUrl(url);
return true;
}
return false;
}
/**
* Return true if there is a history item.
*
* @return
*/
public boolean canGoBack() {
if (super.canGoBack()) {
return true;
}
if (this.urls.size() > 1) {
return true;
}
return false;
}
/**
* Load the specified URL in the Cordova webview or a new browser instance.
@@ -662,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);
}
@@ -699,74 +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() {
Activity action = this.cordova.getActivity();
if(action == null)
{
LOG.i("CordovaLog", "There is no activity. Is this on the lock screen?");
return;
}
int id = getResources().getIdentifier("config", "xml", this.cordova.getActivity().getPackageName());
if(id == 0)
{
id = getResources().getIdentifier("cordova", "xml", this.cordova.getActivity().getPackageName());
Log.i("CordovaLog", "config.xml missing, reverting to cordova.xml");
}
if (id == 0) {
LOG.i("CordovaLog", "cordova.xml missing. Ignoring...");
return;
}
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);
Log.d("CordovaLog", "Found preference for " + 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();
}
}
if("false".equals(this.getProperty("useBrowserHistory", "true")))
{
//Switch back to the old browser history and state the six month policy
this.useBrowserHistory = false;
Log.w(TAG, "useBrowserHistory=false is deprecated as of Cordova 2.2.0 and will be removed six months after the 2.2.0 release. Please use the browser history and use history.back().");
}
if ("true".equals(this.getProperty("fullscreen", "false"))) {
this.cordova.getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
@@ -820,13 +658,20 @@ public class CordovaWebView extends WebView {
}
else if(keyCode == KeyEvent.KEYCODE_BACK)
{
//Because exit is fired on the keyDown and not the key up on Android 4.x
//we need to check for this.
//Also, I really wished "canGoBack" worked!
if(this.useBrowserHistory)
return !(this.startOfHistory()) || this.bound;
else
return this.urls.size() > 1 || this.bound;
return !(this.startOfHistory()) || this.bound;
}
else if(keyCode == KeyEvent.KEYCODE_MENU)
{
//How did we get here? Is there a childView?
View childView = this.getFocusedChild();
if(childView != null)
{
//Make sure we close the keyboard if it's present
InputMethodManager imm = (InputMethodManager) cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(childView.getWindowToken(), 0);
cordova.getActivity().openOptionsMenu();
}
return true;
}
return super.onKeyDown(keyCode, event);
@@ -856,7 +701,9 @@ public class CordovaWebView extends WebView {
// If not, then invoke default behaviour
else {
//this.activityState = ACTIVITY_EXITING;
return false;
//return false;
// If they hit back button when app is initializing, app should exit instead of hang until initilazation (CB2-458)
this.cordova.getActivity().finish();
}
}
}
@@ -939,9 +786,8 @@ public class CordovaWebView extends WebView {
public void handleResume(boolean keepRunning, boolean activityResultKeepRunning)
{
// Send resume event to JavaScript
this.loadUrl("javascript:try{cordova.fireDocumentEvent('resume');}catch(e){console.log('exception firing resume event from native');};");
// Forward to plugins
if (this.pluginManager != null) {
this.pluginManager.onResume(keepRunning);
@@ -955,7 +801,8 @@ public class CordovaWebView extends WebView {
public void handleDestroy()
{
// Send destroy event to JavaScript
this.loadUrl("javascript:try{cordova.require('cordova/channel').onDestroy.fire();}catch(e){console.log('exception firing destroy event from native');};");
// Since baseUrl is set in loadUrlIntoView, if user hit Back button before loadUrl was called, we'll get an NPE on baseUrl (CB-2458)
this.loadUrlIntoView("javascript:try{cordova.require('cordova/channel').onDestroy.fire();}catch(e){console.log('exception firing destroy event from native');};");
// Load blank page so that JavaScript onunload is called
this.loadUrl("about:blank");
@@ -1018,11 +865,14 @@ public class CordovaWebView extends WebView {
{
WebBackForwardList currentList = this.copyBackForwardList();
WebHistoryItem item = currentList.getItemAtIndex(0);
String url = item.getUrl();
String currentUrl = this.getUrl();
LOG.d(TAG, "The current URL is: " + currentUrl);
LOG.d(TAG, "The URL at item 0 is:" + url);
return currentUrl.equals(url);
if( item!=null){ // Null-fence in case they haven't called loadUrl yet (CB-2458)
String url = item.getUrl();
String currentUrl = this.getUrl();
LOG.d(TAG, "The current URL is: " + currentUrl);
LOG.d(TAG, "The URL at item 0 is:" + url);
return currentUrl.equals(url);
}
return false;
}
public void showCustomView(View view, WebChromeClient.CustomViewCallback callback) {
@@ -1077,4 +927,17 @@ public class CordovaWebView extends WebView {
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);
}
}

View File

@@ -18,10 +18,10 @@
*/
package org.apache.cordova;
import java.io.ByteArrayInputStream;
import java.util.Hashtable;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.PluginResult;
import org.apache.cordova.api.LOG;
import org.json.JSONException;
@@ -39,6 +39,7 @@ import android.util.Log;
import android.view.View;
import android.webkit.HttpAuthHandler;
import android.webkit.SslErrorHandler;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
@@ -67,7 +68,7 @@ public class CordovaWebViewClient extends WebViewClient {
/**
* Constructor.
*
*
* @param cordova
* @param view
*/
@@ -78,7 +79,7 @@ public class CordovaWebViewClient extends WebViewClient {
/**
* Constructor.
*
*
* @param view
*/
public void setWebView(CordovaWebView view) {
@@ -102,8 +103,8 @@ public class CordovaWebViewClient extends WebViewClient {
String callbackId = url.substring(idx3 + 1, idx4);
String jsonArgs = url.substring(idx4 + 1);
appView.pluginManager.exec(service, action, callbackId, jsonArgs);
}
}
/**
* Give the host application a chance to take over the control when a new url
* is about to be loaded in the current WebView.
@@ -193,12 +194,8 @@ public class CordovaWebViewClient extends WebViewClient {
// If our app or file:, then load into a new Cordova webview container by starting a new instance of our activity.
// Our app continues to run. When BACK is pressed, our app is redisplayed.
if (url.startsWith("file://") || url.startsWith("data:") || url.indexOf(this.appView.baseUrl) == 0 || this.appView.isUrlWhiteListed(url)) {
//This will fix iFrames
if (appView.useBrowserHistory || url.startsWith("data:"))
return false;
else
this.appView.loadUrl(url);
if (url.startsWith("file://") || url.startsWith("data:") || Config.isUrlWhiteListed(url)) {
return false;
}
// If not our application, let default viewer handle
@@ -215,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
@@ -232,25 +257,23 @@ 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);
}
}
/**
* Notify the host application that a page has started loading.
* This method is called once for each main frame load so a page with iframes or framesets will call onPageStarted
* one time for the main frame. This also means that onPageStarted will not be called when the contents of an
* embedded frame changes, i.e. clicking a link whose target is an iframe.
*
* Notify the host application that a page has started loading.
* This method is called once for each main frame load so a page with iframes or framesets will call onPageStarted
* one time for the main frame. This also means that onPageStarted will not be called when the contents of an
* embedded frame changes, i.e. clicking a link whose target is an iframe.
*
* @param view The webview initiating the callback.
* @param url The url of the page.
*/
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
// Clear history so history.back() doesn't do anything.
// So we can reinit() native side CallbackServer & PluginManager.
if (!this.appView.useBrowserHistory) {
view.clearHistory();
this.doClearHistory = true;
}
// Flush stale messages.
this.appView.jsMessageQueue.reset();
@@ -267,7 +290,7 @@ public class CordovaWebViewClient extends WebViewClient {
/**
* Notify the host application that a page has finished loading.
* This method is called only for main frame. When onPageFinished() is called, the rendering picture may not be updated yet.
*
*
*
* @param view The webview initiating the callback.
* @param url The url of the page.
@@ -295,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);
}
@@ -355,11 +379,11 @@ public class CordovaWebViewClient extends WebViewClient {
}
/**
* Notify the host application that an SSL error occurred while loading a resource.
* The host application must call either handler.cancel() or handler.proceed().
* Note that the decision may be retained for use in response to future SSL errors.
* Notify the host application that an SSL error occurred while loading a resource.
* The host application must call either handler.cancel() or handler.proceed().
* Note that the decision may be retained for use in response to future SSL errors.
* The default behavior is to cancel the load.
*
*
* @param view The WebView that is initiating the callback.
* @param handler An SslErrorHandler object that will handle the user's response.
* @param error The SSL error object.
@@ -388,27 +412,10 @@ public class CordovaWebViewClient extends WebViewClient {
}
}
/**
* Notify the host application to update its visited links database.
*
* @param view The WebView that is initiating the callback.
* @param url The url being visited.
* @param isReload True if this url is being reloaded.
*/
@Override
public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
/*
* If you do a document.location.href the url does not get pushed on the stack
* so we do a check here to see if the url should be pushed.
*/
if (!this.appView.peekAtUrlStack().equals(url)) {
this.appView.pushUrl(url);
}
}
/**
* Sets the authentication token.
*
*
* @param authenticationToken
* @param host
* @param realm
@@ -425,10 +432,10 @@ public class CordovaWebViewClient extends WebViewClient {
/**
* Removes the authentication token.
*
*
* @param host
* @param realm
*
*
* @return the authentication token or null if did not exist
*/
public AuthenticationToken removeAuthenticationToken(String host, String realm) {
@@ -437,16 +444,16 @@ public class CordovaWebViewClient extends WebViewClient {
/**
* Gets the authentication token.
*
*
* In order it tries:
* 1- host + realm
* 2- host
* 3- realm
* 4- no host, no realm
*
*
* @param host
* @param realm
*
*
* @return the authentication token
*/
public AuthenticationToken getAuthenticationToken(String host, String realm) {

View File

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

View File

@@ -28,6 +28,7 @@ import org.apache.cordova.api.LOG;
import org.json.JSONException;
import org.json.JSONObject;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -38,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;
@@ -48,7 +51,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.webkit.WebView;
import android.webkit.ValueCallback;
import android.webkit.WebViewClient;
import android.widget.LinearLayout;
@@ -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);
@@ -314,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()");
@@ -331,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);
@@ -338,6 +364,7 @@ public class DroidGap extends Activity implements CordovaInterface {
// Clear cancel flag
this.cancelLoadUrl = false;
}
/**
@@ -571,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);
}
@@ -581,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);
}
@@ -591,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);
}
@@ -601,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);
}
@@ -800,9 +831,35 @@ 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);
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(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);
}
}
@@ -824,7 +881,7 @@ public class DroidGap extends Activity implements CordovaInterface {
// If errorUrl specified, then load it
final String errorUrl = me.getStringProperty("errorUrl", null);
if ((errorUrl != null) && (errorUrl.startsWith("file://") || 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() {
@@ -840,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);
}
@@ -892,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);
}
/*
@@ -1010,7 +1062,8 @@ public class DroidGap extends Activity implements CordovaInterface {
{
//Get whatever has focus!
View childView = appView.getFocusedChild();
if ((appView.isCustomViewShowing() || childView != null ) && keyCode == KeyEvent.KEYCODE_BACK) {
if ((appView.isCustomViewShowing() || childView != null ) &&
(keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU)) {
return appView.onKeyUp(keyCode, event);
} else {
return super.onKeyUp(keyCode, event);
@@ -1030,7 +1083,7 @@ public class DroidGap extends Activity implements CordovaInterface {
//Get whatever has focus!
View childView = appView.getFocusedChild();
//Determine if the focus is on the current view or not
if (childView != null && keyCode == KeyEvent.KEYCODE_BACK) {
if (childView != null && (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU)) {
return appView.onKeyDown(keyCode, event);
}
else
@@ -1053,9 +1106,9 @@ public class DroidGap extends Activity implements CordovaInterface {
}
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);
if (this.splashDialog == null || !this.splashDialog.isShowing()) {
this.splashscreen = this.getIntegerProperty("splashscreen", 0);
this.showSplashScreen(this.splashscreenTime);
}
}
}
@@ -1082,4 +1135,15 @@ public class DroidGap extends Activity implements CordovaInterface {
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);
}
}
}

View File

@@ -20,24 +20,28 @@ package org.apache.cordova;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.json.JSONArray;
import org.json.JSONException;
public class Echo extends CordovaPlugin {
@Override
public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException {
final String result = args.isNull(0) ? null : args.getString(0);
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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -130,7 +130,8 @@ public class NativeToJsMessageQueue {
}
private void packMessage(JsMessage message, StringBuilder sb) {
sb.append(message.calculateEncodedLength())
int len = message.calculateEncodedLength();
sb.append(len)
.append(' ');
message.encodeAsMessage(sb);
}
@@ -166,7 +167,8 @@ public class NativeToJsMessageQueue {
// Attach a char to indicate that there are more messages pending.
sb.append('*');
}
return sb.toString();
String ret = sb.toString();
return ret;
}
}
@@ -209,7 +211,8 @@ public class NativeToJsMessageQueue {
for (int i = willSendAllMessages ? 1 : 0; i < numMessagesToSend; ++i) {
sb.append('}');
}
return sb.toString();
String ret = sb.toString();
return ret;
}
}
@@ -406,6 +409,12 @@ public class NativeToJsMessageQueue {
case PluginResult.MESSAGE_TYPE_STRING: // s
ret += 1 + pluginResult.getStrMessage().length();
break;
case PluginResult.MESSAGE_TYPE_BINARYSTRING:
ret += 1 + pluginResult.getMessage().length();
break;
case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
ret += 1 + pluginResult.getMessage().length();
break;
case PluginResult.MESSAGE_TYPE_JSON:
default:
ret += pluginResult.getMessage().length();
@@ -445,6 +454,14 @@ public class NativeToJsMessageQueue {
sb.append('s');
sb.append(pluginResult.getStrMessage());
break;
case PluginResult.MESSAGE_TYPE_BINARYSTRING: // S
sb.append('S');
sb.append(pluginResult.getMessage());
break;
case PluginResult.MESSAGE_TYPE_ARRAYBUFFER: // A
sb.append('A');
sb.append(pluginResult.getMessage());
break;
case PluginResult.MESSAGE_TYPE_JSON:
default:
sb.append(pluginResult.getMessage()); // [ or {
@@ -463,9 +480,9 @@ public class NativeToJsMessageQueue {
.append(success)
.append(",")
.append(status)
.append(",")
.append(",[")
.append(pluginResult.getMessage())
.append(",")
.append("],")
.append(pluginResult.getKeepCallback())
.append(");");
}

View File

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

View File

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

View File

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

View File

@@ -19,7 +19,6 @@
package org.apache.cordova.api;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import java.util.concurrent.ExecutorService;
@@ -54,11 +53,6 @@ public interface CordovaInterface {
*/
public abstract Activity getActivity();
@Deprecated
public abstract Context getContext();
@Deprecated
public abstract void cancelLoadUrl();
/**
* Called when a message is sent to plugin.

View File

@@ -18,11 +18,16 @@
*/
package org.apache.cordova.api;
import org.apache.cordova.CordovaArgs;
import org.apache.cordova.CordovaWebView;
import org.json.JSONArray;
import org.json.JSONException;
import android.content.Intent;
import android.annotation.TargetApi;
import android.content.Intent;
import android.os.Build;
import android.util.Log;
import android.webkit.WebResourceResponse;
/**
* Plugins must extend this class and override one of the execute methods.
@@ -76,9 +81,28 @@ public class CordovaPlugin {
* @return Whether the action was valid.
*/
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
CordovaArgs cordovaArgs = new CordovaArgs(args);
return execute(action, cordovaArgs, callbackContext);
}
/**
* Executes the request.
*
* This method is called from the WebView thread. To do a non-trivial amount of work, use:
* cordova.getThreadPool().execute(runnable);
*
* To run on the UI thread, use:
* cordova.getActivity().runOnUiThread(runnable);
*
* @param action The action to execute.
* @param args The exec() arguments, wrapped with some Cordova helpers.
* @param callbackContext The callback context used when calling back into JavaScript.
* @return Whether the action was valid.
*/
public boolean execute(String action, CordovaArgs args, CallbackContext callbackContext) throws JSONException {
return false;
}
/**
* Called when the system is about to start resuming a previous activity.
*
@@ -131,7 +155,7 @@ public class CordovaPlugin {
}
/**
* By specifying a <url-filter> in plugins.xml you can map a URL (using startsWith atm) to this method.
* By specifying a <url-filter> in config.xml you can map a URL (using startsWith atm) to this method.
*
* @param url The URL that is trying to be loaded in the Cordova webview.
* @return Return true to prevent the URL from loading. Default is false.
@@ -140,6 +164,17 @@ public class CordovaPlugin {
return false;
}
/**
* By specifying a <url-filter> in config.xml you can map a URL prefix to this method. It applies to all resources loaded in the WebView, not just top-level navigation.
*
* @param url The URL of the resource to be loaded.
* @return Return a WebResourceResponse for the resource, or null to let the WebView handle it normally.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public WebResourceResponse shouldInterceptRequest(String url) {
return null;
}
/**
* Called when the WebView does a top-level navigation or refreshes.
*

View File

@@ -43,7 +43,6 @@ public class LegacyContext implements CordovaInterface {
@Deprecated
public void cancelLoadUrl() {
Log.i(LOG_TAG, "Replace ctx.cancelLoadUrl() with cordova.cancelLoadUrl()");
this.cordova.cancelLoadUrl();
}
@Deprecated
@@ -55,7 +54,7 @@ public class LegacyContext implements CordovaInterface {
@Deprecated
public Context getContext() {
Log.i(LOG_TAG, "Replace ctx.getContext() with cordova.getContext()");
return this.cordova.getContext();
return this.cordova.getActivity();
}
@Deprecated

View File

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

View File

@@ -21,6 +21,8 @@ package org.apache.cordova.api;
import org.json.JSONArray;
import org.json.JSONObject;
import android.util.Base64;
public class PluginResult {
private final int status;
private final int messageType;
@@ -68,6 +70,16 @@ public class PluginResult {
this.encodedMessage = Boolean.toString(b);
}
public PluginResult(Status status, byte[] data) {
this(status, data, false);
}
public PluginResult(Status status, byte[] data, boolean binaryString) {
this.status = status.ordinal();
this.messageType = binaryString ? MESSAGE_TYPE_BINARYSTRING : MESSAGE_TYPE_ARRAYBUFFER;
this.encodedMessage = Base64.encodeToString(data, Base64.NO_WRAP);
}
public void setKeepCallback(boolean b) {
this.keepCallback = b;
}
@@ -79,7 +91,7 @@ public class PluginResult {
public int getMessageType() {
return messageType;
}
public String getMessage() {
if (encodedMessage == null) {
encodedMessage = JSONObject.quote(strMessage);
@@ -134,7 +146,11 @@ public class PluginResult {
public static final int MESSAGE_TYPE_NUMBER = 3;
public static final int MESSAGE_TYPE_BOOLEAN = 4;
public static final int MESSAGE_TYPE_NULL = 5;
public static final int MESSAGE_TYPE_ARRAYBUFFER = 6;
// Use BINARYSTRING when your string may contain null characters.
// This is required to work around a bug in the platform :(.
public static final int MESSAGE_TYPE_BINARYSTRING = 7;
public static String[] StatusMessages = new String[] {
"No result",
"OK",

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.test" android:versionName="1.1" android:versionCode="5">
package="org.apache.cordova.test" android:versionName="1.0" android:versionCode="1">
<supports-screens
android:largeScreens="true"
android:normalScreens="true"

File diff suppressed because it is too large Load Diff

View File

@@ -8,5 +8,5 @@
# project structure.
# Project target.
target=android-16
target=android-17
android.library.reference.1=../framework

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,14 +21,72 @@ package org.apache.cordova.test;
*/
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.test.util.Purity;
import org.apache.cordova.test.actions.iframe;
import android.app.Activity;
import android.app.Instrumentation;
import android.test.ActivityInstrumentationTestCase2;
import android.test.TouchUtils;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
public class IFrameTest extends ActivityInstrumentationTestCase2<iframe> {
public class IFrameTest extends ActivityInstrumentationTestCase2 {
public IFrameTest() {
super("org.apache.cordova.test",iframe.class);
}
private Instrumentation mInstr;
private Activity testActivity;
private FrameLayout containerView;
private LinearLayout innerContainer;
private CordovaWebView testView;
private TouchUtils touch;
private Purity touchTool;
public IFrameTest() {
super("org.apache.cordova.test",iframe.class);
}
protected void setUp() throws Exception {
super.setUp();
mInstr = this.getInstrumentation();
testActivity = this.getActivity();
containerView = (FrameLayout) testActivity.findViewById(android.R.id.content);
innerContainer = (LinearLayout) containerView.getChildAt(0);
testView = (CordovaWebView) innerContainer.getChildAt(0);
touch = new TouchUtils();
touchTool = new Purity(testActivity, getInstrumentation());
}
public void testIframeDest()
{
testView.sendJavascript("loadUrl('http://maps.google.com/maps?output=embed');");
sleep(3000);
testView.sendJavascript("loadUrl('index2.html')");
sleep(1000);
String url = testView.getUrl();
assertTrue(url.endsWith("index.html"));
}
public void testIframeHistory()
{
testView.sendJavascript("loadUrl('http://maps.google.com/maps?output=embed');");
sleep(3000);
testView.sendJavascript("loadUrl('index2.html')");
sleep(1000);
String url = testView.getUrl();
testView.backHistory();
sleep(1000);
assertTrue(url.endsWith("index.html"));
}
private void sleep(int timeout) {
try {
Thread.sleep(timeout);
} catch (InterruptedException e) {
fail("Unexpected Timeout");
}
}
}

View File

@@ -21,15 +21,55 @@ package org.apache.cordova.test;
*/
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.test.util.Purity;
import org.apache.cordova.test.actions.jqmtabbackbutton;
import android.app.Instrumentation;
import android.test.ActivityInstrumentationTestCase2;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
public class JQMTabTest extends ActivityInstrumentationTestCase2<jqmtabbackbutton> {
public JQMTabTest(Class<jqmtabbackbutton> activityClass) {
super(activityClass);
// TODO Auto-generated constructor stub
private Instrumentation mInstr;
private jqmtabbackbutton testActivity;
private FrameLayout containerView;
private LinearLayout innerContainer;
private CordovaWebView testView;
private Purity touchTool;
public JQMTabTest()
{
super("org.apache.cordova.test.activity", jqmtabbackbutton.class);
}
protected void setUp() throws Exception {
super.setUp();
mInstr = this.getInstrumentation();
testActivity = this.getActivity();
containerView = (FrameLayout) testActivity.findViewById(android.R.id.content);
innerContainer = (LinearLayout) containerView.getChildAt(0);
testView = (CordovaWebView) innerContainer.getChildAt(0);
touchTool = new Purity(testActivity, getInstrumentation());
}
public void testTouch()
{
sleep(5000);
int viewportHeight = touchTool.getViewportHeight() - 40;
int viewportWidth = touchTool.getViewportWidth();
touchTool.touch(50, viewportHeight);
sleep(10000);
}
private void sleep(int timeout) {
try {
Thread.sleep(timeout);
} catch (InterruptedException e) {
fail("Unexpected Timeout");
}
}
}

View File

@@ -34,7 +34,8 @@ public class basicauth extends DroidGap {
super.setAuthenticationToken(token, "browserspy.dk:80", "BrowserSpy.dk - HTTP Password Test");
// Add web site to whitelist
super.appView.addWhiteListEntry("http://browserspy.dk*", true);
Config.init();
Config.addWhiteListEntry("http://browserspy.dk*", true);
// Load test
super.loadUrl("file:///android_asset/www/basicauth/index.html");

View File

@@ -0,0 +1,171 @@
/*
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.
*/
/*
* Purity is a small set of Android utility methods that allows us to simulate touch events on
* Android applications. This is important for simulating some of the most annoying tests.
*/
package org.apache.cordova.test.util;
import android.app.Instrumentation;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Picture;
import android.os.SystemClock;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.webkit.WebView;
public class Purity {
Instrumentation inst;
int width, height;
float density;
Bitmap state;
boolean fingerDown = false;
public Purity(Context ctx, Instrumentation i)
{
inst = i;
DisplayMetrics display = ctx.getResources().getDisplayMetrics();
density = display.density;
width = display.widthPixels;
height = display.heightPixels;
}
/*
* WebKit doesn't give you real pixels anymore, this is done for subpixel fonts to appear on
* iOS and Android. However, Android automation requires real pixels
*/
private int getRealCoord(int coord)
{
return (int) (coord * density);
}
public int getViewportWidth()
{
return (int) (width/density);
}
public int getViewportHeight()
{
return (int) (height/density);
}
public void touch(int x, int y)
{
int realX = getRealCoord(x);
int realY = getRealCoord(y);
long downTime = SystemClock.uptimeMillis();
// event time MUST be retrieved only by this way!
long eventTime = SystemClock.uptimeMillis();
if(!fingerDown)
{
MotionEvent downEvent = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, realX, realY, 0);
inst.sendPointerSync(downEvent);
}
MotionEvent upEvent = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, realX, realY, 0);
inst.sendPointerSync(upEvent);
}
public void touchStart(int x, int y)
{
int realX = getRealCoord(x);
int realY = getRealCoord(y);
long downTime = SystemClock.uptimeMillis();
// event time MUST be retrieved only by this way!
long eventTime = SystemClock.uptimeMillis();
MotionEvent event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, realX, realY, 0);
inst.sendPointerSync(event);
fingerDown = true;
}
//Move from the touch start
public void touchMove(int x, int y)
{
if(!fingerDown)
touchStart(x,y);
else
{
int realX = getRealCoord(x);
int realY = getRealCoord(y);
long downTime = SystemClock.uptimeMillis();
// event time MUST be retrieved only by this way!
long eventTime = SystemClock.uptimeMillis();
MotionEvent event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, realX, realY, 0);
inst.sendPointerSync(event);
}
}
public void touchEnd(int x, int y)
{
if(!fingerDown)
{
touch(x, y);
}
else
{
int realX = getRealCoord(x);
int realY = getRealCoord(y);
long downTime = SystemClock.uptimeMillis();
// event time MUST be retrieved only by this way!
long eventTime = SystemClock.uptimeMillis();
MotionEvent event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, realX, realY, 0);
inst.sendPointerSync(event);
fingerDown = false;
}
}
public void setBitmap(WebView view)
{
Picture p = view.capturePicture();
state = Bitmap.createBitmap(p.getWidth(), p.getHeight(), Bitmap.Config.ARGB_8888);
}
public boolean checkRenderView(WebView view)
{
if(state == null)
{
setBitmap(view);
return false;
}
else
{
Picture p = view.capturePicture();
Bitmap newState = Bitmap.createBitmap(p.getWidth(), p.getHeight(), Bitmap.Config.ARGB_8888);
boolean result = newState.equals(state);
newState.recycle();
return result;
}
}
public void clearBitmap()
{
if(state != null)
state.recycle();
}
protected void finalize()
{
clearBitmap();
}
}