Compare commits

...

286 Commits

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

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

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

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

Tested on Androdi 2.2: works
Tested on Android 4.1: doesn't work. It seems videos are handled differently (without the use of "Custom views"). To make video playing work on Android 4, add the android:hardwareAccelerated="true" attribute to the main activity of the AndroidManifest.
2012-08-14 18:13:35 +02:00
120 changed files with 13207 additions and 3188 deletions

19
.gitignore vendored
View File

@@ -4,28 +4,29 @@ gen
assets/www/cordova.js
framework/assets/www/.tmp*
local.properties
framework/proguard.cfg
framework/cordova.jar
framework/cordova-*.jar
framework/phonegap-*.jar
framework/lib
proguard.cfg
proguard.cfg
proguard-project.txt
framework/bin
framework/test/org/apache/cordova/*.class
framework/assets/www/.DS_Store
framework/assets/www/cordova-*.js
framework/assets/www/phonegap-*.js
framework/libs
test/libs
example
./test
test/bin
test/assets/www/.tmp*
tmp/**
*.tmp
test/libs/*.jar
bin/node_modules
.metadata
*.bak
tmp/**/*
*.swp
Thumbs.db
Desktop.ini
*.tmp
*.bak
*.swp
*.class
*.jar

View File

@@ -15,8 +15,8 @@ indicate that the project has yet to be fully endorsed by the ASF.
Requires
---
- Java JDK 1.5
- Apache ANT
- Java JDK 1.5 or greater
- Apache ANT 1.8.0 or greater
- Android SDK [http://developer.android.com](http://developer.android.com)
- Apache Commons Codec [http://commons.apache.org/codec/](http://commons.apache.org/codec/)
@@ -29,11 +29,11 @@ Building
To create your cordova.jar, copy the commons codec:
mv commons-codec-1.6.jar framework/libs
mv commons-codec-1.7.jar framework/libs
then run in the framework directory:
android update project -p . -t android-15
android update project -p . -t android-17
ant jar
@@ -61,7 +61,7 @@ Project Commands
These commands live in a generated Cordova Android project.
./cordovap/debug [path] ..................... install to first device
./cordova/debug [path] ..................... install to first device
./cordova/emulate .......................... start avd (emulator) named default
./cordova/log .............................. starts logcat

View File

@@ -1 +1 @@
2.1.0
2.3.0

View File

@@ -1,4 +1,4 @@
#! /bin/sh
#! /bin/bash
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
@@ -23,17 +23,17 @@
#
set -e
if [ -n "$1" ] && [ "$1" == "-h" ]
if [ -z "$1" ] || [ "$1" == "-h" ]
then
echo 'usage: create path package activity'
echo "Make sure the Android SDK tools folder is in your PATH!"
exit 0
fi
BUILD_PATH=$( cd "$( dirname "$0" )/.." && pwd )
BUILD_PATH="$( cd "$( dirname "$0" )/.." && pwd )"
VERSION=$(cat "$BUILD_PATH"/VERSION)
PROJECT_PATH=${1:-'./example'}
PROJECT_PATH="${1:-'./example'}"
PACKAGE=${2:-"org.apache.cordova.example"}
ACTIVITY=${3:-"cordovaExample"}
@@ -58,13 +58,22 @@ function on_exit {
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 error occured. Deleting project..."
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 pattern=$1
local filename=$2
# Mac OS X requires -i argument
if [[ "$OSTYPE" =~ "darwin" ]]
@@ -77,30 +86,32 @@ function replace {
}
# we do not want the script to silently fail
trap 'previous_command=$this_command; this_command=$BASH_COMMAND' DEBUG
trap on_error ERR
trap on_exit EXIT
ANDROID_BIN=$( which android )
ANDROID_BIN="${ANDROID_BIN:=$( which android )}"
PACKAGE_AS_PATH=$(echo $PACKAGE | sed 's/\./\//g')
ACTIVITY_PATH="$PROJECT_PATH"/src/$PACKAGE_AS_PATH/$ACTIVITY.java
MANIFEST_PATH="$PROJECT_PATH"/AndroidManifest.xml
TARGET=$($ANDROID_BIN list targets | grep id: | tail -1 | cut -f 2 -d ' ' )
TARGET=$("$ANDROID_BIN" list targets | grep id: | tail -1 | cut -f 2 -d ' ' )
API_LEVEL=$("$ANDROID_BIN" list target | grep "API level:" | tail -n 1 | cut -f 2 -d ':' | tr -d ' ')
# if this a distribution release no need to build a jar
if [ ! -e "$BUILD_PATH"/cordova-$VERSION.jar ] && [ -d "$BUILD_PATH"/framework ]
then
# update the cordova-android framework for the desired target
$ANDROID_BIN update project --target $TARGET --path "$BUILD_PATH"/framework &> /dev/null
"$ANDROID_BIN" update project --target $TARGET --path "$BUILD_PATH"/framework &> /dev/null
if [ ! -e "$BUILD_PATH"/framework/libs/commons-codec-1.6.jar ]; then
if [ ! -e "$BUILD_PATH"/framework/libs/commons-codec-1.7.jar ]; then
# Use curl to get the jar (TODO: Support Apache Mirrors)
curl -OL http://mirror.symnds.com/software/Apache//commons/codec/binaries/commons-codec-1.6-bin.zip &> /dev/null
unzip commons-codec-1.6-bin.zip &> /dev/null
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.6/commons-codec-1.6.jar "$BUILD_PATH"/framework/libs
cp commons-codec-1.7/commons-codec-1.7.jar "$BUILD_PATH"/framework/libs
# cleanup yo
rm commons-codec-1.6-bin.zip && rm -rf commons-codec-1.6
rm commons-codec-1.7-bin.zip && rm -rf commons-codec-1.7
fi
# compile cordova.js and cordova.jar
@@ -108,7 +119,7 @@ then
fi
# create new android project
$ANDROID_BIN create project --target $TARGET --path "$PROJECT_PATH" --package $PACKAGE --activity $ACTIVITY &> /dev/null
"$ANDROID_BIN" create project --target $TARGET --path "$PROJECT_PATH" --package $PACKAGE --activity $ACTIVITY &> /dev/null
# copy project template
cp -r "$BUILD_PATH"/bin/templates/project/assets "$PROJECT_PATH"
@@ -134,13 +145,15 @@ replace "s/__ID__/${PACKAGE}/g" "$ACTIVITY_PATH"
cp "$BUILD_PATH"/bin/templates/project/AndroidManifest.xml "$MANIFEST_PATH"
replace "s/__ACTIVITY__/${ACTIVITY}/g" "$MANIFEST_PATH"
replace "s/__PACKAGE__/${PACKAGE}/g" "$MANIFEST_PATH"
replace "s/__APILEVEL__/${API_LEVEL}/g" "$MANIFEST_PATH"
# creating cordova folder and copying emulate/debug/log/launch scripts
# creating cordova folder and copying run/build/log/launch scripts
mkdir "$PROJECT_PATH"/cordova
createAppInfoJar
cp "$BUILD_PATH"/bin/templates/cordova/appinfo.jar "$PROJECT_PATH"/cordova/appinfo.jar
cp "$BUILD_PATH"/bin/templates/cordova/cordova "$PROJECT_PATH"/cordova/cordova
cp "$BUILD_PATH"/bin/templates/cordova/debug "$PROJECT_PATH"/cordova/debug
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/emulate "$PROJECT_PATH"/cordova/emulate
cp "$BUILD_PATH"/bin/templates/cordova/BOOM "$PROJECT_PATH"/cordova/BOOM
cp "$BUILD_PATH"/bin/templates/cordova/run "$PROJECT_PATH"/cordova/run

View File

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

View File

@@ -37,6 +37,10 @@ function setTarget() {
var targets = shell.Exec('android.bat list targets').StdOut.ReadAll().match(/id:\s\d+/g);
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);
return targets[targets.length - 1].replace(/API level: /, "");
}
function write(filename, contents) {
var fso=WScript.CreateObject("Scripting.FileSystemObject");
var f=fso.OpenTextFile(filename, 2, true);
@@ -49,10 +53,26 @@ function replaceInFile(filename, regexp, replacement) {
function exec(command) {
var oShell=shell.Exec(command);
while (oShell.Status == 0) {
if(!oShell.StdOut.AtEndOfStream) {
var line = oShell.StdOut.ReadLine();
// XXX: Change to verbose mode
// WScript.StdOut.WriteLine(line);
}
WScript.sleep(100);
}
}
function createAppInfoJar() {
if(!fso.FileExists(ROOT+"\\bin\\templates\\cordova\\appinfo.jar")) {
WScript.Echo("Creating appinfo.jar...");
var cur = shell.CurrentDirectory;
shell.CurrentDirectory = ROOT+"\\bin\\templates\\cordova\\ApplicationInfo";
exec("javac ApplicationInfo.java");
exec("jar -cfe ..\\appinfo.jar ApplicationInfo ApplicationInfo.class");
shell.CurrentDirectory = cur;
}
}
function cleanup() {
// Cleanup
// if(fso.FileExists(ROOT + '\\framework\\libs\\commons-codec-1.6.jar')) {
@@ -68,11 +88,11 @@ function cleanup() {
}
function downloadCommonsCodec() {
if (!fso.FileExists(ROOT + '\\framework\\libs\\commons-codec-1.6.jar')) {
if (!fso.FileExists(ROOT + '\\framework\\libs\\commons-codec-1.7.jar')) {
// We need the .jar
var url = 'http://mirror.symnds.com/software/Apache//commons/codec/binaries/commons-codec-1.6-bin.zip';
var url = 'http://archive.apache.org/dist/commons/codec/binaries/commons-codec-1.7-bin.zip';
var libsPath = ROOT + '\\framework\\libs';
var savePath = libsPath + '\\commons-codec-1.6-bin.zip';
var savePath = libsPath + '\\commons-codec-1.7-bin.zip';
if (!fso.FileExists(savePath)) {
if(!fso.FolderExists(ROOT + '\\framework\\libs')) {
fso.CreateFolder(libsPath);
@@ -99,11 +119,11 @@ function downloadCommonsCodec() {
target.CopyHere(source, 256);
// Move the jar into libs
fso.MoveFile(ROOT + '\\framework\\libs\\commons-codec-1.6\\commons-codec-1.6.jar', ROOT + '\\framework\\libs\\commons-codec-1.6.jar');
fso.MoveFile(ROOT + '\\framework\\libs\\commons-codec-1.7\\commons-codec-1.7.jar', ROOT + '\\framework\\libs\\commons-codec-1.7.jar');
// Clean up
fso.DeleteFile(ROOT + '\\framework\\libs\\commons-codec-1.6-bin.zip');
fso.DeleteFolder(ROOT + '\\framework\\libs\\commons-codec-1.6', true);
fso.DeleteFile(ROOT + '\\framework\\libs\\commons-codec-1.7-bin.zip');
fso.DeleteFolder(ROOT + '\\framework\\libs\\commons-codec-1.7', true);
}
}
@@ -129,58 +149,66 @@ var PACKAGE_AS_PATH=PACKAGE.replace(/\./g, '\\');
var ACTIVITY_PATH=PROJECT_PATH+'\\src\\'+PACKAGE_AS_PATH+'\\'+ACTIVITY+'.java';
var MANIFEST_PATH=PROJECT_PATH+'\\AndroidManifest.xml';
var TARGET=setTarget();
var API_LEVEL=setApiLevel();
var VERSION=read(ROOT+'\\VERSION').replace(/\r\n/,'').replace(/\n/,'');
// create the project
WScript.Echo("Creating new android project...");
exec('android.bat create project --target '+TARGET+' --path '+PROJECT_PATH+' --package '+PACKAGE+' --activity '+ACTIVITY);
// build from source. distro should have these files
if (!fso.FileExists(ROOT+'\\cordova-'+VERSION+'.jar') &&
!fso.FileExists(ROOT+'\\cordova-'+VERSION+'.js')) {
WScript.Echo("Building jar and js files...");
// update the cordova framework project to a target that exists on this machine
exec('android.bat update project --target '+TARGET+' --path '+ROOT+'\\framework');
// pull down commons codec if necessary
downloadCommonsCodec();
exec('ant.bat -f '+ ROOT +'\\framework\\build.xml jar');
exec('ant.bat -f \"'+ ROOT +'\\framework\\build.xml\" jar');
}
// copy in the project template
exec('%comspec% /c xcopy '+ ROOT + '\\bin\\templates\\project\\res '+PROJECT_PATH+'\\res\\ /E /Y');
exec('%comspec% /c xcopy '+ ROOT + '\\bin\\templates\\project\\assets '+PROJECT_PATH+'\\assets\\ /E /Y');
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\project\\AndroidManifest.xml ' + PROJECT_PATH + '\\AndroidManifest.xml /Y');
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\project\\Activity.java '+ ACTIVITY_PATH +' /Y');
WScript.Echo("Copying template files...");
exec('%comspec% /c xcopy "'+ ROOT + '"\\bin\\templates\\project\\res '+PROJECT_PATH+'\\res\\ /E /Y');
exec('%comspec% /c xcopy "'+ ROOT + '"\\bin\\templates\\project\\assets '+PROJECT_PATH+'\\assets\\ /E /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\project\\AndroidManifest.xml ' + PROJECT_PATH + '\\AndroidManifest.xml /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\project\\Activity.java '+ ACTIVITY_PATH +' /Y');
// check if we have the source or the distro files
WScript.Echo("Copying js, jar & config.xml files...");
if(fso.FolderExists(ROOT + '\\framework')) {
exec('%comspec% /c copy '+ROOT+'\\framework\\assets\\www\\cordova-'+VERSION+'.js '+PROJECT_PATH+'\\assets\\www\\cordova-'+VERSION+'.js /Y');
exec('%comspec% /c copy '+ROOT+'\\framework\\cordova-'+VERSION+'.jar '+PROJECT_PATH+'\\libs\\cordova-'+VERSION+'.jar /Y');
exec('%comspec% /c copy "'+ROOT+'"\\framework\\assets\\www\\cordova-'+VERSION+'.js '+PROJECT_PATH+'\\assets\\www\\cordova-'+VERSION+'.js /Y');
exec('%comspec% /c copy "'+ROOT+'"\\framework\\cordova-'+VERSION+'.jar '+PROJECT_PATH+'\\libs\\cordova-'+VERSION+'.jar /Y');
fso.CreateFolder(PROJECT_PATH + '\\res\\xml');
exec('%comspec% /c copy '+ROOT+'\\framework\\res\\xml\\config.xml ' + PROJECT_PATH + '\\res\\xml\\config.xml /Y');
exec('%comspec% /c copy "'+ROOT+'"\\framework\\res\\xml\\config.xml ' + PROJECT_PATH + '\\res\\xml\\config.xml /Y');
} else {
// copy in cordova.js
exec('%comspec% /c copy '+ROOT+'\\cordova-'+VERSION+'.js '+PROJECT_PATH+'\\assets\\www\\cordova-'+VERSION+'.js /Y');
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');
exec('%comspec% /c copy "'+ROOT+'"\\cordova-'+VERSION+'.jar '+PROJECT_PATH+'\\libs\\cordova-'+VERSION+'.jar /Y');
// copy in xml
fso.CreateFolder(PROJECT_PATH + '\\res\\xml');
exec('%comspec% /c copy '+ROOT+'\\xml\\config.xml ' + PROJECT_PATH + '\\res\\xml\\config.xml /Y');
exec('%comspec% /c copy "'+ROOT+'"\\xml\\config.xml ' + PROJECT_PATH + '\\res\\xml\\config.xml /Y');
}
// copy cordova scripts
fso.CreateFolder(PROJECT_PATH + '\\cordova');
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\appinfo.jar ' + PROJECT_PATH + '\\cordova\\appinfo.jar /Y');
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\cordova.js ' + PROJECT_PATH + '\\cordova\\cordova.js /Y');
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\cordova.bat ' + PROJECT_PATH + '\\cordova\\cordova.bat /Y');
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\clean.bat ' + PROJECT_PATH + '\\cordova\\clean.bat /Y');
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\debug.bat ' + PROJECT_PATH + '\\cordova\\debug.bat /Y');
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\log.bat ' + PROJECT_PATH + '\\cordova\\log.bat /Y');
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\emulate.bat ' + PROJECT_PATH + '\\cordova\\emulate.bat /Y');
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\BOOM.bat ' + PROJECT_PATH + '\\cordova\\BOOM.bat /Y');
createAppInfoJar();
WScript.Echo("Copying cordova command tools...");
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\appinfo.jar ' + PROJECT_PATH + '\\cordova\\appinfo.jar /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\cordova.js ' + PROJECT_PATH + '\\cordova\\cordova.js /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\cordova.bat ' + PROJECT_PATH + '\\cordova\\cordova.bat /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\clean.bat ' + PROJECT_PATH + '\\cordova\\clean.bat /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\build.bat ' + PROJECT_PATH + '\\cordova\\build.bat /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\log.bat ' + PROJECT_PATH + '\\cordova\\log.bat /Y');
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\run.bat ' + PROJECT_PATH + '\\cordova\\run.bat /Y');
// interpolate the activity name and package
WScript.Echo("Updating AndroidManifest.xml and Main Activity...");
replaceInFile(ACTIVITY_PATH, /__ACTIVITY__/, ACTIVITY);
replaceInFile(ACTIVITY_PATH, /__ID__/, PACKAGE);
replaceInFile(MANIFEST_PATH, /__ACTIVITY__/, ACTIVITY);
replaceInFile(MANIFEST_PATH, /__PACKAGE__/, PACKAGE);
replaceInFile(MANIFEST_PATH, /__APILEVEL__/, API_LEVEL);
cleanup();

View File

@@ -1,3 +1,20 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import javax.xml.parsers.DocumentBuilder;

View File

@@ -1,7 +0,0 @@
#!/bin/bash
set -e
CORDOVA_PATH=$( cd "$( dirname "$0" )" && pwd )
bash $CORDOVA_PATH/cordova BOOM

View File

@@ -1 +0,0 @@
%~dp0\cordova.bat BOOM

Binary file not shown.

24
bin/templates/cordova/build Executable file
View File

@@ -0,0 +1,24 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#!/bin/bash
set -e
CORDOVA_PATH=$( cd "$( dirname "$0" )" && pwd )
bash "$CORDOVA_PATH"/cordova build

View File

@@ -0,0 +1,18 @@
:: Licensed to the Apache Software Foundation (ASF) under one
:: or more contributor license agreements. See the NOTICE file
:: distributed with this work for additional information
:: regarding copyright ownership. The ASF licenses this file
:: to you under the Apache License, Version 2.0 (the
:: "License"); you may not use this file except in compliance
:: with the License. You may obtain a copy of the License at
::
:: http://www.apache.org/licenses/LICENSE-2.0
::
:: Unless required by applicable law or agreed to in writing,
:: software distributed under the License is distributed on an
:: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
:: KIND, either express or implied. See the License for the
:: specific language governing permissions and limitations
:: under the License.
%~dp0\cordova.bat build

View File

@@ -1,7 +1,24 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#!/bin/bash
set -e
CORDOVA_PATH=$( cd "$( dirname "$0" )" && pwd )
bash $CORDOVA_PATH/cordova clean
bash "$CORDOVA_PATH"/cordova clean

View File

@@ -1 +1,18 @@
:: Licensed to the Apache Software Foundation (ASF) under one
:: or more contributor license agreements. See the NOTICE file
:: distributed with this work for additional information
:: regarding copyright ownership. The ASF licenses this file
:: to you under the Apache License, Version 2.0 (the
:: "License"); you may not use this file except in compliance
:: with the License. You may obtain a copy of the License at
::
:: http://www.apache.org/licenses/LICENSE-2.0
::
:: Unless required by applicable law or agreed to in writing,
:: software distributed under the License is distributed on an
:: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
:: KIND, either express or implied. See the License for the
:: specific language governing permissions and limitations
:: under the License.
%~dp0\cordova.bat clean

View File

@@ -1,11 +1,28 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#!/bin/bash
set -e
PROJECT_PATH=$( cd "$( dirname "$0" )/.." && pwd )
function check_devices {
local devices=`adb devices | awk '/List of devices attached/ { while(getline > 0) { print }}'`
# FIXME
local devices=`adb devices | awk '/List of devices attached/ { while(getline > 0) { print }}' | grep device`
if [ -z "$devices" ] ; then
echo "1"
else
@@ -20,7 +37,6 @@ function emulate {
# Do not launch an emulator if there is already one running or if a device is attached
if [ $(check_devices) == 0 ] ; then
echo "Device attached or emulator already running"
return
fi
@@ -45,8 +61,9 @@ function emulate {
do
echo "$i) ${avd_list[$i]}"
done
echo -n "> "
read avd_id
read -t 5 -p "> " avd_id
# default value if input timeout
if [ $avd_id -eq 1000 ] ; then avd_id=0 ; fi
done
emulator -cpu-delay 0 -no-boot-anim -cache /tmp/cache -avd ${avd_list[$avd_id]} 1> /dev/null 2>&1 &
fi
@@ -56,30 +73,87 @@ function emulate {
function clean {
ant clean
}
# has to be used independently and not in conjuction with other commands
# has to be used independently and not in conjunction with other commands
function log {
adb logcat
}
function debug {
if [ $(check_devices) == 0 ] ; then
ant debug install
function run {
clean && emulate && wait_for_device && install && launch
}
function install {
declare -a devices=($(adb devices | awk '/List of devices attached/ { while(getline > 0) { print }}' | grep device | cut -f 1))
local device_id="1000" #FIXME: hopefully user does not have 1000 AVDs
if [ ${#devices[@]} == 0 ]
then
# should not reach here. Emulator should launch or device should be attached
echo "Emulator not running or device not attached. Could not install debug package"
exit 70
fi
if [ ${#devices[@]} == 1 ]
then
export ANDROID_SERIAL=${devices[0]}
# User has more than 1 AVD
elif [ ${#devices[@]} -gt 1 ]
then
while [ -z ${devices[$device_id]} ]
do
echo "Choose from one of the following devices/emulators [0 to $((${#devices[@]}-1))]:"
for(( i = 0 ; i < ${#devices[@]} ; i++ ))
do
echo "$i) ${devices[$i]}"
done
read -t 5 -p "> " device_id
# default value if input timeout
if [ $device_id -eq 1000 ] ; then device_id=0 ; fi
done
export ANDROID_SERIAL=${devices[$device_id]}
fi
ant debug install
}
function build {
ant debug
}
function release {
ant release
}
function wait_for_device {
local i="0"
echo -n "Waiting for device..."
while [ $i -lt 300 ]
do
if [ $(check_devices) -eq 0 ]
then
break
else
sleep 1
i=$[i+1]
echo -n "."
fi
done
# Device timeout: emulator has not started in time or device not attached
if [ $i -eq 300 ]
then
echo "device timeout!"
exit 69
else
ant debug
echo "##################################################################"
echo "# Plug in your device or launch an emulator with cordova/emulate #"
echo "##################################################################"
echo "connected!"
fi
}
function launch {
local launch_str=$(java -jar $PROJECT_PATH/cordova/appinfo.jar $PROJECT_PATH/AndroidManifest.xml)
local launch_str=$(java -jar "$PROJECT_PATH"/cordova/appinfo.jar "$PROJECT_PATH"/AndroidManifest.xml)
adb shell am start -n $launch_str
}
function BOOM {
clean && debug && launch
}
# TODO parse arguments
(cd $PROJECT_PATH && $1)
(cd "$PROJECT_PATH" && $1)

View File

@@ -1,3 +1,20 @@
:: Licensed to the Apache Software Foundation (ASF) under one
:: or more contributor license agreements. See the NOTICE file
:: distributed with this work for additional information
:: regarding copyright ownership. The ASF licenses this file
:: to you under the Apache License, Version 2.0 (the
:: "License"); you may not use this file except in compliance
:: with the License. You may obtain a copy of the License at
::
:: http://www.apache.org/licenses/LICENSE-2.0
::
:: Unless required by applicable law or agreed to in writing,
:: software distributed under the License is distributed on an
:: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
:: KIND, either express or implied. See the License for the
:: specific language governing permissions and limitations
:: under the License.
@ECHO OFF
IF NOT DEFINED JAVA_HOME GOTO MISSING
FOR %%X in (java.exe ant.bat android.bat) do (

View File

@@ -1,3 +1,20 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
var ROOT = WScript.ScriptFullName.split('\\cordova\\cordova.js').join(''),
shell=WScript.CreateObject("WScript.Shell");
@@ -17,17 +34,19 @@ function exec(command) {
return output;
}
function emulator_running() {
function device_running() {
var local_devices = shell.Exec("%comspec% /c adb devices").StdOut.ReadAll();
if(local_devices.match(/emulator/)) {
if(local_devices.match(/\w+\tdevice/)) {
WScript.Echo("Yes");
return true;
}
WScript.Echo("No");
return false;
}
function emulate() {
// don't run emulator if a device is plugged in or if emulator is already running
if(emulator_running()) {
WScript.Echo("Device or Emulator already running!");
if(device_running()) {
//WScript.Echo("Device or Emulator already running!");
return;
}
var oExec = shell.Exec("%comspec% /c android.bat list avd");
@@ -67,18 +86,18 @@ function emulate() {
}
function clean() {
WScript.Echo("Cleaning project...");
exec("%comspec% /c ant.bat clean -f "+ROOT+"\\build.xml 2>&1");
}
function debug() {
if(emulator_running()) {
exec("%comspec% /c ant.bat debug install -f "+ROOT+"\\build.xml 2>&1");
} else {
exec("%comspec% /c ant.bat debug -f "+ROOT+"\\build.xml 2>&1");
WScript.Echo("##################################################################");
WScript.Echo("# Plug in your device or launch an emulator with cordova/emulate #");
WScript.Echo("##################################################################");
}
function build() {
WScript.Echo("Building project...");
exec("%comspec% /c ant.bat debug -f "+ROOT+"\\build.xml 2>&1");
}
function install() {
WScript.Echo("Building/Installing project...");
exec("%comspec% /c ant.bat debug install -f "+ROOT+"\\build.xml 2>&1");
}
function log() {
@@ -86,14 +105,28 @@ function log() {
}
function launch() {
WScript.Echo("Launching app...");
var launch_str=exec("%comspec% /c java -jar "+ROOT+"\\cordova\\appinfo.jar "+ROOT+"\\AndroidManifest.xml");
//WScript.Echo(launch_str);
exec("%comspec% /c adb shell am start -n "+launch_str+" 2>&1");
}
function BOOM() {
function run() {
var i=0;
clean();
debug();
emulate();
WScript.Stdout.Write('Waiting for device...');
while(!device_running() && i < 300) {
WScript.Stdout.Write('.');
WScript.sleep(1000);
i += 1;
}
if(i == 300) {
WScript.Stderr.WriteLine("device/emulator timeout!");
} else {
WScript.Stdout.WriteLine("connected!");
}
install();
launch();
}
var args = WScript.Arguments;

View File

@@ -1,7 +0,0 @@
#!/bin/bash
set -e
CORDOVA_PATH=$( cd "$( dirname "$0" )" && pwd )
bash $CORDOVA_PATH/cordova debug

View File

@@ -1 +0,0 @@
%~dp0\cordova.bat debug

View File

@@ -1,7 +0,0 @@
#!/bin/bash
set -e
CORDOVA_PATH=$( cd "$( dirname "$0" )" && pwd )
bash $CORDOVA_PATH/cordova emulate

View File

@@ -1 +0,0 @@
%~dp0\cordova.bat emulate

View File

@@ -1,7 +1,24 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#!/bin/bash
set -e
PROJECT_PATH=$( cd "$( dirname "$0" )/.." && pwd )
CORDOVA_PATH=$( cd "$( dirname "$0" )/.." && pwd )
bash $PROJECT_PATH/cordova/cordova log
bash "$CORDOVA_PATH"/cordova/cordova log

View File

@@ -1 +1,18 @@
:: Licensed to the Apache Software Foundation (ASF) under one
:: or more contributor license agreements. See the NOTICE file
:: distributed with this work for additional information
:: regarding copyright ownership. The ASF licenses this file
:: to you under the Apache License, Version 2.0 (the
:: "License"); you may not use this file except in compliance
:: with the License. You may obtain a copy of the License at
::
:: http://www.apache.org/licenses/LICENSE-2.0
::
:: Unless required by applicable law or agreed to in writing,
:: software distributed under the License is distributed on an
:: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
:: KIND, either express or implied. See the License for the
:: specific language governing permissions and limitations
:: under the License.
%~dp0\cordova.bat log

24
bin/templates/cordova/release Executable file
View File

@@ -0,0 +1,24 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#!/bin/bash
set -e
CORDOVA_PATH=$( cd "$( dirname "$0" )" && pwd )
bash "$CORDOVA_PATH"/cordova release

24
bin/templates/cordova/run Executable file
View File

@@ -0,0 +1,24 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#!/bin/bash
set -e
CORDOVA_PATH=$( cd "$( dirname "$0" )" && pwd )
bash "$CORDOVA_PATH"/cordova run

View File

@@ -0,0 +1 @@
%~dp0\cordova.bat run

View File

@@ -19,7 +19,6 @@
package __ID__;
import android.app.Activity;
import android.os.Bundle;
import org.apache.cordova.*;
@@ -29,7 +28,10 @@ public class __ACTIVITY__ extends DroidGap
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
super.loadUrl("file:///android_asset/www/index.html");
Config.init(this);
// 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">
package="__PACKAGE__" android:versionName="1.1" android:versionCode="5" android:hardwareAccelerated="true">
<supports-screens
android:largeScreens="true"
android:normalScreens="true"
@@ -47,8 +47,10 @@
<application android:icon="@drawable/icon" android:label="@string/app_name"
android:hardwareAccelerated="true"
android:debuggable="true">
<activity android:name="__ACTIVITY__" android:label="@string/app_name"
android:theme="@android:style/Theme.Black.NoTitleBar"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -57,5 +59,5 @@
</activity>
</application>
<uses-sdk android:minSdkVersion="7" />
<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="__APILEVEL__"/>
</manifest>

View File

@@ -17,13 +17,13 @@
* under the License.
*/
* {
-webkit-touch-callout: none; /* prevent callout to copy image, etc when tap to hold */
-webkit-text-size-adjust: none; /* prevent webkit from resizing text to fit */
-webkit-tap-highlight-color: rgba(0,0,0,0); /* make transparent link selection, adjust last value opacity 0 to 1.0 */
-webkit-user-select: none; /* prevent copy paste, to allow, change 'none' to 'text' */
}
body {
-webkit-touch-callout: none; /* prevent callout to copy image, etc when tap to hold */
-webkit-text-size-adjust: none; /* prevent webkit from resizing text to fit */
-webkit-user-select: none; /* prevent copy paste, to allow, change 'none' to 'text' */
background-color:#E4E4E4;
background-image:linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%);
background-image:-webkit-linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%);

View File

@@ -33,7 +33,7 @@
<p class="event received">Device is Ready</p>
</div>
</div>
<script type="text/javascript" src="cordova-2.1.0.js"></script>
<script type="text/javascript" src="cordova-2.3.0.js"></script>
<script type="text/javascript" src="js/index.js"></script>
<script type="text/javascript">
app.initialize();

View File

@@ -1,3 +1,20 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
var build_path = __dirname + '/../..',
project_path = '/tmp/example',
package_name = 'org.apache.cordova.example',
@@ -58,7 +75,7 @@ create_project.on('exit', function(code) {
// make sure main Activity was added
path.exists(util.format('%s/src/%s/%s.java', project_path, package_as_path, project_name), function(exists) {
assert(exists, 'Activity did not get created');
// TODO check that package name and activity name were substitued properly
// TODO check that package name and activity name were substituted properly
});
// make sure plugins.xml was added

View File

@@ -1,3 +1,20 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
var build_path = __dirname + '/../..'
project_path = process.env.Temp + '\\example',
package_name = 'org.apache.cordova.example',
@@ -68,17 +85,12 @@ create_project.on('exit', function(code) {
// make sure main Activity was added
path.exists(util.format('%s/src/%s/%s.java', project_path, package_as_path, project_name), function(exists) {
assert(exists, 'Activity did not get created');
// TODO check that package name and activity name were substitued properly
// TODO check that package name and activity name were substituted properly
});
// make sure plugins.xml was added
path.exists(util.format('%s/res/xml/plugins.xml', project_path), function(exists) {
assert(exists, 'plugins.xml did not get created');
});
// make sure cordova.xml was added
path.exists(util.format('%s/res/xml/cordova.xml', project_path), function(exists) {
assert(exists, 'plugins.xml did not get created');
// make sure config.xml was added
path.exists(util.format('%s/res/xml/config.xml', project_path), function(exists) {
assert(exists, 'config.xml did not get created');
});
// make sure cordova.jar was added

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -26,9 +26,37 @@
</filterchain>
</loadfile>
<!-- The local.properties file is created and updated by the 'android' tool.
It contains the path to the SDK. It should *NOT* be checked into
Version Control Systems. -->
<!-- check that the version of ant is at least 1.8.0, as is needed
for the dblQuote property -->
<antversion property="thisantversion" atleast="1.8.0" />
<fail message="The required minimum version of ant is 1.8.0, you have ${ant.version}"
unless="thisantversion" />
<!-- check that commons codec is available. You should copy the codec jar to
framework/libs, as it is not included in the Cordova distribution.
The name of the jar file in framework/libs does not matter. -->
<available classname="org.apache.commons.codec.binary.Base64"
property="exists.base64"
ignoresystemclasses="true">
<classpath>
<pathelement path="${classpath}" />
<fileset dir="libs">
<include name="*.jar" />
</fileset>
</classpath>
</available>
<fail message="You need to put a copy of Apache Commons Codec jar in the framework/libs directory"
unless="exists.base64" />
<!-- The local.properties file is created and updated by the 'android'
tool. (For example "sdkdir/tools/android update project -p ." inside
of this directory where the AndroidManifest.xml file exists. This
properties file that gets built contains the path to the SDK. It
should *NOT* be checked into Version Control Systems since it holds
data about the local machine. -->
<available file="local.properties" property="exists.local.properties" />
<fail message="You need to create the file 'local.properties' by running 'android update project -p .' here."
unless="exists.local.properties" />
<loadproperties srcFile="local.properties" />
<!-- The ant.properties file can be created by you. It is only edited by the
@@ -66,13 +94,13 @@
application and should be checked into Version Control Systems. -->
<loadproperties srcFile="project.properties" />
<!-- quick check on sdk.dir -->
<!-- quick check on sdk.dir -->
<fail
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project'"
unless="sdk.dir"
/>
<!-- version-tag: custom -->
<!-- version-tag: custom -->
<!-- extension targets. Uncomment the ones where you want to do custom work
in between standard targets -->
<!--
@@ -106,7 +134,7 @@
In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
in order to avoid having your file be overridden by tools such as "android update project"
-->
<import file="${sdk.dir}/tools/ant/build.xml" />
<import file="${sdk.dir}/tools/ant/build.xml" />
<!-- Combine JavaScript files into one cordova-uncompressed.js file. -->
<target name="build-javascript" depends="clean">

View File

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

View File

@@ -29,8 +29,11 @@
<!-- <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="false" />
<preference name="useBrowserHistory" value="true" />
<preference name="exit-on-suspend" value="false" />
<plugins>
<plugin name="App" value="org.apache.cordova.App"/>
@@ -45,12 +48,13 @@
<plugin name="NetworkStatus" value="org.apache.cordova.NetworkManager"/>
<plugin name="Notification" value="org.apache.cordova.Notification"/>
<plugin name="Storage" value="org.apache.cordova.Storage"/>
<plugin name="Temperature" value="org.apache.cordova.TempListener"/>
<plugin name="FileTransfer" value="org.apache.cordova.FileTransfer"/>
<plugin name="Capture" value="org.apache.cordova.Capture"/>
<plugin name="Battery" value="org.apache.cordova.BatteryListener"/>
<plugin name="SplashScreen" value="org.apache.cordova.SplashScreen"/>
<plugin name="Echo" value="org.apache.cordova.Echo" />
<plugin name="Globalization" value="org.apache.cordova.Globalization"/>
<plugin name="InAppBrowser" value="org.apache.cordova.InAppBrowser"/>
</plugins>
</cordova>

View File

@@ -19,24 +19,29 @@
package org.apache.cordova;
import java.util.List;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
/**
* This class listens to the accelerometer sensor and stores the latest
* acceleration values x,y,z.
*/
public class AccelListener extends Plugin implements SensorEventListener {
public class AccelListener extends CordovaPlugin implements SensorEventListener {
public static int STOPPED = 0;
public static int STARTING = 1;
@@ -51,7 +56,7 @@ public class AccelListener extends Plugin implements SensorEventListener {
private SensorManager sensorManager; // Sensor manager
private Sensor mSensor; // Acceleration sensor returned by sensor manager
private String callbackId; // Keeps track of the single "start" callback ID passed in from JS
private CallbackContext callbackContext; // Keeps track of the JS callback context.
/**
* Create an accelerometer listener.
@@ -69,29 +74,25 @@ public class AccelListener extends Plugin implements SensorEventListener {
* get file paths associated with the Activity.
*
* @param cordova The context of the main Activity.
* @param webView The associated CordovaWebView.
*/
public void setContext(CordovaInterface cordova) {
super.setContext(cordova);
@Override
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
super.initialize(cordova, webView);
this.sensorManager = (SensorManager) cordova.getActivity().getSystemService(Context.SENSOR_SERVICE);
}
/**
* Executes the request and returns PluginResult.
*
* Executes the request.
*
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param args The exec() arguments.
* @param callbackId The callback id used when calling back into JavaScript.
* @return A PluginResult object with a status and message.
* @return Whether the action was valid.
*/
public PluginResult execute(String action, JSONArray args, String callbackId) {
PluginResult.Status status = PluginResult.Status.NO_RESULT;
String message = "";
PluginResult result = new PluginResult(status, message);
result.setKeepCallback(true);
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) {
if (action.equals("start")) {
this.callbackId = callbackId;
this.callbackContext = callbackContext;
if (this.status != AccelListener.RUNNING) {
// If not running, then this is an async call, so don't worry about waiting
// We drop the callback onto our stack, call start, and let start and the sensor callback fire off the callback down the road
@@ -104,9 +105,13 @@ public class AccelListener extends Plugin implements SensorEventListener {
}
} else {
// Unsupported action
return new PluginResult(PluginResult.Status.INVALID_ACTION);
return false;
}
return result;
PluginResult result = new PluginResult(PluginResult.Status.NO_RESULT, "");
result.setKeepCallback(true);
callbackContext.sendPluginResult(result);
return true;
}
/**
@@ -147,21 +152,15 @@ public class AccelListener extends Plugin implements SensorEventListener {
this.fail(AccelListener.ERROR_FAILED_TO_START, "No sensors found to register accelerometer listening to.");
return this.status;
}
// Wait until running
long timeout = 2000;
while ((this.status == STARTING) && (timeout > 0)) {
timeout = timeout - 100;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (timeout == 0) {
this.setStatus(AccelListener.ERROR_FAILED_TO_START);
this.fail(AccelListener.ERROR_FAILED_TO_START, "Accelerometer could not be started.");
}
// Set a timeout callback on the main thread.
Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(new Runnable() {
public void run() {
AccelListener.this.timeout();
}
}, 2000);
return this.status;
}
@@ -176,6 +175,18 @@ public class AccelListener extends Plugin implements SensorEventListener {
this.accuracy = SensorManager.SENSOR_STATUS_UNRELIABLE;
}
/**
* Returns an error if the sensor hasn't started.
*
* Called two seconds after starting the listener.
*/
private void timeout() {
if (this.status == AccelListener.STARTING) {
this.setStatus(AccelListener.ERROR_FAILED_TO_START);
this.fail(AccelListener.ERROR_FAILED_TO_START, "Accelerometer could not be started.");
}
}
/**
* Called when the accuracy of the sensor has changed.
*
@@ -224,6 +235,16 @@ public class AccelListener extends Plugin implements SensorEventListener {
}
}
/**
* Called when the view navigates.
*/
@Override
public void onReset() {
if (this.status == AccelListener.RUNNING) {
this.stop();
}
}
// Sends an error back to JS
private void fail(int code, String message) {
// Error object
@@ -236,16 +257,14 @@ public class AccelListener extends Plugin implements SensorEventListener {
}
PluginResult err = new PluginResult(PluginResult.Status.ERROR, errorObj);
err.setKeepCallback(true);
this.error(err, this.callbackId);
callbackContext.sendPluginResult(err);
}
private void win() {
// Success return object
PluginResult result = new PluginResult(PluginResult.Status.OK, this.getAccelerationJSON());
result.setKeepCallback(true);
this.success(result, this.callbackId);
callbackContext.sendPluginResult(result);
}
private void setStatus(int status) {

View File

@@ -19,28 +19,30 @@
package org.apache.cordova;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.LOG;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
/**
* This class exposes methods in DroidGap that can be called from JavaScript.
*/
public class App extends Plugin {
public class App extends CordovaPlugin {
/**
* Executes the request and returns PluginResult.
*
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param callbackId The callback id used when calling back into JavaScript.
* @return A PluginResult object with a status and message.
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param callbackContext The callback context from which we were invoked.
* @return A PluginResult object with a status and message.
*/
public PluginResult execute(String action, JSONArray args, String callbackId) {
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
PluginResult.Status status = PluginResult.Status.OK;
String result = "";
@@ -62,7 +64,7 @@ public class App extends Plugin {
this.loadUrl(args.getString(0), args.optJSONObject(1));
}
else if (action.equals("cancelLoadUrl")) {
this.cancelLoadUrl();
//this.cancelLoadUrl();
}
else if (action.equals("clearHistory")) {
this.clearHistory();
@@ -79,9 +81,11 @@ public class App extends Plugin {
else if (action.equals("exitApp")) {
this.exitApp();
}
return new PluginResult(status, result);
callbackContext.sendPluginResult(new PluginResult(status, result));
return true;
} catch (JSONException e) {
return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
return false;
}
}
@@ -156,14 +160,6 @@ public class App extends Plugin {
this.webView.showWebPage(url, openExternal, clearHistory, params);
}
/**
* Cancel loadUrl before it has been loaded (Only works on a CordovaInterface class)
*/
@Deprecated
public void cancelLoadUrl() {
this.cordova.cancelLoadUrl();
}
/**
* Clear page history for the app.
*/

View File

@@ -18,12 +18,14 @@
*/
package org.apache.cordova;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import android.content.Context;
import android.media.AudioManager;
import java.util.ArrayList;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
@@ -40,7 +42,7 @@ import java.util.HashMap;
* android_asset: file name must start with /android_asset/sound.mp3
* sdcard: file name is just sound.mp3
*/
public class AudioHandler extends Plugin {
public class AudioHandler extends CordovaPlugin {
public static String TAG = "AudioHandler";
HashMap<String, AudioPlayer> players; // Audio player object
@@ -58,75 +60,64 @@ public class AudioHandler extends Plugin {
* Executes the request and returns PluginResult.
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param callbackId The callback id used when calling back into JavaScript.
* @param callbackContext The callback context used when calling back into JavaScript.
* @return A PluginResult object with a status and message.
*/
public PluginResult execute(String action, JSONArray args, String callbackId) {
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
PluginResult.Status status = PluginResult.Status.OK;
String result = "";
try {
if (action.equals("startRecordingAudio")) {
this.startRecordingAudio(args.getString(0), FileUtils.stripFileProtocol(args.getString(1)));
}
else if (action.equals("stopRecordingAudio")) {
this.stopRecordingAudio(args.getString(0));
}
else if (action.equals("startPlayingAudio")) {
this.startPlayingAudio(args.getString(0), FileUtils.stripFileProtocol(args.getString(1)));
}
else if (action.equals("seekToAudio")) {
this.seekToAudio(args.getString(0), args.getInt(1));
}
else if (action.equals("pausePlayingAudio")) {
this.pausePlayingAudio(args.getString(0));
}
else if (action.equals("stopPlayingAudio")) {
this.stopPlayingAudio(args.getString(0));
} else if (action.equals("setVolume")) {
try {
this.setVolume(args.getString(0), Float.parseFloat(args.getString(1)));
} catch (NumberFormatException nfe) {
//no-op
}
} else if (action.equals("getCurrentPositionAudio")) {
float f = this.getCurrentPositionAudio(args.getString(0));
return new PluginResult(status, f);
}
else if (action.equals("getDurationAudio")) {
float f = this.getDurationAudio(args.getString(0), args.getString(1));
return new PluginResult(status, f);
}
else if (action.equals("create")) {
String id = args.getString(0);
String src = FileUtils.stripFileProtocol(args.getString(1));
AudioPlayer audio = new AudioPlayer(this, id, src);
this.players.put(id, audio);
}
else if (action.equals("release")) {
boolean b = this.release(args.getString(0));
return new PluginResult(status, b);
}
return new PluginResult(status, result);
} catch (JSONException e) {
e.printStackTrace();
return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
if (action.equals("startRecordingAudio")) {
this.startRecordingAudio(args.getString(0), FileUtils.stripFileProtocol(args.getString(1)));
}
}
/**
* Identifies if action to be executed returns a value and should be run synchronously.
* @param action The action to execute
* @return T=returns value
*/
public boolean isSynch(String action) {
if (action.equals("getCurrentPositionAudio")) {
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)));
}
else if (action.equals("seekToAudio")) {
this.seekToAudio(args.getString(0), args.getInt(1));
}
else if (action.equals("pausePlayingAudio")) {
this.pausePlayingAudio(args.getString(0));
}
else if (action.equals("stopPlayingAudio")) {
this.stopPlayingAudio(args.getString(0));
} else if (action.equals("setVolume")) {
try {
this.setVolume(args.getString(0), Float.parseFloat(args.getString(1)));
} catch (NumberFormatException nfe) {
//no-op
}
} else if (action.equals("getCurrentPositionAudio")) {
float f = this.getCurrentPositionAudio(args.getString(0));
callbackContext.sendPluginResult(new PluginResult(status, f));
return true;
}
else if (action.equals("getDurationAudio")) {
float f = this.getDurationAudio(args.getString(0), args.getString(1));
callbackContext.sendPluginResult(new PluginResult(status, f));
return true;
}
return false;
else if (action.equals("create")) {
String id = args.getString(0);
String src = FileUtils.stripFileProtocol(args.getString(1));
AudioPlayer audio = new AudioPlayer(this, id, src);
this.players.put(id, audio);
}
else if (action.equals("release")) {
boolean b = this.release(args.getString(0));
callbackContext.sendPluginResult(new PluginResult(status, b));
return true;
}
else { // Unrecognized action.
return false;
}
callbackContext.sendPluginResult(new PluginResult(status, result));
return true;
}
/**
@@ -139,6 +130,14 @@ public class AudioHandler extends Plugin {
this.players.clear();
}
/**
* Stop all audio players and recorders on navigate.
*/
@Override
public void onReset() {
onDestroy();
}
/**
* Called when a message is sent to plugin.
*
@@ -235,7 +234,7 @@ public class AudioHandler extends Plugin {
/**
* Seek to a location.
* @param id The id of the audio player
* @param miliseconds int: number of milliseconds to skip 1000 = 1 second
* @param milliseconds int: number of milliseconds to skip 1000 = 1 second
*/
public void seekToAudio(String id, int milliseconds) {
AudioPlayer audio = this.players.get(id);

View File

@@ -31,8 +31,6 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import org.apache.cordova.AudioPlayer.MODE;
/**
* This class implements the audio playback and recording capabilities used by Cordova.
* It is called by the AudioHandler Cordova class.
@@ -135,7 +133,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
switch (this.mode) {
case PLAY:
Log.d(LOG_TAG, "AudioPlayer Error: Can't record in play mode.");
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});");
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});");
break;
case NONE:
this.audioFile = file;
@@ -153,11 +151,11 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
} catch (IOException e) {
e.printStackTrace();
}
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});");
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});");
break;
case RECORD:
Log.d(LOG_TAG, "AudioPlayer Error: Already recording.");
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});");
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});");
}
}
@@ -223,7 +221,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
if (this.readyPlayer(this.audioFile)) {
this.player.seekTo(milliseconds);
Log.d(LOG_TAG, "Send a onStatus update for the new seek");
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_POSITION + ", " + milliseconds / 1000.0f + ");");
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_POSITION + ", " + milliseconds / 1000.0f + ");");
}
else {
this.seekOnPrepared = milliseconds;
@@ -242,7 +240,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
}
else {
Log.d(LOG_TAG, "AudioPlayer Error: pausePlaying() called during invalid state: " + this.state.ordinal());
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_NONE_ACTIVE + "});");
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_NONE_ACTIVE + "});");
}
}
@@ -258,7 +256,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
}
else {
Log.d(LOG_TAG, "AudioPlayer Error: stopPlaying() called during invalid state: " + this.state.ordinal());
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_NONE_ACTIVE + "});");
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_NONE_ACTIVE + "});");
}
}
@@ -280,7 +278,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
public long getCurrentPosition() {
if ((this.state == STATE.MEDIA_RUNNING) || (this.state == STATE.MEDIA_PAUSED)) {
int curPos = this.player.getCurrentPosition();
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_POSITION + ", " + curPos / 1000.0f + ");");
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_POSITION + ", " + curPos / 1000.0f + ");");
return curPos;
}
else {
@@ -359,7 +357,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
this.prepareOnly = true;
// Send status notification to JavaScript
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_DURATION + "," + this.duration + ");");
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_DURATION + "," + this.duration + ");");
}
/**
@@ -387,7 +385,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
this.player.release();
// Send error notification to JavaScript
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', { \"code\":" + arg1 + "});");
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', { \"code\":" + arg1 + "});");
return false;
}
@@ -398,7 +396,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
*/
private void setState(STATE state) {
if (this.state != state) {
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_STATE + ", " + state.ordinal() + ");");
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_STATE + ", " + state.ordinal() + ");");
}
this.state = state;
}
@@ -410,8 +408,8 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
*/
private void setMode(MODE mode) {
if (this.mode != mode) {
//mode is not part of the expected behaviour, so no notification
//this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_STATE + ", " + mode + ");");
//mode is not part of the expected behavior, so no notification
//this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_STATE + ", " + mode + ");");
}
this.mode = mode;
}
@@ -447,7 +445,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
break;
case RECORD:
Log.d(LOG_TAG, "AudioPlayer Error: Can't play in record mode.");
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});");
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});");
return false; //player is not ready
}
return true;
@@ -468,7 +466,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
try {
this.loadAudioFile(file);
} catch (Exception e) {
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});");
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});");
}
return false;
case MEDIA_LOADING:
@@ -493,14 +491,14 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
try {
this.loadAudioFile(file);
} catch (Exception e) {
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});");
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});");
}
//if we had to prepare= the file, we won't be in the correct state for playback
return false;
}
default:
Log.d(LOG_TAG, "AudioPlayer Error: startPlaying() called during invalid state: " + this.state);
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});");
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});");
}
}
return false;
@@ -526,7 +524,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
else {
if (file.startsWith("/android_asset/")) {
String f = file.substring(15);
android.content.res.AssetFileDescriptor fd = this.handler.ctx.getActivity().getAssets().openFd(f);
android.content.res.AssetFileDescriptor fd = this.handler.cordova.getActivity().getAssets().openFd(f);
this.player.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
}
else {

View File

@@ -18,7 +18,8 @@
*/
package org.apache.cordova;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
@@ -30,13 +31,13 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.util.Log;
public class BatteryListener extends Plugin {
public class BatteryListener extends CordovaPlugin {
private static final String LOG_TAG = "BatteryManager";
BroadcastReceiver receiver;
private String batteryCallbackId = null;
private CallbackContext batteryCallbackContext = null;
/**
* Constructor.
@@ -46,22 +47,20 @@ public class BatteryListener extends Plugin {
}
/**
* Executes the request and returns PluginResult.
* Executes the request.
*
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param callbackId The callback id used when calling back into JavaScript.
* @return A PluginResult object with a status and message.
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param callbackContext The callback context used when calling back into JavaScript.
* @return True if the action was valid, false if not.
*/
public PluginResult execute(String action, JSONArray args, String callbackId) {
PluginResult.Status status = PluginResult.Status.INVALID_ACTION;
String result = "Unsupported Operation: " + action;
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) {
if (action.equals("start")) {
if (this.batteryCallbackId != null) {
return new PluginResult(PluginResult.Status.ERROR, "Battery listener already running.");
if (this.batteryCallbackContext != null) {
callbackContext.error( "Battery listener already running.");
return true;
}
this.batteryCallbackId = callbackId;
this.batteryCallbackContext = callbackContext;
// We need to listen to power events to update battery status
IntentFilter intentFilter = new IntentFilter();
@@ -79,17 +78,19 @@ public class BatteryListener extends Plugin {
// Don't return any result now, since status results will be sent when events come in from broadcast receiver
PluginResult pluginResult = new PluginResult(PluginResult.Status.NO_RESULT);
pluginResult.setKeepCallback(true);
return pluginResult;
callbackContext.sendPluginResult(pluginResult);
return true;
}
else if (action.equals("stop")) {
removeBatteryListener();
this.sendUpdate(new JSONObject(), false); // release status callback in JS side
this.batteryCallbackId = null;
return new PluginResult(PluginResult.Status.OK);
this.batteryCallbackContext = null;
callbackContext.success();
return true;
}
return new PluginResult(status, result);
return false;
}
/**
@@ -99,6 +100,13 @@ public class BatteryListener extends Plugin {
removeBatteryListener();
}
/**
* Stop battery receiver.
*/
public void onReset() {
removeBatteryListener();
}
/**
* Stop the battery receiver and set it to null.
*/
@@ -146,10 +154,10 @@ public class BatteryListener extends Plugin {
* @param connection the network info to set as navigator.connection
*/
private void sendUpdate(JSONObject info, boolean keepCallback) {
if (this.batteryCallbackId != null) {
if (this.batteryCallbackContext != null) {
PluginResult result = new PluginResult(PluginResult.Status.OK, info);
result.setKeepCallback(keepCallback);
this.success(result, this.batteryCallbackId);
this.batteryCallbackContext.sendPluginResult(result);
}
}
}

View File

@@ -1,381 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.LinkedList;
/**
* This class provides a way for Java to run JavaScript in the web page that has loaded Cordova.
* The CallbackServer class implements an XHR server and a polling server with a list of JavaScript
* statements that are to be executed on the web page.
*
* The process flow for XHR is:
* 1. JavaScript makes an async XHR call.
* 2. The server holds the connection open until data is available.
* 3. The server writes the data to the client and closes the connection.
* 4. The server immediately starts listening for the next XHR call.
* 5. The client receives this XHR response, processes it.
* 6. The client sends a new async XHR request.
*
* The CallbackServer class requires the following permission in Android manifest file
* <uses-permission android:name="android.permission.INTERNET" />
*
* If the device has a proxy set, then XHR cannot be used, so polling must be used instead.
* This can be determined by the client by calling CallbackServer.usePolling().
*
* The process flow for polling is:
* 1. The client calls CallbackServer.getJavascript() to retrieve next statement.
* 2. If statement available, then client processes it.
* 3. The client repeats #1 in loop.
*/
public class CallbackServer implements Runnable {
@SuppressWarnings("unused")
private static final String LOG_TAG = "CallbackServer";
private ServerSocket waitSocket;
/**
* The list of JavaScript statements to be sent to JavaScript.
* This can be null when there are no messages available.
*/
private NativeToJsMessageQueue jsMessageQueue;
/**
* The port to listen on.
*/
private int port;
/**
* The server thread.
*/
private Thread serverThread;
/**
* Indicates the server is running.
*/
private boolean active;
/**
* Indicates that polling should be used instead of XHR.
*/
private boolean usePolling = true;
/**
* Security token to prevent other apps from accessing this callback server via XHR
*/
private String token;
/**
* Constructor.
*/
public CallbackServer() {
//Log.d(LOG_TAG, "CallbackServer()");
this.active = false;
this.port = 0;
}
/**
* Init callback server and start XHR if running local app.
*
* If Cordova app is loaded from file://, then we can use XHR
* otherwise we have to use polling due to cross-domain security restrictions.
*
* @param url The URL of the Cordova app being loaded
*/
public void init(String url) {
//System.out.println("CallbackServer.start("+url+")");
this.stopServer();
this.port = 0;
// Determine if XHR or polling is to be used
if ((url != null) && !url.startsWith("file://")) {
this.usePolling = true;
this.stopServer();
}
else if (android.net.Proxy.getDefaultHost() != null) {
this.usePolling = true;
this.stopServer();
}
else {
this.usePolling = false;
this.startServer();
}
}
/**
* Return if polling is being used instead of XHR.
* @return
*/
public boolean usePolling() {
return this.usePolling;
}
/**
* Get the port that this server is running on.
* @return
*/
public int getPort() {
return this.port;
}
/**
* Get the security token that this server requires when calling getJavascript().
* @return
*/
public String getToken() {
return this.token;
}
/**
* Start the server on a new thread.
*/
public void startServer() {
//Log.d(LOG_TAG, "CallbackServer.startServer()");
this.active = false;
// Start server on new thread
this.serverThread = new Thread(this);
this.serverThread.start();
}
/**
* Restart the server on a new thread.
*/
public void restartServer() {
// Stop server
this.stopServer();
// Start server again
this.startServer();
}
/**
* Start running the server.
* This is called automatically when the server thread is started.
*/
public void run() {
// Start server
try {
this.active = true;
String request;
waitSocket = new ServerSocket(0);
this.port = waitSocket.getLocalPort();
//Log.d(LOG_TAG, "CallbackServer -- using port " +this.port);
this.token = java.util.UUID.randomUUID().toString();
//Log.d(LOG_TAG, "CallbackServer -- using token "+this.token);
while (this.active) {
//Log.d(LOG_TAG, "CallbackServer: Waiting for data on socket");
Socket connection = waitSocket.accept();
BufferedReader xhrReader = new BufferedReader(new InputStreamReader(connection.getInputStream()), 40);
DataOutputStream output = new DataOutputStream(connection.getOutputStream());
request = xhrReader.readLine();
String response = "";
//Log.d(LOG_TAG, "CallbackServerRequest="+request);
if (this.active && (request != null)) {
if (request.contains("GET")) {
// Get requested file
String[] requestParts = request.split(" ");
// Must have security token
if ((requestParts.length == 3) && (requestParts[1].substring(1).equals(this.token))) {
//Log.d(LOG_TAG, "CallbackServer -- Processing GET request");
String js = null;
// Wait until there is some data to send, or send empty data every 10 sec
// to prevent XHR timeout on the client
synchronized (this) {
while (this.active) {
if (jsMessageQueue != null) {
// TODO(agrieve): Should this use popAll() instead?
js = jsMessageQueue.pop();
if (js != null) {
break;
}
}
try {
this.wait(10000); // prevent timeout from happening
//Log.d(LOG_TAG, "CallbackServer>>> break <<<");
break;
} catch (Exception e) {
}
}
}
// If server is still running
if (this.active) {
// If no data, then send 404 back to client before it times out
if (js == null) {
//Log.d(LOG_TAG, "CallbackServer -- sending data 0");
response = "HTTP/1.1 404 NO DATA\r\n\r\n "; // need to send content otherwise some Android devices fail, so send space
}
else {
//Log.d(LOG_TAG, "CallbackServer -- sending item");
response = "HTTP/1.1 200 OK\r\n\r\n";
response += encode(js, "UTF-8");
}
}
else {
response = "HTTP/1.1 503 Service Unavailable\r\n\r\n ";
}
}
else {
response = "HTTP/1.1 403 Forbidden\r\n\r\n ";
}
}
else {
response = "HTTP/1.1 400 Bad Request\r\n\r\n ";
}
//Log.d(LOG_TAG, "CallbackServer: response="+response);
//Log.d(LOG_TAG, "CallbackServer: closing output");
output.writeBytes(response);
output.flush();
}
output.close();
xhrReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
this.active = false;
//Log.d(LOG_TAG, "CallbackServer.startServer() - EXIT");
}
/**
* Stop server.
* This stops the thread that the server is running on.
*/
public void stopServer() {
//Log.d(LOG_TAG, "CallbackServer.stopServer()");
if (this.active) {
this.active = false;
try { waitSocket.close(); } catch (IOException ignore) {}
// Break out of server wait
synchronized (this) {
this.notify();
}
}
}
/**
* Destroy
*/
public void destroy() {
this.stopServer();
}
public void onNativeToJsMessageAvailable(NativeToJsMessageQueue queue) {
synchronized (this) {
this.jsMessageQueue = queue;
this.notify();
}
}
/* The Following code has been modified from original implementation of URLEncoder */
/* start */
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
static final String digits = "0123456789ABCDEF";
/**
* This will encode the return value to JavaScript. We revert the encoding for
* common characters that don't require encoding to reduce the size of the string
* being passed to JavaScript.
*
* @param s to be encoded
* @param enc encoding type
* @return encoded string
*/
public static String encode(String s, String enc) throws UnsupportedEncodingException {
if (s == null || enc == null) {
throw new NullPointerException();
}
// check for UnsupportedEncodingException
"".getBytes(enc);
// Guess a bit bigger for encoded form
StringBuilder buf = new StringBuilder(s.length() + 16);
int start = -1;
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
|| (ch >= '0' && ch <= '9')
|| " .-*_'(),<>=?@[]{}:~\"\\/;!".indexOf(ch) > -1) {
if (start >= 0) {
convert(s.substring(start, i), buf, enc);
start = -1;
}
if (ch != ' ') {
buf.append(ch);
} else {
buf.append(' ');
}
} else {
if (start < 0) {
start = i;
}
}
}
if (start >= 0) {
convert(s.substring(start, s.length()), buf, enc);
}
return buf.toString();
}
private static void convert(String s, StringBuilder buf, String enc) throws UnsupportedEncodingException {
byte[] bytes = s.getBytes(enc);
for (int j = 0; j < bytes.length; j++) {
buf.append('%');
buf.append(digits.charAt((bytes[j] & 0xf0) >> 4));
buf.append(digits.charAt(bytes[j] & 0xf));
}
}
/* end */
}

View File

@@ -27,8 +27,9 @@ import java.io.IOException;
import java.io.OutputStream;
import org.apache.commons.codec.binary.Base64;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.LOG;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
@@ -53,7 +54,7 @@ import android.util.Log;
* and returns the captured image. When the camera view is closed, the screen displayed before
* the camera view was shown is redisplayed.
*/
public class CameraLauncher extends Plugin implements MediaScannerConnectionClient {
public class CameraLauncher extends CordovaPlugin implements MediaScannerConnectionClient {
private static final int DATA_URL = 0; // Return base64 encoded string
private static final int FILE_URI = 1; // Return file uri (content://media/external/images/media/2 for Android)
@@ -82,9 +83,9 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
private int mediaType; // What type of media to retrieve
private boolean saveToPhotoAlbum; // Should the picture be saved to the device's photo album
private boolean correctOrientation; // Should the pictures orientation be corrected
private boolean allowEdit; // Should we allow the user to crop the image
//private boolean allowEdit; // Should we allow the user to crop the image. UNUSED.
public String callbackId;
public CallbackContext callbackContext;
private int numPics;
private MediaScannerConnection conn; // Used to update gallery app with newly-written files
@@ -110,62 +111,56 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
/**
* Executes the request and returns PluginResult.
*
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param callbackId The callback id used when calling back into JavaScript.
* @return A PluginResult object with a status and message.
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param callbackContext The callback id used when calling back into JavaScript.
* @return A PluginResult object with a status and message.
*/
public PluginResult execute(String action, JSONArray args, String callbackId) {
PluginResult.Status status = PluginResult.Status.OK;
String result = "";
this.callbackId = callbackId;
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
this.callbackContext = callbackContext;
try {
if (action.equals("takePicture")) {
int srcType = CAMERA;
int destType = FILE_URI;
this.saveToPhotoAlbum = false;
this.targetHeight = 0;
this.targetWidth = 0;
this.encodingType = JPEG;
this.mediaType = PICTURE;
this.mQuality = 80;
if (action.equals("takePicture")) {
int srcType = CAMERA;
int destType = FILE_URI;
this.saveToPhotoAlbum = false;
this.targetHeight = 0;
this.targetWidth = 0;
this.encodingType = JPEG;
this.mediaType = PICTURE;
this.mQuality = 80;
this.mQuality = args.getInt(0);
destType = args.getInt(1);
srcType = args.getInt(2);
this.targetWidth = args.getInt(3);
this.targetHeight = args.getInt(4);
this.encodingType = args.getInt(5);
this.mediaType = args.getInt(6);
this.allowEdit = args.getBoolean(7);
this.correctOrientation = args.getBoolean(8);
this.saveToPhotoAlbum = args.getBoolean(9);
this.mQuality = args.getInt(0);
destType = args.getInt(1);
srcType = args.getInt(2);
this.targetWidth = args.getInt(3);
this.targetHeight = args.getInt(4);
this.encodingType = args.getInt(5);
this.mediaType = args.getInt(6);
//this.allowEdit = args.getBoolean(7); // This field is unused.
this.correctOrientation = args.getBoolean(8);
this.saveToPhotoAlbum = args.getBoolean(9);
// If the user specifies a 0 or smaller width/height
// make it -1 so later comparrisions succeed
if (this.targetWidth < 1) {
this.targetWidth = -1;
}
if (this.targetHeight < 1) {
this.targetHeight = -1;
}
if (srcType == CAMERA) {
this.takePicture(destType, encodingType);
}
else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) {
this.getImage(srcType, destType);
}
PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT);
r.setKeepCallback(true);
return r;
// If the user specifies a 0 or smaller width/height
// make it -1 so later comparisons succeed
if (this.targetWidth < 1) {
this.targetWidth = -1;
}
return new PluginResult(status, result);
} catch (JSONException e) {
e.printStackTrace();
return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
if (this.targetHeight < 1) {
this.targetHeight = -1;
}
if (srcType == CAMERA) {
this.takePicture(destType, encodingType);
}
else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) {
this.getImage(srcType, destType);
}
PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT);
r.setKeepCallback(true);
callbackContext.sendPluginResult(r);
return true;
}
return false;
}
//--------------------------------------------------------------------------
@@ -199,7 +194,7 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
this.imageUri = Uri.fromFile(photo);
if (this.cordova != null) {
this.cordova.startActivityForResult((Plugin) this, intent, (CAMERA + 1) * 16 + returnType + 1);
this.cordova.startActivityForResult((CordovaPlugin) this, intent, (CAMERA + 1) * 16 + returnType + 1);
}
// else
// LOG.d(LOG_TAG, "ERROR: You must use the CordovaInterface for this to work correctly. Please implement it in your activity");
@@ -251,7 +246,7 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
intent.setAction(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
if (this.cordova != null) {
this.cordova.startActivityForResult((Plugin) this, Intent.createChooser(intent,
this.cordova.startActivityForResult((CordovaPlugin) this, Intent.createChooser(intent,
new String(title)), (srcType + 1) * 16 + returnType + 1);
}
}
@@ -294,6 +289,17 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
// If sending base64 image back
if (destType == DATA_URL) {
bitmap = getScaledBitmap(FileUtils.stripFileProtocol(imageUri.toString()));
if (bitmap == null) {
// Try to get the bitmap from intent.
bitmap = (Bitmap)intent.getExtras().get("data");
}
// Double-check the bitmap.
if (bitmap == null) {
Log.d(LOG_TAG, "I either have a null image path or bitmap");
this.failPicture("Unable to create bitmap!");
return;
}
if (rotate != 0 && this.correctOrientation) {
bitmap = getRotatedBitmap(rotate, bitmap, exif);
@@ -316,10 +322,11 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
}
// 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.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
this.callbackContext.success(uri.toString());
} else {
bitmap = getScaledBitmap(FileUtils.stripFileProtocol(imageUri.toString()));
@@ -346,7 +353,7 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
}
// Send Uri back to JavaScript for viewing image
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
this.callbackContext.success(uri.toString());
}
this.cleanup(FILE_URI, this.imageUri, uri, bitmap);
@@ -377,22 +384,25 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
// If you ask for video or all media type you will automatically get back a file URI
// and there will be no attempt to resize any returned data
if (this.mediaType != PICTURE) {
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
this.callbackContext.success(uri.toString());
}
else {
// This is a special case to just return the path as no scaling,
// rotating or compression needs to be done
if (this.targetHeight == -1 && this.targetWidth == -1 &&
this.mQuality == 100 && destType == FILE_URI && !this.correctOrientation) {
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
this.callbackContext.success(uri.toString());
} else {
// Get the path to the image. Makes loading so much easier.
String imagePath = FileUtils.getRealPathFromURI(uri, this.cordova);
Log.d(LOG_TAG, "Real path = " + imagePath);
String mimeType = FileUtils.getMimeType(imagePath);
// Log.d(LOG_TAG, "Real path = " + imagePath);
// Log.d(LOG_TAG, "mime type = " + mimeType);
// If we don't have a valid image so quit.
if (imagePath == null) {
if (imagePath == null || mimeType == null ||
!(mimeType.equalsIgnoreCase("image/jpeg") || mimeType.equalsIgnoreCase("image/png"))) {
Log.d(LOG_TAG, "I either have a null image path or bitmap");
this.failPicture("Unable to retreive path to picture!");
this.failPicture("Unable to retrieve path to picture!");
return;
}
Bitmap bitmap = getScaledBitmap(imagePath);
@@ -453,14 +463,14 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
// The resized image is cached by the app in order to get around this and not have to delete you
// application cache I'm adding the current system time to the end of the file url.
this.success(new PluginResult(PluginResult.Status.OK, ("file://" + resizePath + "?" + System.currentTimeMillis())), this.callbackId);
this.callbackContext.success("file://" + resizePath + "?" + System.currentTimeMillis());
} catch (Exception e) {
e.printStackTrace();
this.failPicture("Error retrieving image.");
}
}
else {
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
this.callbackContext.success(uri.toString());
}
}
if (bitmap != null) {
@@ -569,6 +579,9 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
options.inJustDecodeBounds = false;
options.inSampleSize = calculateSampleSize(options.outWidth, options.outHeight, this.targetWidth, this.targetHeight);
Bitmap unscaledBitmap = BitmapFactory.decodeFile(imagePath, options);
if (unscaledBitmap == null) {
return null;
}
return Bitmap.createScaledBitmap(unscaledBitmap, widthHeight[0], widthHeight[1], true);
}
@@ -729,7 +742,7 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
byte[] code = jpeg_data.toByteArray();
byte[] output = Base64.encodeBase64(code);
String js_out = new String(output);
this.success(new PluginResult(PluginResult.Status.OK, js_out), this.callbackId);
this.callbackContext.success(js_out);
js_out = null;
output = null;
code = null;
@@ -746,7 +759,7 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
* @param err
*/
public void failPicture(String err) {
this.error(new PluginResult(PluginResult.Status.ERROR, err), this.callbackId);
this.callbackContext.error(err);
}
private void scanForGallery(Uri newImage) {
@@ -754,7 +767,7 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
if(this.conn != null) {
this.conn.disconnect();
}
this.conn = new MediaScannerConnection(this.ctx.getActivity().getApplicationContext(), this);
this.conn = new MediaScannerConnection(this.cordova.getActivity().getApplicationContext(), this);
conn.connect();
}
@@ -762,7 +775,7 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
try{
this.conn.scanFile(this.scanMe.toString(), "image/*");
} catch (java.lang.IllegalStateException e){
LOG.e(LOG_TAG, "Can't scan file in MediaScanner aftering taking picture");
LOG.e(LOG_TAG, "Can't scan file in MediaScanner after taking picture");
}
}

View File

@@ -23,8 +23,9 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.LOG;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
@@ -41,7 +42,7 @@ import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
public class Capture extends Plugin {
public class Capture extends CordovaPlugin {
private static final String VIDEO_3GPP = "video/3gpp";
private static final String VIDEO_MP4 = "video/mp4";
@@ -57,13 +58,11 @@ public class Capture extends Plugin {
// private static final int CAPTURE_APPLICATION_BUSY = 1;
// private static final int CAPTURE_INVALID_ARGUMENT = 2;
private static final int CAPTURE_NO_MEDIA_FILES = 3;
private static final int CAPTURE_NOT_SUPPORTED = 20;
private String callbackId; // The ID of the callback to be invoked with our result
private CallbackContext callbackContext; // The callback context from which we were invoked.
private long limit; // the number of pics/vids/clips to take
private double duration; // optional duration parameter for video recording
private JSONArray results; // The array of results to be returned to the user
private Uri imageUri; // Uri of captured image
private int numPics; // Number of pictures before capture activity
//private CordovaInterface cordova;
@@ -77,8 +76,8 @@ public class Capture extends Plugin {
// }
@Override
public PluginResult execute(String action, JSONArray args, String callbackId) {
this.callbackId = callbackId;
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
this.callbackContext = callbackContext;
this.limit = 1;
this.duration = 0.0f;
this.results = new JSONArray();
@@ -90,12 +89,9 @@ public class Capture extends Plugin {
}
if (action.equals("getFormatData")) {
try {
JSONObject obj = getFormatData(args.getString(0), args.getString(1));
return new PluginResult(PluginResult.Status.OK, obj);
} catch (JSONException e) {
return new PluginResult(PluginResult.Status.ERROR);
}
JSONObject obj = getFormatData(args.getString(0), args.getString(1));
callbackContext.success(obj);
return true;
}
else if (action.equals("captureAudio")) {
this.captureAudio();
@@ -106,10 +102,11 @@ public class Capture extends Plugin {
else if (action.equals("captureVideo")) {
this.captureVideo(duration);
}
else {
return false;
}
PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT);
r.setKeepCallback(true);
return r;
return true;
}
/**
@@ -119,34 +116,30 @@ public class Capture extends Plugin {
* @param mimeType of the file
* @return a MediaFileData object
*/
private JSONObject getFormatData(String filePath, String mimeType) {
private JSONObject getFormatData(String filePath, String mimeType) throws JSONException {
JSONObject obj = new JSONObject();
try {
// setup defaults
obj.put("height", 0);
obj.put("width", 0);
obj.put("bitrate", 0);
obj.put("duration", 0);
obj.put("codecs", "");
// setup defaults
obj.put("height", 0);
obj.put("width", 0);
obj.put("bitrate", 0);
obj.put("duration", 0);
obj.put("codecs", "");
// If the mimeType isn't set the rest will fail
// so let's see if we can determine it.
if (mimeType == null || mimeType.equals("")) {
mimeType = FileUtils.getMimeType(filePath);
}
Log.d(LOG_TAG, "Mime type = " + mimeType);
// 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);
}
Log.d(LOG_TAG, "Mime type = " + mimeType);
if (mimeType.equals(IMAGE_JPEG) || filePath.endsWith(".jpg")) {
obj = getImageData(filePath, obj);
}
else if (mimeType.endsWith(AUDIO_3GPP)) {
obj = getAudioVideoData(filePath, obj, false);
}
else if (mimeType.equals(VIDEO_3GPP) || mimeType.equals(VIDEO_MP4)) {
obj = getAudioVideoData(filePath, obj, true);
}
} catch (JSONException e) {
Log.d(LOG_TAG, "Error: setting media file data object");
if (mimeType.equals(IMAGE_JPEG) || filePath.endsWith(".jpg")) {
obj = getImageData(filePath, obj);
}
else if (mimeType.endsWith(AUDIO_3GPP)) {
obj = getAudioVideoData(filePath, obj, false);
}
else if (mimeType.equals(VIDEO_3GPP) || mimeType.equals(VIDEO_MP4)) {
obj = getAudioVideoData(filePath, obj, true);
}
return obj;
}
@@ -199,7 +192,7 @@ public class Capture extends Plugin {
private void captureAudio() {
Intent intent = new Intent(android.provider.MediaStore.Audio.Media.RECORD_SOUND_ACTION);
this.cordova.startActivityForResult((Plugin) this, intent, CAPTURE_AUDIO);
this.cordova.startActivityForResult((CordovaPlugin) this, intent, CAPTURE_AUDIO);
}
/**
@@ -214,9 +207,8 @@ public class Capture extends Plugin {
// Specify file so that large image is captured and returned
File photo = new File(DirectoryManager.getTempDirectoryPath(this.cordova.getActivity()), "Capture.jpg");
intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(photo));
this.imageUri = Uri.fromFile(photo);
this.cordova.startActivityForResult((Plugin) this, intent, CAPTURE_IMAGE);
this.cordova.startActivityForResult((CordovaPlugin) this, intent, CAPTURE_IMAGE);
}
/**
@@ -227,7 +219,7 @@ public class Capture extends Plugin {
// Introduced in API 8
//intent.putExtra(android.provider.MediaStore.EXTRA_DURATION_LIMIT, duration);
this.cordova.startActivityForResult((Plugin) this, intent, CAPTURE_VIDEO);
this.cordova.startActivityForResult((CordovaPlugin) this, intent, CAPTURE_VIDEO);
}
/**
@@ -252,7 +244,7 @@ public class Capture extends Plugin {
if (results.length() >= limit) {
// Send Uri back to JavaScript for listening to audio
this.success(new PluginResult(PluginResult.Status.OK, results), this.callbackId);
this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, results));
} else {
// still need to capture more audio clips
captureAudio();
@@ -298,7 +290,7 @@ public class Capture extends Plugin {
if (results.length() >= limit) {
// Send Uri back to JavaScript for viewing image
this.success(new PluginResult(PluginResult.Status.OK, results), this.callbackId);
this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, results));
} else {
// still need to capture more images
captureImage();
@@ -315,7 +307,7 @@ public class Capture extends Plugin {
if (results.length() >= limit) {
// Send Uri back to JavaScript for viewing video
this.success(new PluginResult(PluginResult.Status.OK, results), this.callbackId);
this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, results));
} else {
// still need to capture more video clips
captureVideo(duration);
@@ -326,7 +318,7 @@ public class Capture extends Plugin {
else if (resultCode == Activity.RESULT_CANCELED) {
// If we have partial results send them back to the user
if (results.length() > 0) {
this.success(new PluginResult(PluginResult.Status.OK, results), this.callbackId);
this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, results));
}
// user canceled the action
else {
@@ -337,7 +329,7 @@ public class Capture extends Plugin {
else {
// If we have partial results send them back to the user
if (results.length() > 0) {
this.success(new PluginResult(PluginResult.Status.OK, results), this.callbackId);
this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, results));
}
// something bad happened
else {
@@ -401,7 +393,7 @@ public class Capture extends Plugin {
* @param err
*/
public void fail(JSONObject err) {
this.error(new PluginResult(PluginResult.Status.ERROR, err), this.callbackId);
this.callbackContext.error(err);
}

View File

@@ -20,8 +20,9 @@ package org.apache.cordova;
import java.util.List;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
@@ -33,10 +34,13 @@ import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
/**
* This class listens to the compass sensor and stores the latest heading value.
*/
public class CompassListener extends Plugin implements SensorEventListener {
public class CompassListener extends CordovaPlugin implements SensorEventListener {
public static int STOPPED = 0;
public static int STARTING = 1;
@@ -54,6 +58,8 @@ public class CompassListener extends Plugin implements SensorEventListener {
private SensorManager sensorManager;// Sensor manager
Sensor mSensor; // Compass sensor returned by sensor manager
private CallbackContext callbackContext;
/**
* Constructor.
*/
@@ -68,95 +74,62 @@ public class CompassListener extends Plugin implements SensorEventListener {
* get file paths associated with the Activity.
*
* @param cordova The context of the main Activity.
* @param webView The CordovaWebView Cordova is running in.
*/
public void setContext(CordovaInterface cordova) {
super.setContext(cordova);
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
super.initialize(cordova, webView);
this.sensorManager = (SensorManager) cordova.getActivity().getSystemService(Context.SENSOR_SERVICE);
}
/**
* Executes the request and returns PluginResult.
*
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param callbackId The callback id used when calling back into JavaScript.
* @return A PluginResult object with a status and message.
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param callbackS=Context The callback id used when calling back into JavaScript.
* @return True if the action was valid.
* @throws JSONException
*/
public PluginResult execute(String action, JSONArray args, String callbackId) {
PluginResult.Status status = PluginResult.Status.OK;
String result = "";
try {
if (action.equals("start")) {
this.start();
}
else if (action.equals("stop")) {
this.stop();
}
else if (action.equals("getStatus")) {
int i = this.getStatus();
return new PluginResult(status, i);
}
else if (action.equals("getHeading")) {
// If not running, then this is an async call, so don't worry about waiting
if (this.status != CompassListener.RUNNING) {
int r = this.start();
if (r == CompassListener.ERROR_FAILED_TO_START) {
return new PluginResult(PluginResult.Status.IO_EXCEPTION, CompassListener.ERROR_FAILED_TO_START);
}
// Wait until running
long timeout = 2000;
while ((this.status == STARTING) && (timeout > 0)) {
timeout = timeout - 100;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (timeout == 0) {
return new PluginResult(PluginResult.Status.IO_EXCEPTION, CompassListener.ERROR_FAILED_TO_START);
}
}
return new PluginResult(status, getCompassHeading());
}
else if (action.equals("setTimeout")) {
this.setTimeout(args.getLong(0));
}
else if (action.equals("getTimeout")) {
long l = this.getTimeout();
return new PluginResult(status, l);
} else {
// Unsupported action
return new PluginResult(PluginResult.Status.INVALID_ACTION);
}
return new PluginResult(status, result);
} catch (JSONException e) {
e.printStackTrace();
return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
if (action.equals("start")) {
this.start();
}
}
/**
* Identifies if action to be executed returns a value and should be run synchronously.
*
* @param action The action to execute
* @return T=returns value
*/
public boolean isSynch(String action) {
if (action.equals("getStatus")) {
return true;
else if (action.equals("stop")) {
this.stop();
}
else if (action.equals("getStatus")) {
int i = this.getStatus();
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, i));
}
else if (action.equals("getHeading")) {
// Can only return value if RUNNING
if (this.status == CompassListener.RUNNING) {
return true;
// If not running, then this is an async call, so don't worry about waiting
if (this.status != CompassListener.RUNNING) {
int r = this.start();
if (r == CompassListener.ERROR_FAILED_TO_START) {
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, CompassListener.ERROR_FAILED_TO_START));
return true;
}
// Set a timeout callback on the main thread.
Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(new Runnable() {
public void run() {
CompassListener.this.timeout();
}
}, 2000);
}
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, getCompassHeading()));
}
else if (action.equals("setTimeout")) {
this.setTimeout(args.getLong(0));
}
else if (action.equals("getTimeout")) {
return true;
long l = this.getTimeout();
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, l));
} else {
// Unsupported action
return false;
}
return false;
return true;
}
/**
@@ -166,6 +139,13 @@ public class CompassListener extends Plugin implements SensorEventListener {
this.stop();
}
/**
* Called when app has navigated and JS listeners have been destroyed.
*/
public void onReset() {
this.stop();
}
//--------------------------------------------------------------------------
// LOCAL METHODS
//--------------------------------------------------------------------------
@@ -216,6 +196,18 @@ public class CompassListener extends Plugin implements SensorEventListener {
// TODO Auto-generated method stub
}
/**
* Called after a delay to time out if the listener has not attached fast enough.
*/
private void timeout() {
if (this.status == CompassListener.STARTING) {
this.setStatus(CompassListener.ERROR_FAILED_TO_START);
if (this.callbackContext != null) {
this.callbackContext.error("Compass listener failed to start.");
}
}
}
/**
* Sensor listener event.
*
@@ -287,19 +279,15 @@ public class CompassListener extends Plugin implements SensorEventListener {
*
* @return a compass heading
*/
private JSONObject getCompassHeading() {
private JSONObject getCompassHeading() throws JSONException {
JSONObject obj = new JSONObject();
try {
obj.put("magneticHeading", this.getHeading());
obj.put("trueHeading", this.getHeading());
// Since the magnetic and true heading are always the same our and accuracy
// is defined as the difference between true and magnetic always return zero
obj.put("headingAccuracy", 0);
obj.put("timestamp", this.timeStamp);
} catch (JSONException e) {
// Should never happen
}
obj.put("magneticHeading", this.getHeading());
obj.put("trueHeading", this.getHeading());
// Since the magnetic and true heading are always the same our and accuracy
// is defined as the difference between true and magnetic always return zero
obj.put("headingAccuracy", 0);
obj.put("timestamp", this.timeStamp);
return obj;
}

View File

@@ -0,0 +1,228 @@
/*
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.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");
String value = xml.getAttributeValue(null, "value");
LOG.i("CordovaLog", "Found preference for %s=%s", name, value);
Log.d("CordovaLog", "Found preference for " + name + "=" + value);
action.getIntent().putExtra(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

@@ -28,7 +28,6 @@ import android.content.ContentValues;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.net.Uri;
import android.os.Debug;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.util.Log;
@@ -220,6 +219,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
columnsToFetch.add(ContactsContract.CommonDataKinds.Phone.TYPE);
}
if (isRequired("emails", populate)) {
columnsToFetch.add(ContactsContract.CommonDataKinds.Email._ID);
columnsToFetch.add(ContactsContract.CommonDataKinds.Email.DATA);
columnsToFetch.add(ContactsContract.CommonDataKinds.Email.TYPE);
}
@@ -331,8 +331,6 @@ public class ContactAccessorSdk5 extends ContactAccessor {
JSONArray websites = new JSONArray();
JSONArray photos = new JSONArray();
ArrayList<String> names = new ArrayList<String>();
// Column indices
int colContactId = c.getColumnIndex(ContactsContract.Data.CONTACT_ID);
int colRawContactId = c.getColumnIndex(ContactsContract.Data.RAW_CONTACT_ID);
@@ -795,10 +793,10 @@ public class ContactAccessorSdk5 extends ContactAccessor {
formatted.append(middleName + " ");
}
if (familyName != null) {
formatted.append(familyName + " ");
formatted.append(familyName);
}
if (honorificSuffix != null) {
formatted.append(honorificSuffix + " ");
formatted.append(" " + honorificSuffix);
}
contactName.put("familyName", familyName);
@@ -862,7 +860,8 @@ public class ContactAccessorSdk5 extends ContactAccessor {
im.put("id", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Im._ID)));
im.put("pref", false); // Android does not store pref attribute
im.put("value", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA)));
im.put("type", getContactType(cursor.getInt(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Im.TYPE))));
String type = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Im.PROTOCOL));
im.put("type", getImType(new Integer(type).intValue()));
} catch (JSONException e) {
Log.e(LOG_TAG, e.getMessage(), e);
}
@@ -1248,7 +1247,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
contentValues.put(ContactsContract.Data.RAW_CONTACT_ID, rawId);
contentValues.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE);
contentValues.put(ContactsContract.CommonDataKinds.Im.DATA, getJsonString(im, "value"));
contentValues.put(ContactsContract.CommonDataKinds.Im.TYPE, getContactType(getJsonString(im, "type")));
contentValues.put(ContactsContract.CommonDataKinds.Im.TYPE, getImType(getJsonString(im, "type")));
ops.add(ContentProviderOperation.newInsert(
ContactsContract.Data.CONTENT_URI).withValues(contentValues).build());
@@ -1412,7 +1411,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
retVal = false;
}
// if the save was a succes return the contact ID
// if the save was a success return the contact ID
if (retVal) {
return id;
} else {
@@ -1447,7 +1446,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Im.DATA, getJsonString(im, "value"))
.withValue(ContactsContract.CommonDataKinds.Im.TYPE, getContactType(getJsonString(im, "type")))
.withValue(ContactsContract.CommonDataKinds.Im.TYPE, getImType(getJsonString(im, "type")))
.build());
}
@@ -1502,7 +1501,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Email.DATA, getJsonString(email, "value"))
.withValue(ContactsContract.CommonDataKinds.Email.TYPE, getPhoneType(getJsonString(email, "type")))
.withValue(ContactsContract.CommonDataKinds.Email.TYPE, getContactType(getJsonString(email, "type")))
.build());
}
@@ -1581,7 +1580,7 @@ public class ContactAccessorSdk5 extends ContactAccessor {
Uri uri = Uri.parse(path);
return mApp.getActivity().getContentResolver().openInputStream(uri);
}
if (path.startsWith("http:") || path.startsWith("file:")) {
if (path.startsWith("http:") || path.startsWith("https:") || path.startsWith("file:")) {
URL url = new URL(path);
return url.openStream();
}
@@ -2091,5 +2090,86 @@ public class ContactAccessorSdk5 extends ContactAccessor {
}
return stringType;
}
/**
* Converts a string from the W3C Contact API to it's Android int value.
* @param string
* @return Android int value
*/
private int getImType(String string) {
int type = ContactsContract.CommonDataKinds.Im.PROTOCOL_CUSTOM;
if (string != null) {
if ("aim".equals(string.toLowerCase())) {
return ContactsContract.CommonDataKinds.Im.PROTOCOL_AIM;
}
else if ("google talk".equals(string.toLowerCase())) {
return ContactsContract.CommonDataKinds.Im.PROTOCOL_GOOGLE_TALK;
}
else if ("icq".equals(string.toLowerCase())) {
return ContactsContract.CommonDataKinds.Im.PROTOCOL_ICQ;
}
else if ("jabber".equals(string.toLowerCase())) {
return ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER;
}
else if ("msn".equals(string.toLowerCase())) {
return ContactsContract.CommonDataKinds.Im.PROTOCOL_MSN;
}
else if ("netmeeting".equals(string.toLowerCase())) {
return ContactsContract.CommonDataKinds.Im.PROTOCOL_NETMEETING;
}
else if ("qq".equals(string.toLowerCase())) {
return ContactsContract.CommonDataKinds.Im.PROTOCOL_QQ;
}
else if ("skype".equals(string.toLowerCase())) {
return ContactsContract.CommonDataKinds.Im.PROTOCOL_SKYPE;
}
else if ("yahoo".equals(string.toLowerCase())) {
return ContactsContract.CommonDataKinds.Im.PROTOCOL_YAHOO;
}
}
return type;
}
/**
* getPhoneType converts an Android phone type into a string
* @param type
* @return phone type as string.
*/
private String getImType(int type) {
String stringType;
switch (type) {
case ContactsContract.CommonDataKinds.Im.PROTOCOL_AIM:
stringType = "AIM";
break;
case ContactsContract.CommonDataKinds.Im.PROTOCOL_GOOGLE_TALK:
stringType = "Google Talk";
break;
case ContactsContract.CommonDataKinds.Im.PROTOCOL_ICQ:
stringType = "ICQ";
break;
case ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER:
stringType = "Jabber";
break;
case ContactsContract.CommonDataKinds.Im.PROTOCOL_MSN:
stringType = "MSN";
break;
case ContactsContract.CommonDataKinds.Im.PROTOCOL_NETMEETING:
stringType = "NetMeeting";
break;
case ContactsContract.CommonDataKinds.Im.PROTOCOL_QQ:
stringType = "QQ";
break;
case ContactsContract.CommonDataKinds.Im.PROTOCOL_SKYPE:
stringType = "Skype";
break;
case ContactsContract.CommonDataKinds.Im.PROTOCOL_YAHOO:
stringType = "Yahoo";
break;
default:
stringType = "custom";
break;
}
return stringType;
}
}

View File

@@ -18,14 +18,15 @@
*/
package org.apache.cordova;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.util.Log;
public class ContactManager extends Plugin {
public class ContactManager extends CordovaPlugin {
private ContactAccessor contactAccessor;
private static final String LOG_TAG = "Contact Query";
@@ -47,21 +48,19 @@ public class ContactManager extends Plugin {
/**
* Executes the request and returns PluginResult.
*
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param callbackId The callback id used when calling back into JavaScript.
* @return A PluginResult object with a status and message.
* @param action The action to execute.
* @param args JSONArray of arguments for the plugin.
* @param callbackContext The callback context used when calling back into JavaScript.
* @return True if the action was valid, false otherwise.
*/
public PluginResult execute(String action, JSONArray args, String callbackId) {
PluginResult.Status status = PluginResult.Status.OK;
String result = "";
public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException {
/**
* Check to see if we are on an Android 1.X device. If we are return an error as we
* do not support this as of Cordova 1.0.
*/
if (android.os.Build.VERSION.RELEASE.startsWith("1.")) {
return new PluginResult(PluginResult.Status.ERROR, ContactManager.NOT_SUPPORTED_ERROR);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, ContactManager.NOT_SUPPORTED_ERROR));
return true;
}
/**
@@ -72,30 +71,52 @@ public class ContactManager extends Plugin {
this.contactAccessor = new ContactAccessorSdk5(this.webView, this.cordova);
}
try {
if (action.equals("search")) {
JSONArray res = contactAccessor.search(args.getJSONArray(0), args.optJSONObject(1));
return new PluginResult(status, res);
}
else if (action.equals("save")) {
String id = contactAccessor.save(args.getJSONObject(0));
if (id != null) {
JSONObject res = contactAccessor.getContactById(id);
if (action.equals("search")) {
final JSONArray filter = args.getJSONArray(0);
final JSONObject options = args.getJSONObject(1);
this.cordova.getThreadPool().execute(new Runnable() {
public void run() {
JSONArray res = contactAccessor.search(filter, options);
callbackContext.success(res);
}
});
}
else if (action.equals("save")) {
final JSONObject contact = args.getJSONObject(0);
this.cordova.getThreadPool().execute(new Runnable() {
public void run() {
JSONObject res = null;
String id = contactAccessor.save(contact);
if (id != null) {
try {
res = contactAccessor.getContactById(id);
} catch (JSONException e) {
Log.e(LOG_TAG, "JSON fail.", e);
}
}
if (res != null) {
return new PluginResult(status, res);
callbackContext.success(res);
} else {
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, UNKNOWN_ERROR));
}
}
}
else if (action.equals("remove")) {
if (contactAccessor.remove(args.getString(0))) {
return new PluginResult(status, result);
}
}
// If we get to this point an error has occurred
return new PluginResult(PluginResult.Status.ERROR, ContactManager.UNKNOWN_ERROR);
} catch (JSONException e) {
Log.e(LOG_TAG, e.getMessage(), e);
return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
});
}
else if (action.equals("remove")) {
final String contactId = args.getString(0);
this.cordova.getThreadPool().execute(new Runnable() {
public void run() {
if (contactAccessor.remove(contactId)) {
callbackContext.success();
} else {
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, UNKNOWN_ERROR));
}
}
});
}
else {
return false;
}
return true;
}
}

View File

@@ -20,33 +20,51 @@ package org.apache.cordova;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.LOG;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.webkit.ConsoleMessage;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebStorage;
import android.webkit.WebView;
import android.webkit.GeolocationPermissions.Callback;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
/**
* This class is the WebChromeClient that implements callbacks for our web view.
*/
public class CordovaChromeClient extends WebChromeClient {
public static final int FILECHOOSER_RESULTCODE = 5173;
private static final String LOG_TAG = "CordovaChromeClient";
private String TAG = "CordovaLog";
private long MAX_QUOTA = 100 * 1024 * 1024;
private CordovaInterface cordova;
private CordovaWebView appView;
// the video progress view
private View mVideoProgressView;
// File Chooser
public ValueCallback<Uri> mUploadMessage;
/**
* Constructor.
*
@@ -189,7 +207,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://") || url.indexOf(this.appView.baseUrl) == 0 || Config.isUrlWhiteListed(url)) {
reqOk = true;
}
@@ -202,9 +220,8 @@ public class CordovaChromeClient extends WebChromeClient {
String service = array.getString(0);
String action = array.getString(1);
String callbackId = array.getString(2);
boolean async = array.getBoolean(3);
PluginResult r = this.appView.pluginManager.exec(service, action, callbackId, message, async);
result.confirm(r == null ? "" : r.getJSONString());
String r = this.appView.exposedJsApi.exec(service, action, callbackId, message);
result.confirm(r == null ? "" : r);
} catch (JSONException e) {
e.printStackTrace();
}
@@ -212,15 +229,14 @@ public class CordovaChromeClient extends WebChromeClient {
// Sets the native->JS bridge mode.
else if (reqOk && defaultValue != null && defaultValue.equals("gap_bridge_mode:")) {
this.appView.jsMessageQueue.setBridgeMode(Integer.parseInt(message));
this.appView.exposedJsApi.setNativeToJsBridgeMode(Integer.parseInt(message));
result.confirm("");
}
// Polling for JavaScript messages
else if (reqOk && defaultValue != null && defaultValue.equals("gap_poll:")) {
// TODO(agrieve): Use popAll() here.
String r = this.appView.jsMessageQueue.pop();
result.confirm(r);
String r = this.appView.exposedJsApi.retrieveJsMessages();
result.confirm(r == null ? "" : r);
}
// Do NO-OP so older code doesn't display dialog
@@ -228,24 +244,6 @@ public class CordovaChromeClient extends WebChromeClient {
result.confirm("OK");
}
// Calling into CallbackServer
else if (reqOk && defaultValue != null && defaultValue.equals("gap_callbackServer:")) {
String r = "";
if (message.equals("usePolling")) {
r = "" + this.appView.callbackServer.usePolling();
}
else if (message.equals("restartServer")) {
this.appView.callbackServer.restartServer();
}
else if (message.equals("getPort")) {
r = Integer.toString(this.appView.callbackServer.getPort());
}
else if (message.equals("getToken")) {
r = this.appView.callbackServer.getToken();
}
result.confirm(r);
}
// Show dialog
else {
final JsPromptResult res = result;
@@ -308,12 +306,17 @@ public class CordovaChromeClient extends WebChromeClient {
}
// console.log in api level 7: http://developer.android.com/guide/developing/debug-tasks.html
// Expect this to not compile in a future Android release!
@SuppressWarnings("deprecation")
@Override
public void onConsoleMessage(String message, int lineNumber, String sourceID)
{
LOG.d(TAG, "%s: Line %d : %s", sourceID, lineNumber, message);
super.onConsoleMessage(message, lineNumber, sourceID);
//This is only for Android 2.1
if(android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.ECLAIR_MR1)
{
LOG.d(TAG, "%s: Line %d : %s", sourceID, lineNumber, message);
super.onConsoleMessage(message, lineNumber, sourceID);
}
}
@TargetApi(8)
@@ -336,4 +339,61 @@ public class CordovaChromeClient extends WebChromeClient {
super.onGeolocationPermissionsShowPrompt(origin, callback);
callback.invoke(origin, true, false);
}
// API level 7 is required for this, see if we could lower this using something else
@Override
public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
this.appView.showCustomView(view, callback);
}
@Override
public void onHideCustomView() {
this.appView.hideCustomView();
}
@Override
/**
* Ask the host application for a custom progress view to show while
* a <video> is loading.
* @return View The progress view.
*/
public View getVideoLoadingProgressView() {
if (mVideoProgressView == null) {
// Create a new Loading view programmatically.
// create the linear layout
LinearLayout layout = new LinearLayout(this.appView.getContext());
layout.setOrientation(LinearLayout.VERTICAL);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
layout.setLayoutParams(layoutParams);
// the proress bar
ProgressBar bar = new ProgressBar(this.appView.getContext());
LinearLayout.LayoutParams barLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
barLayoutParams.gravity = Gravity.CENTER;
bar.setLayoutParams(barLayoutParams);
layout.addView(bar);
mVideoProgressView = layout;
}
return mVideoProgressView;
}
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
this.openFileChooser(uploadMsg, "*/*");
}
public void openFileChooser( ValueCallback<Uri> uploadMsg, String acceptType ) {
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

@@ -22,7 +22,8 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.cordova.api.CallbackContext;
import android.location.Location;
import android.location.LocationListener;
@@ -39,8 +40,8 @@ public class CordovaLocationListener implements LocationListener {
private GeoBroker owner;
protected boolean running = false;
public HashMap<String, String> watches = new HashMap<String, String>();
private List<String> callbacks = new ArrayList<String>();
public HashMap<String, CallbackContext> watches = new HashMap<String, CallbackContext>();
private List<CallbackContext> callbacks = new ArrayList<CallbackContext>();
private String TAG = "[Cordova Location Listener]";
@@ -51,30 +52,38 @@ public class CordovaLocationListener implements LocationListener {
}
protected void fail(int code, String message) {
for (String callbackId: this.callbacks)
for (CallbackContext callbackContext: this.callbacks)
{
this.owner.fail(code, message, callbackId);
this.owner.fail(code, message, callbackContext);
}
if(this.owner.isGlobalListener(this))
{
Log.d(TAG, "Stopping global listener");
this.stop();
}
this.callbacks.clear();
Iterator it = this.watches.entrySet().iterator();
Iterator<CallbackContext> it = this.watches.values().iterator();
while (it.hasNext()) {
Map.Entry pairs = (Map.Entry)it.next();
this.owner.fail(code, message, (String)pairs.getValue());
this.owner.fail(code, message, it.next());
}
}
private void win(Location loc) {
for (String callbackId: this.callbacks)
for (CallbackContext callbackContext: this.callbacks)
{
this.owner.win(loc, callbackId);
this.owner.win(loc, callbackContext);
}
if(this.owner.isGlobalListener(this))
{
Log.d(TAG, "Stopping global listener");
this.stop();
}
this.callbacks.clear();
Iterator it = this.watches.entrySet().iterator();
Iterator<CallbackContext> it = this.watches.values().iterator();
while (it.hasNext()) {
Map.Entry pairs = (Map.Entry)it.next();
this.owner.win(loc, (String)pairs.getValue());
this.owner.win(loc, it.next());
}
}
@@ -140,14 +149,14 @@ public class CordovaLocationListener implements LocationListener {
return this.watches.size() + this.callbacks.size();
}
public void addWatch(String timerId, String callbackId) {
this.watches.put(timerId, callbackId);
public void addWatch(String timerId, CallbackContext callbackContext) {
this.watches.put(timerId, callbackContext);
if (this.size() == 1) {
this.start();
}
}
public void addCallback(String callbackId) {
this.callbacks.add(callbackId);
public void addCallback(CallbackContext callbackContext) {
this.callbacks.add(callbackContext);
if (this.size() == 1) {
this.start();
}

View File

@@ -19,78 +19,108 @@
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.CordovaPlugin;
import org.apache.cordova.api.LOG;
import org.apache.cordova.api.PluginManager;
import org.apache.cordova.api.PluginResult;
import org.json.JSONException;
import org.xmlpull.v1.XmlPullParserException;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.res.XmlResourceParser;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.webkit.WebBackForwardList;
import android.webkit.WebHistoryItem;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebSettings.LayoutAlgorithm;
import android.widget.FrameLayout;
public class CordovaWebView extends WebView {
public static final String TAG = "CordovaWebView";
/** The whitelist **/
private ArrayList<Pattern> whiteList = new ArrayList<Pattern>();
private HashMap<String, Boolean> whiteListCache = new HashMap<String, Boolean>();
private ArrayList<Integer> keyDownCodes = new ArrayList<Integer>();
private ArrayList<Integer> keyUpCodes = new ArrayList<Integer>();
public PluginManager pluginManager;
public CallbackServer callbackServer;
private boolean paused;
private BroadcastReceiver receiver;
/** Actvities and other important classes **/
/** Activities and other important classes **/
private CordovaInterface cordova;
CordovaWebViewClient viewClient;
@SuppressWarnings("unused")
private CordovaChromeClient chromeClient;
//This is for the polyfil history
//This is for the polyfill history
private String url;
String baseUrl;
private Stack<String> urls = new Stack<String>();
boolean useBrowserHistory = false;
boolean useBrowserHistory = true;
// Flag to track that a loadUrl timeout occurred
int loadUrlTimeout = 0;
private boolean bound;
private boolean volumedownBound;
private boolean volumeupBound;
private boolean handleButton = false;
private long lastMenuEventTime = 0;
NativeToJsMessageQueue jsMessageQueue;
NativeToJsMessageQueue jsMessageQueue;
ExposedJsApi exposedJsApi;
/** custom view created by the browser (a video player for example) */
private View mCustomView;
private WebChromeClient.CustomViewCallback mCustomViewCallback;
private ActivityResult mResult = null;
class ActivityResult {
int request;
int result;
Intent incoming;
public ActivityResult(int req, int res, Intent intent) {
request = req;
result = res;
incoming = intent;
}
}
static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
Gravity.CENTER);
/**
* Constructor.
*
@@ -151,7 +181,6 @@ public class CordovaWebView extends WebView {
Log.d(TAG, "Your activity must implement CordovaInterface to work");
}
this.setWebChromeClient(new CordovaChromeClient(this.cordova, this));
this.initWebViewClient(this.cordova);
this.loadConfiguration();
this.setup();
}
@@ -164,8 +193,8 @@ public class CordovaWebView extends WebView {
* @param defStyle
* @param privateBrowsing
*/
@TargetApi(11)
public CordovaWebView(Context context, AttributeSet attrs, int defStyle, boolean privateBrowsing) {
@TargetApi(11)
public CordovaWebView(Context context, AttributeSet attrs, int defStyle, boolean privateBrowsing) {
super(context, attrs, defStyle, privateBrowsing);
if (CordovaInterface.class.isInstance(context))
{
@@ -183,7 +212,7 @@ 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)
{
this.setWebViewClient(new CordovaWebViewClient(this.cordova, this));
}
@@ -199,8 +228,6 @@ public class CordovaWebView extends WebView {
@SuppressWarnings("deprecation")
@SuppressLint("NewApi")
private void setup() {
jsMessageQueue = new NativeToJsMessageQueue(this, cordova);
this.setInitialScale(0);
this.setVerticalScrollBarEnabled(false);
this.requestFocusFromTouch();
@@ -210,15 +237,28 @@ public class CordovaWebView extends WebView {
settings.setJavaScriptEnabled(true);
settings.setJavaScriptCanOpenWindowsAutomatically(true);
settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
//Set the nav dump for HTC 2.x devices (disabling for ICS/Jellybean)
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB)
settings.setNavDump(true);
// Set the nav dump for HTC 2.x devices (disabling for ICS, deprecated entirely for Jellybean 4.2)
try {
Method gingerbread_getMethod = WebSettings.class.getMethod("setNavDump", new Class[] { boolean.class });
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB)
{
gingerbread_getMethod.invoke(settings, true);
}
} catch (NoSuchMethodException e) {
Log.d(TAG, "We are on a modern version of Android, we will deprecate HTC 2.3 devices in 2.8");
} catch (IllegalArgumentException e) {
Log.d(TAG, "Doing the NavDump failed with bad arguments");
} catch (IllegalAccessException e) {
Log.d(TAG, "This should never happen: IllegalAccessException means this isn't Android anymore");
} catch (InvocationTargetException e) {
Log.d(TAG, "This should never happen: InvocationTargetException means this isn't Android anymore.");
}
// 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);
Level16Apis.enableUniversalAccess(settings);
// Enable database
settings.setDatabaseEnabled(true);
String databasePath = this.cordova.getActivity().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
@@ -229,30 +269,49 @@ public class CordovaWebView extends WebView {
// Enable built-in geolocation
settings.setGeolocationEnabled(true);
//Start up the plugin manager
try {
this.pluginManager = new PluginManager(this, this.cordova);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
// Fix for CB-1405
// Google issue 4641
this.updateUserAgentString();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
if (this.receiver == null) {
this.receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
updateUserAgentString();
}
};
this.cordova.getActivity().registerReceiver(this.receiver, intentFilter);
}
// end CB-1405
pluginManager = new PluginManager(this, this.cordova);
jsMessageQueue = new NativeToJsMessageQueue(this, cordova);
exposedJsApi = new ExposedJsApi(pluginManager, jsMessageQueue);
exposeJsInterface();
}
private void updateUserAgentString() {
this.getSettings().getUserAgentString();
}
private void exposeJsInterface() {
// addJavascriptInterface crashes on the 2.3 emulator.
if (Build.VERSION.RELEASE.startsWith("2.3") && Build.MANUFACTURER.equals("unknown")) {
int SDK_INT = Build.VERSION.SDK_INT;
boolean isHoneycomb = (SDK_INT >= Build.VERSION_CODES.HONEYCOMB && SDK_INT <= Build.VERSION_CODES.HONEYCOMB_MR2);
if (isHoneycomb || (SDK_INT < Build.VERSION_CODES.GINGERBREAD)) {
Log.i(TAG, "Disabled addJavascriptInterface() bridge since Android version is old.");
// Bug being that Java Strings do not get converted to JS strings automatically.
// This isn't hard to work-around on the JS side, but it's easier to just
// use the prompt bridge instead.
return;
} else if (SDK_INT < Build.VERSION_CODES.HONEYCOMB && Build.MANUFACTURER.equals("unknown")) {
// addJavascriptInterface crashes on the 2.3 emulator.
Log.i(TAG, "Disabled addJavascriptInterface() bridge callback due to a bug on the 2.3 emulator");
return;
}
this.addJavascriptInterface(new Object() {
@SuppressWarnings("unused")
public String exec(String service, String action, String callbackId, String arguments) throws JSONException {
PluginResult r = pluginManager.exec(service, action, callbackId, arguments, true /* async */);
return r == null ? "" : r.getJSONString();
}
}, "_cordovaExec");
this.addJavascriptInterface(exposedJsApi, "_cordovaNative");
}
/**
@@ -274,71 +333,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;
}
/**
@@ -461,8 +458,12 @@ public class CordovaWebView extends WebView {
* @param url
*/
void loadUrlNow(String url) {
LOG.d(TAG, ">>> loadUrlNow()");
super.loadUrl(url);
if (LOG.isLoggable(LOG.DEBUG) && !url.startsWith("javascript:")) {
LOG.d(TAG, ">>> loadUrlNow()");
}
if (url.startsWith("file://") || url.indexOf(this.baseUrl) == 0 || url.startsWith("javascript:") || Config.isUrlWhiteListed(url)) {
super.loadUrl(url);
}
}
/**
@@ -499,7 +500,17 @@ public class CordovaWebView extends WebView {
* @param message
*/
public void sendJavascript(String statement) {
this.jsMessageQueue.add(statement);
this.jsMessageQueue.addJavaScript(statement);
}
/**
* Send a plugin result back to JavaScript.
* (This is a convenience method)
*
* @param message
*/
public void sendPluginResult(PluginResult result, String callbackId) {
this.jsMessageQueue.addPluginResult(result, callbackId);
}
/**
@@ -544,12 +555,14 @@ public class CordovaWebView extends WebView {
// Check webview first to see if there is a history
// This is needed to support curPage#diffLink, since they are added to appView's history, but not our history url array (JQMobile behavior)
if (super.canGoBack()) {
printBackForwardList();
super.goBack();
return true;
}
// If our managed history has prev url
if (this.urls.size() > 1) {
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);
@@ -596,7 +609,7 @@ 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://") || url.indexOf(this.baseUrl) == 0 || Config.isUrlWhiteListed(url)) {
// TODO: What about params?
// Clear out current url from history, since it will be replacing it
@@ -633,70 +646,21 @@ public class CordovaWebView extends WebView {
}
/**
* Load Cordova configuration from res/xml/cordova.xml.
* Check configuration parameters from Config.
* Approved list of URLs that can be loaded into DroidGap
* <access origin="http://server regexp" subdomains="true" />
* Log level: ERROR, WARN, INFO, DEBUG, VERBOSE (default=ERROR)
* <log level="DEBUG" />
*/
private void loadConfiguration() {
int id = getResources().getIdentifier("config", "xml", this.cordova.getActivity().getPackageName());
if(id == 0)
// Config has already been loaded, and it stores these preferences on the Intent.
if("false".equals(this.getProperty("useBrowserHistory", "true")))
{
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();
}
}
// Init preferences
if ("true".equals(this.getProperty("useBrowserHistory", "false"))) {
this.useBrowserHistory = true;
}
else {
//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);
this.cordova.getActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
@@ -731,7 +695,7 @@ public class CordovaWebView extends WebView {
if(keyDownCodes.contains(keyCode))
{
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
// only override default behaviour is event bound
// only override default behavior is event bound
LOG.d(TAG, "Down Key Hit");
this.loadUrl("javascript:cordova.fireDocumentEvent('volumedownbutton');");
return true;
@@ -747,6 +711,17 @@ public class CordovaWebView extends WebView {
return super.onKeyDown(keyCode, event);
}
}
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 super.onKeyDown(keyCode, event);
}
@@ -756,26 +731,35 @@ public class CordovaWebView extends WebView {
{
// If back key
if (keyCode == KeyEvent.KEYCODE_BACK) {
// If back key is bound, then send event to JavaScript
if (this.bound) {
this.loadUrl("javascript:cordova.fireDocumentEvent('backbutton');");
return true;
// A custom view is currently displayed (e.g. playing a video)
if(mCustomView != null) {
this.hideCustomView();
} else {
// If not bound
// Go to previous page in webview if it is possible to go back
if (this.backHistory()) {
// The webview is currently displayed
// If back key is bound, then send event to JavaScript
if (this.bound) {
this.loadUrl("javascript:cordova.fireDocumentEvent('backbutton');");
return true;
}
// If not, then invoke default behaviour
else {
//this.activityState = ACTIVITY_EXITING;
return false;
} else {
// If not bound
// Go to previous page in webview if it is possible to go back
if (this.backHistory()) {
return true;
}
// If not, then invoke default behaviour
else {
//this.activityState = ACTIVITY_EXITING;
return false;
}
}
}
}
// Legacy
else if (keyCode == KeyEvent.KEYCODE_MENU) {
this.loadUrl("javascript:cordova.fireDocumentEvent('menubutton');");
if (this.lastMenuEventTime < event.getEventTime()) {
this.loadUrl("javascript:cordova.fireDocumentEvent('menubutton');");
}
this.lastMenuEventTime = event.getEventTime();
return super.onKeyUp(keyCode, event);
}
// If search key
@@ -789,7 +773,7 @@ public class CordovaWebView extends WebView {
return super.onKeyUp(keyCode, event);
}
//Does webkit change this behaviour?
//Does webkit change this behavior?
return super.onKeyUp(keyCode, event);
}
@@ -848,9 +832,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);
@@ -873,6 +856,15 @@ public class CordovaWebView extends WebView {
if (this.pluginManager != null) {
this.pluginManager.onDestroy();
}
// unregister the receiver
if (this.receiver != null) {
try {
this.cordova.getActivity().unregisterReceiver(this.receiver);
} catch (Exception e) {
Log.e(TAG, "Error unregistering configuration receiver: " + e.getMessage(), e);
}
}
}
public void onNewIntent(Intent intent)
@@ -900,4 +892,94 @@ public class CordovaWebView extends WebView {
settings.setAllowUniversalAccessFromFileURLs(true);
}
}
public void printBackForwardList() {
WebBackForwardList currentList = this.copyBackForwardList();
int currentSize = currentList.getSize();
for(int i = 0; i < currentSize; ++i)
{
WebHistoryItem item = currentList.getItemAtIndex(i);
String url = item.getUrl();
LOG.d(TAG, "The URL at index: " + Integer.toString(i) + "is " + url );
}
}
//Can Go Back is BROKEN!
public boolean startOfHistory()
{
WebBackForwardList currentList = this.copyBackForwardList();
WebHistoryItem item = currentList.getItemAtIndex(0);
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);
}
public void showCustomView(View view, WebChromeClient.CustomViewCallback callback) {
// This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
Log.d(TAG, "showing Custom View");
// if a view already exists then immediately terminate the new one
if (mCustomView != null) {
callback.onCustomViewHidden();
return;
}
// Store the view and its callback for later (to kill it properly)
mCustomView = view;
mCustomViewCallback = callback;
// Add the custom view to its container.
ViewGroup parent = (ViewGroup) this.getParent();
parent.addView(view, COVER_SCREEN_GRAVITY_CENTER);
// Hide the content view.
this.setVisibility(View.GONE);
// Finally show the custom view container.
parent.setVisibility(View.VISIBLE);
parent.bringToFront();
}
public void hideCustomView() {
// This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
Log.d(TAG, "Hidding Custom View");
if (mCustomView == null) return;
// Hide the custom view.
mCustomView.setVisibility(View.GONE);
// Remove the custom view from its container.
ViewGroup parent = (ViewGroup) this.getParent();
parent.removeView(mCustomView);
mCustomView = null;
mCustomViewCallback.onCustomViewHidden();
// Show the content view.
this.setVisibility(View.VISIBLE);
}
/**
* if the video overlay is showing then we need to know
* as it effects back button handling
*
* @return
*/
public boolean isCustomViewShowing() {
return mCustomView != null;
}
public WebBackForwardList restoreState(Bundle savedInstanceState)
{
WebBackForwardList myList = super.restoreState(savedInstanceState);
Log.d(TAG, "WebView restoration crew now restoring!");
//Initialize the plugin manager once more
this.pluginManager.init();
return myList;
}
public void storeResult(int requestCode, int resultCode, Intent intent) {
mResult = new ActivityResult(requestCode, resultCode, intent);
}
}

View File

@@ -21,22 +21,16 @@ package org.apache.cordova;
import java.util.Hashtable;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.PluginResult;
import java.io.IOException;
import java.io.InputStream;
import org.apache.cordova.api.LOG;
import org.json.JSONException;
import org.json.JSONObject;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.net.Uri;
import android.net.http.SslError;
@@ -44,7 +38,6 @@ 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;
@@ -54,9 +47,6 @@ import android.webkit.WebViewClient;
public class CordovaWebViewClient extends WebViewClient {
private static final String TAG = "Cordova";
// Disable URL-based exec() bridge by default since it's a bit of a
// security concern.
private static boolean ENABLE_LOCATION_CHANGE_EXEC_MODE = false;
private static final String CORDOVA_EXEC_URL_PREFIX = "http://cdv_exec/";
CordovaInterface cordova;
CordovaWebView appView;
@@ -110,11 +100,7 @@ public class CordovaWebViewClient extends WebViewClient {
String action = url.substring(idx2 + 1, idx3);
String callbackId = url.substring(idx3 + 1, idx4);
String jsonArgs = url.substring(idx4 + 1);
PluginResult r = appView.pluginManager.exec(service, action, callbackId, jsonArgs, true /* async */);
String callbackString = r.toCallbackString(callbackId);
if (r != null) {
appView.sendJavascript(callbackString);
}
appView.pluginManager.exec(service, action, callbackId, jsonArgs);
}
/**
@@ -128,7 +114,7 @@ public class CordovaWebViewClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// Check if it's an exec() bridge command message.
if (ENABLE_LOCATION_CHANGE_EXEC_MODE && url.startsWith(CORDOVA_EXEC_URL_PREFIX)) {
if (NativeToJsMessageQueue.ENABLE_LOCATION_CHANGE_EXEC_MODE && url.startsWith(CORDOVA_EXEC_URL_PREFIX)) {
handleExecUrl(url);
}
@@ -206,7 +192,7 @@ 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)) {
if (url.startsWith("file://") || url.startsWith("data:") || url.indexOf(this.appView.baseUrl) == 0 || Config.isUrlWhiteListed(url)) {
//This will fix iFrames
if (appView.useBrowserHistory || url.startsWith("data:"))
return false;
@@ -268,14 +254,13 @@ public class CordovaWebViewClient extends WebViewClient {
// Flush stale messages.
this.appView.jsMessageQueue.reset();
// Create callback server
if (this.appView.callbackServer == null) {
this.appView.callbackServer = new CallbackServer();
}
this.appView.callbackServer.init(url);
// Broadcast message that page has loaded
this.appView.postMessage("onPageStarted", url);
// Notify all plugins of the navigation, so they can clean up if necessary.
if (this.appView.pluginManager != null) {
this.appView.pluginManager.onReset();
}
}
/**
@@ -309,6 +294,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);
}
@@ -336,9 +322,6 @@ public class CordovaWebViewClient extends WebViewClient {
// Shutdown if blank loaded
if (url.equals("about:blank")) {
if (this.appView.callbackServer != null) {
this.appView.callbackServer.destroy();
}
appView.postMessage("exit", null);
}
}

View File

@@ -20,9 +20,10 @@ package org.apache.cordova;
import java.util.TimeZone;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.LOG;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
@@ -35,10 +36,10 @@ import android.content.IntentFilter;
import android.provider.Settings;
import android.telephony.TelephonyManager;
public class Device extends Plugin {
public class Device extends CordovaPlugin {
public static final String TAG = "Device";
public static String cordovaVersion = "2.1.0"; // Cordova version
public static String cordovaVersion = "2.3.0"; // Cordova version
public static String platform = "Android"; // Device OS
public static String uuid; // Device UUID
@@ -55,9 +56,10 @@ public class Device extends Plugin {
* get file paths associated with the Activity.
*
* @param cordova The context of the main Activity.
* @param webView The CordovaWebView Cordova is running in.
*/
public void setContext(CordovaInterface cordova) {
super.setContext(cordova);
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
super.initialize(cordova, webView);
Device.uuid = getUuid();
this.initTelephonyReceiver();
}
@@ -65,45 +67,26 @@ public class Device extends Plugin {
/**
* Executes the request and returns PluginResult.
*
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param callbackId The callback id used when calling back into JavaScript.
* @return A PluginResult object with a status and message.
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param callbackContext The callback id used when calling back into JavaScript.
* @return True if the action was valid, false if not.
*/
public PluginResult execute(String action, JSONArray args, String callbackId) {
PluginResult.Status status = PluginResult.Status.OK;
String result = "";
try {
if (action.equals("getDeviceInfo")) {
JSONObject r = new JSONObject();
r.put("uuid", Device.uuid);
r.put("version", this.getOSVersion());
r.put("platform", Device.platform);
r.put("name", this.getProductName());
r.put("cordova", Device.cordovaVersion);
//JSONObject pg = new JSONObject();
//pg.put("version", Device.CordovaVersion);
//r.put("cordova", pg);
return new PluginResult(status, r);
}
return new PluginResult(status, result);
} catch (JSONException e) {
return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
}
}
/**
* Identifies if action to be executed returns a value and should be run synchronously.
*
* @param action The action to execute
* @return T=returns value
*/
public boolean isSynch(String action) {
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
if (action.equals("getDeviceInfo")) {
return true;
JSONObject r = new JSONObject();
r.put("uuid", Device.uuid);
r.put("version", this.getOSVersion());
r.put("platform", Device.platform);
r.put("name", this.getProductName());
r.put("cordova", Device.cordovaVersion);
r.put("model", this.getModel());
callbackContext.success(r);
}
return false;
else {
return false;
}
return true;
}
/**

View File

@@ -19,10 +19,12 @@
package org.apache.cordova;
import java.util.HashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.cordova.api.IPlugin;
import org.apache.cordova.api.LOG;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.LOG;
import org.json.JSONException;
import org.json.JSONObject;
@@ -36,6 +38,7 @@ 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;
@@ -47,6 +50,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.webkit.ValueCallback;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.LinearLayout;
@@ -142,6 +146,8 @@ public class DroidGap extends Activity implements CordovaInterface {
protected LinearLayout root;
protected boolean cancelLoadUrl = false;
protected ProgressDialog spinnerDialog = null;
private final ExecutorService threadPool = Executors.newCachedThreadPool();
// The initial URL for our app
// ie http://server/path/index.html#abc?query
@@ -152,13 +158,8 @@ public class DroidGap extends Activity implements CordovaInterface {
private static int ACTIVITY_EXITING = 2;
private int activityState = 0; // 0=starting, 1=running (after 1st resume), 2=shutting down
// The base of the initial URL for our app.
// Does not include file name. Ends with /
// ie http://server/path/
String baseUrl = null;
// Plugin to call when activity result is received
protected IPlugin activityResultCallback = null;
protected CordovaPlugin activityResultCallback = null;
protected boolean activityResultKeepRunning;
// Default background color for activity
@@ -172,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;
@@ -182,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.
*
@@ -248,11 +261,15 @@ public class DroidGap extends Activity implements CordovaInterface {
@SuppressWarnings("deprecation")
@Override
public void onCreate(Bundle savedInstanceState) {
//preferences = new PreferenceSet();
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);
@@ -297,7 +314,16 @@ public class DroidGap extends Activity implements CordovaInterface {
*/
public void init() {
CordovaWebView webView = new CordovaWebView(DroidGap.this);
this.init(webView, new CordovaWebViewClient(this, webView), new CordovaChromeClient(this, webView));
CordovaWebViewClient webViewClient;
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB)
{
webViewClient = new CordovaWebViewClient(this, webView);
}
else
{
webViewClient = new IceCreamCordovaWebViewClient(this, webView);
}
this.init(webView, webViewClient, new CordovaChromeClient(this, webView));
}
/**
@@ -331,6 +357,7 @@ public class DroidGap extends Activity implements CordovaInterface {
// Clear cancel flag
this.cancelLoadUrl = false;
}
/**
@@ -406,6 +433,8 @@ public class DroidGap extends Activity implements CordovaInterface {
}
this.splashscreenTime = time;
this.splashscreen = this.getIntegerProperty("splashscreen", 0);
this.showSplashScreen(this.splashscreenTime);
this.appView.loadUrl(url, time);
}
@@ -716,7 +745,7 @@ public class DroidGap extends Activity implements CordovaInterface {
*/
public void sendJavascript(String statement) {
if (this.appView != null) {
this.appView.jsMessageQueue.add(statement);
this.appView.jsMessageQueue.addJavaScript(statement);
}
}
@@ -767,7 +796,7 @@ public class DroidGap extends Activity implements CordovaInterface {
* @param intent The intent to start
* @param requestCode The request code that is passed to callback to identify the activity
*/
public void startActivityForResult(IPlugin command, Intent intent, int requestCode) {
public void startActivityForResult(CordovaPlugin command, Intent intent, int requestCode) {
this.activityResultCallback = command;
this.activityResultKeepRunning = this.keepRunning;
@@ -791,14 +820,40 @@ public class DroidGap extends Activity implements CordovaInterface {
* @param data An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
*/
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
LOG.d(TAG, "Incoming Result");
super.onActivityResult(requestCode, resultCode, intent);
IPlugin callback = this.activityResultCallback;
if (callback != null) {
Log.d(TAG, "Request code = " + requestCode);
ValueCallback<Uri> mUploadMessage = this.appView.getWebChromeClient().getValueCallback();
if (requestCode == CordovaChromeClient.FILECHOOSER_RESULTCODE) {
Log.d(TAG, "did we get here?");
if (null == mUploadMessage)
return;
Uri result = intent == null || resultCode != Activity.RESULT_OK ? null : intent.getData();
Log.d(TAG, "result = " + result);
// Uri filepath = Uri.parse("file://" + FileUtils.getRealPathFromURI(result, this));
// Log.d(TAG, "result = " + filepath);
mUploadMessage.onReceiveValue(result);
mUploadMessage = null;
}
CordovaPlugin callback = this.activityResultCallback;
if(callback == null)
{
if(initCallbackClass != null)
{
this.activityResultCallback = appView.pluginManager.getPlugin(initCallbackClass);
callback = activityResultCallback;
LOG.d(TAG, "We have a callback to send this result to");
callback.onActivityResult(requestCode, resultCode, intent);
}
}
else
{
LOG.d(TAG, "We have a callback to send this result to");
callback.onActivityResult(requestCode, resultCode, intent);
}
}
public void setActivityResultCallback(IPlugin plugin) {
public void setActivityResultCallback(CordovaPlugin plugin) {
this.activityResultCallback = plugin;
}
@@ -815,7 +870,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://") || errorUrl.indexOf(me.baseUrl) == 0 || this.appView.isUrlWhiteListed(errorUrl)) && (!failingUrl.equals(errorUrl))) {
if ((errorUrl != null) && (errorUrl.startsWith("file://") || Config.isUrlWhiteListed(errorUrl)) && (!failingUrl.equals(errorUrl))) {
// Load URL on UI thread
me.runOnUiThread(new Runnable() {
@@ -831,8 +886,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);
}
@@ -883,11 +937,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);
}
/*
@@ -903,7 +953,7 @@ public class DroidGap extends Activity implements CordovaInterface {
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
this.postMessage("onPrepareOptionsMenu", menu);
return super.onPrepareOptionsMenu(menu);
return true;
}
@Override
@@ -996,20 +1046,39 @@ public class DroidGap extends Activity implements CordovaInterface {
this.runOnUiThread(runnable);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event)
{
//Determine if the focus is on the current view or not
if (appView.getHitTestResult() != null &&
appView.getHitTestResult().getType() == WebView.HitTestResult.EDIT_TEXT_TYPE &&
keyCode == KeyEvent.KEYCODE_BACK) {
return appView.onKeyUp(keyCode, event);
//Get whatever has focus!
View childView = appView.getFocusedChild();
if ((appView.isCustomViewShowing() || childView != null ) && keyCode == KeyEvent.KEYCODE_BACK) {
return appView.onKeyUp(keyCode, event);
} else {
return super.onKeyUp(keyCode, event);
}
else
return super.onKeyUp(keyCode, event);
}
/*
* Android 2.x needs to be able to check where the cursor is. Android 4.x does not
*
* (non-Javadoc)
* @see android.app.Activity#onKeyDown(int, android.view.KeyEvent)
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
//Get whatever has focus!
View childView = appView.getFocusedChild();
//Determine if the focus is on the current view or not
if (childView != null && keyCode == KeyEvent.KEYCODE_BACK) {
return appView.onKeyDown(keyCode, event);
}
else
return super.onKeyDown(keyCode, event);
}
/**
* Called when a message is sent to plugin.
*
@@ -1024,8 +1093,11 @@ public class DroidGap extends Activity implements CordovaInterface {
this.removeSplashScreen();
}
else {
this.splashscreen = this.getIntegerProperty("splashscreen", 0);
this.showSplashScreen(this.splashscreenTime);
// If the splash dialog is showing don't try to show it again
if (this.splashDialog == null || !this.splashDialog.isShowing()) {
this.splashscreen = this.getIntegerProperty("splashscreen", 0);
this.showSplashScreen(this.splashscreenTime);
}
}
}
else if ("spinner".equals(id)) {
@@ -1047,4 +1119,19 @@ public class DroidGap extends Activity implements CordovaInterface {
}
return null;
}
public ExecutorService getThreadPool() {
return threadPool;
}
protected void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
if(this.activityResultCallback != null)
{
String cClass = this.activityResultCallback.getClass().getName();
outState.putString("callbackClass", cClass);
}
}
}

View File

@@ -18,40 +18,27 @@
*/
package org.apache.cordova;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.PluginResult;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.json.JSONArray;
import org.json.JSONException;
public class Echo extends Plugin {
public class Echo extends CordovaPlugin {
/**
* Executes the request and returns PluginResult.
*
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param callbackId The callback id used when calling back into JavaScript.
* @return A PluginResult object with a status and message.
*/
public PluginResult execute(String action, JSONArray args, String callbackId) {
try {
String result = args.getString(0);
if ("echo".equals(action) || "echoAsync".equals(action)) {
return new PluginResult(PluginResult.Status.OK, result);
}
return new PluginResult(PluginResult.Status.INVALID_ACTION);
} catch (JSONException e) {
return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
@Override
public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException {
final String result = args.isNull(0) ? null : args.getString(0);
if ("echo".equals(action)) {
callbackContext.success(result);
return true;
} else if ("echoAsync".equals(action)) {
cordova.getThreadPool().execute(new Runnable() {
public void run() {
callbackContext.success(result);
}
});
return true;
}
}
/**
* Identifies if action to be executed returns a value and should be run synchronously.
*
* @param action The action to execute
* @return T=returns value
*/
public boolean isSynch(String action) {
return "echo".equals(action);
return false;
}
}

View File

@@ -23,7 +23,7 @@ import java.io.IOException;
import android.media.ExifInterface;
public class ExifHelper {
private String aperature = null;
private String aperture = null;
private String datetime = null;
private String exposureTime = null;
private String flash = null;
@@ -70,7 +70,7 @@ public class ExifHelper {
* Reads all the EXIF data from the input file.
*/
public void readExifData() {
this.aperature = inFile.getAttribute(ExifInterface.TAG_APERTURE);
this.aperture = inFile.getAttribute(ExifInterface.TAG_APERTURE);
this.datetime = inFile.getAttribute(ExifInterface.TAG_DATETIME);
this.exposureTime = inFile.getAttribute(ExifInterface.TAG_EXPOSURE_TIME);
this.flash = inFile.getAttribute(ExifInterface.TAG_FLASH);
@@ -102,8 +102,8 @@ public class ExifHelper {
return;
}
if (this.aperature != null) {
this.outFile.setAttribute(ExifInterface.TAG_APERTURE, this.aperature);
if (this.aperture != null) {
this.outFile.setAttribute(ExifInterface.TAG_APERTURE, this.aperture);
}
if (this.datetime != null) {
this.outFile.setAttribute(ExifInterface.TAG_DATETIME, this.datetime);

View File

@@ -0,0 +1,65 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import android.webkit.JavascriptInterface;
import org.apache.cordova.api.PluginManager;
import org.apache.cordova.api.PluginResult;
import org.json.JSONException;
/**
* Contains APIs that the JS can call. All functions in here should also have
* an equivalent entry in CordovaChromeClient.java, and be added to
* cordova-js/lib/android/plugin/android/promptbasednativeapi.js
*/
/* package */ class ExposedJsApi {
private PluginManager pluginManager;
private NativeToJsMessageQueue jsMessageQueue;
public ExposedJsApi(PluginManager pluginManager, NativeToJsMessageQueue jsMessageQueue) {
this.pluginManager = pluginManager;
this.jsMessageQueue = jsMessageQueue;
}
@JavascriptInterface
public String exec(String service, String action, String callbackId, String arguments) throws JSONException {
jsMessageQueue.setPaused(true);
try {
boolean wasSync = pluginManager.exec(service, action, callbackId, arguments);
String ret = "";
if (!NativeToJsMessageQueue.DISABLE_EXEC_CHAINING || wasSync) {
ret = jsMessageQueue.popAndEncode();
}
return ret;
} finally {
jsMessageQueue.setPaused(false);
}
}
@JavascriptInterface
public void setNativeToJsBridgeMode(int value) {
jsMessageQueue.setBridgeMode(value);
}
@JavascriptInterface
public String retrieveJsMessages() {
return jsMessageQueue.popAndEncode();
}
}

View File

@@ -0,0 +1,63 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Encapsulates in-progress status of uploading or downloading a file to a remote server.
*/
public class FileProgressResult {
private boolean lengthComputable = false; // declares whether total is known
private long loaded = 0; // bytes sent so far
private long total = 0; // bytes total, if known
public boolean getLengthComputable() {
return lengthComputable;
}
public void setLengthComputable(boolean computable) {
this.lengthComputable = computable;
}
public long getLoaded() {
return loaded;
}
public void setLoaded(long bytes) {
this.loaded = bytes;
}
public long getTotal() {
return total;
}
public void setTotal(long bytes) {
this.total = bytes;
}
public JSONObject toJSONObject() throws JSONException {
return new JSONObject(
"{loaded:" + loaded +
",total:" + total +
",lengthComputable:" + (lengthComputable ? "true" : "false") + "}");
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -29,6 +29,7 @@ public class FileUploadResult {
private long bytesSent = 0; // bytes sent
private int responseCode = -1; // HTTP response code
private String response = null; // HTTP response
private String objectId = null; // FileTransfer object id
public long getBytesSent() {
return bytesSent;
@@ -54,10 +55,19 @@ public class FileUploadResult {
this.response = response;
}
public String getObjectId() {
return objectId;
}
public void setObjectId(String objectId) {
this.objectId = objectId;
}
public JSONObject toJSONObject() throws JSONException {
return new JSONObject(
"{bytesSent:" + bytesSent +
",responseCode:" + responseCode +
",response:" + JSONObject.quote(response) + "}");
",response:" + JSONObject.quote(response) +
",objectId:" + JSONObject.quote(objectId) + "}");
}
}

View File

@@ -25,8 +25,9 @@ import java.net.URLDecoder;
import java.nio.channels.FileChannel;
import org.apache.commons.codec.binary.Base64;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.PluginResult;
import org.apache.cordova.file.EncodingException;
import org.apache.cordova.file.FileExistsException;
@@ -42,6 +43,7 @@ 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;
@@ -50,7 +52,7 @@ import android.webkit.MimeTypeMap;
* This class provides SD card file and directory services to JavaScript.
* Only files on the SD card can be accessed.
*/
public class FileUtils extends Plugin {
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
@@ -84,83 +86,96 @@ public class FileUtils extends Plugin {
}
/**
* Executes the request and returns PluginResult.
* 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 callbackId The callback id used when calling back into JavaScript.
* @return A PluginResult object with a status and message.
* @param args JSONArry of arguments for the plugin.
* @param callbackContext The callback context used when calling back into JavaScript.
* @return True if the action was valid, false otherwise.
*/
public PluginResult execute(String action, JSONArray args, String callbackId) {
PluginResult.Status status = PluginResult.Status.OK;
String result = "";
//System.out.println("FileUtils.execute("+action+")");
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
try {
if (action.equals("testSaveLocationExists")) {
boolean b = DirectoryManager.testSaveLocationExists();
return new PluginResult(status, b);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b));
}
else if (action.equals("getFreeDiskSpace")) {
long l = DirectoryManager.getFreeDiskSpace(false);
return new PluginResult(status, l);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, l));
}
else if (action.equals("testFileExists")) {
boolean b = DirectoryManager.testFileExists(args.getString(0));
return new PluginResult(status, b);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b));
}
else if (action.equals("testDirectoryExists")) {
boolean b = DirectoryManager.testFileExists(args.getString(0));
return new PluginResult(status, b);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b));
}
else if (action.equals("readAsText")) {
String s = this.readAsText(args.getString(0), args.getString(1));
return new PluginResult(status, s);
int start = 0;
int end = Integer.MAX_VALUE;
if (args.length() >= 3) {
start = args.getInt(2);
}
if (args.length() >= 4) {
end = args.getInt(3);
}
String s = this.readAsText(args.getString(0), args.getString(1), start, end);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, s));
}
else if (action.equals("readAsDataURL")) {
String s = this.readAsDataURL(args.getString(0));
return new PluginResult(status, s);
int start = 0;
int end = Integer.MAX_VALUE;
if (args.length() >= 2) {
start = args.getInt(1);
}
if (args.length() >= 3) {
end = args.getInt(2);
}
String s = this.readAsDataURL(args.getString(0), start, end);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, s));
}
else if (action.equals("write")) {
long fileSize = this.write(args.getString(0), args.getString(1), args.getInt(2));
return new PluginResult(status, fileSize);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, fileSize));
}
else if (action.equals("truncate")) {
long fileSize = this.truncateFile(args.getString(0), args.getLong(1));
return new PluginResult(status, fileSize);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, fileSize));
}
else if (action.equals("requestFileSystem")) {
long size = args.optLong(1);
if (size != 0) {
if (size > (DirectoryManager.getFreeDiskSpace(true) * 1024)) {
return new PluginResult(PluginResult.Status.ERROR, FileUtils.QUOTA_EXCEEDED_ERR);
}
if (size != 0 && size > (DirectoryManager.getFreeDiskSpace(true) * 1024)) {
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, FileUtils.QUOTA_EXCEEDED_ERR));
} else {
JSONObject obj = requestFileSystem(args.getInt(0));
callbackContext.success(obj);
}
JSONObject obj = requestFileSystem(args.getInt(0));
return new PluginResult(status, obj);
}
else if (action.equals("resolveLocalFileSystemURI")) {
JSONObject obj = resolveLocalFileSystemURI(args.getString(0));
return new PluginResult(status, obj);
callbackContext.success(obj);
}
else if (action.equals("getMetadata")) {
return new PluginResult(status, getMetadata(args.getString(0)));
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, getMetadata(args.getString(0))));
}
else if (action.equals("getFileMetadata")) {
JSONObject obj = getFileMetadata(args.getString(0));
return new PluginResult(status, obj);
callbackContext.success(obj);
}
else if (action.equals("getParent")) {
JSONObject obj = getParent(args.getString(0));
return new PluginResult(status, obj);
callbackContext.success(obj);
}
else if (action.equals("getDirectory")) {
JSONObject obj = getFile(args.getString(0), args.getString(1), args.optJSONObject(2), true);
return new PluginResult(status, obj);
callbackContext.success(obj);
}
else if (action.equals("getFile")) {
JSONObject obj = getFile(args.getString(0), args.getString(1), args.optJSONObject(2), false);
return new PluginResult(status, obj);
callbackContext.success(obj);
}
else if (action.equals("remove")) {
boolean success;
@@ -169,51 +184,52 @@ public class FileUtils extends Plugin {
if (success) {
notifyDelete(args.getString(0));
return new PluginResult(status);
callbackContext.success();
} else {
return new PluginResult(PluginResult.Status.ERROR, FileUtils.NO_MODIFICATION_ALLOWED_ERR);
callbackContext.error(FileUtils.NO_MODIFICATION_ALLOWED_ERR);
}
}
else if (action.equals("removeRecursively")) {
boolean success = removeRecursively(args.getString(0));
if (success) {
return new PluginResult(status);
callbackContext.success();
} else {
return new PluginResult(PluginResult.Status.ERROR, FileUtils.NO_MODIFICATION_ALLOWED_ERR);
callbackContext.error(FileUtils.NO_MODIFICATION_ALLOWED_ERR);
}
}
else if (action.equals("moveTo")) {
JSONObject entry = transferTo(args.getString(0), args.getString(1), args.getString(2), true);
return new PluginResult(status, entry);
callbackContext.success(entry);
}
else if (action.equals("copyTo")) {
JSONObject entry = transferTo(args.getString(0), args.getString(1), args.getString(2), false);
return new PluginResult(status, entry);
callbackContext.success(entry);
}
else if (action.equals("readEntries")) {
JSONArray entries = readEntries(args.getString(0));
return new PluginResult(status, entries);
callbackContext.success(entries);
}
else {
return false;
}
return new PluginResult(status, result);
} catch (FileNotFoundException e) {
return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_FOUND_ERR);
callbackContext.error(FileUtils.NOT_FOUND_ERR);
} catch (FileExistsException e) {
return new PluginResult(PluginResult.Status.ERROR, FileUtils.PATH_EXISTS_ERR);
callbackContext.error(FileUtils.PATH_EXISTS_ERR);
} catch (NoModificationAllowedException e) {
return new PluginResult(PluginResult.Status.ERROR, FileUtils.NO_MODIFICATION_ALLOWED_ERR);
} catch (JSONException e) {
return new PluginResult(PluginResult.Status.ERROR, FileUtils.NO_MODIFICATION_ALLOWED_ERR);
callbackContext.error(FileUtils.NO_MODIFICATION_ALLOWED_ERR);
} catch (InvalidModificationException e) {
return new PluginResult(PluginResult.Status.ERROR, FileUtils.INVALID_MODIFICATION_ERR);
callbackContext.error(FileUtils.INVALID_MODIFICATION_ERR);
} catch (MalformedURLException e) {
return new PluginResult(PluginResult.Status.ERROR, FileUtils.ENCODING_ERR);
callbackContext.error(FileUtils.ENCODING_ERR);
} catch (IOException e) {
return new PluginResult(PluginResult.Status.ERROR, FileUtils.INVALID_MODIFICATION_ERR);
callbackContext.error(FileUtils.INVALID_MODIFICATION_ERR);
} catch (EncodingException e) {
return new PluginResult(PluginResult.Status.ERROR, FileUtils.ENCODING_ERR);
callbackContext.error(FileUtils.ENCODING_ERR);
} catch (TypeMismatchException e) {
return new PluginResult(PluginResult.Status.ERROR, FileUtils.TYPE_MISMATCH_ERR);
callbackContext.error(FileUtils.TYPE_MISMATCH_ERR);
}
return true;
}
/**
@@ -223,9 +239,15 @@ public class FileUtils extends Plugin {
*/
private void notifyDelete(String filePath) {
String newFilePath = stripFileProtocol(filePath);
int result = this.cordova.getActivity().getContentResolver().delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
MediaStore.Images.Media.DATA + " = ?",
new String[] { newFilePath });
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.
}
}
/**
@@ -320,8 +342,9 @@ public class FileUtils extends Plugin {
* @throws InvalidModificationException
* @throws EncodingException
* @throws JSONException
* @throws FileExistsException
*/
private JSONObject transferTo(String fileName, String newParent, String newName, boolean move) throws JSONException, NoModificationAllowedException, IOException, InvalidModificationException, EncodingException {
private JSONObject transferTo(String fileName, String newParent, String newName, boolean move) throws JSONException, NoModificationAllowedException, IOException, InvalidModificationException, EncodingException, FileExistsException {
fileName = stripFileProtocol(fileName);
newParent = stripFileProtocol(newParent);
@@ -409,23 +432,31 @@ public class FileUtils extends Plugin {
throw new InvalidModificationException("Can't rename a file to a directory");
}
FileChannel input = new FileInputStream(srcFile).getChannel();
FileChannel output = new FileOutputStream(destFile).getChannel();
input.transferTo(0, input.size(), output);
input.close();
output.close();
/*
if (srcFile.length() != destFile.length()) {
return false;
}
*/
copyAction(srcFile, destFile);
return getEntry(destFile);
}
/**
* Moved this code into it's own method so moveTo could use it when the move is across file systems
*/
private void copyAction(File srcFile, File destFile)
throws FileNotFoundException, IOException {
FileInputStream istream = new FileInputStream(srcFile);
FileOutputStream ostream = new FileOutputStream(destFile);
FileChannel input = istream.getChannel();
FileChannel output = ostream.getChannel();
try {
input.transferTo(0, input.size(), output);
} finally {
istream.close();
ostream.close();
input.close();
output.close();
}
}
/**
* Copy a directory
*
@@ -480,7 +511,7 @@ public class FileUtils extends Plugin {
// This weird test is to determine if we are copying or moving a directory into itself.
// Copy /sdcard/myDir to /sdcard/myDir-backup is okay but
// Copy /sdcard/myDir to /sdcard/myDir/backup should thow an INVALID_MODIFICATION_ERR
// Copy /sdcard/myDir to /sdcard/myDir/backup should throw an INVALID_MODIFICATION_ERR
if (dest.startsWith(src) && dest.indexOf(File.separator, src.length() - 1) != -1) {
return true;
}
@@ -498,7 +529,7 @@ public class FileUtils extends Plugin {
* @throws InvalidModificationException
* @throws JSONException
*/
private JSONObject moveFile(File srcFile, File destFile) throws JSONException, InvalidModificationException {
private JSONObject moveFile(File srcFile, File destFile) throws IOException, JSONException, InvalidModificationException {
// Renaming a file to an existing directory should fail
if (destFile.exists() && destFile.isDirectory()) {
throw new InvalidModificationException("Can't rename a file to a directory");
@@ -510,6 +541,12 @@ public class FileUtils extends Plugin {
// Now we have to do things the hard way
// 1) Copy all the old file
// 2) delete the src file
copyAction(srcFile, destFile);
if (destFile.exists()) {
srcFile.delete();
} else {
throw new IOException("moved failed");
}
}
return getEntry(destFile);
@@ -524,8 +561,10 @@ public class FileUtils extends Plugin {
* @throws JSONException
* @throws IOException
* @throws InvalidModificationException
* @throws NoModificationAllowedException
* @throws FileExistsException
*/
private JSONObject moveDirectory(File srcDir, File destinationDir) throws JSONException, InvalidModificationException {
private JSONObject moveDirectory(File srcDir, File destinationDir) throws IOException, JSONException, InvalidModificationException, NoModificationAllowedException, FileExistsException {
// Renaming a file to an existing directory should fail
if (destinationDir.exists() && destinationDir.isFile()) {
throw new InvalidModificationException("Can't rename a file to a directory");
@@ -549,6 +588,12 @@ public class FileUtils extends Plugin {
// Now we have to do things the hard way
// 1) Copy all the old files
// 2) delete the src directory
copyDirectory(srcDir, destinationDir);
if (destinationDir.exists()) {
removeDirRecursively(srcDir);
} else {
throw new IOException("moved failed");
}
}
return getEntry(destinationDir);
@@ -911,17 +956,27 @@ public class FileUtils extends Plugin {
* @param filename The name of the file.
* @param encoding The encoding to return contents as. Typical value is UTF-8.
* (see http://www.iana.org/assignments/character-sets)
* @param start Start position in the file.
* @param end End position to stop at (exclusive).
* @return Contents of file.
* @throws FileNotFoundException, IOException
*/
public String readAsText(String filename, String encoding) throws FileNotFoundException, IOException {
public String readAsText(String filename, String encoding, int start, int end) throws FileNotFoundException, IOException {
int diff = end - start;
byte[] bytes = new byte[1000];
BufferedInputStream bis = new BufferedInputStream(getPathFromUri(filename), 1024);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int numRead = 0;
while ((numRead = bis.read(bytes, 0, 1000)) >= 0) {
if (start > 0) {
bis.skip(start);
}
while ( diff > 0 && (numRead = bis.read(bytes, 0, Math.min(1000, diff))) >= 0) {
diff -= numRead;
bos.write(bytes, 0, numRead);
}
return new String(bos.toByteArray(), encoding);
}
@@ -932,12 +987,19 @@ public class FileUtils extends Plugin {
* @return Contents of file = data:<media type>;base64,<data>
* @throws FileNotFoundException, IOException
*/
public String readAsDataURL(String filename) throws FileNotFoundException, IOException {
public String readAsDataURL(String filename, int start, int end) throws FileNotFoundException, IOException {
int diff = end - start;
byte[] bytes = new byte[1000];
BufferedInputStream bis = new BufferedInputStream(getPathFromUri(filename), 1024);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int numRead = 0;
while ((numRead = bis.read(bytes, 0, 1000)) >= 0) {
if (start > 0) {
bis.skip(start);
}
while (diff > 0 && (numRead = bis.read(bytes, 0, Math.min(1000, diff))) >= 0) {
diff -= numRead;
bos.write(bytes, 0, numRead);
}
@@ -963,8 +1025,22 @@ public class FileUtils extends Plugin {
* @return a mime type
*/
public static String getMimeType(String filename) {
MimeTypeMap map = MimeTypeMap.getSingleton();
return map.getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(filename));
if (filename != null) {
// Stupid bug in getFileExtensionFromUrl when the file name has a space
// So we need to replace the space with a url encoded %20
// CB-2185: Stupid bug not putting JPG extension in the mime-type map
String url = filename.replace(" ", "%20").toLowerCase();
MimeTypeMap map = MimeTypeMap.getSingleton();
String extension = MimeTypeMap.getFileExtensionFromUrl(url);
if (extension.toLowerCase().equals("3ga")) {
return "audio/3gpp";
} else {
return map.getMimeTypeFromExtension(extension);
}
} else {
return "";
}
}
/**
@@ -1008,14 +1084,17 @@ public class FileUtils extends Plugin {
filename = stripFileProtocol(filename);
RandomAccessFile raf = new RandomAccessFile(filename, "rw");
if (raf.length() >= size) {
FileChannel channel = raf.getChannel();
channel.truncate(size);
return size;
try {
if (raf.length() >= size) {
FileChannel channel = raf.getChannel();
channel.truncate(size);
return size;
}
return raf.length();
} finally {
raf.close();
}
return raf.length();
}
/**
@@ -1040,15 +1119,23 @@ public class FileUtils extends Plugin {
* 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 applicaiton context
* @param cordova the current application context
* @return the full path to the file
*/
@SuppressWarnings("deprecation")
protected static String getRealPathFromURI(Uri contentUri, CordovaInterface cordova) {
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);
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

@@ -18,7 +18,8 @@
*/
package org.apache.cordova;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
@@ -34,7 +35,7 @@ import android.location.LocationManager;
* This class only starts and stops various GeoListeners, which consist of a GPS and a Network Listener
*/
public class GeoBroker extends Plugin {
public class GeoBroker extends CordovaPlugin {
private GPSListener gpsListener;
private NetworkListener networkListener;
private LocationManager locationManager;
@@ -49,53 +50,51 @@ public class GeoBroker extends Plugin {
* Executes the request and returns PluginResult.
*
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param callbackId The callback id used when calling back into JavaScript.
* @return A PluginResult object with a status and message.
* @param args JSONArry of arguments for the plugin.
* @param callbackContext The callback id used when calling back into JavaScript.
* @return True if the action was valid, or false if not.
*/
public PluginResult execute(String action, JSONArray args, String callbackId) {
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
if (this.locationManager == null) {
this.locationManager = (LocationManager) this.cordova.getActivity().getSystemService(Context.LOCATION_SERVICE);
this.networkListener = new NetworkListener(this.locationManager, this);
this.gpsListener = new GPSListener(this.locationManager, this);
}
PluginResult.Status status = PluginResult.Status.NO_RESULT;
String message = "Location API is not available for this device.";
PluginResult result = new PluginResult(status, message);
if ( locationManager.isProviderEnabled( LocationManager.GPS_PROVIDER ) ||
locationManager.isProviderEnabled( LocationManager.NETWORK_PROVIDER )) {
result.setKeepCallback(true);
try {
if (action.equals("getLocation")) {
boolean enableHighAccuracy = args.getBoolean(0);
int maximumAge = args.getInt(1);
Location last = this.locationManager.getLastKnownLocation((enableHighAccuracy ? LocationManager.GPS_PROVIDER : LocationManager.NETWORK_PROVIDER));
// Check if we can use lastKnownLocation to get a quick reading and use less battery
if (last != null && (System.currentTimeMillis() - last.getTime()) <= maximumAge) {
result = new PluginResult(PluginResult.Status.OK, this.returnLocationJSON(last));
} else {
this.getCurrentLocation(callbackId, enableHighAccuracy);
}
}
else if (action.equals("addWatch")) {
String id = args.getString(0);
boolean enableHighAccuracy = args.getBoolean(1);
this.addWatch(id, callbackId, enableHighAccuracy);
}
else if (action.equals("clearWatch")) {
String id = args.getString(0);
this.clearWatch(id);
}
} catch (JSONException e) {
result = new PluginResult(PluginResult.Status.JSON_EXCEPTION, e.getMessage());
}
locationManager.isProviderEnabled( LocationManager.NETWORK_PROVIDER )) {
if (action.equals("getLocation")) {
boolean enableHighAccuracy = args.getBoolean(0);
int maximumAge = args.getInt(1);
Location last = this.locationManager.getLastKnownLocation((enableHighAccuracy ? LocationManager.GPS_PROVIDER : LocationManager.NETWORK_PROVIDER));
// Check if we can use lastKnownLocation to get a quick reading and use less battery
if (last != null && (System.currentTimeMillis() - last.getTime()) <= maximumAge) {
PluginResult result = new PluginResult(PluginResult.Status.OK, this.returnLocationJSON(last));
callbackContext.sendPluginResult(result);
} else {
this.getCurrentLocation(callbackContext, enableHighAccuracy);
}
}
else if (action.equals("addWatch")) {
String id = args.getString(0);
boolean enableHighAccuracy = args.getBoolean(1);
this.addWatch(id, callbackContext, enableHighAccuracy);
}
else if (action.equals("clearWatch")) {
String id = args.getString(0);
this.clearWatch(id);
}
else {
return false;
}
} else {
PluginResult.Status status = PluginResult.Status.NO_RESULT;
String message = "Location API is not available for this device.";
PluginResult result = new PluginResult(status, message);
callbackContext.sendPluginResult(result);
}
return result;
return true;
}
private void clearWatch(String id) {
@@ -103,42 +102,43 @@ public class GeoBroker extends Plugin {
this.networkListener.clearWatch(id);
}
private void getCurrentLocation(String callbackId, boolean enableHighAccuracy) {
private void getCurrentLocation(CallbackContext callbackContext, boolean enableHighAccuracy) {
if (enableHighAccuracy) {
this.gpsListener.addCallback(callbackId);
this.gpsListener.addCallback(callbackContext);
} else {
this.networkListener.addCallback(callbackId);
this.networkListener.addCallback(callbackContext);
}
}
private void addWatch(String timerId, String callbackId, boolean enableHighAccuracy) {
private void addWatch(String timerId, CallbackContext callbackContext, boolean enableHighAccuracy) {
if (enableHighAccuracy) {
this.gpsListener.addWatch(timerId, callbackId);
this.gpsListener.addWatch(timerId, callbackContext);
} else {
this.networkListener.addWatch(timerId, callbackId);
this.networkListener.addWatch(timerId, callbackContext);
}
}
/**
* 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) {
// Starting listeners is easier to run on main thread, so don't run async.
return true;
}
/**
* Called when the activity is to be shut down.
* Stop listener.
*/
public void onDestroy() {
this.networkListener.destroy();
this.gpsListener.destroy();
this.networkListener = null;
this.gpsListener = null;
if (this.networkListener != null) {
this.networkListener.destroy();
this.networkListener = null;
}
if (this.gpsListener != null) {
this.gpsListener.destroy();
this.gpsListener = null;
}
}
/**
* Called when the view navigates.
* Stop the listeners.
*/
public void onReset() {
this.onDestroy();
}
public JSONObject returnLocationJSON(Location loc) {
@@ -160,9 +160,9 @@ public class GeoBroker extends Plugin {
return o;
}
public void win(Location loc, String callbackId) {
public void win(Location loc, CallbackContext callbackContext) {
PluginResult result = new PluginResult(PluginResult.Status.OK, this.returnLocationJSON(loc));
this.success(result, callbackId);
callbackContext.sendPluginResult(result);
}
/**
@@ -172,7 +172,7 @@ public class GeoBroker extends Plugin {
* @param msg The error message
* @throws JSONException
*/
public void fail(int code, String msg, String callbackId) {
public void fail(int code, String msg, CallbackContext callbackContext) {
JSONObject obj = new JSONObject();
String backup = null;
try {
@@ -189,6 +189,16 @@ public class GeoBroker extends Plugin {
result = new PluginResult(PluginResult.Status.ERROR, backup);
}
this.error(result, callbackId);
callbackContext.sendPluginResult(result);
}
public boolean isGlobalListener(CordovaLocationListener listener)
{
if (gpsListener != null && networkListener != null)
{
return gpsListener.equals(listener) || networkListener.equals(listener);
}
else
return false;
}
}

View File

@@ -0,0 +1,577 @@
/*
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.text.DateFormat;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Currency;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.annotation.TargetApi;
import android.text.format.Time;
/**
*
*/
public class Globalization extends CordovaPlugin {
//GlobalizationCommand Plugin Actions
public static final String GETLOCALENAME = "getLocaleName";
public static final String DATETOSTRING = "dateToString";
public static final String STRINGTODATE = "stringToDate";
public static final String GETDATEPATTERN = "getDatePattern";
public static final String GETDATENAMES = "getDateNames";
public static final String ISDAYLIGHTSAVINGSTIME = "isDayLightSavingsTime";
public static final String GETFIRSTDAYOFWEEK = "getFirstDayOfWeek";
public static final String NUMBERTOSTRING = "numberToString";
public static final String STRINGTONUMBER = "stringToNumber";
public static final String GETNUMBERPATTERN = "getNumberPattern";
public static final String GETCURRENCYPATTERN = "getCurrencyPattern";
public static final String GETPREFERREDLANGUAGE = "getPreferredLanguage";
//GlobalizationCommand Option Parameters
public static final String OPTIONS = "options";
public static final String FORMATLENGTH = "formatLength";
//public static final String SHORT = "short"; //default for dateToString format
public static final String MEDIUM = "medium";
public static final String LONG = "long";
public static final String FULL = "full";
public static final String SELECTOR = "selector";
//public static final String DATEANDTIME = "date and time"; //default for dateToString
public static final String DATE = "date";
public static final String TIME = "time";
public static final String DATESTRING = "dateString";
public static final String TYPE = "type";
public static final String ITEM = "item";
public static final String NARROW = "narrow";
public static final String WIDE = "wide";
public static final String MONTHS = "months";
public static final String DAYS = "days";
//public static final String DECMIAL = "wide"; //default for numberToString
public static final String NUMBER = "number";
public static final String NUMBERSTRING = "numberString";
public static final String PERCENT = "percent";
public static final String CURRENCY = "currency";
public static final String CURRENCYCODE = "currencyCode";
@Override
public boolean execute(String action, JSONArray data, CallbackContext callbackContext) {
JSONObject obj = new JSONObject();
try{
if (action.equals(GETLOCALENAME)){
obj = getLocaleName();
}else if (action.equals(GETPREFERREDLANGUAGE)){
obj = getPreferredLanguage();
} else if (action.equalsIgnoreCase(DATETOSTRING)) {
obj = getDateToString(data);
}else if(action.equalsIgnoreCase(STRINGTODATE)){
obj = getStringtoDate(data);
}else if(action.equalsIgnoreCase(GETDATEPATTERN)){
obj = getDatePattern(data);
}else if(action.equalsIgnoreCase(GETDATENAMES)){
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.GINGERBREAD) {
throw new GlobalizationError(GlobalizationError.UNKNOWN_ERROR);
} else {
obj = getDateNames(data);
}
}else if(action.equalsIgnoreCase(ISDAYLIGHTSAVINGSTIME)){
obj = getIsDayLightSavingsTime(data);
}else if(action.equalsIgnoreCase(GETFIRSTDAYOFWEEK)){
obj = getFirstDayOfWeek(data);
}else if(action.equalsIgnoreCase(NUMBERTOSTRING)){
obj = getNumberToString(data);
}else if(action.equalsIgnoreCase(STRINGTONUMBER)){
obj = getStringToNumber(data);
}else if(action.equalsIgnoreCase(GETNUMBERPATTERN)){
obj = getNumberPattern(data);
}else if(action.equalsIgnoreCase(GETCURRENCYPATTERN)){
obj = getCurrencyPattern(data);
}else {
return false;
}
callbackContext.success(obj);
}catch (GlobalizationError ge){
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, ge.toJson()));
}catch (Exception e){
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
}
return true;
}
/*
* @Description: Returns the string identifier for the client's current locale setting
*
* @Return: JSONObject
* Object.value {String}: The locale identifier
*
* @throws: GlobalizationError.UNKNOWN_ERROR
*/
private JSONObject getLocaleName() throws GlobalizationError{
JSONObject obj = new JSONObject();
try{
obj.put("value",Locale.getDefault().toString());//get the locale from the Android Device
return obj;
}catch(Exception e){
throw new GlobalizationError(GlobalizationError.UNKNOWN_ERROR);
}
}
/*
* @Description: Returns the string identifier for the client's current language
*
* @Return: JSONObject
* Object.value {String}: The language identifier
*
* @throws: GlobalizationError.UNKNOWN_ERROR
*/
private JSONObject getPreferredLanguage() throws GlobalizationError {
JSONObject obj = new JSONObject();
try {
obj.put("value", Locale.getDefault().getDisplayLanguage().toString());
return obj;
} catch (Exception e) {
throw new GlobalizationError(GlobalizationError.UNKNOWN_ERROR);
}
}
/*
* @Description: Returns a date formatted as a string according to the client's user preferences and
* calendar using the time zone of the client.
*
* @Return: JSONObject
* Object.value {String}: The localized date string
*
* @throws: GlobalizationError.FORMATTING_ERROR
*/
private JSONObject getDateToString(JSONArray options) throws GlobalizationError{
JSONObject obj = new JSONObject();
try{
Date date = new Date((Long)options.getJSONObject(0).get(DATE));
//get formatting pattern from android device (Will only have device specific formatting for short form of date) or options supplied
JSONObject datePattern = getDatePattern(options);
SimpleDateFormat fmt = new SimpleDateFormat(datePattern.getString("pattern"));
//return formatted date
return obj.put("value",fmt.format(date));
}catch(Exception ge){
throw new GlobalizationError(GlobalizationError.FORMATTING_ERROR);
}
}
/*
* @Description: Parses a date formatted as a string according to the client's user
* preferences and calendar using the time zone of the client and returns
* the corresponding date object
* @Return: JSONObject
* Object.year {Number}: The four digit year
* Object.month {Number}: The month from (0 - 11)
* Object.day {Number}: The day from (1 - 31)
* Object.hour {Number}: The hour from (0 - 23)
* Object.minute {Number}: The minute from (0 - 59)
* Object.second {Number}: The second from (0 - 59)
* Object.millisecond {Number}: The milliseconds (from 0 - 999), not available on all platforms
*
* @throws: GlobalizationError.PARSING_ERROR
*/
private JSONObject getStringtoDate(JSONArray options)throws GlobalizationError{
JSONObject obj = new JSONObject();
Date date;
try{
//get format pattern from android device (Will only have device specific formatting for short form of date) or options supplied
DateFormat fmt = new SimpleDateFormat(getDatePattern(options).getString("pattern"));
//attempt parsing string based on user preferences
date = fmt.parse(options.getJSONObject(0).get(DATESTRING).toString());
//set Android Time object
Time time = new Time();
time.set(date.getTime());
//return properties;
obj.put("year", time.year);
obj.put("month", time.month);
obj.put("day", time.monthDay);
obj.put("hour", time.hour);
obj.put("minute", time.minute);
obj.put("second", time.second);
obj.put("millisecond", new Long(0));
return obj;
}catch(Exception ge){
throw new GlobalizationError(GlobalizationError.PARSING_ERROR);
}
}
/*
* @Description: Returns a pattern string for formatting and parsing dates according to the client's
* user preferences.
* @Return: JSONObject
*
* Object.pattern {String}: The date and time pattern for formatting and parsing dates.
* The patterns follow Unicode Technical Standard #35
* http://unicode.org/reports/tr35/tr35-4.html
* Object.timezone {String}: The abbreviated name of the time zone on the client
* Object.utc_offset {Number}: The current difference in seconds between the client's
* time zone and coordinated universal time.
* Object.dst_offset {Number}: The current daylight saving time offset in seconds
* between the client's non-daylight saving's time zone
* and the client's daylight saving's time zone.
*
* @throws: GlobalizationError.PATTERN_ERROR
*/
private JSONObject getDatePattern(JSONArray options) throws GlobalizationError{
JSONObject obj = new JSONObject();
try{
SimpleDateFormat fmtDate = (SimpleDateFormat)android.text.format.DateFormat.getDateFormat(this.cordova.getActivity()); //default user preference for date
SimpleDateFormat fmtTime = (SimpleDateFormat)android.text.format.DateFormat.getTimeFormat(this.cordova.getActivity()); //default user preference for time
String fmt = fmtDate.toLocalizedPattern() + " " + fmtTime.toLocalizedPattern(); //default SHORT date/time format. ex. dd/MM/yyyy h:mm a
//get Date value + options (if available)
if (options.getJSONObject(0).length() > 1){
//options were included
//get formatLength option
if (!((JSONObject)options.getJSONObject(0).get(OPTIONS)).isNull(FORMATLENGTH)){
String fmtOpt = (String)((JSONObject)options.getJSONObject(0).get(OPTIONS)).get(FORMATLENGTH);
if (fmtOpt.equalsIgnoreCase(MEDIUM)){//medium
fmtDate = (SimpleDateFormat)android.text.format.DateFormat.getMediumDateFormat(this.cordova.getActivity());
}else if (fmtOpt.equalsIgnoreCase(LONG) || fmtOpt.equalsIgnoreCase(FULL)){ //long/full
fmtDate = (SimpleDateFormat)android.text.format.DateFormat.getLongDateFormat(this.cordova.getActivity());
}
}
//return pattern type
fmt = fmtDate.toLocalizedPattern() + " " + fmtTime.toLocalizedPattern();
if (!((JSONObject)options.getJSONObject(0).get(OPTIONS)).isNull(SELECTOR)){
String selOpt = (String)((JSONObject)options.getJSONObject(0).get(OPTIONS)).get(SELECTOR);
if (selOpt.equalsIgnoreCase(DATE)){
fmt = fmtDate.toLocalizedPattern();
}else if (selOpt.equalsIgnoreCase(TIME)){
fmt = fmtTime.toLocalizedPattern();
}
}
}
//TimeZone from users device
//TimeZone tz = Calendar.getInstance(Locale.getDefault()).getTimeZone(); //substitute method
TimeZone tz = TimeZone.getTimeZone(Time.getCurrentTimezone());
obj.put("pattern", fmt);
obj.put("timezone", tz.getDisplayName(tz.inDaylightTime(Calendar.getInstance().getTime()),TimeZone.SHORT));
obj.put("utc_offset", tz.getRawOffset()/1000);
obj.put("dst_offset", tz.getDSTSavings()/1000);
return obj;
}catch(Exception ge){
throw new GlobalizationError(GlobalizationError.PATTERN_ERROR);
}
}
/*
* @Description: Returns an array of either the names of the months or days of the week
* according to the client's user preferences and calendar
* @Return: JSONObject
* Object.value {Array{String}}: The array of names starting from either
* the first month in the year or the
* first day of the week.
*
* @throws: GlobalizationError.UNKNOWN_ERROR
*/
@TargetApi(9)
private JSONObject getDateNames(JSONArray options) throws GlobalizationError{
JSONObject obj = new JSONObject();
//String[] value;
JSONArray value = new JSONArray();
List<String> namesList = new ArrayList<String>();
final Map<String,Integer> namesMap; // final needed for sorting with anonymous comparator
try{
int type = 0; //default wide
int item = 0; //default months
//get options if available
if (options.getJSONObject(0).length() > 0){
//get type if available
if (!((JSONObject)options.getJSONObject(0).get(OPTIONS)).isNull(TYPE)){
String t = (String)((JSONObject)options.getJSONObject(0).get(OPTIONS)).get(TYPE);
if (t.equalsIgnoreCase(NARROW)){type++;} //DateUtils.LENGTH_MEDIUM
}
//get item if available
if (!((JSONObject)options.getJSONObject(0).get(OPTIONS)).isNull(ITEM)){
String t = (String)((JSONObject)options.getJSONObject(0).get(OPTIONS)).get(ITEM);
if (t.equalsIgnoreCase(DAYS)){item += 10;} //Days of week start at 1
}
}
//determine return value
int method = item + type;
if (method == 1) { //months and narrow
namesMap = Calendar.getInstance().getDisplayNames(Calendar.MONTH, Calendar.SHORT, Locale.getDefault());
} else if (method == 10) { //days and wide
namesMap = Calendar.getInstance().getDisplayNames(Calendar.DAY_OF_WEEK, Calendar.LONG, Locale.getDefault());
} else if (method == 11) { //days and narrow
namesMap = Calendar.getInstance().getDisplayNames(Calendar.DAY_OF_WEEK, Calendar.SHORT, Locale.getDefault());
} else { //default: months and wide
namesMap = Calendar.getInstance().getDisplayNames(Calendar.MONTH, Calendar.LONG, Locale.getDefault());
}
// save names as a list
for(String name : namesMap.keySet()) {
namesList.add(name);
}
// sort the list according to values in namesMap
Collections.sort(namesList, new Comparator<String>() {
public int compare(String arg0, String arg1) {
return namesMap.get(arg0).compareTo(namesMap.get(arg1));
}
});
// convert nameList into JSONArray of String objects
for (int i = 0; i < namesList.size(); i ++){
value.put(namesList.get(i));
}
//return array of names
return obj.put("value", value);
}catch(Exception ge){
throw new GlobalizationError(GlobalizationError.UNKNOWN_ERROR);
}
}
/*
* @Description: Returns whether daylight savings time is in effect for a given date using the client's
* time zone and calendar.
* @Return: JSONObject
* Object.dst {Boolean}: The value "true" indicates that daylight savings time is
* in effect for the given date and "false" indicate that it is not. *
*
* @throws: GlobalizationError.UNKNOWN_ERROR
*/
private JSONObject getIsDayLightSavingsTime(JSONArray options) throws GlobalizationError{
JSONObject obj = new JSONObject();
boolean dst = false;
try{
Date date = new Date((Long)options.getJSONObject(0).get(DATE));
//TimeZone tz = Calendar.getInstance(Locale.getDefault()).getTimeZone();
TimeZone tz = TimeZone.getTimeZone(Time.getCurrentTimezone());
dst = tz.inDaylightTime(date); //get daylight savings data from date object and user timezone settings
return obj.put("dst",dst);
}catch(Exception ge){
throw new GlobalizationError(GlobalizationError.UNKNOWN_ERROR);
}
}
/*
* @Description: Returns the first day of the week according to the client's user preferences and calendar.
* The days of the week are numbered starting from 1 where 1 is considered to be Sunday.
* @Return: JSONObject
* Object.value {Number}: The number of the first day of the week.
*
* @throws: GlobalizationError.UNKNOWN_ERROR
*/
private JSONObject getFirstDayOfWeek(JSONArray options) throws GlobalizationError{
JSONObject obj = new JSONObject();
try{
int value = Calendar.getInstance(Locale.getDefault()).getFirstDayOfWeek(); //get first day of week based on user locale settings
return obj.put("value", value);
}catch(Exception ge){
throw new GlobalizationError(GlobalizationError.UNKNOWN_ERROR);
}
}
/*
* @Description: Returns a number formatted as a string according to the client's user preferences.
* @Return: JSONObject
* Object.value {String}: The formatted number string.
*
* @throws: GlobalizationError.FORMATTING_ERROR
*/
private JSONObject getNumberToString(JSONArray options) throws GlobalizationError{
JSONObject obj = new JSONObject();
String value = "";
try{
DecimalFormat fmt = getNumberFormatInstance(options);//returns Decimal/Currency/Percent instance
value = fmt.format(options.getJSONObject(0).get(NUMBER));
return obj.put("value", value);
}catch(Exception ge){
throw new GlobalizationError(GlobalizationError.FORMATTING_ERROR);
}
}
/*
* @Description: Parses a number formatted as a string according to the client's user preferences and
* returns the corresponding number.
* @Return: JSONObject
* Object.value {Number}: The parsed number.
*
* @throws: GlobalizationError.PARSING_ERROR
*/
private JSONObject getStringToNumber(JSONArray options) throws GlobalizationError{
JSONObject obj = new JSONObject();
Number value;
try{
DecimalFormat fmt = getNumberFormatInstance(options); //returns Decimal/Currency/Percent instance
value = fmt.parse((String)options.getJSONObject(0).get(NUMBERSTRING));
return obj.put("value", value);
}catch(Exception ge){
throw new GlobalizationError(GlobalizationError.PARSING_ERROR);
}
}
/*
* @Description: Returns a pattern string for formatting and parsing numbers according to the client's user
* preferences.
* @Return: JSONObject
* Object.pattern {String}: The number pattern for formatting and parsing numbers.
* The patterns follow Unicode Technical Standard #35.
* http://unicode.org/reports/tr35/tr35-4.html
* Object.symbol {String}: The symbol to be used when formatting and parsing
* e.g., percent or currency symbol.
* Object.fraction {Number}: The number of fractional digits to use when parsing and
* formatting numbers.
* Object.rounding {Number}: The rounding increment to use when parsing and formatting.
* Object.positive {String}: The symbol to use for positive numbers when parsing and formatting.
* Object.negative: {String}: The symbol to use for negative numbers when parsing and formatting.
* Object.decimal: {String}: The decimal symbol to use for parsing and formatting.
* Object.grouping: {String}: The grouping symbol to use for parsing and formatting.
*
* @throws: GlobalizationError.PATTERN_ERROR
*/
private JSONObject getNumberPattern(JSONArray options) throws GlobalizationError{
JSONObject obj = new JSONObject();
try{
//uses java.text.DecimalFormat to format value
DecimalFormat fmt = (DecimalFormat) DecimalFormat.getInstance(Locale.getDefault()); //default format
String symbol = String.valueOf(fmt.getDecimalFormatSymbols().getDecimalSeparator());
//get Date value + options (if available)
if (options.getJSONObject(0).length() > 0){
//options were included
if (!((JSONObject)options.getJSONObject(0).get(OPTIONS)).isNull(TYPE)){
String fmtOpt = (String)((JSONObject)options.getJSONObject(0).get(OPTIONS)).get(TYPE);
if (fmtOpt.equalsIgnoreCase(CURRENCY)){
fmt = (DecimalFormat) DecimalFormat.getCurrencyInstance(Locale.getDefault());
symbol = fmt.getDecimalFormatSymbols().getCurrencySymbol();
}else if(fmtOpt.equalsIgnoreCase(PERCENT)){
fmt = (DecimalFormat) DecimalFormat.getPercentInstance(Locale.getDefault());
symbol = String.valueOf(fmt.getDecimalFormatSymbols().getPercent());
}
}
}
//return properties
obj.put("pattern", fmt.toPattern());
obj.put("symbol", symbol);
obj.put("fraction", fmt.getMinimumFractionDigits());
obj.put("rounding", new Integer(0));
obj.put("positive", fmt.getPositivePrefix());
obj.put("negative", fmt.getNegativePrefix());
obj.put("decimal", String.valueOf(fmt.getDecimalFormatSymbols().getDecimalSeparator()));
obj.put("grouping", String.valueOf(fmt.getDecimalFormatSymbols().getGroupingSeparator()));
return obj;
}catch(Exception ge){
throw new GlobalizationError(GlobalizationError.PATTERN_ERROR);
}
}
/*
* @Description: Returns a pattern string for formatting and parsing currency values according to the client's
* user preferences and ISO 4217 currency code.
* @Return: JSONObject
* Object.pattern {String}: The currency pattern for formatting and parsing currency values.
* The patterns follow Unicode Technical Standard #35
* http://unicode.org/reports/tr35/tr35-4.html
* Object.code {String}: The ISO 4217 currency code for the pattern.
* Object.fraction {Number}: The number of fractional digits to use when parsing and
* formatting currency.
* Object.rounding {Number}: The rounding increment to use when parsing and formatting.
* Object.decimal: {String}: The decimal symbol to use for parsing and formatting.
* Object.grouping: {String}: The grouping symbol to use for parsing and formatting.
*
* @throws: GlobalizationError.FORMATTING_ERROR
*/
private JSONObject getCurrencyPattern(JSONArray options) throws GlobalizationError{
JSONObject obj = new JSONObject();
try{
//get ISO 4217 currency code
String code = options.getJSONObject(0).getString(CURRENCYCODE);
//uses java.text.DecimalFormat to format value
DecimalFormat fmt = (DecimalFormat) DecimalFormat.getCurrencyInstance(Locale.getDefault());
//set currency format
Currency currency = Currency.getInstance(code);
fmt.setCurrency(currency);
//return properties
obj.put("pattern", fmt.toPattern());
obj.put("code", currency.getCurrencyCode());
obj.put("fraction", fmt.getMinimumFractionDigits());
obj.put("rounding", new Integer(0));
obj.put("decimal", String.valueOf(fmt.getDecimalFormatSymbols().getDecimalSeparator()));
obj.put("grouping", String.valueOf(fmt.getDecimalFormatSymbols().getGroupingSeparator()));
return obj;
}catch(Exception ge){
throw new GlobalizationError(GlobalizationError.FORMATTING_ERROR);
}
}
/*
* @Description: Parses a JSONArray from user options and returns the correct Instance of Decimal/Percent/Currency.
* @Return: DecimalFormat : The Instance to use.
*
* @throws: JSONException
*/
private DecimalFormat getNumberFormatInstance(JSONArray options) throws JSONException{
DecimalFormat fmt = (DecimalFormat)DecimalFormat.getInstance(Locale.getDefault()); //default format
try{
if (options.getJSONObject(0).length() > 1){
//options were included
if (!((JSONObject)options.getJSONObject(0).get(OPTIONS)).isNull(TYPE)){
String fmtOpt = (String)((JSONObject)options.getJSONObject(0).get(OPTIONS)).get(TYPE);
if (fmtOpt.equalsIgnoreCase(CURRENCY)){
fmt = (DecimalFormat)DecimalFormat.getCurrencyInstance(Locale.getDefault());
}else if(fmtOpt.equalsIgnoreCase(PERCENT)){
fmt = (DecimalFormat)DecimalFormat.getPercentInstance(Locale.getDefault());
}
}
}
}catch (JSONException je){}
return fmt;
}
}

View File

@@ -0,0 +1,108 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import org.json.JSONException;
import org.json.JSONObject;
/**
* @description Exception class representing defined Globalization error codes
* @Globalization error codes:
* GlobalizationError.UNKNOWN_ERROR = 0;
* GlobalizationError.FORMATTING_ERROR = 1;
* GlobalizationError.PARSING_ERROR = 2;
* GlobalizationError.PATTERN_ERROR = 3;
*/
public class GlobalizationError extends Exception{
/**
*
*/
private static final long serialVersionUID = 1L;
public static final String UNKNOWN_ERROR = "UNKNOWN_ERROR";
public static final String FORMATTING_ERROR = "FORMATTING_ERROR";
public static final String PARSING_ERROR = "PARSING_ERROR";
public static final String PATTERN_ERROR = "PATTERN_ERROR";
int error = 0; //default unknown error thrown
/**
* Default constructor
*/
public GlobalizationError() {}
/**
* Create an exception returning an error code
*
* @param s
*/
public GlobalizationError(String s) {
if (s.equalsIgnoreCase(FORMATTING_ERROR)){
error = 1;
}else if (s.equalsIgnoreCase(PARSING_ERROR)){
error = 2;
}else if (s.equalsIgnoreCase(PATTERN_ERROR)){
error = 3;
}
}
/**
* get error string based on error code
*
* @param String msg
*/
public String getErrorString(){
String msg = "";
switch (error){
case 0:
msg = UNKNOWN_ERROR;
break;
case 1:
msg = FORMATTING_ERROR;
break;
case 2:
msg = PARSING_ERROR;
break;
case 3:
msg = PATTERN_ERROR;
break;
}
return msg;
}
/**
* get error code
*
* @param String msg
*/
public int getErrorCode(){
return error;
}
/**
* get the json version of this object to return to javascript
* @return
*/
public JSONObject toJson() {
JSONObject obj = new JSONObject();
try {
obj.put("code", getErrorCode());
obj.put("message", getErrorString());
} catch (JSONException e) {
// never happens
}
return obj;
}
}

View File

@@ -0,0 +1,534 @@
/*
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.util.HashMap;
import java.util.StringTokenizer;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.text.InputType;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
@SuppressLint("SetJavaScriptEnabled")
public class InAppBrowser extends CordovaPlugin {
private static final String NULL = "null";
protected static final String LOG_TAG = "InAppBrowser";
private static final String SELF = "_self";
private static final String SYSTEM = "_system";
// private static final String BLANK = "_blank";
private static final String LOCATION = "location";
private static final String EXIT_EVENT = "exit";
private static final String LOAD_START_EVENT = "loadstart";
private static final String LOAD_STOP_EVENT = "loadstop";
private Dialog dialog;
private WebView inAppWebView;
private EditText edittext;
private boolean showLocationBar = true;
private CallbackContext callbackContext;
/**
* Executes the request and returns PluginResult.
*
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param callbackId The callback id used when calling back into JavaScript.
* @return A PluginResult object with a status and message.
*/
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
PluginResult.Status status = PluginResult.Status.OK;
String result = "";
this.callbackContext = callbackContext;
try {
if (action.equals("open")) {
String url = args.getString(0);
String target = args.optString(1);
if (target == null || target.equals("") || target.equals(NULL)) {
target = SELF;
}
HashMap<String, Boolean> features = parseFeature(args.optString(2));
Log.d(LOG_TAG, "target = " + target);
url = updateUrl(url);
// SELF
if (SELF.equals(target)) {
Log.d(LOG_TAG, "in self");
// load in webview
if (url.startsWith("file://") || url.startsWith("javascript:")
|| Config.isUrlWhiteListed(url)) {
this.webView.loadUrl(url);
}
// load in InAppBrowser
else {
result = this.showWebPage(url, features);
}
}
// SYSTEM
else if (SYSTEM.equals(target)) {
Log.d(LOG_TAG, "in system");
result = this.openExternal(url);
}
// BLANK - or anything else
else {
Log.d(LOG_TAG, "in blank");
result = this.showWebPage(url, features);
}
}
else if (action.equals("close")) {
closeDialog();
PluginResult pluginResult = new PluginResult(PluginResult.Status.OK);
pluginResult.setKeepCallback(false);
this.callbackContext.sendPluginResult(pluginResult);
}
else {
status = PluginResult.Status.INVALID_ACTION;
}
PluginResult pluginResult = new PluginResult(status, result);
pluginResult.setKeepCallback(true);
this.callbackContext.sendPluginResult(pluginResult);
} catch (JSONException e) {
this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
}
return true;
}
/**
* Put the list of features into a hash map
*
* @param optString
* @return
*/
private HashMap<String, Boolean> parseFeature(String optString) {
if (optString.equals(NULL)) {
return null;
} else {
HashMap<String, Boolean> map = new HashMap<String, Boolean>();
StringTokenizer features = new StringTokenizer(optString, ",");
StringTokenizer option;
while(features.hasMoreElements()) {
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);
}
}
return map;
}
}
/**
* Convert relative URL to full path
*
* @param url
* @return
*/
private String updateUrl(String url) {
Uri newUrl = Uri.parse(url);
if (newUrl.isRelative()) {
url = this.webView.getUrl().substring(0, this.webView.getUrl().lastIndexOf("/")+1) + url;
}
return url;
}
/**
* Display a new browser with the specified URL.
*
* @param url The url to load.
* @param usePhoneGap Load url in PhoneGap webview
* @return "" if ok, or error message.
*/
public String openExternal(String url) {
try {
Intent intent = null;
intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
this.cordova.getActivity().startActivity(intent);
return "";
} catch (android.content.ActivityNotFoundException e) {
Log.d(LOG_TAG, "InAppBrowser: Error loading url "+url+":"+ e.toString());
return e.toString();
}
}
/**
* Closes the dialog
*/
private void closeDialog() {
try {
JSONObject obj = new JSONObject();
obj.put("type", EXIT_EVENT);
sendUpdate(obj, false);
} catch (JSONException ex) {
Log.d(LOG_TAG, "Should never happen");
}
if (dialog != null) {
dialog.dismiss();
}
}
/**
* Checks to see if it is possible to go back one page in history, then does so.
*/
private void goBack() {
if (this.inAppWebView.canGoBack()) {
this.inAppWebView.goBack();
}
}
/**
* Checks to see if it is possible to go forward one page in history, then does so.
*/
private void goForward() {
if (this.inAppWebView.canGoForward()) {
this.inAppWebView.goForward();
}
}
/**
* Navigate to the new page
*
* @param url to load
*/
private void navigate(String url) {
InputMethodManager imm = (InputMethodManager)this.cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(edittext.getWindowToken(), 0);
if (!url.startsWith("http") && !url.startsWith("file:")) {
this.inAppWebView.loadUrl("http://" + url);
} else {
this.inAppWebView.loadUrl(url);
}
this.inAppWebView.requestFocus();
}
/**
* Should we show the location bar?
*
* @return boolean
*/
private boolean getShowLocationBar() {
return this.showLocationBar;
}
/**
* Display a new browser with the specified URL.
*
* @param url The url to load.
* @param jsonObject
*/
public String showWebPage(final String url, HashMap<String, Boolean> features) {
// Determine if we should hide the location bar.
showLocationBar = true;
if (features != null) {
showLocationBar = features.get(LOCATION).booleanValue();
}
final CordovaWebView thatWebView = this.webView;
// Create dialog in new thread
Runnable runnable = new Runnable() {
/**
* Convert our DIP units to Pixels
*
* @return int
*/
private int dpToPixels(int dipValue) {
int value = (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP,
(float) dipValue,
cordova.getActivity().getResources().getDisplayMetrics()
);
return value;
}
public void run() {
// Let's create the main dialog
dialog = new Dialog(cordova.getActivity(), android.R.style.Theme_NoTitleBar);
dialog.getWindow().getAttributes().windowAnimations = android.R.style.Animation_Dialog;
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setCancelable(true);
dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
public void onDismiss(DialogInterface dialog) {
try {
JSONObject obj = new JSONObject();
obj.put("type", EXIT_EVENT);
sendUpdate(obj, false);
} catch (JSONException e) {
Log.d(LOG_TAG, "Should never happen");
}
}
});
// Main container layout
LinearLayout main = new LinearLayout(cordova.getActivity());
main.setOrientation(LinearLayout.VERTICAL);
// Toolbar layout
RelativeLayout toolbar = new RelativeLayout(cordova.getActivity());
toolbar.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, this.dpToPixels(44)));
toolbar.setPadding(this.dpToPixels(2), this.dpToPixels(2), this.dpToPixels(2), this.dpToPixels(2));
toolbar.setHorizontalGravity(Gravity.LEFT);
toolbar.setVerticalGravity(Gravity.TOP);
// Action Button Container layout
RelativeLayout actionButtonContainer = new RelativeLayout(cordova.getActivity());
actionButtonContainer.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
actionButtonContainer.setHorizontalGravity(Gravity.LEFT);
actionButtonContainer.setVerticalGravity(Gravity.CENTER_VERTICAL);
actionButtonContainer.setId(1);
// Back button
Button back = new Button(cordova.getActivity());
RelativeLayout.LayoutParams backLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
backLayoutParams.addRule(RelativeLayout.ALIGN_LEFT);
back.setLayoutParams(backLayoutParams);
back.setContentDescription("Back Button");
back.setId(2);
back.setText("<");
back.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
goBack();
}
});
// Forward button
Button forward = new Button(cordova.getActivity());
RelativeLayout.LayoutParams forwardLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
forwardLayoutParams.addRule(RelativeLayout.RIGHT_OF, 2);
forward.setLayoutParams(forwardLayoutParams);
forward.setContentDescription("Forward Button");
forward.setId(3);
forward.setText(">");
forward.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
goForward();
}
});
// Edit Text Box
edittext = new EditText(cordova.getActivity());
RelativeLayout.LayoutParams textLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
textLayoutParams.addRule(RelativeLayout.RIGHT_OF, 1);
textLayoutParams.addRule(RelativeLayout.LEFT_OF, 5);
edittext.setLayoutParams(textLayoutParams);
edittext.setId(4);
edittext.setSingleLine(true);
edittext.setText(url);
edittext.setInputType(InputType.TYPE_TEXT_VARIATION_URI);
edittext.setImeOptions(EditorInfo.IME_ACTION_GO);
edittext.setInputType(InputType.TYPE_NULL); // Will not except input... Makes the text NON-EDITABLE
edittext.setOnKeyListener(new View.OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
// If the event is a key-down event on the "enter" button
if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
navigate(edittext.getText().toString());
return true;
}
return false;
}
});
// Close button
Button close = new Button(cordova.getActivity());
RelativeLayout.LayoutParams closeLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
closeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
close.setLayoutParams(closeLayoutParams);
forward.setContentDescription("Close Button");
close.setId(5);
close.setText("Done");
close.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
closeDialog();
}
});
// WebView
inAppWebView = new WebView(cordova.getActivity());
inAppWebView.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
inAppWebView.setWebChromeClient(new WebChromeClient());
WebViewClient client = new InAppBrowserClient(thatWebView, edittext);
inAppWebView.setWebViewClient(client);
WebSettings settings = inAppWebView.getSettings();
settings.setJavaScriptEnabled(true);
settings.setJavaScriptCanOpenWindowsAutomatically(true);
settings.setBuiltInZoomControls(true);
/**
* We need to be careful of this line as a future Android release may deprecate it out of existence.
* Can't replace it with the API 8 level call right now as our minimum SDK is 7 until May 2013
*/
// @TODO: replace with settings.setPluginState(android.webkit.WebSettings.PluginState.ON)
settings.setPluginsEnabled(true);
settings.setDatabaseEnabled(true);
String databasePath = cordova.getActivity().getApplicationContext().getDir("inAppBrowserDB", Context.MODE_PRIVATE).getPath();
settings.setDatabasePath(databasePath);
settings.setDomStorageEnabled(true);
inAppWebView.loadUrl(url);
inAppWebView.setId(6);
inAppWebView.getSettings().setLoadWithOverviewMode(true);
inAppWebView.getSettings().setUseWideViewPort(true);
inAppWebView.requestFocus();
inAppWebView.requestFocusFromTouch();
// Add the back and forward buttons to our action button container layout
actionButtonContainer.addView(back);
actionButtonContainer.addView(forward);
// Add the views to our toolbar
toolbar.addView(actionButtonContainer);
toolbar.addView(edittext);
toolbar.addView(close);
// Don't add the toolbar if its been disabled
if (getShowLocationBar()) {
// Add our toolbar to our main view/layout
main.addView(toolbar);
}
// Add our webview to our main view/layout
main.addView(inAppWebView);
WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
lp.copyFrom(dialog.getWindow().getAttributes());
lp.width = WindowManager.LayoutParams.MATCH_PARENT;
lp.height = WindowManager.LayoutParams.MATCH_PARENT;
dialog.setContentView(main);
dialog.show();
dialog.getWindow().setAttributes(lp);
}
};
this.cordova.getActivity().runOnUiThread(runnable);
return "";
}
/**
* Create a new plugin 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);
result.setKeepCallback(keepCallback);
this.callbackContext.sendPluginResult(result);
}
/**
* The webview client receives notifications about appView
*/
public class InAppBrowserClient extends WebViewClient {
EditText edittext;
CordovaWebView webView;
/**
* Constructor.
*
* @param mContext
* @param edittext
*/
public InAppBrowserClient(CordovaWebView webView, EditText mEditText) {
this.webView = webView;
this.edittext = mEditText;
}
/**
* Notify the host application that a page has started loading.
*
* @param view The webview initiating the callback.
* @param url The url of the page.
*/
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
String newloc;
if (url.startsWith("http:") || url.startsWith("https:") || url.startsWith("file:")) {
newloc = url;
} else {
newloc = "http://" + url;
}
if (!newloc.equals(edittext.getText().toString())) {
edittext.setText(newloc);
}
try {
JSONObject obj = new JSONObject();
obj.put("type", LOAD_START_EVENT);
obj.put("url", newloc);
sendUpdate(obj, true);
} catch (JSONException ex) {
Log.d(LOG_TAG, "Should never happen");
}
}
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
try {
JSONObject obj = new JSONObject();
obj.put("type", LOAD_STOP_EVENT);
obj.put("url", url);
sendUpdate(obj, true);
} catch (JSONException ex) {
Log.d(LOG_TAG, "Should never happen");
}
}
}
}

View File

@@ -19,12 +19,11 @@
package org.apache.cordova;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedList;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.PluginResult;
import android.os.Message;
import android.util.Log;
@@ -37,17 +36,43 @@ public class NativeToJsMessageQueue {
private static final String LOG_TAG = "JsMessageQueue";
// This must match the default value in incubator-cordova-js/lib/android/exec.js
private static final int DEFAULT_BRIDGE_MODE = 1;
private static final int DEFAULT_BRIDGE_MODE = 2;
// Set this to true to force plugin results to be encoding as
// JS instead of the custom format (useful for benchmarking).
private static final boolean FORCE_ENCODE_USING_EVAL = false;
// Disable URL-based exec() bridge by default since it's a bit of a
// security concern.
static final boolean ENABLE_LOCATION_CHANGE_EXEC_MODE = false;
// Disable sending back native->JS messages during an exec() when the active
// exec() is asynchronous. Set this to true when running bridge benchmarks.
static final boolean DISABLE_EXEC_CHAINING = false;
// Upper limit for how much data to send to JS in one shot.
// TODO(agrieve): This is currently disable. It should be re-enabled once we
// remove support for returning values from exec() calls. This was
// deprecated in 2.2.0.
// Also, this currently only chops up on message boundaries. It may be useful
// to allow it to break up messages.
private static int MAX_PAYLOAD_SIZE = -1; //50 * 1024 * 10240;
/**
* The index into registeredListeners to treat as active.
*/
private int activeListenerIndex;
/**
* When true, the active listener is not fired upon enqueue. When set to false,
* the active listener will be fired if the queue is non-empty.
*/
private boolean paused;
/**
* The list of JavaScript statements to be sent to JavaScript.
*/
private final LinkedList<String> queue = new LinkedList<String>();
private final LinkedList<JsMessage> queue = new LinkedList<JsMessage>();
/**
* The array of listeners that can be used to send messages to JS.
@@ -56,16 +81,15 @@ public class NativeToJsMessageQueue {
private final CordovaInterface cordova;
private final CordovaWebView webView;
public NativeToJsMessageQueue(CordovaWebView webView, CordovaInterface cordova) {
this.cordova = cordova;
this.webView = webView;
registeredListeners = new BridgeMode[5];
registeredListeners = new BridgeMode[4];
registeredListeners[0] = null; // Polling. Requires no logic.
registeredListeners[1] = new CallbackBridgeMode();
registeredListeners[2] = new LoadUrlBridgeMode();
registeredListeners[3] = new OnlineEventsBridgeMode();
registeredListeners[4] = new PrivateApiBridgeMode();
registeredListeners[1] = new LoadUrlBridgeMode();
registeredListeners[2] = new OnlineEventsBridgeMode();
registeredListeners[3] = new PrivateApiBridgeMode();
reset();
}
@@ -81,7 +105,7 @@ public class NativeToJsMessageQueue {
synchronized (this) {
activeListenerIndex = value;
BridgeMode activeListener = registeredListeners[value];
if (!queue.isEmpty() && activeListener != null) {
if (!paused && !queue.isEmpty() && activeListener != null) {
activeListener.onNativeToJsMessageAvailable();
}
}
@@ -99,79 +123,174 @@ public class NativeToJsMessageQueue {
}
}
private int calculatePackedMessageLength(JsMessage message) {
int messageLen = message.calculateEncodedLength();
String messageLenStr = String.valueOf(messageLen);
return messageLenStr.length() + messageLen + 1;
}
private void packMessage(JsMessage message, StringBuilder sb) {
sb.append(message.calculateEncodedLength())
.append(' ');
message.encodeAsMessage(sb);
}
/**
* Removes and returns the last statement in the queue.
* Combines and returns queued messages combined into a single string.
* Combines as many messages as possible, while staying under MAX_PAYLOAD_SIZE.
* Returns null if the queue is empty.
*/
public String pop() {
public String popAndEncode() {
synchronized (this) {
if (queue.isEmpty()) {
return null;
}
return queue.remove(0);
int totalPayloadLen = 0;
int numMessagesToSend = 0;
for (JsMessage message : queue) {
int messageSize = calculatePackedMessageLength(message);
if (numMessagesToSend > 0 && totalPayloadLen + messageSize > MAX_PAYLOAD_SIZE && MAX_PAYLOAD_SIZE > 0) {
break;
}
totalPayloadLen += messageSize;
numMessagesToSend += 1;
}
StringBuilder sb = new StringBuilder(totalPayloadLen);
for (int i = 0; i < numMessagesToSend; ++i) {
JsMessage message = queue.removeFirst();
packMessage(message, sb);
}
if (!queue.isEmpty()) {
// Attach a char to indicate that there are more messages pending.
sb.append('*');
}
return sb.toString();
}
}
/**
* Combines and returns all statements. Clears the queue.
* Returns null if the queue is empty.
* Same as popAndEncode(), except encodes in a form that can be executed as JS.
*/
public String popAll() {
private String popAndEncodeAsJs() {
synchronized (this) {
int length = queue.size();
if (length == 0) {
return null;
}
StringBuffer sb = new StringBuffer();
int totalPayloadLen = 0;
int numMessagesToSend = 0;
for (JsMessage message : queue) {
int messageSize = message.calculateEncodedLength() + 50; // overestimate.
if (numMessagesToSend > 0 && totalPayloadLen + messageSize > MAX_PAYLOAD_SIZE && MAX_PAYLOAD_SIZE > 0) {
break;
}
totalPayloadLen += messageSize;
numMessagesToSend += 1;
}
boolean willSendAllMessages = numMessagesToSend == queue.size();
StringBuilder sb = new StringBuilder(totalPayloadLen + (willSendAllMessages ? 0 : 100));
// Wrap each statement in a try/finally so that if one throws it does
// not affect the next.
int i = 0;
for (String message : queue) {
if (++i == length) {
sb.append(message);
for (int i = 0; i < numMessagesToSend; ++i) {
JsMessage message = queue.removeFirst();
if (willSendAllMessages && (i + 1 == numMessagesToSend)) {
message.encodeAsJsMessage(sb);
} else {
sb.append("try{")
.append(message)
.append("}finally{");
sb.append("try{");
message.encodeAsJsMessage(sb);
sb.append("}finally{");
}
}
for ( i = 1; i < length; ++i) {
if (!willSendAllMessages) {
sb.append("window.setTimeout(function(){cordova.require('cordova/plugin/android/polling').pollOnce();},0);");
}
for (int i = willSendAllMessages ? 1 : 0; i < numMessagesToSend; ++i) {
sb.append('}');
}
queue.clear();
return sb.toString();
}
}
}
/**
* Add a JavaScript statement to the list.
*/
public void add(String statement) {
public void addJavaScript(String statement) {
enqueueMessage(new JsMessage(statement));
}
/**
* Add a JavaScript statement to the list.
*/
public void addPluginResult(PluginResult result, String callbackId) {
if (callbackId == null) {
Log.e(LOG_TAG, "Got plugin result with no callbackId", new Throwable());
return;
}
// Don't send anything if there is no result and there is no need to
// clear the callbacks.
boolean noResult = result.getStatus() == PluginResult.Status.NO_RESULT.ordinal();
boolean keepCallback = result.getKeepCallback();
if (noResult && keepCallback) {
return;
}
JsMessage message = new JsMessage(result, callbackId);
if (FORCE_ENCODE_USING_EVAL) {
StringBuilder sb = new StringBuilder(message.calculateEncodedLength() + 50);
message.encodeAsJsMessage(sb);
message = new JsMessage(sb.toString());
}
enqueueMessage(message);
}
private void enqueueMessage(JsMessage message) {
synchronized (this) {
queue.add(statement);
if (registeredListeners[activeListenerIndex] != null) {
queue.add(message);
if (!paused && registeredListeners[activeListenerIndex] != null) {
registeredListeners[activeListenerIndex].onNativeToJsMessageAvailable();
}
}
}
public void setPaused(boolean value) {
if (paused && value) {
// This should never happen. If a use-case for it comes up, we should
// change pause to be a counter.
Log.e(LOG_TAG, "nested call to setPaused detected.", new Throwable());
}
paused = value;
if (!value) {
synchronized (this) {
if (!queue.isEmpty() && registeredListeners[activeListenerIndex] != null) {
registeredListeners[activeListenerIndex].onNativeToJsMessageAvailable();
}
}
}
}
public boolean getPaused() {
return paused;
}
private interface BridgeMode {
void onNativeToJsMessageAvailable();
}
/** Uses a local server to send messages to JS via an XHR */
private class CallbackBridgeMode implements BridgeMode {
public void onNativeToJsMessageAvailable() {
if (webView.callbackServer != null) {
webView.callbackServer.onNativeToJsMessageAvailable(NativeToJsMessageQueue.this);
}
}
}
/** Uses webView.loadUrl("javascript:") to execute messages. */
private class LoadUrlBridgeMode implements BridgeMode {
final Runnable runnable = new Runnable() {
public void run() {
String js = popAndEncodeAsJs();
if (js != null) {
webView.loadUrlNow("javascript:" + js);
}
}
};
public void onNativeToJsMessageAvailable() {
webView.loadUrlNow("javascript:" + popAll());
cordova.getActivity().runOnUiThread(runnable);
}
}
@@ -179,7 +298,6 @@ public class NativeToJsMessageQueue {
private class OnlineEventsBridgeMode implements BridgeMode {
boolean online = true;
final Runnable runnable = new Runnable() {
@Override
public void run() {
if (!queue.isEmpty()) {
online = !online;
@@ -187,7 +305,9 @@ public class NativeToJsMessageQueue {
}
}
};
OnlineEventsBridgeMode() {
webView.setNetworkAvailable(true);
}
public void onNativeToJsMessageAvailable() {
cordova.getActivity().runOnUiThread(runnable);
}
@@ -241,7 +361,7 @@ public class NativeToJsMessageQueue {
}
// webViewCore is lazily initialized, and so may not be available right away.
if (sendMessageMethod != null) {
String js = popAll();
String js = popAndEncodeAsJs();
Message execJsMessage = Message.obtain(null, EXECUTE_JS, js);
try {
sendMessageMethod.invoke(webViewCore, execJsMessage);
@@ -251,4 +371,104 @@ public class NativeToJsMessageQueue {
}
}
}
private static class JsMessage {
final String jsPayloadOrCallbackId;
final PluginResult pluginResult;
JsMessage(String js) {
if (js == null) {
throw new NullPointerException();
}
jsPayloadOrCallbackId = js;
pluginResult = null;
}
JsMessage(PluginResult pluginResult, String callbackId) {
if (callbackId == null || pluginResult == null) {
throw new NullPointerException();
}
jsPayloadOrCallbackId = callbackId;
this.pluginResult = pluginResult;
}
int calculateEncodedLength() {
if (pluginResult == null) {
return jsPayloadOrCallbackId.length() + 1;
}
int statusLen = String.valueOf(pluginResult.getStatus()).length();
int ret = 2 + statusLen + 1 + jsPayloadOrCallbackId.length() + 1;
switch (pluginResult.getMessageType()) {
case PluginResult.MESSAGE_TYPE_BOOLEAN: // f or t
case PluginResult.MESSAGE_TYPE_NULL: // N
ret += 1;
break;
case PluginResult.MESSAGE_TYPE_NUMBER: // n
ret += 1 + pluginResult.getMessage().length();
break;
case PluginResult.MESSAGE_TYPE_STRING: // s
ret += 1 + pluginResult.getStrMessage().length();
break;
case PluginResult.MESSAGE_TYPE_JSON:
default:
ret += pluginResult.getMessage().length();
}
return ret;
}
void encodeAsMessage(StringBuilder sb) {
if (pluginResult == null) {
sb.append('J')
.append(jsPayloadOrCallbackId);
return;
}
int status = pluginResult.getStatus();
boolean noResult = status == PluginResult.Status.NO_RESULT.ordinal();
boolean resultOk = status == PluginResult.Status.OK.ordinal();
boolean keepCallback = pluginResult.getKeepCallback();
sb.append((noResult || resultOk) ? 'S' : 'F')
.append(keepCallback ? '1' : '0')
.append(status)
.append(' ')
.append(jsPayloadOrCallbackId)
.append(' ');
switch (pluginResult.getMessageType()) {
case PluginResult.MESSAGE_TYPE_BOOLEAN:
sb.append(pluginResult.getMessage().charAt(0)); // t or f.
break;
case PluginResult.MESSAGE_TYPE_NULL: // N
sb.append('N');
break;
case PluginResult.MESSAGE_TYPE_NUMBER: // n
sb.append('n')
.append(pluginResult.getMessage());
break;
case PluginResult.MESSAGE_TYPE_STRING: // s
sb.append('s');
sb.append(pluginResult.getStrMessage());
break;
case PluginResult.MESSAGE_TYPE_JSON:
default:
sb.append(pluginResult.getMessage()); // [ or {
}
}
void encodeAsJsMessage(StringBuilder sb) {
if (pluginResult == null) {
sb.append(jsPayloadOrCallbackId);
} else {
int status = pluginResult.getStatus();
boolean success = (status == PluginResult.Status.OK.ordinal()) || (status == PluginResult.Status.NO_RESULT.ordinal());
sb.append("cordova.callbackFromNative('")
.append(jsPayloadOrCallbackId)
.append("',")
.append(success)
.append(",")
.append(status)
.append(",")
.append(pluginResult.getMessage())
.append(",")
.append(pluginResult.getKeepCallback())
.append(");");
}
}
}
}

View File

@@ -18,8 +18,9 @@
*/
package org.apache.cordova;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
@@ -31,7 +32,7 @@ import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.util.Log;
public class NetworkManager extends Plugin {
public class NetworkManager extends CordovaPlugin {
public static int NOT_REACHABLE = 0;
public static int REACHABLE_VIA_CARRIER_DATA_NETWORK = 1;
@@ -68,10 +69,12 @@ public class NetworkManager extends Plugin {
private static final String LOG_TAG = "NetworkManager";
private String connectionCallbackId;
private CallbackContext connectionCallbackContext;
private boolean registered = false;
ConnectivityManager sockMan;
BroadcastReceiver receiver;
private String lastStatus = "";
/**
* Constructor.
@@ -85,24 +88,27 @@ public class NetworkManager extends Plugin {
* get file paths associated with the Activity.
*
* @param cordova The context of the main Activity.
* @param webView The CordovaWebView Cordova is running in.
*/
public void setContext(CordovaInterface cordova) {
super.setContext(cordova);
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
super.initialize(cordova, webView);
this.sockMan = (ConnectivityManager) cordova.getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
this.connectionCallbackId = null;
this.connectionCallbackContext = null;
// We need to listen to connectivity events to update navigator.connection
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
if (this.receiver == null) {
this.receiver = new BroadcastReceiver() {
@SuppressWarnings("deprecation")
@Override
public void onReceive(Context context, Intent intent) {
updateConnectionInfo((NetworkInfo) intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO));
// (The null check is for the ARM Emulator, please use Intel Emulator for better results)
if(NetworkManager.this.webView != null)
updateConnectionInfo(sockMan.getActiveNetworkInfo());
}
};
cordova.getActivity().registerReceiver(this.receiver, intentFilter);
this.registered = true;
}
}
@@ -110,43 +116,31 @@ public class NetworkManager extends Plugin {
/**
* Executes the request and returns PluginResult.
*
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param callbackId The callback id used when calling back into JavaScript.
* @return A PluginResult object with a status and message.
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param callbackContext The callback id used when calling back into JavaScript.
* @return True if the action was valid, false otherwise.
*/
public PluginResult execute(String action, JSONArray args, String callbackId) {
PluginResult.Status status = PluginResult.Status.INVALID_ACTION;
String result = "Unsupported Operation: " + action;
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) {
if (action.equals("getConnectionInfo")) {
this.connectionCallbackId = callbackId;
this.connectionCallbackContext = callbackContext;
NetworkInfo info = sockMan.getActiveNetworkInfo();
PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, this.getConnectionInfo(info));
pluginResult.setKeepCallback(true);
return pluginResult;
callbackContext.sendPluginResult(pluginResult);
return true;
}
return new PluginResult(status, result);
}
/**
* Identifies if action to be executed returns a value and should be run synchronously.
*
* @param action The action to execute
* @return T=returns value
*/
public boolean isSynch(String action) {
return true;
return false;
}
/**
* Stop network receiver.
*/
public void onDestroy() {
if (this.receiver != null) {
if (this.receiver != null && this.registered) {
try {
this.cordova.getActivity().unregisterReceiver(this.receiver);
this.registered = false;
} catch (Exception e) {
Log.e(LOG_TAG, "Error unregistering network receiver: " + e.getMessage(), e);
}
@@ -165,7 +159,14 @@ public class NetworkManager extends Plugin {
*/
private void updateConnectionInfo(NetworkInfo info) {
// send update to javascript "navigator.network.connection"
sendUpdate(this.getConnectionInfo(info));
// Jellybean sends its own info
String thisStatus = this.getConnectionInfo(info);
if(!thisStatus.equals(lastStatus))
{
sendUpdate(thisStatus);
lastStatus = thisStatus;
}
}
/**
@@ -185,6 +186,7 @@ public class NetworkManager extends Plugin {
type = getType(info);
}
}
Log.d("CordovaNetworkManager", "Connection Type: " + type);
return type;
}
@@ -194,11 +196,11 @@ public class NetworkManager extends Plugin {
* @param connection the network info to set as navigator.connection
*/
private void sendUpdate(String type) {
PluginResult result = new PluginResult(PluginResult.Status.OK, type);
result.setKeepCallback(true);
this.success(result, this.connectionCallbackId);
// Send to all plugins
if (connectionCallbackContext != null) {
PluginResult result = new PluginResult(PluginResult.Status.OK, type);
result.setKeepCallback(true);
connectionCallbackContext.sendPluginResult(result);
}
webView.postMessage("networkconnection", type);
}

View File

@@ -18,8 +18,9 @@
*/
package org.apache.cordova;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
@@ -35,7 +36,7 @@ import android.os.Vibrator;
/**
* This class provides access to notifications on the device.
*/
public class Notification extends Plugin {
public class Notification extends CordovaPlugin {
public int confirmResult = -1;
public ProgressDialog spinnerDialog = null;
@@ -50,86 +51,48 @@ public class Notification extends Plugin {
/**
* Executes the request and returns PluginResult.
*
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param callbackId The callback id used when calling back into JavaScript.
* @return A PluginResult object with a status and message.
* @param action The action to execute.
* @param args JSONArray of arguments for the plugin.
* @param callbackContext The callback context used when calling back into JavaScript.
* @return True when the action was valid, false otherwise.
*/
public PluginResult execute(String action, JSONArray args, String callbackId) {
PluginResult.Status status = PluginResult.Status.OK;
String result = "";
try {
if (action.equals("beep")) {
this.beep(args.getLong(0));
}
else if (action.equals("vibrate")) {
this.vibrate(args.getLong(0));
}
else if (action.equals("alert")) {
this.alert(args.getString(0), args.getString(1), args.getString(2), callbackId);
PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT);
r.setKeepCallback(true);
return r;
}
else if (action.equals("confirm")) {
this.confirm(args.getString(0), args.getString(1), args.getString(2), callbackId);
PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT);
r.setKeepCallback(true);
return r;
}
else if (action.equals("activityStart")) {
this.activityStart(args.getString(0), args.getString(1));
}
else if (action.equals("activityStop")) {
this.activityStop();
}
else if (action.equals("progressStart")) {
this.progressStart(args.getString(0), args.getString(1));
}
else if (action.equals("progressValue")) {
this.progressValue(args.getInt(0));
}
else if (action.equals("progressStop")) {
this.progressStop();
}
return new PluginResult(status, result);
} catch (JSONException e) {
return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
if (action.equals("beep")) {
this.beep(args.getLong(0));
}
}
/**
* 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("alert")) {
else if (action.equals("vibrate")) {
this.vibrate(args.getLong(0));
}
else if (action.equals("alert")) {
this.alert(args.getString(0), args.getString(1), args.getString(2), callbackContext);
return true;
}
else if (action.equals("confirm")) {
this.confirm(args.getString(0), args.getString(1), args.getString(2), callbackContext);
return true;
}
else if (action.equals("activityStart")) {
return true;
this.activityStart(args.getString(0), args.getString(1));
}
else if (action.equals("activityStop")) {
return true;
this.activityStop();
}
else if (action.equals("progressStart")) {
return true;
this.progressStart(args.getString(0), args.getString(1));
}
else if (action.equals("progressValue")) {
return true;
this.progressValue(args.getInt(0));
}
else if (action.equals("progressStop")) {
return true;
this.progressStop();
}
else {
return false;
}
// Only alert and confirm are async.
callbackContext.success();
return true;
}
//--------------------------------------------------------------------------
@@ -177,15 +140,14 @@ public class Notification extends Plugin {
/**
* Builds and shows a native Android alert with given Strings
* @param message The message the alert should display
* @param title The title of the alert
* @param buttonLabel The label of the button
* @param callbackId The callback id
* @param message The message the alert should display
* @param title The title of the alert
* @param buttonLabel The label of the button
* @param callbackContext The callback context
*/
public synchronized void alert(final String message, final String title, final String buttonLabel, final String callbackId) {
public synchronized void alert(final String message, final String title, final String buttonLabel, final CallbackContext callbackContext) {
final CordovaInterface cordova = this.cordova;
final Notification notification = this;
Runnable runnable = new Runnable() {
public void run() {
@@ -198,9 +160,17 @@ public class Notification extends Plugin {
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
notification.success(new PluginResult(PluginResult.Status.OK, 0), callbackId);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 0));
}
});
dlg.setOnCancelListener(new AlertDialog.OnCancelListener() {
public void onCancel(DialogInterface dialog)
{
dialog.dismiss();
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 0));
}
});
dlg.create();
dlg.show();
};
@@ -213,15 +183,14 @@ public class Notification extends Plugin {
* This dialog only shows up to 3 buttons. Any labels after that will be ignored.
* The index of the button pressed will be returned to the JavaScript callback identified by callbackId.
*
* @param message The message the dialog should display
* @param title The title of the dialog
* @param buttonLabels A comma separated list of button labels (Up to 3 buttons)
* @param callbackId The callback id
* @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 confirm(final String message, final String title, String buttonLabels, final String callbackId) {
public synchronized void confirm(final String message, final String title, String buttonLabels, final CallbackContext callbackContext) {
final CordovaInterface cordova = this.cordova;
final Notification notification = this;
final String[] fButtons = buttonLabels.split(",");
Runnable runnable = new Runnable() {
@@ -237,7 +206,7 @@ public class Notification extends Plugin {
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
notification.success(new PluginResult(PluginResult.Status.OK, 1), callbackId);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 1));
}
});
}
@@ -248,7 +217,7 @@ public class Notification extends Plugin {
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
notification.success(new PluginResult(PluginResult.Status.OK, 2), callbackId);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 2));
}
});
}
@@ -259,11 +228,18 @@ public class Notification extends Plugin {
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
notification.success(new PluginResult(PluginResult.Status.OK, 3), callbackId);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 3));
}
}
);
}
dlg.setOnCancelListener(new AlertDialog.OnCancelListener() {
public void onCancel(DialogInterface dialog)
{
dialog.dismiss();
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 0));
}
});
dlg.create();
dlg.show();
@@ -283,14 +259,13 @@ public class Notification extends Plugin {
this.spinnerDialog.dismiss();
this.spinnerDialog = null;
}
final Notification notification = this;
final CordovaInterface cordova = this.cordova;
Runnable runnable = new Runnable() {
public void run() {
notification.spinnerDialog = ProgressDialog.show(cordova.getActivity(), title, message, true, true,
Notification.this.spinnerDialog = ProgressDialog.show(cordova.getActivity(), title, message, true, true,
new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
notification.spinnerDialog = null;
Notification.this.spinnerDialog = null;
}
});
}

View File

@@ -19,26 +19,25 @@
package org.apache.cordova;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.PluginResult;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.json.JSONArray;
public class SplashScreen extends Plugin {
public class SplashScreen extends CordovaPlugin {
@Override
public PluginResult execute(String action, JSONArray args, String callbackId) {
PluginResult.Status status = PluginResult.Status.OK;
String result = "";
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) {
if (action.equals("hide")) {
this.webView.postMessage("splashscreen", "hide");
} else if (action.equals("show")){
this.webView.postMessage("splashscreen", "show");
}
else {
status = PluginResult.Status.INVALID_ACTION;
return false;
}
return new PluginResult(status, result);
callbackContext.success();
return true;
}
}

View File

@@ -20,7 +20,8 @@ package org.apache.cordova;
import java.io.File;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
@@ -35,7 +36,7 @@ import android.database.sqlite.*;
* Android 3.0 devices. It is not used for other versions of Android, since
* HTML5 database is built in to the browser.
*/
public class Storage extends Plugin {
public class Storage extends CordovaPlugin {
// Data Definition Language
private static final String ALTER = "alter";
@@ -60,47 +61,32 @@ public class Storage extends Plugin {
* The action to execute.
* @param args
* JSONArry of arguments for the plugin.
* @param callbackId
* The callback id used when calling back into JavaScript.
* @return A PluginResult object with a status and message.
* @param callbackContext
* The callback context used when calling back into JavaScript.
* @return True if the action was valid, false otherwise.
*/
public PluginResult execute(String action, JSONArray args, String callbackId) {
PluginResult.Status status = PluginResult.Status.OK;
String result = "";
try {
if (action.equals("openDatabase")) {
this.openDatabase(args.getString(0), args.getString(1),
args.getString(2), args.getLong(3));
} else if (action.equals("executeSql")) {
String[] s = null;
if (args.isNull(1)) {
s = new String[0];
} else {
JSONArray a = args.getJSONArray(1);
int len = a.length();
s = new String[len];
for (int i = 0; i < len; i++) {
s[i] = a.getString(i);
}
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
if (action.equals("openDatabase")) {
this.openDatabase(args.getString(0), args.getString(1),
args.getString(2), args.getLong(3));
} else if (action.equals("executeSql")) {
String[] s = null;
if (args.isNull(1)) {
s = new String[0];
} else {
JSONArray a = args.getJSONArray(1);
int len = a.length();
s = new String[len];
for (int i = 0; i < len; i++) {
s[i] = a.getString(i);
}
this.executeSql(args.getString(0), s, args.getString(2));
}
return new PluginResult(status, result);
} catch (JSONException e) {
return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
this.executeSql(args.getString(0), s, args.getString(2));
}
}
/**
* 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) {
else {
return false;
}
callbackContext.success();
return true;
}
@@ -115,6 +101,13 @@ public class Storage extends Plugin {
}
}
/**
* Clean up on navigation/refresh.
*/
public void onReset() {
this.onDestroy();
}
// --------------------------------------------------------------------------
// LOCAL METHODS
// --------------------------------------------------------------------------
@@ -144,7 +137,20 @@ public class Storage extends Plugin {
this.path = this.cordova.getActivity().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
}
this.dbName = this.path + File.pathSeparator + db + ".db";
this.dbName = this.path + File.separator + db + ".db";
/*
* What is all this nonsense? Well the separator was incorrect so the db was showing up in the wrong
* directory. This bit of code fixes that issue and moves the db to the correct directory.
*/
File oldDbFile = new File(this.path + File.pathSeparator + db + ".db");
if (oldDbFile.exists()) {
File dbPath = new File(this.path);
File dbFile = new File(dbName);
dbPath.mkdirs();
oldDbFile.renameTo(dbFile);
}
this.myDb = SQLiteDatabase.openOrCreateDatabase(this.dbName, null);
}
@@ -162,7 +168,7 @@ public class Storage extends Plugin {
try {
if (isDDL(query)) {
this.myDb.execSQL(query);
this.sendJavascript("cordova.require('cordova/plugin/android/storage').completeQuery('" + tx_id + "', '');");
this.webView.sendJavascript("cordova.require('cordova/plugin/android/storage').completeQuery('" + tx_id + "', '');");
}
else {
Cursor myCursor = this.myDb.rawQuery(query, params);
@@ -175,12 +181,12 @@ public class Storage extends Plugin {
System.out.println("Storage.executeSql(): Error=" + ex.getMessage());
// Send error message back to JavaScript
this.sendJavascript("cordova.require('cordova/plugin/android/storage').failQuery('" + ex.getMessage() + "','" + tx_id + "');");
this.webView.sendJavascript("cordova.require('cordova/plugin/android/storage').failQuery('" + ex.getMessage() + "','" + tx_id + "');");
}
}
/**
* Checks to see the the query is a Data Definintion command
* Checks to see the the query is a Data Definition command
*
* @param query to be executed
* @return true if it is a DDL command, false otherwise
@@ -233,7 +239,7 @@ public class Storage extends Plugin {
}
// Let JavaScript know that there are no more rows
this.sendJavascript("cordova.require('cordova/plugin/android/storage').completeQuery('" + tx_id + "', " + result + ");");
this.webView.sendJavascript("cordova.require('cordova/plugin/android/storage').completeQuery('" + tx_id + "', " + result + ");");
}
}

View File

@@ -1,112 +0,0 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import java.util.List;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.content.Context;
public class TempListener extends Plugin implements SensorEventListener {
Sensor mSensor;
private SensorManager sensorManager;
/**
* Constructor.
*/
public TempListener() {
}
/**
* Sets the context of the Command. This can then be used to do things like
* get file paths associated with the Activity.
*
* @param cordova The context of the main Activity.
*/
public void setContext(CordovaInterface cordova) {
super.setContext(cordova);
this.sensorManager = (SensorManager) cordova.getActivity().getSystemService(Context.SENSOR_SERVICE);
}
/**
* Executes the request and returns PluginResult.
*
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param callbackId The callback id used when calling back into JavaScript.
* @return A PluginResult object with a status and message.
*/
public PluginResult execute(String action, JSONArray args, String callbackId) {
PluginResult.Status status = PluginResult.Status.OK;
String result = "";
if (action.equals("start")) {
this.start();
}
else if (action.equals("stop")) {
this.stop();
}
return new PluginResult(status, result);
}
/**
* Called by AccelBroker when listener is to be shut down.
* Stop listener.
*/
public void onDestroy() {
this.stop();
}
//--------------------------------------------------------------------------
// LOCAL METHODS
//--------------------------------------------------------------------------
public void start() {
@SuppressWarnings("deprecation")
List<Sensor> list = this.sensorManager.getSensorList(Sensor.TYPE_TEMPERATURE);
if (list.size() > 0) {
this.mSensor = list.get(0);
this.sensorManager.registerListener(this, this.mSensor, SensorManager.SENSOR_DELAY_NORMAL);
}
}
public void stop() {
this.sensorManager.unregisterListener(this);
}
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// TODO Auto-generated method stub
}
public void onSensorChanged(SensorEvent event) {
// We want to know what temp this is.
float temp = event.values[0];
this.sendJavascript("gotTemp(" + temp + ");");
}
}

View File

@@ -0,0 +1,111 @@
package org.apache.cordova.api;
import org.json.JSONArray;
import android.util.Log;
import org.apache.cordova.CordovaWebView;
import org.json.JSONObject;
public class CallbackContext {
private static final String LOG_TAG = "CordovaPlugin";
private String callbackId;
private CordovaWebView webView;
private boolean finished;
private int changingThreads;
public CallbackContext(String callbackId, CordovaWebView webView) {
this.callbackId = callbackId;
this.webView = webView;
}
public boolean isFinished() {
return finished;
}
public boolean isChangingThreads() {
return changingThreads > 0;
}
public String getCallbackId() {
return callbackId;
}
public void sendPluginResult(PluginResult pluginResult) {
synchronized (this) {
if (finished) {
Log.w(LOG_TAG, "Attempted to send a second callback for ID: " + callbackId + "\nResult was: " + pluginResult.getMessage());
return;
} else {
finished = !pluginResult.getKeepCallback();
}
}
webView.sendPluginResult(pluginResult, callbackId);
}
/**
* Helper for success callbacks that just returns the Status.OK by default
*
* @param message The message to add to the success result.
*/
public void success(JSONObject 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(String 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(JSONArray 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() {
sendPluginResult(new PluginResult(PluginResult.Status.OK));
}
/**
* Helper for error callbacks that just returns the Status.ERROR by default
*
* @param message The message to add to the error result.
*/
public void error(JSONObject message) {
sendPluginResult(new PluginResult(PluginResult.Status.ERROR, message));
}
/**
* Helper for error callbacks that just returns the Status.ERROR by default
*
* @param message The message to add to the error result.
* @param callbackId The callback id used when calling back into JavaScript.
*/
public void error(String message) {
sendPluginResult(new PluginResult(PluginResult.Status.ERROR, message));
}
/**
* Helper for error callbacks that just returns the Status.ERROR by default
*
* @param message The message to add to the error result.
* @param callbackId The callback id used when calling back into JavaScript.
*/
public void error(int message) {
sendPluginResult(new PluginResult(PluginResult.Status.ERROR, message));
}
}

View File

@@ -22,6 +22,8 @@ import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import java.util.concurrent.ExecutorService;
/**
* The Cordova activity abstract class that is extended by DroidGap.
* It is used to isolate plugin development, and remove dependency on entire Cordova library.
@@ -36,15 +38,14 @@ public interface CordovaInterface {
* @param intent The intent to start
* @param requestCode The request code that is passed to callback to identify the activity
*/
abstract public void startActivityForResult(IPlugin command, Intent intent, int requestCode);
abstract public void startActivityForResult(CordovaPlugin command, Intent intent, int requestCode);
/**
* Set the plugin to be called when a sub-activity exits.
*
* @param plugin The plugin on which onActivityResult is to be called
*/
abstract public void setActivityResultCallback(IPlugin plugin);
abstract public void setActivityResultCallback(CordovaPlugin plugin);
/**
* Get the Android activity.
@@ -53,11 +54,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.
@@ -67,5 +63,9 @@ public interface CordovaInterface {
* @return Object or null
*/
public Object onMessage(String id, Object data);
/**
* Returns a shared thread pool that can be used for background tasks.
*/
public ExecutorService getThreadPool();
}

View File

@@ -20,74 +20,92 @@ package org.apache.cordova.api;
import org.apache.cordova.CordovaWebView;
import org.json.JSONArray;
//import android.content.Context;
import org.json.JSONException;
import android.content.Intent;
/**
* Plugin interface must be implemented by any plugin classes.
*
* The execute method is called by the PluginManager.
* Plugins must extend this class and override one of the execute methods.
*/
public interface IPlugin {
public class CordovaPlugin {
public String id;
public CordovaWebView webView; // WebView object
public CordovaInterface cordova;
/**
* Executes the request and returns PluginResult.
* @param cordova The context of the main Activity.
* @param webView The associated CordovaWebView.
*/
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
assert this.cordova == null;
this.cordova = cordova;
this.webView = webView;
}
/**
* Executes the request.
*
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param callbackId The callback id used when calling back into JavaScript.
* @return A PluginResult object with a status and message.
*/
PluginResult execute(String action, JSONArray args, String callbackId);
/**
* Identifies if action to be executed returns a value and should be run synchronously.
* This method is called from the WebView thread. To do a non-trivial amount of work, use:
* cordova.getThreadPool().execute(runnable);
*
* @param action The action to execute
* @return T=returns value
* To run on the UI thread, use:
* cordova.getActivity().runOnUiThread(runnable);
*
* @param action The action to execute.
* @param rawArgs The exec() arguments in JSON form.
* @param callbackContext The callback context used when calling back into JavaScript.
* @return Whether the action was valid.
*/
public boolean isSynch(String action);
public boolean execute(String action, String rawArgs, CallbackContext callbackContext) throws JSONException {
JSONArray args = new JSONArray(rawArgs);
return execute(action, args, callbackContext);
}
/**
* Sets the context of the Plugin. This can then be used to do things like
* get file paths associated with the Activity.
*
* @param ctx The context of the main Activity.
* 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.
* @param callbackContext The callback context used when calling back into JavaScript.
* @return Whether the action was valid.
*/
void setContext(CordovaInterface ctx);
/**
* Sets the main View of the application, this is the WebView within which
* a Cordova app runs.
*
* @param webView The Cordova WebView
*/
void setView(CordovaWebView webView);
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
return false;
}
/**
* Called when the system is about to start resuming a previous activity.
*
* @param multitasking Flag indicating if multitasking is turned on for app
*/
void onPause(boolean multitasking);
public void onPause(boolean multitasking) {
}
/**
* Called when the activity will start interacting with the user.
*
* @param multitasking Flag indicating if multitasking is turned on for app
*/
void onResume(boolean multitasking);
public void onResume(boolean multitasking) {
}
/**
* Called when the activity receives a new intent.
*/
void onNewIntent(Intent intent);
public void onNewIntent(Intent intent) {
}
/**
* The final call you receive before your activity is destroyed.
*/
void onDestroy();
public void onDestroy() {
}
/**
* Called when a message is sent to plugin.
@@ -96,7 +114,9 @@ public interface IPlugin {
* @param data The message data
* @return Object to stop propagation or null
*/
public Object onMessage(String id, Object data);
public Object onMessage(String id, Object data) {
return null;
}
/**
* Called when an activity you launched exits, giving you the requestCode you started it with,
@@ -107,7 +127,8 @@ public interface IPlugin {
* @param resultCode The integer result code returned by the child activity through its setResult().
* @param data An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
*/
void onActivityResult(int requestCode, int resultCode, Intent intent);
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
}
/**
* By specifying a <url-filter> in plugins.xml you can map a URL (using startsWith atm) to this method.
@@ -115,5 +136,17 @@ public interface IPlugin {
* @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.
*/
boolean onOverrideUrlLoading(String url);
public boolean onOverrideUrlLoading(String url) {
return false;
}
/**
* Called when the WebView does a top-level navigation or refreshes.
*
* Plugins should stop any long-running processes and clean up internal state.
*
* Does nothing by default.
*/
public void onReset() {
}
}

View File

@@ -1,3 +1,20 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cordova.api;
import android.app.Activity;
@@ -12,6 +29,8 @@ import android.content.res.AssetManager;
import android.content.res.Resources;
import android.util.Log;
import java.util.concurrent.ExecutorService;
@Deprecated
public class LegacyContext implements CordovaInterface {
private static final String LOG_TAG = "Deprecation Notice";
@@ -24,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
@@ -36,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
@@ -46,13 +64,13 @@ public class LegacyContext implements CordovaInterface {
}
@Deprecated
public void setActivityResultCallback(IPlugin arg0) {
public void setActivityResultCallback(CordovaPlugin arg0) {
Log.i(LOG_TAG, "Replace ctx.setActivityResultCallback() with cordova.setActivityResultCallback()");
this.cordova.setActivityResultCallback(arg0);
}
@Deprecated
public void startActivityForResult(IPlugin arg0, Intent arg1, int arg2) {
public void startActivityForResult(CordovaPlugin arg0, Intent arg1, int arg2) {
Log.i(LOG_TAG, "Replace ctx.startActivityForResult() with cordova.startActivityForResult()");
this.cordova.startActivityForResult(arg0, arg1, arg2);
}
@@ -128,4 +146,9 @@ public class LegacyContext implements CordovaInterface {
Log.i(LOG_TAG, "Replace ctx.unbindService() with cordova.getActivity().unbindService()");
this.cordova.getActivity().unbindService(conn);
}
public ExecutorService getThreadPool() {
Log.i(LOG_TAG, "Replace ctx.getThreadPool() with cordova.getThreadPool()");
return this.cordova.getThreadPool();
}
}

View File

@@ -20,40 +20,29 @@ package org.apache.cordova.api;
import org.apache.cordova.CordovaWebView;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Intent;
/**
* Plugin interface must be implemented by any plugin classes.
*
* The execute method is called by the PluginManager.
* Legacy Plugin class. This acts as a shim to support the old execute() signature.
* New plugins should extend CordovaPlugin directly.
*/
public abstract class Plugin implements IPlugin {
public String id;
public CordovaWebView webView; // WebView object
@Deprecated
public abstract class Plugin extends CordovaPlugin {
public LegacyContext ctx; // LegacyContext object
public CordovaInterface cordova;
/**
* Executes the request and returns PluginResult.
*
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param callbackId The callback id used when calling back into JavaScript.
* @return A PluginResult object with a status and message.
*/
public abstract PluginResult execute(String action, JSONArray args, String callbackId);
/**
* Identifies if action to be executed returns a value and should be run synchronously.
*
* @param action The action to execute
* @return T=returns value
*/
public boolean isSynch(String action) {
return false;
}
@Override
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
super.initialize(cordova, webView);
this.setContext(cordova);
this.setView(webView);
}
/**
* Sets the context of the Plugin. This can then be used to do things like
@@ -75,78 +64,52 @@ public abstract class Plugin implements IPlugin {
public void setView(CordovaWebView webView) {
this.webView = webView;
}
/**
* Called when the system is about to start resuming a previous activity.
*
* @param multitasking Flag indicating if multitasking is turned on for app
*/
public void onPause(boolean multitasking) {
}
/**
* Called when the activity will start interacting with the user.
*
* @param multitasking Flag indicating if multitasking is turned on for app
*/
public void onResume(boolean multitasking) {
}
/**
* Called when the activity receives a new intent.
*/
public void onNewIntent(Intent intent) {
}
/**
* The final call you receive before your activity is destroyed.
*/
public void onDestroy() {
}
/**
* Called when a message is sent to plugin.
*
* @param id The message id
* @param data The message data
* @return Object to stop propagation or null
*/
public Object onMessage(String id, Object data) {
return null;
}
/**
* Called when an activity you launched exits, giving you the requestCode you started it with,
* the resultCode it returned, and any additional data from it.
*
* @param requestCode The request code originally supplied to startActivityForResult(),
* allowing you to identify who this result came from.
* @param resultCode The integer result code returned by the child activity through its setResult().
* @param data An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
*/
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
}
/**
* By specifying a <url-filter> in plugins.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.
*/
public boolean onOverrideUrlLoading(String url) {
return false;
@Override
public boolean execute(final String action, final JSONArray args, final CallbackContext callbackContext) throws JSONException {
final String callbackId = callbackContext.getCallbackId();
boolean runAsync = !isSynch(action);
if (runAsync) {
// Run this on a different thread so that this one can return back to JS
cordova.getThreadPool().execute(new Runnable() {
public void run() {
PluginResult cr;
try {
cr = execute(action, args, callbackId);
} catch (Throwable e) {
cr = new PluginResult(PluginResult.Status.ERROR, e.getMessage());
}
sendPluginResult(cr, callbackId);
}
});
} else {
PluginResult cr = execute(action, args, callbackId);
// Interpret a null response as NO_RESULT, which *does* clear the callbacks on the JS side.
if (cr == null) {
cr = new PluginResult(PluginResult.Status.NO_RESULT);
}
callbackContext.sendPluginResult(cr);
}
return true;
}
/**
* Send generic JavaScript statement back to JavaScript.
* success(...) and error(...) should be used instead where possible.
*
* @param statement
* sendPluginResult() should be used instead where possible.
*/
public void sendJavascript(String statement) {
this.webView.sendJavascript(statement);
}
/**
* Send generic JavaScript statement back to JavaScript.
*/
public void sendPluginResult(PluginResult pluginResult, String callbackId) {
this.webView.sendPluginResult(pluginResult, callbackId);
}
/**
* Call the JavaScript success callback for this plugin.
*
@@ -154,60 +117,61 @@ public abstract class Plugin implements IPlugin {
* that execute should return null and the callback from the async operation can
* call success(...) or error(...)
*
* @param pluginResult The result to return.
* @param callbackId The callback id used when calling back into JavaScript.
* @param pluginResult The result to return.
* @param callbackId The callback id used when calling back into JavaScript.
*/
public void success(PluginResult pluginResult, String callbackId) {
this.webView.sendJavascript(pluginResult.toSuccessCallbackString(callbackId));
this.webView.sendPluginResult(pluginResult, callbackId);
}
/**
* Helper for success callbacks that just returns the Status.OK by default
*
* @param message The message to add to the success result.
* @param callbackId The callback id used when calling back into JavaScript.
* @param message The message to add to the success result.
* @param callbackId The callback id used when calling back into JavaScript.
*/
public void success(JSONObject message, String callbackId) {
this.webView.sendJavascript(new PluginResult(PluginResult.Status.OK, message).toSuccessCallbackString(callbackId));
this.webView.sendPluginResult(new PluginResult(PluginResult.Status.OK, message), callbackId);
}
/**
* Helper for success callbacks that just returns the Status.OK by default
*
* @param message The message to add to the success result.
* @param callbackId The callback id used when calling back into JavaScript.
* @param message The message to add to the success result.
* @param callbackId The callback id used when calling back into JavaScript.
*/
public void success(String message, String callbackId) {
this.webView.sendJavascript(new PluginResult(PluginResult.Status.OK, message).toSuccessCallbackString(callbackId));
this.webView.sendPluginResult(new PluginResult(PluginResult.Status.OK, message), callbackId);
}
/**
* Call the JavaScript error callback for this plugin.
*
* @param pluginResult The result to return.
* @param callbackId The callback id used when calling back into JavaScript.
* @param pluginResult The result to return.
* @param callbackId The callback id used when calling back into JavaScript.
*/
public void error(PluginResult pluginResult, String callbackId) {
this.webView.sendJavascript(pluginResult.toErrorCallbackString(callbackId));
this.webView.sendPluginResult(pluginResult, callbackId);
}
/**
* Helper for error callbacks that just returns the Status.ERROR by default
*
* @param message The message to add to the error result.
* @param callbackId The callback id used when calling back into JavaScript.
* @param message The message to add to the error result.
* @param callbackId The callback id used when calling back into JavaScript.
*/
public void error(JSONObject message, String callbackId) {
this.webView.sendJavascript(new PluginResult(PluginResult.Status.ERROR, message).toErrorCallbackString(callbackId));
this.webView.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, message), callbackId);
}
/**
* Helper for error callbacks that just returns the Status.ERROR by default
*
* @param message The message to add to the error result.
* @param callbackId The callback id used when calling back into JavaScript.
* @param message The message to add to the error result.
* @param callbackId The callback id used when calling back into JavaScript.
*/
public void error(String message, String callbackId) {
this.webView.sendJavascript(new PluginResult(PluginResult.Status.ERROR, message).toErrorCallbackString(callbackId));
this.webView.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, message), callbackId);
}
}

View File

@@ -43,7 +43,7 @@ public class PluginEntry {
* Plugin objects are only created when they are called from JavaScript. (see PluginManager.exec)
* The exception is if the onload flag is set, then they are created when PluginManager is initialized.
*/
public IPlugin plugin = null;
public CordovaPlugin plugin = null;
/**
* Flag that indicates the plugin object should be created when PluginManager is initialized.
@@ -69,7 +69,7 @@ public class PluginEntry {
*
* @return The plugin object
*/
public IPlugin createPlugin(CordovaWebView webView, CordovaInterface ctx) {
public CordovaPlugin createPlugin(CordovaWebView webView, CordovaInterface ctx) {
if (this.plugin != null) {
return this.plugin;
}
@@ -77,9 +77,8 @@ public class PluginEntry {
@SuppressWarnings("rawtypes")
Class c = getClassByName(this.pluginClass);
if (isCordovaPlugin(c)) {
this.plugin = (IPlugin) c.newInstance();
this.plugin.setContext(ctx);
this.plugin.setView(webView);
this.plugin = (CordovaPlugin) c.newInstance();
this.plugin.initialize(ctx, webView);
return plugin;
}
} catch (Exception e) {
@@ -106,16 +105,12 @@ public class PluginEntry {
}
/**
* Get the interfaces that a class implements and see if it implements the
* org.apache.cordova.api.Plugin interface.
*
* @param c The class to check the interfaces of.
* @return Boolean indicating if the class implements org.apache.cordova.api.Plugin
* Returns whether the given class extends CordovaPlugin.
*/
@SuppressWarnings("rawtypes")
private boolean isCordovaPlugin(Class c) {
if (c != null) {
return org.apache.cordova.api.Plugin.class.isAssignableFrom(c) || org.apache.cordova.api.IPlugin.class.isAssignableFrom(c);
return org.apache.cordova.api.CordovaPlugin.class.isAssignableFrom(c);
}
return false;
}

View File

@@ -22,6 +22,8 @@ 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;
@@ -30,6 +32,7 @@ import org.xmlpull.v1.XmlPullParserException;
import android.content.Intent;
import android.content.res.XmlResourceParser;
import android.util.Log;
/**
* PluginManager is exposed to JavaScript in the Cordova WebView.
@@ -200,72 +203,41 @@ public class PluginManager {
* or execute the class denoted by the clazz argument.
*
* @param service String containing the service to run
* @param action String containt the action that the class is supposed to perform. This is
* @param action String containing the action that the class is supposed to perform. This is
* passed to the plugin execute method and it is up to the plugin developer
* how to deal with it.
* @param callbackId String containing the id of the callback that is execute in JavaScript if
* this is an async plugin call.
* @param args An Array literal string containing any arguments needed in the
* @param rawArgs An Array literal string containing any arguments needed in the
* plugin execute method.
* @param async Boolean indicating whether the calling JavaScript code is expecting an
* immediate return value. If true, either Cordova.callbackSuccess(...) or
* Cordova.callbackError(...) is called once the plugin code has executed.
*
* @return PluginResult to send to the page, or null if no response is ready yet.
* @return Whether the task completed synchronously.
*/
public PluginResult exec(final String service, final String action, final String callbackId, final String jsonArgs, final boolean async) {
PluginResult cr = null;
boolean runAsync = async;
public boolean exec(String service, String action, String callbackId, String rawArgs) {
CordovaPlugin plugin = this.getPlugin(service);
if (plugin == null) {
PluginResult cr = new PluginResult(PluginResult.Status.CLASS_NOT_FOUND_EXCEPTION);
app.sendPluginResult(cr, callbackId);
return true;
}
try {
final JSONArray args = new JSONArray(jsonArgs);
final IPlugin plugin = this.getPlugin(service);
//final CordovaInterface ctx = this.ctx;
if (plugin != null) {
runAsync = async && !plugin.isSynch(action);
if (runAsync) {
// Run this on a different thread so that this one can return back to JS
Thread thread = new Thread(new Runnable() {
public void run() {
try {
// Call execute on the plugin so that it can do it's thing
PluginResult cr = plugin.execute(action, args, callbackId);
String callbackString = cr.toCallbackString(callbackId);
if (callbackString != null) {
app.sendJavascript(callbackString);
}
} catch (Exception e) {
PluginResult cr = new PluginResult(PluginResult.Status.ERROR, e.getMessage());
app.sendJavascript(cr.toErrorCallbackString(callbackId));
}
}
});
thread.start();
return null;
} else {
// Call execute on the plugin so that it can do it's thing
cr = plugin.execute(action, args, callbackId);
// If no result to be sent and keeping callback, then no need to sent back to JavaScript
if ((cr.getStatus() == PluginResult.Status.NO_RESULT.ordinal()) && cr.getKeepCallback()) {
return null;
}
}
CallbackContext callbackContext = new CallbackContext(callbackId, app);
boolean wasValidAction = plugin.execute(action, rawArgs, callbackContext);
if (!wasValidAction) {
PluginResult cr = new PluginResult(PluginResult.Status.INVALID_ACTION);
app.sendPluginResult(cr, callbackId);
return true;
}
return callbackContext.isFinished();
} catch (JSONException e) {
System.out.println("ERROR: " + e.toString());
cr = new PluginResult(PluginResult.Status.JSON_EXCEPTION);
PluginResult cr = new PluginResult(PluginResult.Status.JSON_EXCEPTION);
app.sendPluginResult(cr, callbackId);
return true;
}
// if async we have already returned at this point unless there was an error...
if (runAsync) {
if (cr == null) {
cr = new PluginResult(PluginResult.Status.CLASS_NOT_FOUND_EXCEPTION);
}
app.sendJavascript(cr.toErrorCallbackString(callbackId));
}
if (cr == null) {
cr = new PluginResult(PluginResult.Status.NO_RESULT);
}
return cr;
}
@Deprecated
public boolean exec(String service, String action, String callbackId, String jsonArgs, boolean async) {
return exec(service, action, callbackId, jsonArgs);
}
/**
@@ -274,14 +246,14 @@ public class PluginManager {
* If the service doesn't exist, then return null.
*
* @param service The name of the service.
* @return IPlugin or null
* @return CordovaPlugin or null
*/
private IPlugin getPlugin(String service) {
public CordovaPlugin getPlugin(String service) {
PluginEntry entry = this.entries.get(service);
if (entry == null) {
return null;
}
IPlugin plugin = entry.plugin;
CordovaPlugin plugin = entry.plugin;
if (plugin == null) {
plugin = entry.createPlugin(this.app, this.ctx);
}
@@ -398,6 +370,20 @@ public class PluginManager {
return false;
}
/**
* Called when the app navigates or refreshes.
*/
public void onReset() {
Iterator<PluginEntry> it = this.entries.values().iterator();
while (it.hasNext()) {
CordovaPlugin plugin = it.next().plugin;
if (plugin != null) {
plugin.onReset();
}
}
}
private void pluginConfigurationMissing() {
LOG.e(TAG, "=====================================================================================");
LOG.e(TAG, "ERROR: plugin.xml is missing. Add res/xml/plugins.xml to your project.");

View File

@@ -23,43 +23,49 @@ import org.json.JSONObject;
public class PluginResult {
private final int status;
private final String message;
private final int messageType;
private boolean keepCallback = false;
private String strMessage;
private String encodedMessage;
public PluginResult(Status status) {
this.status = status.ordinal();
this.message = "\"" + PluginResult.StatusMessages[this.status] + "\"";
this(status, PluginResult.StatusMessages[status.ordinal()]);
}
public PluginResult(Status status, String message) {
this.status = status.ordinal();
this.message = JSONObject.quote(message);
this.messageType = message == null ? MESSAGE_TYPE_NULL : MESSAGE_TYPE_STRING;
this.strMessage = message;
}
public PluginResult(Status status, JSONArray message) {
this.status = status.ordinal();
this.message = message.toString();
this.messageType = MESSAGE_TYPE_JSON;
encodedMessage = message.toString();
}
public PluginResult(Status status, JSONObject message) {
this.status = status.ordinal();
this.message = message.toString();
this.messageType = MESSAGE_TYPE_JSON;
encodedMessage = message.toString();
}
public PluginResult(Status status, int i) {
this.status = status.ordinal();
this.message = ""+i;
this.messageType = MESSAGE_TYPE_NUMBER;
this.encodedMessage = ""+i;
}
public PluginResult(Status status, float f) {
this.status = status.ordinal();
this.message = ""+f;
this.messageType = MESSAGE_TYPE_NUMBER;
this.encodedMessage = ""+f;
}
public PluginResult(Status status, boolean b) {
this.status = status.ordinal();
this.message = ""+b;
this.messageType = MESSAGE_TYPE_BOOLEAN;
this.encodedMessage = Boolean.toString(b);
}
public void setKeepCallback(boolean b) {
@@ -70,18 +76,35 @@ public class PluginResult {
return status;
}
public int getMessageType() {
return messageType;
}
public String getMessage() {
return message;
if (encodedMessage == null) {
encodedMessage = JSONObject.quote(strMessage);
}
return encodedMessage;
}
/**
* If messageType == MESSAGE_TYPE_STRING, then returns the message string.
* Otherwise, returns null.
*/
public String getStrMessage() {
return strMessage;
}
public boolean getKeepCallback() {
return this.keepCallback;
}
@Deprecated // Use sendPluginResult instead of sendJavascript.
public String getJSONString() {
return "{\"status\":" + this.status + ",\"message\":" + this.message + ",\"keepCallback\":" + this.keepCallback + "}";
return "{\"status\":" + this.status + ",\"message\":" + this.getMessage() + ",\"keepCallback\":" + this.keepCallback + "}";
}
@Deprecated // Use sendPluginResult instead of sendJavascript.
public String toCallbackString(String callbackId) {
// If no result to be sent and keeping callback, then no need to sent back to JavaScript
if ((status == PluginResult.Status.NO_RESULT.ordinal()) && keepCallback) {
@@ -95,14 +118,23 @@ public class PluginResult {
return toErrorCallbackString(callbackId);
}
@Deprecated // Use sendPluginResult instead of sendJavascript.
public String toSuccessCallbackString(String callbackId) {
return "cordova.callbackSuccess('"+callbackId+"',"+this.getJSONString()+");";
}
@Deprecated // Use sendPluginResult instead of sendJavascript.
public String toErrorCallbackString(String callbackId) {
return "cordova.callbackError('"+callbackId+"', " + this.getJSONString()+ ");";
}
public static final int MESSAGE_TYPE_STRING = 1;
public static final int MESSAGE_TYPE_JSON = 2;
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 String[] StatusMessages = new String[] {
"No result",
"OK",

View File

@@ -59,24 +59,17 @@
android:windowSoftInputMode="adjustPan"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden"
android:name=".CordovaWebViewTestActivity" >
android:name=".actions.CordovaWebViewTestActivity" >
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="tests" android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.SAMPLE_CODE" />
</intent-filter>
</activity>
<activity
android:windowSoftInputMode="adjustPan"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden"
android:name=".JailActivity" >
android:name=".actions.backbbuttonmultipage" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.SAMPLE_CODE" />
@@ -86,8 +79,8 @@
android:windowSoftInputMode="adjustPan"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden"
android:name=".CordovaDriverAction" >
<intent-filter >
android:name=".actions.background" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.SAMPLE_CODE" />
</intent-filter>
@@ -96,62 +89,181 @@
android:windowSoftInputMode="adjustPan"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden"
android:name=".CordovaActivity" >
android:name=".actions.basicauth" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.SAMPLE_CODE" />
</intent-filter>
</activity>
<activity android:name="splashscreen" android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden">
<activity
android:windowSoftInputMode="adjustPan"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden"
android:name=".actions.CordovaActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.SAMPLE_CODE" />
</intent-filter>
</activity>
<activity android:name="timeout" android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden">
<activity
android:windowSoftInputMode="adjustPan"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden"
android:name=".actions.CordovaDriverAction" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.SAMPLE_CODE" />
</intent-filter>
</activity>
<activity android:name="htmlnotfound" android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden">
<activity
android:windowSoftInputMode="adjustPan"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden"
android:name=".actions.errorurl" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.SAMPLE_CODE" />
</intent-filter>
</activity>
<activity android:name="errorurl" android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden">
<activity
android:windowSoftInputMode="adjustPan"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden"
android:name=".actions.fullscreen" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.SAMPLE_CODE" />
</intent-filter>
</activity>
<activity android:name="userwebview" android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden">
<activity
android:windowSoftInputMode="adjustPan"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden"
android:name=".actions.htmlnotfound" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.SAMPLE_CODE" />
</intent-filter>
</activity>
<activity android:name="menus" android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden">
<activity
android:windowSoftInputMode="adjustPan"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden"
android:name=".actions.iframe" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.SAMPLE_CODE" />
</intent-filter>
</activity>
<activity android:name="loading" android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden">
<activity
android:windowSoftInputMode="adjustPan"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden"
android:name=".actions.jqmtabbackbutton" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.SAMPLE_CODE" />
</intent-filter>
</activity>
<activity android:name="lifecycle" android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden">
<activity
android:windowSoftInputMode="adjustPan"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden"
android:name=".actions.lifecycle" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.SAMPLE_CODE" />
</intent-filter>
</activity>
<activity android:name="jqmtabbackbutton" android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden">
<activity
android:windowSoftInputMode="adjustPan"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden"
android:name=".actions.loading" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.SAMPLE_CODE" />
</intent-filter>
</activity>
<activity android:name="backbuttonmultipage" android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden">
<activity
android:windowSoftInputMode="adjustPan"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden"
android:name=".actions.menus" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.SAMPLE_CODE" />
</intent-filter>
</activity>
<activity android:name="whitelist" android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden">
<activity
android:windowSoftInputMode="adjustPan"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden"
android:name=".actions.splashscreen" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.SAMPLE_CODE" />
</intent-filter>
</activity>
<activity android:name="background" android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden">
<activity
android:windowSoftInputMode="adjustPan"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden"
android:name=".actions.tests" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.SAMPLE_CODE" />
</intent-filter>
</activity>
<activity android:name="iframe" android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden">
<activity
android:windowSoftInputMode="adjustPan"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden"
android:name=".actions.timeout" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.SAMPLE_CODE" />
</intent-filter>
</activity>
<activity android:name="xhr" android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden">
<activity
android:windowSoftInputMode="adjustPan"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden"
android:name=".actions.userwebview" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.SAMPLE_CODE" />
</intent-filter>
</activity>
<activity android:name="basicauth" android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden">
<activity
android:windowSoftInputMode="adjustPan"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden"
android:name=".actions.whitelist" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.SAMPLE_CODE" />
</intent-filter>
</activity>
<activity android:name="fullscreen" android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden">
<activity
android:windowSoftInputMode="adjustPan"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden"
android:name=".actions.xhr" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.SAMPLE_CODE" />
</intent-filter>
</activity>
<activity android:name="backgroundcolor" android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden">
<activity
android:windowSoftInputMode="adjustPan"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden"
android:name=".actions.backbuttonmultipage" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.SAMPLE_CODE" />
</intent-filter>
</activity>
</application>
</manifest>

File diff suppressed because it is too large Load Diff

View File

@@ -45,24 +45,24 @@
<h4>Cordova Version: <span id="cordova">&nbsp;</span></h4>
</div>
<div id="info">
<h4>Run each of the tests below:</h4>
<h4>Run each of the test activities below:</h4>
</div>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.jqmtabbackbutton');">Backbutton jQM tab</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.backbuttonmultipage');">Backbutton with multiple pages</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.backgroundcolor');">Background Color</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.basicauth');">Basic Authentication</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.errorurl');">Error URL</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.fullscreen');">Full Screen</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.htmlnotfound');">HTML not found</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.iframe');">IFrame</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.lifecycle');">Lifecycle</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.loading');">Loading indicator</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.menus');">Menus</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.background');">No multitasking</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.splashscreen');">Splash screen</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.timeout');">Load timeout</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.userwebview');">User WebView/Client/Chrome</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.whitelist');">Whitelist</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.xhr');">XHR</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.actions.jqmtabbackbutton');">Backbutton jQM tab</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.actions.backbuttonmultipage');">Backbutton with multiple pages</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.actions.backgroundcolor');">Background Color</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.actions.basicauth');">Basic Authentication</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.actions.errorurl');">Error URL</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.actions.fullscreen');">Full Screen</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.actions.htmlnotfound');">HTML not found</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.actions.iframe');">IFrame</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.actions.lifecycle');">Lifecycle</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.actions.loading');">Loading indicator</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.actions.menus');">Menus</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.actions.background');">No multitasking</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.actions.splashscreen');">Splash screen</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.actions.timeout');">Load timeout</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.actions.userwebview');">User WebView/Client/Chrome</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.actions.whitelist');">Whitelist</button>
<button class="btn large" onclick="startActivity('org.apache.cordova.test.actions.xhr');">XHR</button>
</body>
</html>

View File

@@ -9,3 +9,4 @@
# Project target.
target=android-16
android.library.reference.1=../framework

56
test/res/xml/config.xml Normal file
View File

@@ -0,0 +1,56 @@
<?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=".*"/>
<log level="DEBUG"/>
<preference name="useBrowserHistory" value="true" />
<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"/>
<plugin name="Echo" value="org.apache.cordova.Echo" />
</plugins>
</cordova>

View File

@@ -1,9 +1,32 @@
package org.apache.cordova.test;
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.test.actions.backbuttonmultipage;
import android.test.ActivityInstrumentationTestCase2;
import android.view.KeyEvent;
import android.view.inputmethod.BaseInputConnection;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
@@ -75,6 +98,54 @@ public class BackButtonMultiPageTest extends ActivityInstrumentationTestCase2<ba
assertTrue(didGoBack);
}
public void testViaBackButtonOnView() {
testView.loadUrl("file:///android_asset/www/backbuttonmultipage/sample2.html");
sleep();
String url = testView.getUrl();
assertTrue(url.endsWith("sample2.html"));
testView.loadUrl("file:///android_asset/www/backbuttonmultipage/sample3.html");
sleep();
url = testView.getUrl();
assertTrue(url.endsWith("sample3.html"));
BaseInputConnection viewConnection = new BaseInputConnection(testView, true);
KeyEvent backDown = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
KeyEvent backUp = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
viewConnection.sendKeyEvent(backDown);
viewConnection.sendKeyEvent(backUp);
sleep();
url = testView.getUrl();
assertTrue(url.endsWith("sample2.html"));
viewConnection.sendKeyEvent(backDown);
viewConnection.sendKeyEvent(backUp);
sleep();
url = testView.getUrl();
assertTrue(url.endsWith("index.html"));
}
public void testViaBackButtonOnLayout() {
testView.loadUrl("file:///android_asset/www/backbuttonmultipage/sample2.html");
sleep();
String url = testView.getUrl();
assertTrue(url.endsWith("sample2.html"));
testView.loadUrl("file:///android_asset/www/backbuttonmultipage/sample3.html");
sleep();
url = testView.getUrl();
assertTrue(url.endsWith("sample3.html"));
BaseInputConnection viewConnection = new BaseInputConnection(containerView, true);
KeyEvent backDown = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
KeyEvent backUp = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
viewConnection.sendKeyEvent(backDown);
viewConnection.sendKeyEvent(backUp);
sleep();
url = testView.getUrl();
assertTrue(url.endsWith("sample2.html"));
viewConnection.sendKeyEvent(backDown);
viewConnection.sendKeyEvent(backUp);
sleep();
url = testView.getUrl();
assertTrue(url.endsWith("index.html"));
}
private void sleep() {
try {
Thread.sleep(TIMEOUT);

View File

@@ -21,6 +21,7 @@ package org.apache.cordova.test;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.api.PluginManager;
import org.apache.cordova.test.actions.CordovaActivity;
import android.app.Instrumentation;
import android.test.ActivityInstrumentationTestCase2;

View File

@@ -20,6 +20,7 @@ package org.apache.cordova.test;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.api.PluginManager;
import org.apache.cordova.test.actions.CordovaWebViewTestActivity;
import android.app.Instrumentation;
import android.test.ActivityInstrumentationTestCase2;
@@ -48,10 +49,13 @@ public class CordovaTest extends
}
public void testForCordovaView() {
//Sleep for no reason!!!!
sleep();
String className = testView.getClass().getSimpleName();
assertTrue(className.equals("CordovaWebView"));
}
/*
/*
public void testForPluginManager() {
CordovaWebView v = (CordovaWebView) testView;
PluginManager p = v.getPluginManager();

View File

@@ -1,6 +1,28 @@
package org.apache.cordova.test;
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.test.actions.errorurl;
import android.test.ActivityInstrumentationTestCase2;
import android.widget.FrameLayout;

View File

@@ -22,6 +22,7 @@ package org.apache.cordova.test;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CordovaChromeClient;
import org.apache.cordova.api.PluginManager;
import org.apache.cordova.test.actions.CordovaWebViewTestActivity;
import android.content.Context;
import android.content.res.AssetManager;

View File

@@ -1,6 +1,28 @@
package org.apache.cordova.test;
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.test.actions.htmlnotfound;
import android.test.ActivityInstrumentationTestCase2;
import android.widget.FrameLayout;

View File

@@ -1,4 +1,27 @@
package org.apache.cordova.test;
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
import org.apache.cordova.test.actions.iframe;
import android.test.ActivityInstrumentationTestCase2;

View File

@@ -1,4 +1,27 @@
package org.apache.cordova.test;
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
import org.apache.cordova.test.actions.jqmtabbackbutton;
import android.test.ActivityInstrumentationTestCase2;

View File

@@ -1,4 +1,27 @@
package org.apache.cordova.test;
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
import org.apache.cordova.test.actions.lifecycle;
import android.test.ActivityInstrumentationTestCase2;

View File

@@ -21,6 +21,7 @@ package org.apache.cordova.test;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.api.PluginManager;
import org.apache.cordova.test.actions.CordovaWebViewTestActivity;
import android.test.ActivityInstrumentationTestCase2;
import android.view.View;

View File

@@ -1,6 +1,28 @@
package org.apache.cordova.test;
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.test.actions.splashscreen;
import android.app.Dialog;
import android.test.ActivityInstrumentationTestCase2;

View File

@@ -1,8 +1,30 @@
package org.apache.cordova.test;
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CordovaWebViewClient;
import org.apache.cordova.CordovaChromeClient;
import org.apache.cordova.test.actions.userwebview;
import android.test.ActivityInstrumentationTestCase2;
import android.widget.FrameLayout;

View File

@@ -1,4 +1,27 @@
package org.apache.cordova.test;
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
import org.apache.cordova.test.actions.xhr;
import android.test.ActivityInstrumentationTestCase2;

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