Compare commits
494 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5462eddfdb | ||
|
|
fef51f12c6 | ||
|
|
5cd17730b1 | ||
|
|
cb192056f8 | ||
|
|
892f96e305 | ||
|
|
13ef58a5bb | ||
|
|
a45d5a98dd | ||
|
|
a31714f8a4 | ||
|
|
23d2a806f0 | ||
|
|
c20b2330ab | ||
|
|
8613551aec | ||
|
|
2ab01dadc0 | ||
|
|
790636c8cd | ||
|
|
23938830f7 | ||
|
|
674b87057a | ||
|
|
83d9248ec8 | ||
|
|
f9d27e4a67 | ||
|
|
2683803ef3 | ||
|
|
dd86d7a5ed | ||
|
|
1246a81d39 | ||
|
|
8ab7278db2 | ||
|
|
db099e7722 | ||
|
|
fcc01bc37e | ||
|
|
a18dacf5f2 | ||
|
|
77f9cae50b | ||
|
|
3610bbf21b | ||
|
|
d5e3be9a55 | ||
|
|
80b369d6d5 | ||
|
|
d29eb84010 | ||
|
|
381ce535bf | ||
|
|
2e20bb0639 | ||
|
|
56cd24797e | ||
|
|
431ca99c23 | ||
|
|
6ced2ff293 | ||
|
|
31055bb303 | ||
|
|
24a53e39dd | ||
|
|
2ab113b695 | ||
|
|
9a0481a750 | ||
|
|
09035eb4c4 | ||
|
|
1adf268e71 | ||
|
|
23f57ad5a7 | ||
|
|
d9b15cf69e | ||
|
|
dbfe12a993 | ||
|
|
2b32dfd99d | ||
|
|
679de40780 | ||
|
|
66f15fdd37 | ||
|
|
038f0e45b1 | ||
|
|
033bfcc804 | ||
|
|
fa87c08a29 | ||
|
|
dfb799739a | ||
|
|
1193f7ed22 | ||
|
|
7530c21a9f | ||
|
|
a120614617 | ||
|
|
0311f0db38 | ||
|
|
547b683e61 | ||
|
|
ff1d943a69 | ||
|
|
15a5c89e86 | ||
|
|
03b974ee3f | ||
|
|
f145605c63 | ||
|
|
29230d0316 | ||
|
|
57fc49ddc2 | ||
|
|
5ac6853fed | ||
|
|
b870214cca | ||
|
|
55074b925f | ||
|
|
958424ce59 | ||
|
|
d04fc289ac | ||
|
|
e14edf134d | ||
|
|
dbb127447f | ||
|
|
dc94fc39ec | ||
|
|
6db9a7cb12 | ||
|
|
1f39386616 | ||
|
|
25aef945d1 | ||
|
|
c9aa43afe0 | ||
|
|
913e177f6f | ||
|
|
ae431aec12 | ||
|
|
8ac15048cd | ||
|
|
a1cfe87f1e | ||
|
|
c130396d4e | ||
|
|
bc2e7cf317 | ||
|
|
7ace1d652d | ||
|
|
26effd1def | ||
|
|
5f6824e5dd | ||
|
|
4589bdd31f | ||
|
|
72e0b49e0b | ||
|
|
3caa45d860 | ||
|
|
7c069f14f8 | ||
|
|
552885dbd3 | ||
|
|
6efeb1471c | ||
|
|
01f062d2ba | ||
|
|
2a42c463d2 | ||
|
|
182843edf6 | ||
|
|
9a9d36e9d9 | ||
|
|
7d5249eea6 | ||
|
|
f7910c41c3 | ||
|
|
3973f4f952 | ||
|
|
8a19769a47 | ||
|
|
c0ee593c10 | ||
|
|
c806451b8a | ||
|
|
00e5ff1964 | ||
|
|
432aec62a9 | ||
|
|
c8ec7e5191 | ||
|
|
a0d2b96de6 | ||
|
|
2c202b82d7 | ||
|
|
a42dc08756 | ||
|
|
48f58110fe | ||
|
|
7b3724972b | ||
|
|
9ca2a16218 | ||
|
|
f1e8400abf | ||
|
|
11e0ffa90a | ||
|
|
2ee4326a4d | ||
|
|
226e72ac18 | ||
|
|
65c78b8f3f | ||
|
|
6137c7ca06 | ||
|
|
5bebf11b37 | ||
|
|
68161d2714 | ||
|
|
a6473cb826 | ||
|
|
0084c6f96a | ||
|
|
a87825dbee | ||
|
|
3566154cd0 | ||
|
|
92d69e320f | ||
|
|
08a190ef5b | ||
|
|
98339ee5d8 | ||
|
|
fa387fd758 | ||
|
|
54979f2fc4 | ||
|
|
538e90f23a | ||
|
|
d9107bcac6 | ||
|
|
3f3a0b9140 | ||
|
|
e1347e434e | ||
|
|
7657faa9c3 | ||
|
|
28ef765913 | ||
|
|
d2f59391a2 | ||
|
|
df90bdb350 | ||
|
|
c416c77d7a | ||
|
|
ce05a720d1 | ||
|
|
6c19a440f5 | ||
|
|
f4612fdb5d | ||
|
|
04b9a0b09e | ||
|
|
f93c438067 | ||
|
|
e1d608443a | ||
|
|
9233c3a898 | ||
|
|
dfa514334b | ||
|
|
5810a96e62 | ||
|
|
70473a80af | ||
|
|
525fd30cb2 | ||
|
|
5212cd4dcd | ||
|
|
e95bde62a2 | ||
|
|
4fe73cf6ad | ||
|
|
78b2835da4 | ||
|
|
f9a49efae9 | ||
|
|
b9ddc9e678 | ||
|
|
dc459c84a3 | ||
|
|
1d26239809 | ||
|
|
5ca233779d | ||
|
|
e51b4897a3 | ||
|
|
81f283e56f | ||
|
|
ccdd2fd2ca | ||
|
|
69f11a29e1 | ||
|
|
cf494f3238 | ||
|
|
d5895c635a | ||
|
|
2ac9873613 | ||
|
|
eb59e76cde | ||
|
|
d9db845b43 | ||
|
|
e55327b064 | ||
|
|
bdd5a4e053 | ||
|
|
ac2e2c9a42 | ||
|
|
76f9d49e24 | ||
|
|
6ec8ab95fc | ||
|
|
9c98625610 | ||
|
|
f270cde47d | ||
|
|
9de7efd072 | ||
|
|
7b81d317a0 | ||
|
|
876f975aa2 | ||
|
|
3c5815ac0f | ||
|
|
678ae2d684 | ||
|
|
e4f8f44fb0 | ||
|
|
49566d29f8 | ||
|
|
7f4ee7b20a | ||
|
|
32526a8c16 | ||
|
|
71a7f72ab9 | ||
|
|
4d0824f4a4 | ||
|
|
d56dd40d06 | ||
|
|
6aafd6dc3a | ||
|
|
011b512f28 | ||
|
|
aa2d17e489 | ||
|
|
0eee2293dc | ||
|
|
a2f35d2bda | ||
|
|
58f58d9ee8 | ||
|
|
412bb349ac | ||
|
|
652f15f893 | ||
|
|
8512ebb923 | ||
|
|
f0ac173ec8 | ||
|
|
bef0d47924 | ||
|
|
cba0d59021 | ||
|
|
7d3afcab94 | ||
|
|
5f1cda07e7 | ||
|
|
e11beade4b | ||
|
|
6a1e089b73 | ||
|
|
0aa98ac2da | ||
|
|
f9ef38cc7a | ||
|
|
a3a215a1ba | ||
|
|
d3ee322d7c | ||
|
|
7ec20e7752 | ||
|
|
08dfb13dbf | ||
|
|
6a5cddd907 | ||
|
|
dc5078306d | ||
|
|
1bc032853c | ||
|
|
e562e4e7b9 | ||
|
|
0ffffa9029 | ||
|
|
0f2303e8d5 | ||
|
|
31f7f8149e | ||
|
|
fe1f57c23f | ||
|
|
29a0b010da | ||
|
|
621e1163f8 | ||
|
|
17d64cfcbe | ||
|
|
7379d2135d | ||
|
|
c55fd06b99 | ||
|
|
d81727a08c | ||
|
|
b582e1592a | ||
|
|
dd8533a320 | ||
|
|
d72a8cbf89 | ||
|
|
fe0876ded6 | ||
|
|
fa15763c5d | ||
|
|
205215d409 | ||
|
|
076bfcde87 | ||
|
|
10510484b5 | ||
|
|
e1dea5b4d3 | ||
|
|
891f8d00cf | ||
|
|
0d409f0fe3 | ||
|
|
4e0c8982c9 | ||
|
|
a741c66c97 | ||
|
|
3e6a7cbdf5 | ||
|
|
5d34aa0afe | ||
|
|
979ae94698 | ||
|
|
8d7b85b26a | ||
|
|
686977a986 | ||
|
|
9c6c782146 | ||
|
|
ca9539b5b6 | ||
|
|
ff25be8839 | ||
|
|
d1ab1b59be | ||
|
|
05bc1865a6 | ||
|
|
6e6e0275ad | ||
|
|
ec3c5b2ca2 | ||
|
|
5289d569b0 | ||
|
|
6f873ff6b5 | ||
|
|
467cbe972c | ||
|
|
bfd1bfe9f0 | ||
|
|
3404a6c699 | ||
|
|
17a4b5155e | ||
|
|
d406e2ed22 | ||
|
|
0bfc9935b2 | ||
|
|
64c6cbe303 | ||
|
|
2245db3e80 | ||
|
|
6f19a50c98 | ||
|
|
c7ce9598a8 | ||
|
|
afcdccf783 | ||
|
|
1bf12842ca | ||
|
|
da8fbee256 | ||
|
|
4021f26e76 | ||
|
|
8eab8438cf | ||
|
|
1b4096b01d | ||
|
|
54caa6e438 | ||
|
|
486eb149f2 | ||
|
|
faa034a205 | ||
|
|
2cd3ebc7a8 | ||
|
|
7e3af6c235 | ||
|
|
dd4de16d1d | ||
|
|
ba8577fa5f | ||
|
|
6192319f8c | ||
|
|
fed368d553 | ||
|
|
20c885418e | ||
|
|
9318ee30bd | ||
|
|
8b6c9574df | ||
|
|
313148136a | ||
|
|
6e1fdc77ae | ||
|
|
2a9582ebb1 | ||
|
|
dd1cd46719 | ||
|
|
9961d9e54d | ||
|
|
7eb12110d1 | ||
|
|
3d62744601 | ||
|
|
17af417235 | ||
|
|
df9d314361 | ||
|
|
610e0c984a | ||
|
|
3688fca126 | ||
|
|
9bc89c784f | ||
|
|
79682f5d52 | ||
|
|
c206ac0335 | ||
|
|
34840175f3 | ||
|
|
6312457425 | ||
|
|
f71e664952 | ||
|
|
80d559f17e | ||
|
|
772aedc263 | ||
|
|
45d7c124c8 | ||
|
|
73abb20b3d | ||
|
|
0baf104a75 | ||
|
|
302d51cdfd | ||
|
|
d3cbfd5467 | ||
|
|
9e3e7e1820 | ||
|
|
18893bf6cd | ||
|
|
f53161d6f5 | ||
|
|
4c9a571106 | ||
|
|
365edcad16 | ||
|
|
ae9047a708 | ||
|
|
9c0e58df8d | ||
|
|
ee34f11c29 | ||
|
|
6ca6d88bff | ||
|
|
65a397fb63 | ||
|
|
0a669077fb | ||
|
|
451688a12e | ||
|
|
d181d89dd2 | ||
|
|
ac14b0d73b | ||
|
|
0f42c65792 | ||
|
|
37b3e980dc | ||
|
|
eb49e011e2 | ||
|
|
e0a73f72ee | ||
|
|
e217ab28c5 | ||
|
|
ca583865ea | ||
|
|
5e7efde311 | ||
|
|
2c7c13420b | ||
|
|
ac4fc3e54e | ||
|
|
46db36a05e | ||
|
|
3d073be990 | ||
|
|
1bc49fe450 | ||
|
|
1f7fe9abcc | ||
|
|
5217abf57a | ||
|
|
2ecbde891a | ||
|
|
bf7fc66646 | ||
|
|
5a94b38e2f | ||
|
|
1bc55f5937 | ||
|
|
04c9542f94 | ||
|
|
17e739f68a | ||
|
|
4f5515fde3 | ||
|
|
ae3ba129ea | ||
|
|
6b92a0fff7 | ||
|
|
d859bb8e67 | ||
|
|
f12bbf71ed | ||
|
|
b723beb545 | ||
|
|
47daaaf14f | ||
|
|
9ba5bae34d | ||
|
|
dbfa2d7994 | ||
|
|
8134f86d1f | ||
|
|
5c60b09bf4 | ||
|
|
20a19d67d0 | ||
|
|
311a2f6023 | ||
|
|
59a3cf93e6 | ||
|
|
a42f095cef | ||
|
|
a29340523f | ||
|
|
5ad7a7c014 | ||
|
|
c6fa7e4aad | ||
|
|
d4b248fbe3 | ||
|
|
48881d081a | ||
|
|
331024414e | ||
|
|
9d0c5349bb | ||
|
|
dc40d8afac | ||
|
|
005877b4b8 | ||
|
|
774d21747a | ||
|
|
12e5b39c05 | ||
|
|
4d5e452ece | ||
|
|
1ba3ecbef3 | ||
|
|
db6695cb02 | ||
|
|
b3f5e039f2 | ||
|
|
c3e17fb185 | ||
|
|
f7ae7fe43a | ||
|
|
e07822350e | ||
|
|
07439ff99c | ||
|
|
f111c245c1 | ||
|
|
c3502da4a0 | ||
|
|
4012108d48 | ||
|
|
4a0605e09b | ||
|
|
250380d73e | ||
|
|
b30f5d782d | ||
|
|
b00cd9b557 | ||
|
|
e11f8f646b | ||
|
|
92b1de8cf8 | ||
|
|
bbafe53a2b | ||
|
|
e239fd970f | ||
|
|
7fa4515c28 | ||
|
|
b40eb0a454 | ||
|
|
5e3e9ddb8e | ||
|
|
a9a5284a67 | ||
|
|
afe504dbbf | ||
|
|
0c484ddcf7 | ||
|
|
8d0e80620a | ||
|
|
1d28506b09 | ||
|
|
1b33dbe2ae | ||
|
|
80654c059d | ||
|
|
999c548e6e | ||
|
|
e42913ae8a | ||
|
|
ee07cbecba | ||
|
|
fffaa9bced | ||
|
|
6195b2c99d | ||
|
|
06aafc96c9 | ||
|
|
2dc0727e36 | ||
|
|
a219feaa60 | ||
|
|
f3a09da340 | ||
|
|
946e345a3f | ||
|
|
6cb8d11b22 | ||
|
|
fdcf9c5327 | ||
|
|
45c714cbb5 | ||
|
|
7352a309a0 | ||
|
|
b297fe6f59 | ||
|
|
e575212c49 | ||
|
|
c52dc10c9e | ||
|
|
d35c913249 | ||
|
|
9bac59b952 | ||
|
|
5016253922 | ||
|
|
03893071fc | ||
|
|
d3dc94c04b | ||
|
|
af0feabb6a | ||
|
|
81ab0a414f | ||
|
|
ecd6ca0172 | ||
|
|
db7ee192f7 | ||
|
|
2ec0b601fa | ||
|
|
79feb6d5d2 | ||
|
|
8013b760e3 | ||
|
|
a29b8e5b36 | ||
|
|
9ef487a7a5 | ||
|
|
563fa46ba4 | ||
|
|
7865c06863 | ||
|
|
3d53b9244d | ||
|
|
f2afa4dd50 | ||
|
|
893ecec55e | ||
|
|
401584dbd8 | ||
|
|
b234b0bded | ||
|
|
b9b2c6a013 | ||
|
|
1d2efa0d25 | ||
|
|
93ec092eaf | ||
|
|
29ae492983 | ||
|
|
b9f6a59a20 | ||
|
|
d74551216f | ||
|
|
d4302ae51b | ||
|
|
9d5fb0b201 | ||
|
|
e0a5fe4002 | ||
|
|
f9d9a0a4bd | ||
|
|
78f0c7b119 | ||
|
|
c6d8343de2 | ||
|
|
0ccd11e587 | ||
|
|
b486711d68 | ||
|
|
2eb4c5e960 | ||
|
|
85aa740c98 | ||
|
|
6415848383 | ||
|
|
beb9460538 | ||
|
|
c030770be7 | ||
|
|
0180342dff | ||
|
|
b97748d3dc | ||
|
|
9d4977db00 | ||
|
|
f095284faa | ||
|
|
401c2f42f9 | ||
|
|
eb0348d47c | ||
|
|
1f46240ba9 | ||
|
|
14870726e0 | ||
|
|
c7d6a2eecb | ||
|
|
ce61eb2174 | ||
|
|
f3df21ef0a | ||
|
|
5eb554e008 | ||
|
|
e5e7c3fad3 | ||
|
|
2a8b9ab75e | ||
|
|
c8f0ffb42f | ||
|
|
b3e68b96cf | ||
|
|
ae7a550a09 | ||
|
|
e069bbb800 | ||
|
|
17ff6be6a9 | ||
|
|
d42489c67a | ||
|
|
3ea72e5d21 | ||
|
|
762854ad7a | ||
|
|
10465066ee | ||
|
|
0cf9f51816 | ||
|
|
3d5e2340ca | ||
|
|
e2047afa42 | ||
|
|
231b39d2dc | ||
|
|
dddce30368 | ||
|
|
e0e4ba2bd7 | ||
|
|
e0eadb6b76 | ||
|
|
483e5dfbea | ||
|
|
8aa9d8213d | ||
|
|
a74f71c935 | ||
|
|
87b81e53f0 | ||
|
|
a2816e31c3 | ||
|
|
f78af9f27b | ||
|
|
98138a0a60 | ||
|
|
e639b6303e | ||
|
|
99fb3ebe00 | ||
|
|
5829840409 | ||
|
|
4699ab5500 | ||
|
|
69fc7f39b7 | ||
|
|
510a962a52 | ||
|
|
570fc3cfb2 | ||
|
|
5d211f2fa6 | ||
|
|
dcb127c14d | ||
|
|
d9e7984279 | ||
|
|
e5b9900d3b | ||
|
|
fc3f1431b2 | ||
|
|
c8bf2f4cb1 | ||
|
|
d16555ec4b | ||
|
|
3c9415b1c2 |
26
.gitignore
vendored
@@ -2,19 +2,31 @@
|
||||
default.properties
|
||||
gen
|
||||
assets/www/cordova.js
|
||||
framework/assets/www/.tmp*
|
||||
local.properties
|
||||
framework/proguard.cfg
|
||||
framework/cordova.jar
|
||||
framework/cordova-*.jar
|
||||
framework/phonegap-*.jar
|
||||
framework/lib
|
||||
proguard.cfg
|
||||
proguard.cfg
|
||||
proguard-project.txt
|
||||
framework/bin
|
||||
framework/test/org/apache/cordova/*.class
|
||||
framework/assets/www/.DS_Store
|
||||
framework/assets/www/cordova-*.js
|
||||
framework/assets/www/phonegap-*.js
|
||||
framework/libs
|
||||
test/libs
|
||||
example
|
||||
./test
|
||||
tmp
|
||||
*.tmp
|
||||
test/libs/*.jar
|
||||
test/bin
|
||||
test/assets/www/.tmp*
|
||||
tmp/**
|
||||
bin/node_modules
|
||||
.metadata
|
||||
tmp/**/*
|
||||
Thumbs.db
|
||||
Desktop.ini
|
||||
*.tmp
|
||||
*.bak
|
||||
*.swp
|
||||
*.class
|
||||
*.jar
|
||||
|
||||
37
README.md
@@ -1,22 +1,18 @@
|
||||
Cordova Android
|
||||
===
|
||||
|
||||
Cordova Android is an Android application library that allows for Cordova based projects to be built for the Android Platform. Cordova based applications are, at the core, an application written with web technology: HTML, CSS and JavaScript.
|
||||
Cordova Android is an Android application library that allows for Cordova-based
|
||||
projects to be built for the Android Platform. Cordova based applications are,
|
||||
at the core, applications written with web technology: HTML, CSS and JavaScript.
|
||||
|
||||
[Apache Cordova](http://cordova.io) is a project at The Apache Software Foundation (ASF).
|
||||
|
||||
Apache Cordova is an effort undergoing incubation at The Apache
|
||||
Software Foundation (ASF), sponsored by the Apache Incubator project.
|
||||
Incubation is required of all newly accepted projects until a further
|
||||
review indicates that the infrastructure, communications, and decision
|
||||
making process have stabilized in a manner consistent with other
|
||||
successful ASF projects. While incubation status is not necessarily
|
||||
a reflection of the completeness or stability of the code, it does
|
||||
indicate that the project has yet to be fully endorsed by the ASF.
|
||||
|
||||
Requires
|
||||
---
|
||||
|
||||
- Java JDK 1.5
|
||||
- Apache ANT
|
||||
- Java JDK 1.5 or greater
|
||||
- Apache ANT 1.8.0 or greater
|
||||
- Android SDK [http://developer.android.com](http://developer.android.com)
|
||||
- Apache Commons Codec [http://commons.apache.org/codec/](http://commons.apache.org/codec/)
|
||||
|
||||
@@ -27,13 +23,13 @@ Test Requirements
|
||||
Building
|
||||
---
|
||||
|
||||
To create your cordova.jar, copy the commons codec:
|
||||
To create your `cordova.jar` file, copy the commons codec:
|
||||
|
||||
mv commons-codec-1.6.jar framework/libs
|
||||
mv commons-codec-1.7.jar framework/libs
|
||||
|
||||
then run in the framework directory:
|
||||
|
||||
android update project -p . -t android-15
|
||||
android update project -p . -t android-17
|
||||
ant jar
|
||||
|
||||
|
||||
@@ -61,7 +57,7 @@ Project Commands
|
||||
|
||||
These commands live in a generated Cordova Android project.
|
||||
|
||||
./cordovap/debug [path] ..................... install to first device
|
||||
./cordova/debug [path] ..................... install to first device
|
||||
./cordova/emulate .......................... start avd (emulator) named default
|
||||
./cordova/log .............................. starts logcat
|
||||
|
||||
@@ -82,15 +78,10 @@ Start adb logcat (console.log calls output here):
|
||||
|
||||
./cordova/log
|
||||
|
||||
Running the [callback/callback-test](http://github.com/callback/callback-test) tests:
|
||||
---
|
||||
|
||||
./bin/test
|
||||
|
||||
Creating a new Cordova Android Project
|
||||
---
|
||||
|
||||
./bin/create ~/Desktop/myapp com.phonegap.special MyApp
|
||||
./bin/create ~/Desktop/myapp com.myapp.special MyApp
|
||||
|
||||
Importing a Cordova Android Project into Eclipse
|
||||
----
|
||||
@@ -110,5 +101,5 @@ Further Reading
|
||||
---
|
||||
|
||||
- [http://developer.android.com](http://developer.android.com)
|
||||
- [http://docs.phonegap.com](http://docs.phonegap.com)
|
||||
- [http://wiki.phonegap.com](http://wiki.phonegap.com)
|
||||
- [http://incubator.apache.org/cordova/](http://incubator.apache.org/cordova/)
|
||||
- [http://wiki.apache.org/cordova/](http://wiki.apache.org/cordova/)
|
||||
|
||||
21
bin/BOOM
@@ -1,21 +0,0 @@
|
||||
#! /bin/sh
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
#
|
||||
./bin/create
|
||||
cd ./example && ./cordova/debug && ./cordova/log
|
||||
157
bin/create
@@ -1,4 +1,4 @@
|
||||
#! /bin/sh
|
||||
#! /bin/bash
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
@@ -23,22 +23,22 @@
|
||||
#
|
||||
set -e
|
||||
|
||||
if [ -n "$1" ] && [ "$1" == "-h" ]
|
||||
if [ -z "$1" ] || [ "$1" == "-h" ]
|
||||
then
|
||||
echo 'usage: create path package activity'
|
||||
echo "Make sure the Android SDK tools folder is in your PATH!"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
BUILD_PATH=$( cd "$( dirname "$0" )/.." && pwd )
|
||||
VERSION=$(cat $BUILD_PATH/VERSION)
|
||||
BUILD_PATH="$( cd "$( dirname "$0" )/.." && pwd )"
|
||||
VERSION=$(cat "$BUILD_PATH"/VERSION)
|
||||
|
||||
PROJECT_PATH=${1:-"./example"}
|
||||
PROJECT_PATH="${1:-'./example'}"
|
||||
PACKAGE=${2:-"org.apache.cordova.example"}
|
||||
ACTIVITY=${3:-"cordovaExample"}
|
||||
|
||||
# clobber any existing example
|
||||
if [ -d $PROJECT_PATH ]
|
||||
if [ -d "$PROJECT_PATH" ]
|
||||
then
|
||||
echo "Project already exists! Delete and recreate"
|
||||
exit 1
|
||||
@@ -46,80 +46,121 @@ fi
|
||||
|
||||
# cleanup after exit and/or on error
|
||||
function on_exit {
|
||||
echo "Cleaning up ..."
|
||||
# [ -f $BUILD_PATH/framework/libs/commons-codec-1.6.jar ] && rm $BUILD_PATH/framework/libs/commons-codec-1.6.jar
|
||||
# [ -d $BUILD_PATH/framework/libs ] && rmdir $BUILD_PATH/framework/libs
|
||||
[ -f $BUILD_PATH/framework/assets/www/cordova-$VERSION.js ] && rm $BUILD_PATH/framework/assets/www/cordova-$VERSION.js
|
||||
[ -f $BUILD_PATH/framework/cordova-$VERSION.jar ] && rm $BUILD_PATH/framework/cordova-$VERSION.jar
|
||||
# [ -f "$BUILD_PATH"/framework/libs/commons-codec-1.6.jar ] && rm "$BUILD_PATH"/framework/libs/commons-codec-1.6.jar
|
||||
# [ -d "$BUILD_PATH"/framework/libs ] && rmdir "$BUILD_PATH"/framework/libs
|
||||
if [ -f "$BUILD_PATH"/framework/assets/www/cordova-$VERSION.js ]
|
||||
then
|
||||
rm "$BUILD_PATH"/framework/assets/www/cordova-$VERSION.js
|
||||
fi
|
||||
if [ -f "$BUILD_PATH"/framework/cordova-$VERSION.jar ]
|
||||
then
|
||||
rm "$BUILD_PATH"/framework/cordova-$VERSION.jar
|
||||
fi
|
||||
}
|
||||
|
||||
function createAppInfoJar {
|
||||
(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..."
|
||||
[ -d $PROJECT_PATH ] && rm -rf $PROJECT_PATH
|
||||
echo "An unexpected error occurred: $previous_command exited with $?"
|
||||
echo "Deleting project..."
|
||||
[ -d "$PROJECT_PATH" ] && rm -rf "$PROJECT_PATH"
|
||||
exit 1
|
||||
}
|
||||
|
||||
function replace {
|
||||
local pattern=$1
|
||||
local filename=$2
|
||||
# Mac OS X requires -i argument
|
||||
if [[ "$OSTYPE" =~ "darwin" ]]
|
||||
then
|
||||
/usr/bin/sed -i '' -e $pattern "$filename"
|
||||
elif [[ "$OSTYPE" =~ "linux" ]]
|
||||
then
|
||||
/bin/sed -i -e $pattern "$filename"
|
||||
fi
|
||||
}
|
||||
|
||||
# we do not want the script to silently fail
|
||||
trap 'previous_command=$this_command; this_command=$BASH_COMMAND' DEBUG
|
||||
trap on_error ERR
|
||||
trap on_exit EXIT
|
||||
|
||||
ANDROID_BIN=$( which android )
|
||||
ANDROID_BIN="${ANDROID_BIN:=$( which android )}"
|
||||
PACKAGE_AS_PATH=$(echo $PACKAGE | sed 's/\./\//g')
|
||||
ACTIVITY_PATH=$PROJECT_PATH/src/$PACKAGE_AS_PATH/$ACTIVITY.java
|
||||
MANIFEST_PATH=$PROJECT_PATH/AndroidManifest.xml
|
||||
ACTIVITY_PATH="$PROJECT_PATH"/src/$PACKAGE_AS_PATH/$ACTIVITY.java
|
||||
MANIFEST_PATH="$PROJECT_PATH"/AndroidManifest.xml
|
||||
|
||||
TARGET=$($ANDROID_BIN list targets | grep id: | tail -1 | cut -f 2 -d ' ' )
|
||||
TARGET=$("$ANDROID_BIN" list targets | grep id: | tail -1 | cut -f 2 -d ' ' )
|
||||
API_LEVEL=$("$ANDROID_BIN" list target | grep "API level:" | tail -n 1 | cut -f 2 -d ':' | tr -d ' ')
|
||||
|
||||
# update the cordova-android framework for the desired target
|
||||
$ANDROID_BIN update project --target $TARGET --path $BUILD_PATH/framework &> /dev/null
|
||||
|
||||
if [ ! -e $BUILD_PATH/framework/libs/commons-codec-1.6.jar ]; then
|
||||
# Use curl to get the jar (TODO: Support Apache Mirrors)
|
||||
echo "Downloading common-codecs-1.6 ..."
|
||||
curl -OL http://mirror.symnds.com/software/Apache//commons/codec/binaries/commons-codec-1.6-bin.zip &> /dev/null
|
||||
unzip commons-codec-1.6-bin.zip &> /dev/null
|
||||
mkdir -p $BUILD_PATH/framework/libs
|
||||
cp commons-codec-1.6/commons-codec-1.6.jar $BUILD_PATH/framework/libs
|
||||
# cleanup yo
|
||||
rm commons-codec-1.6-bin.zip && rm -rf commons-codec-1.6
|
||||
# check that build targets exist
|
||||
if [ -z "$TARGET" ] || [ -z "$API_LEVEL" ]
|
||||
then
|
||||
echo "No Android Targets are installed. Please install at least one via the android SDK"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# if this a distribution release no need to build a jar
|
||||
if [ ! -e "$BUILD_PATH"/cordova-$VERSION.jar ] && [ -d "$BUILD_PATH"/framework ]
|
||||
then
|
||||
# update the cordova-android framework for the desired target
|
||||
"$ANDROID_BIN" update project --target $TARGET --path "$BUILD_PATH"/framework &> /dev/null
|
||||
|
||||
if [ ! -e "$BUILD_PATH"/framework/libs/commons-codec-1.7.jar ]; then
|
||||
# Use curl to get the jar (TODO: Support Apache Mirrors)
|
||||
curl -OL http://archive.apache.org/dist/commons/codec/binaries/commons-codec-1.7-bin.zip &> /dev/null
|
||||
unzip commons-codec-1.7-bin.zip &> /dev/null
|
||||
mkdir -p "$BUILD_PATH"/framework/libs
|
||||
cp commons-codec-1.7/commons-codec-1.7.jar "$BUILD_PATH"/framework/libs
|
||||
# cleanup yo
|
||||
rm commons-codec-1.7-bin.zip && rm -rf commons-codec-1.7
|
||||
fi
|
||||
|
||||
# compile cordova.js and cordova.jar
|
||||
echo "Building cordova-$VERSION.jar and cordova-$VERSION.js ..."
|
||||
(cd $BUILD_PATH/framework && ant jar &> /dev/null )
|
||||
(cd "$BUILD_PATH"/framework && ant jar &> /dev/null )
|
||||
fi
|
||||
|
||||
# create new android project
|
||||
echo "Creating a new cordova android project ..."
|
||||
$ANDROID_BIN create project --target $TARGET --path $PROJECT_PATH --package $PACKAGE --activity $ACTIVITY &> /dev/null
|
||||
"$ANDROID_BIN" create project --target $TARGET --path "$PROJECT_PATH" --package $PACKAGE --activity $ACTIVITY &> /dev/null
|
||||
|
||||
# copy project template
|
||||
echo "Copying assets and resources ..."
|
||||
cp -r $BUILD_PATH/bin/templates/project/assets $PROJECT_PATH
|
||||
cp -r $BUILD_PATH/bin/templates/project/res $PROJECT_PATH
|
||||
cp -r "$BUILD_PATH"/bin/templates/project/assets "$PROJECT_PATH"
|
||||
cp -r "$BUILD_PATH"/bin/templates/project/res "$PROJECT_PATH"
|
||||
|
||||
# copy cordova.js, cordova.jar and res/xml
|
||||
echo "Setting up config and plugins list ..."
|
||||
cp -r $BUILD_PATH/framework/res/xml $PROJECT_PATH/res
|
||||
|
||||
echo "Adding cordova-$VERSION.js and cordova-$VERSION.jar ..."
|
||||
cp $BUILD_PATH/framework/assets/www/cordova-$VERSION.js $PROJECT_PATH/assets/www/cordova-$VERSION.js
|
||||
cp $BUILD_PATH/framework/cordova-$VERSION.jar $PROJECT_PATH/libs/cordova-$VERSION.jar
|
||||
if [ -d "$BUILD_PATH"/framework ]
|
||||
then
|
||||
cp -r "$BUILD_PATH"/framework/res/xml "$PROJECT_PATH"/res
|
||||
cp "$BUILD_PATH"/framework/assets/www/cordova-$VERSION.js "$PROJECT_PATH"/assets/www/cordova-$VERSION.js
|
||||
cp "$BUILD_PATH"/framework/cordova-$VERSION.jar "$PROJECT_PATH"/libs/cordova-$VERSION.jar
|
||||
else
|
||||
cp -r "$BUILD_PATH"/xml "$PROJECT_PATH"/res/xml
|
||||
cp "$BUILD_PATH"/cordova-$VERSION.js "$PROJECT_PATH"/assets/www/cordova-$VERSION.js
|
||||
cp "$BUILD_PATH"/cordova-$VERSION.jar "$PROJECT_PATH"/libs/cordova-$VERSION.jar
|
||||
fi
|
||||
|
||||
# interpolate the activity name and package
|
||||
echo "Updating Activity and AndroidManifest ..."
|
||||
cp $BUILD_PATH/bin/templates/project/Activity.java $ACTIVITY_PATH
|
||||
sed -i '' -e "s/__ACTIVITY__/${ACTIVITY}/g" $ACTIVITY_PATH
|
||||
sed -i '' -e "s/__ID__/${PACKAGE}/g" $ACTIVITY_PATH
|
||||
cp "$BUILD_PATH"/bin/templates/project/Activity.java "$ACTIVITY_PATH"
|
||||
replace "s/__ACTIVITY__/${ACTIVITY}/g" "$ACTIVITY_PATH"
|
||||
replace "s/__ID__/${PACKAGE}/g" "$ACTIVITY_PATH"
|
||||
|
||||
cp $BUILD_PATH/bin/templates/project/AndroidManifest.xml $MANIFEST_PATH
|
||||
sed -i '' -e "s/__ACTIVITY__/${ACTIVITY}/g" $MANIFEST_PATH
|
||||
sed -i '' -e "s/__PACKAGE__/${PACKAGE}/g" $MANIFEST_PATH
|
||||
cp "$BUILD_PATH"/bin/templates/project/AndroidManifest.xml "$MANIFEST_PATH"
|
||||
replace "s/__ACTIVITY__/${ACTIVITY}/g" "$MANIFEST_PATH"
|
||||
replace "s/__PACKAGE__/${PACKAGE}/g" "$MANIFEST_PATH"
|
||||
replace "s/__APILEVEL__/${API_LEVEL}/g" "$MANIFEST_PATH"
|
||||
|
||||
# creating cordova folder and copying emulate/debug/log/launch scripts
|
||||
mkdir $PROJECT_PATH/cordova
|
||||
cp $BUILD_PATH/bin/templates/cordova/appinfo.jar $PROJECT_PATH/cordova/appinfo.jar
|
||||
cp $BUILD_PATH/bin/templates/cordova/cordova $PROJECT_PATH/cordova/cordova
|
||||
cp $BUILD_PATH/bin/templates/cordova/debug $PROJECT_PATH/cordova/debug
|
||||
cp $BUILD_PATH/bin/templates/cordova/clean $PROJECT_PATH/cordova/clean
|
||||
cp $BUILD_PATH/bin/templates/cordova/log $PROJECT_PATH/cordova/log
|
||||
cp $BUILD_PATH/bin/templates/cordova/emulate $PROJECT_PATH/cordova/emulate
|
||||
cp $BUILD_PATH/bin/templates/cordova/BOOM $PROJECT_PATH/cordova/BOOM
|
||||
# creating cordova folder and copying run/build/log/launch scripts
|
||||
mkdir "$PROJECT_PATH"/cordova
|
||||
createAppInfoJar
|
||||
cp "$BUILD_PATH"/bin/templates/cordova/appinfo.jar "$PROJECT_PATH"/cordova/appinfo.jar
|
||||
cp "$BUILD_PATH"/bin/templates/cordova/cordova "$PROJECT_PATH"/cordova/cordova
|
||||
cp "$BUILD_PATH"/bin/templates/cordova/build "$PROJECT_PATH"/cordova/build
|
||||
cp "$BUILD_PATH"/bin/templates/cordova/release "$PROJECT_PATH"/cordova/release
|
||||
cp "$BUILD_PATH"/bin/templates/cordova/clean "$PROJECT_PATH"/cordova/clean
|
||||
cp "$BUILD_PATH"/bin/templates/cordova/log "$PROJECT_PATH"/cordova/log
|
||||
cp "$BUILD_PATH"/bin/templates/cordova/run "$PROJECT_PATH"/cordova/run
|
||||
|
||||
@@ -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:
|
||||
|
||||
115
bin/create.js
@@ -33,10 +33,24 @@ function read(filename) {
|
||||
f.Close();
|
||||
return s;
|
||||
}
|
||||
|
||||
function checkTargets(targets) {
|
||||
if(!targets) {
|
||||
WScript.Echo("You do not have any android targets setup. Please create at least one target with the `android` command");
|
||||
WScript.Quit(69);
|
||||
}
|
||||
}
|
||||
|
||||
function setTarget() {
|
||||
var targets = shell.Exec('android.bat list targets').StdOut.ReadAll().match(/id:\s\d+/g);
|
||||
checkTargets(targets);
|
||||
return targets[targets.length - 1].replace(/id: /, ""); // TODO: give users the option to set their target
|
||||
}
|
||||
function setApiLevel() {
|
||||
var targets = shell.Exec('android.bat list targets').StdOut.ReadAll().match(/API level:\s\d+/g);
|
||||
checkTargets(targets);
|
||||
return targets[targets.length - 1].replace(/API level: /, "");
|
||||
}
|
||||
function write(filename, contents) {
|
||||
var fso=WScript.CreateObject("Scripting.FileSystemObject");
|
||||
var f=fso.OpenTextFile(filename, 2, true);
|
||||
@@ -49,10 +63,26 @@ function replaceInFile(filename, regexp, replacement) {
|
||||
function exec(command) {
|
||||
var oShell=shell.Exec(command);
|
||||
while (oShell.Status == 0) {
|
||||
if(!oShell.StdOut.AtEndOfStream) {
|
||||
var line = oShell.StdOut.ReadLine();
|
||||
// XXX: Change to verbose mode
|
||||
// WScript.StdOut.WriteLine(line);
|
||||
}
|
||||
WScript.sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
function createAppInfoJar() {
|
||||
if(!fso.FileExists(ROOT+"\\bin\\templates\\cordova\\appinfo.jar")) {
|
||||
WScript.Echo("Creating appinfo.jar...");
|
||||
var cur = shell.CurrentDirectory;
|
||||
shell.CurrentDirectory = ROOT+"\\bin\\templates\\cordova\\ApplicationInfo";
|
||||
exec("javac ApplicationInfo.java");
|
||||
exec("jar -cfe ..\\appinfo.jar ApplicationInfo ApplicationInfo.class");
|
||||
shell.CurrentDirectory = cur;
|
||||
}
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
// Cleanup
|
||||
// if(fso.FileExists(ROOT + '\\framework\\libs\\commons-codec-1.6.jar')) {
|
||||
@@ -68,11 +98,11 @@ function cleanup() {
|
||||
}
|
||||
|
||||
function downloadCommonsCodec() {
|
||||
if (!fso.FileExists(ROOT + '\\framework\\libs\\commons-codec-1.6.jar')) {
|
||||
if (!fso.FileExists(ROOT + '\\framework\\libs\\commons-codec-1.7.jar')) {
|
||||
// We need the .jar
|
||||
var url = 'http://mirror.symnds.com/software/Apache//commons/codec/binaries/commons-codec-1.6-bin.zip';
|
||||
var url = 'http://archive.apache.org/dist/commons/codec/binaries/commons-codec-1.7-bin.zip';
|
||||
var libsPath = ROOT + '\\framework\\libs';
|
||||
var savePath = libsPath + '\\commons-codec-1.6-bin.zip';
|
||||
var savePath = libsPath + '\\commons-codec-1.7-bin.zip';
|
||||
if (!fso.FileExists(savePath)) {
|
||||
if(!fso.FolderExists(ROOT + '\\framework\\libs')) {
|
||||
fso.CreateFolder(libsPath);
|
||||
@@ -99,11 +129,11 @@ function downloadCommonsCodec() {
|
||||
target.CopyHere(source, 256);
|
||||
|
||||
// Move the jar into libs
|
||||
fso.MoveFile(ROOT + '\\framework\\libs\\commons-codec-1.6\\commons-codec-1.6.jar', ROOT + '\\framework\\libs\\commons-codec-1.6.jar');
|
||||
fso.MoveFile(ROOT + '\\framework\\libs\\commons-codec-1.7\\commons-codec-1.7.jar', ROOT + '\\framework\\libs\\commons-codec-1.7.jar');
|
||||
|
||||
// Clean up
|
||||
fso.DeleteFile(ROOT + '\\framework\\libs\\commons-codec-1.6-bin.zip');
|
||||
fso.DeleteFolder(ROOT + '\\framework\\libs\\commons-codec-1.6', true);
|
||||
fso.DeleteFile(ROOT + '\\framework\\libs\\commons-codec-1.7-bin.zip');
|
||||
fso.DeleteFolder(ROOT + '\\framework\\libs\\commons-codec-1.7', true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,51 +159,66 @@ var PACKAGE_AS_PATH=PACKAGE.replace(/\./g, '\\');
|
||||
var ACTIVITY_PATH=PROJECT_PATH+'\\src\\'+PACKAGE_AS_PATH+'\\'+ACTIVITY+'.java';
|
||||
var MANIFEST_PATH=PROJECT_PATH+'\\AndroidManifest.xml';
|
||||
var TARGET=setTarget();
|
||||
var API_LEVEL=setApiLevel();
|
||||
var VERSION=read(ROOT+'\\VERSION').replace(/\r\n/,'').replace(/\n/,'');
|
||||
// create the project
|
||||
WScript.Echo("Creating new android project...");
|
||||
exec('android.bat create project --target '+TARGET+' --path '+PROJECT_PATH+' --package '+PACKAGE+' --activity '+ACTIVITY);
|
||||
|
||||
// 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');
|
||||
// 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');
|
||||
}
|
||||
|
||||
// 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');
|
||||
|
||||
// copy in cordova.js
|
||||
exec('%comspec% /c copy '+ROOT+'\\framework\\assets\\www\\cordova-'+VERSION+'.js '+PROJECT_PATH+'\\assets\\www\\cordova-'+VERSION+'.js /Y');
|
||||
|
||||
// copy in cordova.jar
|
||||
exec('%comspec% /c copy '+ROOT+'\\framework\\cordova-'+VERSION+'.jar '+PROJECT_PATH+'\\libs\\cordova-'+VERSION+'.jar /Y');
|
||||
|
||||
// copy in xml
|
||||
fso.CreateFolder(PROJECT_PATH + '\\res\\xml');
|
||||
exec('%comspec% /c copy '+ROOT+'\\framework\\res\\xml\\cordova.xml ' + PROJECT_PATH + '\\res\\xml\\cordova.xml /Y');
|
||||
exec('%comspec% /c copy '+ROOT+'\\framework\\res\\xml\\plugins.xml ' + PROJECT_PATH + '\\res\\xml\\plugins.xml /Y');
|
||||
// check if we have the source or the distro files
|
||||
WScript.Echo("Copying js, jar & config.xml files...");
|
||||
if(fso.FolderExists(ROOT + '\\framework')) {
|
||||
exec('%comspec% /c copy "'+ROOT+'"\\framework\\assets\\www\\cordova-'+VERSION+'.js '+PROJECT_PATH+'\\assets\\www\\cordova-'+VERSION+'.js /Y');
|
||||
exec('%comspec% /c copy "'+ROOT+'"\\framework\\cordova-'+VERSION+'.jar '+PROJECT_PATH+'\\libs\\cordova-'+VERSION+'.jar /Y');
|
||||
fso.CreateFolder(PROJECT_PATH + '\\res\\xml');
|
||||
exec('%comspec% /c copy "'+ROOT+'"\\framework\\res\\xml\\config.xml ' + PROJECT_PATH + '\\res\\xml\\config.xml /Y');
|
||||
} else {
|
||||
// copy in cordova.js
|
||||
exec('%comspec% /c copy "'+ROOT+'"\\cordova-'+VERSION+'.js '+PROJECT_PATH+'\\assets\\www\\cordova-'+VERSION+'.js /Y');
|
||||
// copy in cordova.jar
|
||||
exec('%comspec% /c copy "'+ROOT+'"\\cordova-'+VERSION+'.jar '+PROJECT_PATH+'\\libs\\cordova-'+VERSION+'.jar /Y');
|
||||
// copy in xml
|
||||
fso.CreateFolder(PROJECT_PATH + '\\res\\xml');
|
||||
exec('%comspec% /c copy "'+ROOT+'"\\xml\\config.xml ' + PROJECT_PATH + '\\res\\xml\\config.xml /Y');
|
||||
}
|
||||
|
||||
// copy cordova scripts
|
||||
fso.CreateFolder(PROJECT_PATH + '\\cordova');
|
||||
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\appinfo.jar ' + PROJECT_PATH + '\\cordova\\appinfo.jar /Y');
|
||||
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\cordova.js ' + PROJECT_PATH + '\\cordova\\cordova.js /Y');
|
||||
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\cordova.bat ' + PROJECT_PATH + '\\cordova\\cordova.bat /Y');
|
||||
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\clean.bat ' + PROJECT_PATH + '\\cordova\\clean.bat /Y');
|
||||
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\debug.bat ' + PROJECT_PATH + '\\cordova\\debug.bat /Y');
|
||||
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\log.bat ' + PROJECT_PATH + '\\cordova\\log.bat /Y');
|
||||
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\emulate.bat ' + PROJECT_PATH + '\\cordova\\emulate.bat /Y');
|
||||
exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\BOOM.bat ' + PROJECT_PATH + '\\cordova\\BOOM.bat /Y');
|
||||
createAppInfoJar();
|
||||
WScript.Echo("Copying cordova command tools...");
|
||||
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\appinfo.jar ' + PROJECT_PATH + '\\cordova\\appinfo.jar /Y');
|
||||
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\cordova.js ' + PROJECT_PATH + '\\cordova\\cordova.js /Y');
|
||||
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\cordova.bat ' + PROJECT_PATH + '\\cordova\\cordova.bat /Y');
|
||||
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\clean.bat ' + PROJECT_PATH + '\\cordova\\clean.bat /Y');
|
||||
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\build.bat ' + PROJECT_PATH + '\\cordova\\build.bat /Y');
|
||||
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\log.bat ' + PROJECT_PATH + '\\cordova\\log.bat /Y');
|
||||
exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\run.bat ' + PROJECT_PATH + '\\cordova\\run.bat /Y');
|
||||
|
||||
// interpolate the activity name and package
|
||||
WScript.Echo("Updating AndroidManifest.xml and Main Activity...");
|
||||
replaceInFile(ACTIVITY_PATH, /__ACTIVITY__/, ACTIVITY);
|
||||
replaceInFile(ACTIVITY_PATH, /__ID__/, PACKAGE);
|
||||
|
||||
replaceInFile(MANIFEST_PATH, /__ACTIVITY__/, ACTIVITY);
|
||||
replaceInFile(MANIFEST_PATH, /__PACKAGE__/, PACKAGE);
|
||||
replaceInFile(MANIFEST_PATH, /__APILEVEL__/, API_LEVEL);
|
||||
|
||||
cleanup();
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
CORDOVA_PATH=$( cd "$( dirname "$0" )" && pwd )
|
||||
|
||||
bash $CORDOVA_PATH/cordova BOOM
|
||||
@@ -1 +0,0 @@
|
||||
%~dp0\cordova.bat BOOM
|
||||
24
bin/templates/cordova/build
Executable file
@@ -0,0 +1,24 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
CORDOVA_PATH=$( cd "$( dirname "$0" )" && pwd )
|
||||
|
||||
bash "$CORDOVA_PATH"/cordova build
|
||||
18
bin/templates/cordova/build.bat
Normal 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
|
||||
19
bin/templates/cordova/clean
Normal file → Executable 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
|
||||
|
||||
@@ -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
|
||||
|
||||
110
bin/templates/cordova/cordova
Normal file → Executable file
@@ -1,11 +1,28 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
PROJECT_PATH=$( cd "$( dirname "$0" )/.." && pwd )
|
||||
|
||||
function check_devices {
|
||||
local devices=`adb devices | awk '/List of devices attached/ { while(getline > 0) { print }}'`
|
||||
# FIXME
|
||||
local devices=`adb devices | awk '/List of devices attached/ { while(getline > 0) { print }}' | grep device`
|
||||
if [ -z "$devices" ] ; then
|
||||
echo "1"
|
||||
else
|
||||
@@ -20,7 +37,6 @@ function emulate {
|
||||
|
||||
# Do not launch an emulator if there is already one running or if a device is attached
|
||||
if [ $(check_devices) == 0 ] ; then
|
||||
echo "Device attached or emulator already running"
|
||||
return
|
||||
fi
|
||||
|
||||
@@ -45,8 +61,9 @@ function emulate {
|
||||
do
|
||||
echo "$i) ${avd_list[$i]}"
|
||||
done
|
||||
echo -n "> "
|
||||
read avd_id
|
||||
read -t 5 -p "> " avd_id
|
||||
# default value if input timeout
|
||||
if [ $avd_id -eq 1000 ] ; then avd_id=0 ; fi
|
||||
done
|
||||
emulator -cpu-delay 0 -no-boot-anim -cache /tmp/cache -avd ${avd_list[$avd_id]} 1> /dev/null 2>&1 &
|
||||
fi
|
||||
@@ -56,36 +73,87 @@ function emulate {
|
||||
function clean {
|
||||
ant clean
|
||||
}
|
||||
# has to be used independently and not in conjuction with other commands
|
||||
# has to be used independently and not in conjunction with other commands
|
||||
function log {
|
||||
adb logcat
|
||||
}
|
||||
|
||||
function debug_install {
|
||||
function run {
|
||||
clean && emulate && wait_for_device && install && launch
|
||||
}
|
||||
|
||||
function install {
|
||||
|
||||
declare -a devices=($(adb devices | awk '/List of devices attached/ { while(getline > 0) { print }}' | grep device | cut -f 1))
|
||||
local device_id="1000" #FIXME: hopefully user does not have 1000 AVDs
|
||||
|
||||
if [ ${#devices[@]} == 0 ]
|
||||
then
|
||||
# should not reach here. Emulator should launch or device should be attached
|
||||
echo "Emulator not running or device not attached. Could not install debug package"
|
||||
exit 70
|
||||
fi
|
||||
|
||||
if [ ${#devices[@]} == 1 ]
|
||||
then
|
||||
export ANDROID_SERIAL=${devices[0]}
|
||||
# User has more than 1 AVD
|
||||
elif [ ${#devices[@]} -gt 1 ]
|
||||
then
|
||||
while [ -z ${devices[$device_id]} ]
|
||||
do
|
||||
echo "Choose from one of the following devices/emulators [0 to $((${#devices[@]}-1))]:"
|
||||
for(( i = 0 ; i < ${#devices[@]} ; i++ ))
|
||||
do
|
||||
echo "$i) ${devices[$i]}"
|
||||
done
|
||||
read -t 5 -p "> " device_id
|
||||
# default value if input timeout
|
||||
if [ $device_id -eq 1000 ] ; then device_id=0 ; fi
|
||||
done
|
||||
export ANDROID_SERIAL=${devices[$device_id]}
|
||||
fi
|
||||
|
||||
ant debug install
|
||||
}
|
||||
|
||||
function debug {
|
||||
function build {
|
||||
ant debug
|
||||
}
|
||||
|
||||
function launch {
|
||||
local launch_str=$(java -jar $PROJECT_PATH/cordova/appinfo.jar $PROJECT_PATH/AndroidManifest.xml)
|
||||
adb shell am start -n $launch_str
|
||||
function release {
|
||||
ant release
|
||||
}
|
||||
|
||||
function BOOM {
|
||||
clean
|
||||
if [ $(check_devices) == 0 ] ; then
|
||||
debug_install && launch
|
||||
return
|
||||
function wait_for_device {
|
||||
local i="0"
|
||||
echo -n "Waiting for device..."
|
||||
|
||||
while [ $i -lt 300 ]
|
||||
do
|
||||
if [ $(check_devices) -eq 0 ]
|
||||
then
|
||||
break
|
||||
else
|
||||
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
|
||||
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)
|
||||
adb shell am start -n $launch_str
|
||||
}
|
||||
|
||||
# TODO parse arguments
|
||||
(cd $PROJECT_PATH && $1)
|
||||
(cd "$PROJECT_PATH" && $1)
|
||||
|
||||
@@ -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 (
|
||||
|
||||
63
bin/templates/cordova/cordova.js
vendored
@@ -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");
|
||||
|
||||
@@ -8,7 +25,7 @@ function exec(command) {
|
||||
if(!oExec.StdOut.AtEndOfStream) {
|
||||
var line = oExec.StdOut.ReadLine();
|
||||
// XXX: Change to verbose mode
|
||||
//WScript.StdOut.WriteLine(line);
|
||||
// WScript.StdOut.WriteLine(line);
|
||||
output += line;
|
||||
}
|
||||
WScript.sleep(100);
|
||||
@@ -17,17 +34,19 @@ function exec(command) {
|
||||
return output;
|
||||
}
|
||||
|
||||
function emulator_running() {
|
||||
function device_running() {
|
||||
var local_devices = shell.Exec("%comspec% /c adb devices").StdOut.ReadAll();
|
||||
if(local_devices.match(/emulator/)) {
|
||||
if(local_devices.match(/\w+\tdevice/)) {
|
||||
WScript.Echo("Yes");
|
||||
return true;
|
||||
}
|
||||
WScript.Echo("No");
|
||||
return false;
|
||||
}
|
||||
function emulate() {
|
||||
// don't run emulator if a device is plugged in or if emulator is already running
|
||||
if(emulator_running()) {
|
||||
WScript.Echo("Device or Emulator already running!");
|
||||
if(device_running()) {
|
||||
//WScript.Echo("Device or Emulator already running!");
|
||||
return;
|
||||
}
|
||||
var oExec = shell.Exec("%comspec% /c android.bat list avd");
|
||||
@@ -67,38 +86,48 @@ function emulate() {
|
||||
}
|
||||
|
||||
function clean() {
|
||||
WScript.Echo("Cleaning project...");
|
||||
exec("%comspec% /c ant.bat clean -f "+ROOT+"\\build.xml 2>&1");
|
||||
}
|
||||
|
||||
function debug() {
|
||||
function build() {
|
||||
WScript.Echo("Building project...");
|
||||
exec("%comspec% /c ant.bat debug -f "+ROOT+"\\build.xml 2>&1");
|
||||
}
|
||||
|
||||
function debug_install() {
|
||||
function install() {
|
||||
WScript.Echo("Building/Installing project...");
|
||||
exec("%comspec% /c ant.bat debug install -f "+ROOT+"\\build.xml 2>&1");
|
||||
}
|
||||
|
||||
function log() {
|
||||
WScript.Echo(exec("%comspec% /c adb.bat logcat"));
|
||||
shell.Run("%comspec% /c adb logcat");
|
||||
}
|
||||
|
||||
function launch() {
|
||||
WScript.Echo("Launching app...");
|
||||
var launch_str=exec("%comspec% /c java -jar "+ROOT+"\\cordova\\appinfo.jar "+ROOT+"\\AndroidManifest.xml");
|
||||
//WScript.Echo(launch_str);
|
||||
exec("%comspec% /c adb shell am start -n "+launch_str+" 2>&1");
|
||||
}
|
||||
|
||||
function BOOM() {
|
||||
function run() {
|
||||
var i=0;
|
||||
clean();
|
||||
if(emulator_running()) {
|
||||
debug_install();
|
||||
launch();
|
||||
} else {
|
||||
debug();
|
||||
WScript.Echo("##################################################################");
|
||||
WScript.Echo("# Plug in your device or launch an emulator with cordova/emulate #");
|
||||
WScript.Echo("##################################################################");
|
||||
emulate();
|
||||
WScript.Stdout.Write('Waiting for device...');
|
||||
while(!device_running() && i < 300) {
|
||||
WScript.Stdout.Write('.');
|
||||
WScript.sleep(1000);
|
||||
i += 1;
|
||||
}
|
||||
if(i == 300) {
|
||||
WScript.Stderr.WriteLine("device/emulator timeout!");
|
||||
} else {
|
||||
WScript.Stdout.WriteLine("connected!");
|
||||
}
|
||||
install();
|
||||
launch();
|
||||
}
|
||||
var args = WScript.Arguments;
|
||||
if(args.count() != 1) {
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
CORDOVA_PATH=$( cd "$( dirname "$0" )" && pwd )
|
||||
|
||||
bash $CORDOVA_PATH/cordova debug
|
||||
@@ -1 +0,0 @@
|
||||
%~dp0\cordova.bat debug
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
CORDOVA_PATH=$( cd "$( dirname "$0" )" && pwd )
|
||||
|
||||
bash $CORDOVA_PATH/cordova emulate
|
||||
@@ -1 +0,0 @@
|
||||
%~dp0\cordova.bat emulate
|
||||
21
bin/templates/cordova/log
Normal file → Executable 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
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
:: Licensed to the Apache Software Foundation (ASF) under one
|
||||
:: or more contributor license agreements. See the NOTICE file
|
||||
:: distributed with this work for additional information
|
||||
:: regarding copyright ownership. The ASF licenses this file
|
||||
:: to you under the Apache License, Version 2.0 (the
|
||||
:: "License"); you may not use this file except in compliance
|
||||
:: with the License. You may obtain a copy of the License at
|
||||
::
|
||||
:: http://www.apache.org/licenses/LICENSE-2.0
|
||||
::
|
||||
:: Unless required by applicable law or agreed to in writing,
|
||||
:: software distributed under the License is distributed on an
|
||||
:: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
:: KIND, either express or implied. See the License for the
|
||||
:: specific language governing permissions and limitations
|
||||
:: under the License.
|
||||
|
||||
%~dp0\cordova.bat log
|
||||
|
||||
24
bin/templates/cordova/release
Executable 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
@@ -0,0 +1,24 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
CORDOVA_PATH=$( cd "$( dirname "$0" )" && pwd )
|
||||
|
||||
bash "$CORDOVA_PATH"/cordova run
|
||||
1
bin/templates/cordova/run.bat
Normal file
@@ -0,0 +1 @@
|
||||
%~dp0\cordova.bat run
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
package __ID__;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import org.apache.cordova.*;
|
||||
|
||||
@@ -29,7 +28,9 @@ public class __ACTIVITY__ extends DroidGap
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
super.loadUrl("file:///android_asset/www/index.html");
|
||||
// Set by <content src="index.html" /> in config.xml
|
||||
super.loadUrl(Config.getStartUrl());
|
||||
//super.loadUrl("file:///android_asset/www/index.html")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
under the License.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:windowSoftInputMode="adjustPan"
|
||||
package="__PACKAGE__" android:versionName="1.1" android:versionCode="5">
|
||||
package="__PACKAGE__" android:versionName="1.0" android:versionCode="1" android:hardwareAccelerated="true">
|
||||
<supports-screens
|
||||
android:largeScreens="true"
|
||||
android:normalScreens="true"
|
||||
@@ -47,9 +47,11 @@
|
||||
|
||||
|
||||
<application android:icon="@drawable/icon" android:label="@string/app_name"
|
||||
android:hardwareAccelerated="true"
|
||||
android:debuggable="true">
|
||||
<activity android:name="__ACTIVITY__" android:label="@string/app_name"
|
||||
android:configChanges="orientation|keyboardHidden">
|
||||
<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" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
@@ -57,5 +59,5 @@
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
<uses-sdk android:minSdkVersion="5" />
|
||||
<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="__APILEVEL__"/>
|
||||
</manifest>
|
||||
|
||||
115
bin/templates/project/assets/www/css/index.css
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
* {
|
||||
-webkit-tap-highlight-color: rgba(0,0,0,0); /* make transparent link selection, adjust last value opacity 0 to 1.0 */
|
||||
}
|
||||
|
||||
body {
|
||||
-webkit-touch-callout: none; /* prevent callout to copy image, etc when tap to hold */
|
||||
-webkit-text-size-adjust: none; /* prevent webkit from resizing text to fit */
|
||||
-webkit-user-select: none; /* prevent copy paste, to allow, change 'none' to 'text' */
|
||||
background-color:#E4E4E4;
|
||||
background-image:linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%);
|
||||
background-image:-webkit-linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%);
|
||||
background-image:-ms-linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%);
|
||||
background-image:-webkit-gradient(
|
||||
linear,
|
||||
left top,
|
||||
left bottom,
|
||||
color-stop(0, #A7A7A7),
|
||||
color-stop(0.51, #E4E4E4)
|
||||
);
|
||||
background-attachment:fixed;
|
||||
font-family:'HelveticaNeue-Light', 'HelveticaNeue', Helvetica, Arial, sans-serif;
|
||||
font-size:12px;
|
||||
height:100%;
|
||||
margin:0px;
|
||||
padding:0px;
|
||||
text-transform:uppercase;
|
||||
width:100%;
|
||||
}
|
||||
|
||||
/* Portrait layout (default) */
|
||||
.app {
|
||||
background:url(../img/logo.png) no-repeat center top; /* 170px x 200px */
|
||||
position:absolute; /* position in the center of the screen */
|
||||
left:50%;
|
||||
top:50%;
|
||||
height:50px; /* text area height */
|
||||
width:225px; /* text area width */
|
||||
text-align:center;
|
||||
padding:180px 0px 0px 0px; /* image height is 200px (bottom 20px are overlapped with text) */
|
||||
margin:-115px 0px 0px -112px; /* offset vertical: half of image height and text area height */
|
||||
/* offset horizontal: half of text area width */
|
||||
}
|
||||
|
||||
/* Landscape layout (with min-width) */
|
||||
@media screen and (min-aspect-ratio: 1/1) and (min-width:400px) {
|
||||
.app {
|
||||
background-position:left center;
|
||||
padding:75px 0px 75px 170px; /* padding-top + padding-bottom + text area = image height */
|
||||
margin:-90px 0px 0px -198px; /* offset vertical: half of image height */
|
||||
/* offset horizontal: half of image width and text area width */
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size:24px;
|
||||
font-weight:normal;
|
||||
margin:0px;
|
||||
overflow:visible;
|
||||
padding:0px;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
.event {
|
||||
border-radius:4px;
|
||||
-webkit-border-radius:4px;
|
||||
color:#FFFFFF;
|
||||
font-size:12px;
|
||||
margin:0px 30px;
|
||||
padding:2px 0px;
|
||||
}
|
||||
|
||||
.event.listening {
|
||||
background-color:#333333;
|
||||
display:block;
|
||||
}
|
||||
|
||||
.event.received {
|
||||
background-color:#4B946A;
|
||||
display:none;
|
||||
}
|
||||
|
||||
@keyframes fade {
|
||||
from { opacity: 1.0; }
|
||||
50% { opacity: 0.4; }
|
||||
to { opacity: 1.0; }
|
||||
}
|
||||
|
||||
@-webkit-keyframes fade {
|
||||
from { opacity: 1.0; }
|
||||
50% { opacity: 0.4; }
|
||||
to { opacity: 1.0; }
|
||||
}
|
||||
|
||||
.blink {
|
||||
animation:fade 3000ms infinite;
|
||||
-webkit-animation:fade 3000ms infinite;
|
||||
}
|
||||
BIN
bin/templates/project/assets/www/img/cordova.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
bin/templates/project/assets/www/img/logo.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
@@ -1,60 +1,42 @@
|
||||
<!DOCTYPE HTML>
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=320; user-scalable=no" />
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||
<title>PhoneGap</title>
|
||||
<link rel="stylesheet" href="master.css" type="text/css" media="screen" title="no title">
|
||||
<script type="text/javascript" charset="utf-8" src="cordova-1.9.0rc1.js"></script>
|
||||
<script type="text/javascript" charset="utf-8" src="main.js"></script>
|
||||
|
||||
</head>
|
||||
<body onload="init();" id="stage" class="theme">
|
||||
<h1>Welcome to Cordova!</h1>
|
||||
<h2>this file is located at assets/www/index.html</h2>
|
||||
<div id="info">
|
||||
<h4>Platform: <span id="platform"> </span>, Version: <span id="version"> </span></h4>
|
||||
<h4>UUID: <span id="uuid"> </span>, Name: <span id="name"> </span></h4>
|
||||
<h4>Width: <span id="width"> </span>, Height: <span id="height">
|
||||
</span>, Color Depth: <span id="colorDepth"></span></h4>
|
||||
</div>
|
||||
<dl id="accel-data">
|
||||
<dt>X:</dt><dd id="x"> </dd>
|
||||
<dt>Y:</dt><dd id="y"> </dd>
|
||||
<dt>Z:</dt><dd id="z"> </dd>
|
||||
</dl>
|
||||
<a href="#" class="btn large" onclick="toggleAccel();">Toggle Accelerometer</a>
|
||||
<a href="#" class="btn large" onclick="getLocation();">Get Location</a>
|
||||
<a href="tel:411" class="btn large">Call 411</a>
|
||||
<a href="#" class="btn large" onclick="beep();">Beep</a>
|
||||
<a href="#" class="btn large" onclick="vibrate();">Vibrate</a>
|
||||
<a href="#" class="btn large" onclick="show_pic();">Get a Picture</a>
|
||||
<a href="#" class="btn large" onclick="get_contacts();return false;">Get Phone's Contacts</a>
|
||||
<a href="#" class="btn large" onclick="check_network();return false;">Check Network</a>
|
||||
<dl>
|
||||
<dt>Compass Heading:</dt><dd id="h">Off</dd>
|
||||
</dl>
|
||||
<a href="#" class="btn large" onclick="toggleCompass();return false;">Toggle Compass</a>
|
||||
<div id="viewport" class="viewport" style="display: none;">
|
||||
<img style="width:60px;height:60px" id="test_img" src="" />
|
||||
</div>
|
||||
</body>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
|
||||
<link rel="stylesheet" type="text/css" href="css/index.css" />
|
||||
<title>Hello World</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
<h1>Apache Cordova</h1>
|
||||
<div id="deviceready" class="blink">
|
||||
<p class="event listening">Connecting to Device</p>
|
||||
<p class="event received">Device is Ready</p>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="cordova-2.5.0rc1.js"></script>
|
||||
<script type="text/javascript" src="js/index.js"></script>
|
||||
<script type="text/javascript">
|
||||
app.initialize();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
49
bin/templates/project/assets/www/js/index.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
var app = {
|
||||
// Application Constructor
|
||||
initialize: function() {
|
||||
this.bindEvents();
|
||||
},
|
||||
// Bind Event Listeners
|
||||
//
|
||||
// Bind any events that are required on startup. Common events are:
|
||||
// 'load', 'deviceready', 'offline', and 'online'.
|
||||
bindEvents: function() {
|
||||
document.addEventListener('deviceready', this.onDeviceReady, false);
|
||||
},
|
||||
// deviceready Event Handler
|
||||
//
|
||||
// The scope of 'this' is the event. In order to call the 'receivedEvent'
|
||||
// function, we must explicity call 'app.receivedEvent(...);'
|
||||
onDeviceReady: function() {
|
||||
app.receivedEvent('deviceready');
|
||||
},
|
||||
// Update DOM on a Received Event
|
||||
receivedEvent: function(id) {
|
||||
var parentElement = document.getElementById(id);
|
||||
var listeningElement = parentElement.querySelector('.listening');
|
||||
var receivedElement = parentElement.querySelector('.received');
|
||||
|
||||
listeningElement.setAttribute('style', 'display:none;');
|
||||
receivedElement.setAttribute('style', 'display:block;');
|
||||
|
||||
console.log('Received Event: ' + id);
|
||||
}
|
||||
};
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 213 KiB |
|
After Width: | Height: | Size: 217 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 478 KiB |
|
After Width: | Height: | Size: 493 KiB |
68
bin/templates/project/assets/www/spec.html
Normal file
@@ -0,0 +1,68 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<title>Jasmine Spec Runner</title>
|
||||
|
||||
<!-- jasmine source -->
|
||||
<link rel="shortcut icon" type="image/png" href="spec/lib/jasmine-1.2.0/jasmine_favicon.png">
|
||||
<link rel="stylesheet" type="text/css" href="spec/lib/jasmine-1.2.0/jasmine.css">
|
||||
<script type="text/javascript" src="spec/lib/jasmine-1.2.0/jasmine.js"></script>
|
||||
<script type="text/javascript" src="spec/lib/jasmine-1.2.0/jasmine-html.js"></script>
|
||||
|
||||
<!-- include source files here... -->
|
||||
<script type="text/javascript" src="js/index.js"></script>
|
||||
|
||||
<!-- include spec files here... -->
|
||||
<script type="text/javascript" src="spec/helper.js"></script>
|
||||
<script type="text/javascript" src="spec/index.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
var jasmineEnv = jasmine.getEnv();
|
||||
jasmineEnv.updateInterval = 1000;
|
||||
|
||||
var htmlReporter = new jasmine.HtmlReporter();
|
||||
|
||||
jasmineEnv.addReporter(htmlReporter);
|
||||
|
||||
jasmineEnv.specFilter = function(spec) {
|
||||
return htmlReporter.specFilter(spec);
|
||||
};
|
||||
|
||||
var currentWindowOnload = window.onload;
|
||||
|
||||
window.onload = function() {
|
||||
if (currentWindowOnload) {
|
||||
currentWindowOnload();
|
||||
}
|
||||
execJasmine();
|
||||
};
|
||||
|
||||
function execJasmine() {
|
||||
jasmineEnv.execute();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="stage" style="display:none;"></div>
|
||||
</body>
|
||||
</html>
|
||||
33
bin/templates/project/assets/www/spec/helper.js
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
afterEach(function() {
|
||||
document.getElementById('stage').innerHTML = '';
|
||||
});
|
||||
|
||||
var helper = {
|
||||
trigger: function(obj, name) {
|
||||
var e = document.createEvent('Event');
|
||||
e.initEvent(name, true, true);
|
||||
obj.dispatchEvent(e);
|
||||
},
|
||||
getComputedStyle: function(querySelector, property) {
|
||||
var element = document.querySelector(querySelector);
|
||||
return window.getComputedStyle(element).getPropertyValue(property);
|
||||
}
|
||||
};
|
||||
67
bin/templates/project/assets/www/spec/index.js
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
describe('app', function() {
|
||||
describe('initialize', function() {
|
||||
it('should bind deviceready', function() {
|
||||
runs(function() {
|
||||
spyOn(app, 'onDeviceReady');
|
||||
app.initialize();
|
||||
helper.trigger(window.document, 'deviceready');
|
||||
});
|
||||
|
||||
waitsFor(function() {
|
||||
return (app.onDeviceReady.calls.length > 0);
|
||||
}, 'onDeviceReady should be called once', 500);
|
||||
|
||||
runs(function() {
|
||||
expect(app.onDeviceReady).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('onDeviceReady', function() {
|
||||
it('should report that it fired', function() {
|
||||
spyOn(app, 'receivedEvent');
|
||||
app.onDeviceReady();
|
||||
expect(app.receivedEvent).toHaveBeenCalledWith('deviceready');
|
||||
});
|
||||
});
|
||||
|
||||
describe('receivedEvent', function() {
|
||||
beforeEach(function() {
|
||||
var el = document.getElementById('stage');
|
||||
el.innerHTML = ['<div id="deviceready">',
|
||||
' <p class="event listening">Listening</p>',
|
||||
' <p class="event received">Received</p>',
|
||||
'</div>'].join('\n');
|
||||
});
|
||||
|
||||
it('should hide the listening element', function() {
|
||||
app.receivedEvent('deviceready');
|
||||
var displayStyle = helper.getComputedStyle('#deviceready .listening', 'display');
|
||||
expect(displayStyle).toEqual('none');
|
||||
});
|
||||
|
||||
it('should show the received element', function() {
|
||||
app.receivedEvent('deviceready');
|
||||
var displayStyle = helper.getComputedStyle('#deviceready .received', 'display');
|
||||
expect(displayStyle).toEqual('block');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
Copyright (c) 2008-2011 Pivotal Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,616 @@
|
||||
jasmine.HtmlReporterHelpers = {};
|
||||
|
||||
jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) {
|
||||
var el = document.createElement(type);
|
||||
|
||||
for (var i = 2; i < arguments.length; i++) {
|
||||
var child = arguments[i];
|
||||
|
||||
if (typeof child === 'string') {
|
||||
el.appendChild(document.createTextNode(child));
|
||||
} else {
|
||||
if (child) {
|
||||
el.appendChild(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var attr in attrs) {
|
||||
if (attr == "className") {
|
||||
el[attr] = attrs[attr];
|
||||
} else {
|
||||
el.setAttribute(attr, attrs[attr]);
|
||||
}
|
||||
}
|
||||
|
||||
return el;
|
||||
};
|
||||
|
||||
jasmine.HtmlReporterHelpers.getSpecStatus = function(child) {
|
||||
var results = child.results();
|
||||
var status = results.passed() ? 'passed' : 'failed';
|
||||
if (results.skipped) {
|
||||
status = 'skipped';
|
||||
}
|
||||
|
||||
return status;
|
||||
};
|
||||
|
||||
jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) {
|
||||
var parentDiv = this.dom.summary;
|
||||
var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite';
|
||||
var parent = child[parentSuite];
|
||||
|
||||
if (parent) {
|
||||
if (typeof this.views.suites[parent.id] == 'undefined') {
|
||||
this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views);
|
||||
}
|
||||
parentDiv = this.views.suites[parent.id].element;
|
||||
}
|
||||
|
||||
parentDiv.appendChild(childElement);
|
||||
};
|
||||
|
||||
|
||||
jasmine.HtmlReporterHelpers.addHelpers = function(ctor) {
|
||||
for(var fn in jasmine.HtmlReporterHelpers) {
|
||||
ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn];
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter = function(_doc) {
|
||||
var self = this;
|
||||
var doc = _doc || window.document;
|
||||
|
||||
var reporterView;
|
||||
|
||||
var dom = {};
|
||||
|
||||
// Jasmine Reporter Public Interface
|
||||
self.logRunningSpecs = false;
|
||||
|
||||
self.reportRunnerStarting = function(runner) {
|
||||
var specs = runner.specs() || [];
|
||||
|
||||
if (specs.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
createReporterDom(runner.env.versionString());
|
||||
doc.body.appendChild(dom.reporter);
|
||||
|
||||
reporterView = new jasmine.HtmlReporter.ReporterView(dom);
|
||||
reporterView.addSpecs(specs, self.specFilter);
|
||||
};
|
||||
|
||||
self.reportRunnerResults = function(runner) {
|
||||
reporterView && reporterView.complete();
|
||||
};
|
||||
|
||||
self.reportSuiteResults = function(suite) {
|
||||
reporterView.suiteComplete(suite);
|
||||
};
|
||||
|
||||
self.reportSpecStarting = function(spec) {
|
||||
if (self.logRunningSpecs) {
|
||||
self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
|
||||
}
|
||||
};
|
||||
|
||||
self.reportSpecResults = function(spec) {
|
||||
reporterView.specComplete(spec);
|
||||
};
|
||||
|
||||
self.log = function() {
|
||||
var console = jasmine.getGlobal().console;
|
||||
if (console && console.log) {
|
||||
if (console.log.apply) {
|
||||
console.log.apply(console, arguments);
|
||||
} else {
|
||||
console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.specFilter = function(spec) {
|
||||
if (!focusedSpecName()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return spec.getFullName().indexOf(focusedSpecName()) === 0;
|
||||
};
|
||||
|
||||
return self;
|
||||
|
||||
function focusedSpecName() {
|
||||
var specName;
|
||||
|
||||
(function memoizeFocusedSpec() {
|
||||
if (specName) {
|
||||
return;
|
||||
}
|
||||
|
||||
var paramMap = [];
|
||||
var params = doc.location.search.substring(1).split('&');
|
||||
|
||||
for (var i = 0; i < params.length; i++) {
|
||||
var p = params[i].split('=');
|
||||
paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
|
||||
}
|
||||
|
||||
specName = paramMap.spec;
|
||||
})();
|
||||
|
||||
return specName;
|
||||
}
|
||||
|
||||
function createReporterDom(version) {
|
||||
dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' },
|
||||
dom.banner = self.createDom('div', { className: 'banner' },
|
||||
self.createDom('span', { className: 'title' }, "Jasmine "),
|
||||
self.createDom('span', { className: 'version' }, version)),
|
||||
|
||||
dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}),
|
||||
dom.alert = self.createDom('div', {className: 'alert'}),
|
||||
dom.results = self.createDom('div', {className: 'results'},
|
||||
dom.summary = self.createDom('div', { className: 'summary' }),
|
||||
dom.details = self.createDom('div', { id: 'details' }))
|
||||
);
|
||||
}
|
||||
};
|
||||
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);jasmine.HtmlReporter.ReporterView = function(dom) {
|
||||
this.startedAt = new Date();
|
||||
this.runningSpecCount = 0;
|
||||
this.completeSpecCount = 0;
|
||||
this.passedCount = 0;
|
||||
this.failedCount = 0;
|
||||
this.skippedCount = 0;
|
||||
|
||||
this.createResultsMenu = function() {
|
||||
this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'},
|
||||
this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'),
|
||||
' | ',
|
||||
this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing'));
|
||||
|
||||
this.summaryMenuItem.onclick = function() {
|
||||
dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, '');
|
||||
};
|
||||
|
||||
this.detailsMenuItem.onclick = function() {
|
||||
showDetails();
|
||||
};
|
||||
};
|
||||
|
||||
this.addSpecs = function(specs, specFilter) {
|
||||
this.totalSpecCount = specs.length;
|
||||
|
||||
this.views = {
|
||||
specs: {},
|
||||
suites: {}
|
||||
};
|
||||
|
||||
for (var i = 0; i < specs.length; i++) {
|
||||
var spec = specs[i];
|
||||
this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views);
|
||||
if (specFilter(spec)) {
|
||||
this.runningSpecCount++;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.specComplete = function(spec) {
|
||||
this.completeSpecCount++;
|
||||
|
||||
if (isUndefined(this.views.specs[spec.id])) {
|
||||
this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom);
|
||||
}
|
||||
|
||||
var specView = this.views.specs[spec.id];
|
||||
|
||||
switch (specView.status()) {
|
||||
case 'passed':
|
||||
this.passedCount++;
|
||||
break;
|
||||
|
||||
case 'failed':
|
||||
this.failedCount++;
|
||||
break;
|
||||
|
||||
case 'skipped':
|
||||
this.skippedCount++;
|
||||
break;
|
||||
}
|
||||
|
||||
specView.refresh();
|
||||
this.refresh();
|
||||
};
|
||||
|
||||
this.suiteComplete = function(suite) {
|
||||
var suiteView = this.views.suites[suite.id];
|
||||
if (isUndefined(suiteView)) {
|
||||
return;
|
||||
}
|
||||
suiteView.refresh();
|
||||
};
|
||||
|
||||
this.refresh = function() {
|
||||
|
||||
if (isUndefined(this.resultsMenu)) {
|
||||
this.createResultsMenu();
|
||||
}
|
||||
|
||||
// currently running UI
|
||||
if (isUndefined(this.runningAlert)) {
|
||||
this.runningAlert = this.createDom('a', {href: "?", className: "runningAlert bar"});
|
||||
dom.alert.appendChild(this.runningAlert);
|
||||
}
|
||||
this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount);
|
||||
|
||||
// skipped specs UI
|
||||
if (isUndefined(this.skippedAlert)) {
|
||||
this.skippedAlert = this.createDom('a', {href: "?", className: "skippedAlert bar"});
|
||||
}
|
||||
|
||||
this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
|
||||
|
||||
if (this.skippedCount === 1 && isDefined(dom.alert)) {
|
||||
dom.alert.appendChild(this.skippedAlert);
|
||||
}
|
||||
|
||||
// passing specs UI
|
||||
if (isUndefined(this.passedAlert)) {
|
||||
this.passedAlert = this.createDom('span', {href: "?", className: "passingAlert bar"});
|
||||
}
|
||||
this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount);
|
||||
|
||||
// failing specs UI
|
||||
if (isUndefined(this.failedAlert)) {
|
||||
this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"});
|
||||
}
|
||||
this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount);
|
||||
|
||||
if (this.failedCount === 1 && isDefined(dom.alert)) {
|
||||
dom.alert.appendChild(this.failedAlert);
|
||||
dom.alert.appendChild(this.resultsMenu);
|
||||
}
|
||||
|
||||
// summary info
|
||||
this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount);
|
||||
this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing";
|
||||
};
|
||||
|
||||
this.complete = function() {
|
||||
dom.alert.removeChild(this.runningAlert);
|
||||
|
||||
this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
|
||||
|
||||
if (this.failedCount === 0) {
|
||||
dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount)));
|
||||
} else {
|
||||
showDetails();
|
||||
}
|
||||
|
||||
dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"));
|
||||
};
|
||||
|
||||
return this;
|
||||
|
||||
function showDetails() {
|
||||
if (dom.reporter.className.search(/showDetails/) === -1) {
|
||||
dom.reporter.className += " showDetails";
|
||||
}
|
||||
}
|
||||
|
||||
function isUndefined(obj) {
|
||||
return typeof obj === 'undefined';
|
||||
}
|
||||
|
||||
function isDefined(obj) {
|
||||
return !isUndefined(obj);
|
||||
}
|
||||
|
||||
function specPluralizedFor(count) {
|
||||
var str = count + " spec";
|
||||
if (count > 1) {
|
||||
str += "s"
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView);
|
||||
|
||||
|
||||
jasmine.HtmlReporter.SpecView = function(spec, dom, views) {
|
||||
this.spec = spec;
|
||||
this.dom = dom;
|
||||
this.views = views;
|
||||
|
||||
this.symbol = this.createDom('li', { className: 'pending' });
|
||||
this.dom.symbolSummary.appendChild(this.symbol);
|
||||
|
||||
this.summary = this.createDom('div', { className: 'specSummary' },
|
||||
this.createDom('a', {
|
||||
className: 'description',
|
||||
href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
|
||||
title: this.spec.getFullName()
|
||||
}, this.spec.description)
|
||||
);
|
||||
|
||||
this.detail = this.createDom('div', { className: 'specDetail' },
|
||||
this.createDom('a', {
|
||||
className: 'description',
|
||||
href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
|
||||
title: this.spec.getFullName()
|
||||
}, this.spec.getFullName())
|
||||
);
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter.SpecView.prototype.status = function() {
|
||||
return this.getSpecStatus(this.spec);
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter.SpecView.prototype.refresh = function() {
|
||||
this.symbol.className = this.status();
|
||||
|
||||
switch (this.status()) {
|
||||
case 'skipped':
|
||||
break;
|
||||
|
||||
case 'passed':
|
||||
this.appendSummaryToSuiteDiv();
|
||||
break;
|
||||
|
||||
case 'failed':
|
||||
this.appendSummaryToSuiteDiv();
|
||||
this.appendFailureDetail();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() {
|
||||
this.summary.className += ' ' + this.status();
|
||||
this.appendToSummary(this.spec, this.summary);
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() {
|
||||
this.detail.className += ' ' + this.status();
|
||||
|
||||
var resultItems = this.spec.results().getItems();
|
||||
var messagesDiv = this.createDom('div', { className: 'messages' });
|
||||
|
||||
for (var i = 0; i < resultItems.length; i++) {
|
||||
var result = resultItems[i];
|
||||
|
||||
if (result.type == 'log') {
|
||||
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
|
||||
} else if (result.type == 'expect' && result.passed && !result.passed()) {
|
||||
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
|
||||
|
||||
if (result.trace.stack) {
|
||||
messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (messagesDiv.childNodes.length > 0) {
|
||||
this.detail.appendChild(messagesDiv);
|
||||
this.dom.details.appendChild(this.detail);
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) {
|
||||
this.suite = suite;
|
||||
this.dom = dom;
|
||||
this.views = views;
|
||||
|
||||
this.element = this.createDom('div', { className: 'suite' },
|
||||
this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(this.suite.getFullName()) }, this.suite.description)
|
||||
);
|
||||
|
||||
this.appendToSummary(this.suite, this.element);
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter.SuiteView.prototype.status = function() {
|
||||
return this.getSpecStatus(this.suite);
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter.SuiteView.prototype.refresh = function() {
|
||||
this.element.className += " " + this.status();
|
||||
};
|
||||
|
||||
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView);
|
||||
|
||||
/* @deprecated Use jasmine.HtmlReporter instead
|
||||
*/
|
||||
jasmine.TrivialReporter = function(doc) {
|
||||
this.document = doc || document;
|
||||
this.suiteDivs = {};
|
||||
this.logRunningSpecs = false;
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
|
||||
var el = document.createElement(type);
|
||||
|
||||
for (var i = 2; i < arguments.length; i++) {
|
||||
var child = arguments[i];
|
||||
|
||||
if (typeof child === 'string') {
|
||||
el.appendChild(document.createTextNode(child));
|
||||
} else {
|
||||
if (child) { el.appendChild(child); }
|
||||
}
|
||||
}
|
||||
|
||||
for (var attr in attrs) {
|
||||
if (attr == "className") {
|
||||
el[attr] = attrs[attr];
|
||||
} else {
|
||||
el.setAttribute(attr, attrs[attr]);
|
||||
}
|
||||
}
|
||||
|
||||
return el;
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
|
||||
var showPassed, showSkipped;
|
||||
|
||||
this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' },
|
||||
this.createDom('div', { className: 'banner' },
|
||||
this.createDom('div', { className: 'logo' },
|
||||
this.createDom('span', { className: 'title' }, "Jasmine"),
|
||||
this.createDom('span', { className: 'version' }, runner.env.versionString())),
|
||||
this.createDom('div', { className: 'options' },
|
||||
"Show ",
|
||||
showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
|
||||
this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
|
||||
showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
|
||||
this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
|
||||
)
|
||||
),
|
||||
|
||||
this.runnerDiv = this.createDom('div', { className: 'runner running' },
|
||||
this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
|
||||
this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
|
||||
this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
|
||||
);
|
||||
|
||||
this.document.body.appendChild(this.outerDiv);
|
||||
|
||||
var suites = runner.suites();
|
||||
for (var i = 0; i < suites.length; i++) {
|
||||
var suite = suites[i];
|
||||
var suiteDiv = this.createDom('div', { className: 'suite' },
|
||||
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
|
||||
this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
|
||||
this.suiteDivs[suite.id] = suiteDiv;
|
||||
var parentDiv = this.outerDiv;
|
||||
if (suite.parentSuite) {
|
||||
parentDiv = this.suiteDivs[suite.parentSuite.id];
|
||||
}
|
||||
parentDiv.appendChild(suiteDiv);
|
||||
}
|
||||
|
||||
this.startedAt = new Date();
|
||||
|
||||
var self = this;
|
||||
showPassed.onclick = function(evt) {
|
||||
if (showPassed.checked) {
|
||||
self.outerDiv.className += ' show-passed';
|
||||
} else {
|
||||
self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
|
||||
}
|
||||
};
|
||||
|
||||
showSkipped.onclick = function(evt) {
|
||||
if (showSkipped.checked) {
|
||||
self.outerDiv.className += ' show-skipped';
|
||||
} else {
|
||||
self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
|
||||
var results = runner.results();
|
||||
var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
|
||||
this.runnerDiv.setAttribute("class", className);
|
||||
//do it twice for IE
|
||||
this.runnerDiv.setAttribute("className", className);
|
||||
var specs = runner.specs();
|
||||
var specCount = 0;
|
||||
for (var i = 0; i < specs.length; i++) {
|
||||
if (this.specFilter(specs[i])) {
|
||||
specCount++;
|
||||
}
|
||||
}
|
||||
var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
|
||||
message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
|
||||
this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
|
||||
|
||||
this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
|
||||
var results = suite.results();
|
||||
var status = results.passed() ? 'passed' : 'failed';
|
||||
if (results.totalCount === 0) { // todo: change this to check results.skipped
|
||||
status = 'skipped';
|
||||
}
|
||||
this.suiteDivs[suite.id].className += " " + status;
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
|
||||
if (this.logRunningSpecs) {
|
||||
this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
|
||||
var results = spec.results();
|
||||
var status = results.passed() ? 'passed' : 'failed';
|
||||
if (results.skipped) {
|
||||
status = 'skipped';
|
||||
}
|
||||
var specDiv = this.createDom('div', { className: 'spec ' + status },
|
||||
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
|
||||
this.createDom('a', {
|
||||
className: 'description',
|
||||
href: '?spec=' + encodeURIComponent(spec.getFullName()),
|
||||
title: spec.getFullName()
|
||||
}, spec.description));
|
||||
|
||||
|
||||
var resultItems = results.getItems();
|
||||
var messagesDiv = this.createDom('div', { className: 'messages' });
|
||||
for (var i = 0; i < resultItems.length; i++) {
|
||||
var result = resultItems[i];
|
||||
|
||||
if (result.type == 'log') {
|
||||
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
|
||||
} else if (result.type == 'expect' && result.passed && !result.passed()) {
|
||||
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
|
||||
|
||||
if (result.trace.stack) {
|
||||
messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (messagesDiv.childNodes.length > 0) {
|
||||
specDiv.appendChild(messagesDiv);
|
||||
}
|
||||
|
||||
this.suiteDivs[spec.suite.id].appendChild(specDiv);
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.log = function() {
|
||||
var console = jasmine.getGlobal().console;
|
||||
if (console && console.log) {
|
||||
if (console.log.apply) {
|
||||
console.log.apply(console, arguments);
|
||||
} else {
|
||||
console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.getLocation = function() {
|
||||
return this.document.location;
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.specFilter = function(spec) {
|
||||
var paramMap = {};
|
||||
var params = this.getLocation().search.substring(1).split('&');
|
||||
for (var i = 0; i < params.length; i++) {
|
||||
var p = params[i].split('=');
|
||||
paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
|
||||
}
|
||||
|
||||
if (!paramMap.spec) {
|
||||
return true;
|
||||
}
|
||||
return spec.getFullName().indexOf(paramMap.spec) === 0;
|
||||
};
|
||||
@@ -0,0 +1,81 @@
|
||||
body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; }
|
||||
|
||||
#HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; }
|
||||
#HTMLReporter a { text-decoration: none; }
|
||||
#HTMLReporter a:hover { text-decoration: underline; }
|
||||
#HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; }
|
||||
#HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; }
|
||||
#HTMLReporter #jasmine_content { position: fixed; right: 100%; }
|
||||
#HTMLReporter .version { color: #aaaaaa; }
|
||||
#HTMLReporter .banner { margin-top: 14px; }
|
||||
#HTMLReporter .duration { color: #aaaaaa; float: right; }
|
||||
#HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; }
|
||||
#HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; }
|
||||
#HTMLReporter .symbolSummary li.passed { font-size: 14px; }
|
||||
#HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; }
|
||||
#HTMLReporter .symbolSummary li.failed { line-height: 9px; }
|
||||
#HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; }
|
||||
#HTMLReporter .symbolSummary li.skipped { font-size: 14px; }
|
||||
#HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; }
|
||||
#HTMLReporter .symbolSummary li.pending { line-height: 11px; }
|
||||
#HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; }
|
||||
#HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
|
||||
#HTMLReporter .runningAlert { background-color: #666666; }
|
||||
#HTMLReporter .skippedAlert { background-color: #aaaaaa; }
|
||||
#HTMLReporter .skippedAlert:first-child { background-color: #333333; }
|
||||
#HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; }
|
||||
#HTMLReporter .passingAlert { background-color: #a6b779; }
|
||||
#HTMLReporter .passingAlert:first-child { background-color: #5e7d00; }
|
||||
#HTMLReporter .failingAlert { background-color: #cf867e; }
|
||||
#HTMLReporter .failingAlert:first-child { background-color: #b03911; }
|
||||
#HTMLReporter .results { margin-top: 14px; }
|
||||
#HTMLReporter #details { display: none; }
|
||||
#HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; }
|
||||
#HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
|
||||
#HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
|
||||
#HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
|
||||
#HTMLReporter.showDetails .summary { display: none; }
|
||||
#HTMLReporter.showDetails #details { display: block; }
|
||||
#HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
|
||||
#HTMLReporter .summary { margin-top: 14px; }
|
||||
#HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; }
|
||||
#HTMLReporter .summary .specSummary.passed a { color: #5e7d00; }
|
||||
#HTMLReporter .summary .specSummary.failed a { color: #b03911; }
|
||||
#HTMLReporter .description + .suite { margin-top: 0; }
|
||||
#HTMLReporter .suite { margin-top: 14px; }
|
||||
#HTMLReporter .suite a { color: #333333; }
|
||||
#HTMLReporter #details .specDetail { margin-bottom: 28px; }
|
||||
#HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; }
|
||||
#HTMLReporter .resultMessage { padding-top: 14px; color: #333333; }
|
||||
#HTMLReporter .resultMessage span.result { display: block; }
|
||||
#HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; }
|
||||
|
||||
#TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ }
|
||||
#TrivialReporter a:visited, #TrivialReporter a { color: #303; }
|
||||
#TrivialReporter a:hover, #TrivialReporter a:active { color: blue; }
|
||||
#TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; }
|
||||
#TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; }
|
||||
#TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; }
|
||||
#TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; }
|
||||
#TrivialReporter .runner.running { background-color: yellow; }
|
||||
#TrivialReporter .options { text-align: right; font-size: .8em; }
|
||||
#TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; }
|
||||
#TrivialReporter .suite .suite { margin: 5px; }
|
||||
#TrivialReporter .suite.passed { background-color: #dfd; }
|
||||
#TrivialReporter .suite.failed { background-color: #fdd; }
|
||||
#TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; }
|
||||
#TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; }
|
||||
#TrivialReporter .spec.failed { background-color: #fbb; border-color: red; }
|
||||
#TrivialReporter .spec.passed { background-color: #bfb; border-color: green; }
|
||||
#TrivialReporter .spec.skipped { background-color: #bbb; }
|
||||
#TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; }
|
||||
#TrivialReporter .passed { background-color: #cfc; display: none; }
|
||||
#TrivialReporter .failed { background-color: #fbb; }
|
||||
#TrivialReporter .skipped { color: #777; background-color: #eee; display: none; }
|
||||
#TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; }
|
||||
#TrivialReporter .resultMessage .mismatch { color: black; }
|
||||
#TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; }
|
||||
#TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; }
|
||||
#TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; }
|
||||
#TrivialReporter #jasmine_content { position: fixed; right: 100%; }
|
||||
#TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; }
|
||||
2529
bin/templates/project/assets/www/spec/lib/jasmine-1.2.0/jasmine.js
Normal 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="src" path="gen"/>
|
||||
<classpathentry kind="lib" path="libs/commons-codec-1.6.jar"/>
|
||||
<classpathentry kind="lib" path="libs/commons-codec-1.7.jar"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
|
||||
<classpathentry kind="output" path="bin/classes"/>
|
||||
</classpath>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
under the License.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:windowSoftInputMode="adjustPan"
|
||||
package="org.apache.cordova" android:versionName="1.1" android:versionCode="5">
|
||||
package="org.apache.cordova" android:versionName="1.0" android:versionCode="1">
|
||||
<supports-screens
|
||||
android:largeScreens="true"
|
||||
android:normalScreens="true"
|
||||
@@ -51,7 +51,7 @@
|
||||
<application android:icon="@drawable/icon" android:label="@string/app_name"
|
||||
android:debuggable="true">
|
||||
<activity android:name=".StandAlone" android:windowSoftInputMode="adjustPan"
|
||||
android:label="@string/app_name" android:configChanges="orientation|keyboardHidden">
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
@@ -64,5 +64,5 @@
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
<uses-sdk android:minSdkVersion="2" />
|
||||
<uses-sdk android:minSdkVersion="7" />
|
||||
</manifest>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
<script src="cordova-1.9.0rc1.js"></script>
|
||||
<script src="cordova-2.5.0rc1.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -10,5 +10,7 @@
|
||||
# Indicates whether an apk should be generated for each density.
|
||||
split.density=false
|
||||
# Project target.
|
||||
target=Google Inc.:Google APIs:15
|
||||
target=Google Inc.:Google APIs:17
|
||||
apk-configurations=
|
||||
renderscript.opt.level=O0
|
||||
android.library=true
|
||||
|
||||
24
framework/res/xml/plugins.xml → framework/res/xml/config.xml
Executable file → Normal file
@@ -17,6 +17,24 @@
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
<cordova>
|
||||
<!--
|
||||
access elements control the Android whitelist.
|
||||
Domains are assumed blocked unless set otherwise
|
||||
-->
|
||||
|
||||
<access origin="http://127.0.0.1*"/> <!-- allow local pages -->
|
||||
|
||||
<!-- <access origin="https://example.com" /> allow any secure requests to example.com -->
|
||||
<!-- <access origin="https://example.com" subdomains="true" /> such as above, but including subdomains, such as www -->
|
||||
<access origin=".*"/>
|
||||
|
||||
<!-- <content src="http://mysite.com/myapp.html" /> for external pages -->
|
||||
<content src="index.html" />
|
||||
|
||||
<log level="DEBUG"/>
|
||||
<preference name="useBrowserHistory" value="true" />
|
||||
<preference name="exit-on-suspend" value="false" />
|
||||
<plugins>
|
||||
<plugin name="App" value="org.apache.cordova.App"/>
|
||||
<plugin name="Geolocation" value="org.apache.cordova.GeoBroker"/>
|
||||
@@ -30,9 +48,13 @@
|
||||
<plugin name="NetworkStatus" value="org.apache.cordova.NetworkManager"/>
|
||||
<plugin name="Notification" value="org.apache.cordova.Notification"/>
|
||||
<plugin name="Storage" value="org.apache.cordova.Storage"/>
|
||||
<plugin name="Temperature" value="org.apache.cordova.TempListener"/>
|
||||
<plugin name="FileTransfer" value="org.apache.cordova.FileTransfer"/>
|
||||
<plugin name="Capture" value="org.apache.cordova.Capture"/>
|
||||
<plugin name="Battery" value="org.apache.cordova.BatteryListener"/>
|
||||
<plugin name="SplashScreen" value="org.apache.cordova.SplashScreen"/>
|
||||
<plugin name="Echo" value="org.apache.cordova.Echo" />
|
||||
<plugin name="Globalization" value="org.apache.cordova.Globalization"/>
|
||||
<plugin name="InAppBrowser" value="org.apache.cordova.InAppBrowser"/>
|
||||
</plugins>
|
||||
</cordova>
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
<cordova>
|
||||
<!--
|
||||
access elements control the Android whitelist.
|
||||
Domains are assumed blocked unless set otherwise
|
||||
-->
|
||||
|
||||
<access origin="http://127.0.0.1*"/> <!-- allow local pages -->
|
||||
|
||||
<!-- <access origin="https://example.com" /> allow any secure requests to example.com -->
|
||||
<!-- <access origin="https://example.com" subdomains="true" /> such as above, but including subdomains, such as www -->
|
||||
<!-- <access origin=".*"/> Allow all domains, suggested development use only -->
|
||||
|
||||
<log level="DEBUG"/>
|
||||
<preference name="useBrowserHistory" value="false" />
|
||||
</cordova>
|
||||
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package com.phonegap.api;
|
||||
|
||||
/**
|
||||
* Plugin interface must be implemented by any plugin classes.
|
||||
*
|
||||
* The execute method is called by the PluginManager.
|
||||
*/
|
||||
public interface IPlugin extends org.apache.cordova.api.IPlugin {
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package com.phonegap.api;
|
||||
|
||||
/**
|
||||
* Log to Android logging system.
|
||||
*
|
||||
* Log message can be a string or a printf formatted string with arguments.
|
||||
* See http://developer.android.com/reference/java/util/Formatter.html
|
||||
*/
|
||||
public class LOG extends org.apache.cordova.api.LOG {
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package com.phonegap.api;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
/**
|
||||
* The Cordova activity abstract class that is extended by DroidGap.
|
||||
* It is used to isolate plugin development, and remove dependency on entire Cordova library.
|
||||
*/
|
||||
public abstract class PhonegapActivity extends Activity implements org.apache.cordova.api.CordovaInterface {
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package com.phonegap.api;
|
||||
|
||||
/**
|
||||
* Plugin interface must be implemented by any plugin classes.
|
||||
*
|
||||
* The execute method is called by the PluginManager.
|
||||
*/
|
||||
public abstract class Plugin extends org.apache.cordova.api.Plugin {
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package com.phonegap.api;
|
||||
|
||||
import org.apache.cordova.CordovaWebView;
|
||||
import org.apache.cordova.api.CordovaInterface;
|
||||
|
||||
import android.webkit.WebView;
|
||||
|
||||
/**
|
||||
* PluginManager is exposed to JavaScript in the Cordova WebView.
|
||||
*
|
||||
* Calling native plugin code can be done by calling PluginManager.exec(...)
|
||||
* from JavaScript.
|
||||
*/
|
||||
public class PluginManager extends org.apache.cordova.api.PluginManager {
|
||||
|
||||
public PluginManager(WebView app, CordovaInterface ctx) throws Exception {
|
||||
super((CordovaWebView) app, ctx);
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package com.phonegap.api;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class PluginResult extends org.apache.cordova.api.PluginResult {
|
||||
|
||||
public PluginResult(Status status) {
|
||||
super(status);
|
||||
}
|
||||
|
||||
public PluginResult(Status status, String message) {
|
||||
super(status, message);
|
||||
}
|
||||
|
||||
public PluginResult(Status status, JSONArray message) {
|
||||
super(status, message);
|
||||
}
|
||||
|
||||
public PluginResult(Status status, JSONObject message) {
|
||||
super(status, message);
|
||||
}
|
||||
|
||||
public PluginResult(Status status, int i) {
|
||||
super(status, i);
|
||||
}
|
||||
|
||||
public PluginResult(Status status, float f) {
|
||||
super(status, f);
|
||||
}
|
||||
|
||||
public PluginResult(Status status, boolean b) {
|
||||
super(status, b);
|
||||
}
|
||||
}
|
||||
@@ -19,24 +19,29 @@
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.cordova.api.CallbackContext;
|
||||
import org.apache.cordova.api.CordovaInterface;
|
||||
import org.apache.cordova.api.Plugin;
|
||||
import org.apache.cordova.api.CordovaPlugin;
|
||||
import org.apache.cordova.api.PluginResult;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.content.Context;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
/**
|
||||
* This class listens to the accelerometer sensor and stores the latest
|
||||
* acceleration values x,y,z.
|
||||
*/
|
||||
public class AccelListener extends Plugin implements SensorEventListener {
|
||||
public class AccelListener extends CordovaPlugin implements SensorEventListener {
|
||||
|
||||
public static int STOPPED = 0;
|
||||
public static int STARTING = 1;
|
||||
@@ -51,7 +56,7 @@ public class AccelListener extends Plugin implements SensorEventListener {
|
||||
private SensorManager sensorManager; // Sensor manager
|
||||
private Sensor mSensor; // Acceleration sensor returned by sensor manager
|
||||
|
||||
private String callbackId; // Keeps track of the single "start" callback ID passed in from JS
|
||||
private CallbackContext callbackContext; // Keeps track of the JS callback context.
|
||||
|
||||
/**
|
||||
* Create an accelerometer listener.
|
||||
@@ -69,29 +74,25 @@ public class AccelListener extends Plugin implements SensorEventListener {
|
||||
* get file paths associated with the Activity.
|
||||
*
|
||||
* @param cordova The context of the main Activity.
|
||||
* @param webView The associated CordovaWebView.
|
||||
*/
|
||||
|
||||
public void setContext(CordovaInterface cordova) {
|
||||
super.setContext(cordova);
|
||||
@Override
|
||||
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
|
||||
super.initialize(cordova, webView);
|
||||
this.sensorManager = (SensorManager) cordova.getActivity().getSystemService(Context.SENSOR_SERVICE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the request and returns PluginResult.
|
||||
*
|
||||
* Executes the request.
|
||||
*
|
||||
* @param action The action to execute.
|
||||
* @param args JSONArry of arguments for the plugin.
|
||||
* @param args The exec() arguments.
|
||||
* @param callbackId The callback id used when calling back into JavaScript.
|
||||
* @return A PluginResult object with a status and message.
|
||||
* @return Whether the action was valid.
|
||||
*/
|
||||
public PluginResult execute(String action, JSONArray args, String callbackId) {
|
||||
PluginResult.Status status = PluginResult.Status.NO_RESULT;
|
||||
String message = "";
|
||||
PluginResult result = new PluginResult(status, message);
|
||||
result.setKeepCallback(true);
|
||||
|
||||
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) {
|
||||
if (action.equals("start")) {
|
||||
this.callbackId = callbackId;
|
||||
this.callbackContext = callbackContext;
|
||||
if (this.status != AccelListener.RUNNING) {
|
||||
// If not running, then this is an async call, so don't worry about waiting
|
||||
// We drop the callback onto our stack, call start, and let start and the sensor callback fire off the callback down the road
|
||||
@@ -104,9 +105,13 @@ public class AccelListener extends Plugin implements SensorEventListener {
|
||||
}
|
||||
} else {
|
||||
// Unsupported action
|
||||
return new PluginResult(PluginResult.Status.INVALID_ACTION);
|
||||
return false;
|
||||
}
|
||||
return result;
|
||||
|
||||
PluginResult result = new PluginResult(PluginResult.Status.NO_RESULT, "");
|
||||
result.setKeepCallback(true);
|
||||
callbackContext.sendPluginResult(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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.
|
||||
*
|
||||
@@ -215,7 +226,7 @@ public class AccelListener extends Plugin implements SensorEventListener {
|
||||
if (this.accuracy >= SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM) {
|
||||
|
||||
// Save time that event was received
|
||||
this.timestamp = System.nanoTime();
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
this.x = event.values[0];
|
||||
this.y = event.values[1];
|
||||
this.z = event.values[2];
|
||||
@@ -224,6 +235,16 @@ public class AccelListener extends Plugin implements SensorEventListener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the view navigates.
|
||||
*/
|
||||
@Override
|
||||
public void onReset() {
|
||||
if (this.status == AccelListener.RUNNING) {
|
||||
this.stop();
|
||||
}
|
||||
}
|
||||
|
||||
// Sends an error back to JS
|
||||
private void fail(int code, String message) {
|
||||
// Error object
|
||||
@@ -236,16 +257,14 @@ public class AccelListener extends Plugin implements SensorEventListener {
|
||||
}
|
||||
PluginResult err = new PluginResult(PluginResult.Status.ERROR, errorObj);
|
||||
err.setKeepCallback(true);
|
||||
|
||||
this.error(err, this.callbackId);
|
||||
callbackContext.sendPluginResult(err);
|
||||
}
|
||||
|
||||
private void win() {
|
||||
// Success return object
|
||||
PluginResult result = new PluginResult(PluginResult.Status.OK, this.getAccelerationJSON());
|
||||
result.setKeepCallback(true);
|
||||
|
||||
this.success(result, this.callbackId);
|
||||
callbackContext.sendPluginResult(result);
|
||||
}
|
||||
|
||||
private void setStatus(int status) {
|
||||
|
||||
@@ -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 = "";
|
||||
|
||||
@@ -48,7 +50,10 @@ public class App extends Plugin {
|
||||
if (action.equals("clearCache")) {
|
||||
this.clearCache();
|
||||
}
|
||||
else if (action.equals("show")) { // TODO @bc - Not in master branch. When should this be called?
|
||||
else if (action.equals("show")) {
|
||||
// This gets called from JavaScript onCordovaReady to show the webview.
|
||||
// I recommend we change the name of the Message as spinner/stop is not
|
||||
// indicative of what this actually does (shows the webview).
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
webView.postMessage("spinner", "stop");
|
||||
@@ -59,7 +64,7 @@ public class App extends Plugin {
|
||||
this.loadUrl(args.getString(0), args.optJSONObject(1));
|
||||
}
|
||||
else if (action.equals("cancelLoadUrl")) {
|
||||
this.cancelLoadUrl();
|
||||
//this.cancelLoadUrl();
|
||||
}
|
||||
else if (action.equals("clearHistory")) {
|
||||
this.clearHistory();
|
||||
@@ -76,9 +81,11 @@ public class App extends Plugin {
|
||||
else if (action.equals("exitApp")) {
|
||||
this.exitApp();
|
||||
}
|
||||
return new PluginResult(status, result);
|
||||
callbackContext.sendPluginResult(new PluginResult(status, result));
|
||||
return true;
|
||||
} catch (JSONException e) {
|
||||
return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
|
||||
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,14 +160,6 @@ public class App extends Plugin {
|
||||
this.webView.showWebPage(url, openExternal, clearHistory, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel loadUrl before it has been loaded (Only works on a CordovaInterface class)
|
||||
*/
|
||||
@Deprecated
|
||||
public void cancelLoadUrl() {
|
||||
this.cordova.cancelLoadUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear page history for the app.
|
||||
*/
|
||||
@@ -173,7 +172,11 @@ public class App extends Plugin {
|
||||
* This is the same as pressing the backbutton on Android device.
|
||||
*/
|
||||
public void backHistory() {
|
||||
this.webView.backHistory();
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
webView.backHistory();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -199,6 +202,15 @@ public class App extends Plugin {
|
||||
webView.bindButton(button, override);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the Android back button is overridden by the user.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean isBackbuttonOverridden() {
|
||||
return webView.isBackButtonBound();
|
||||
}
|
||||
|
||||
/**
|
||||
* Exit the Android application.
|
||||
*/
|
||||
|
||||
132
framework/src/org/apache/cordova/AudioHandler.java
Executable file → Normal file
@@ -18,11 +18,14 @@
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import org.apache.cordova.api.CallbackContext;
|
||||
import org.apache.cordova.api.CordovaPlugin;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.apache.cordova.api.Plugin;
|
||||
import org.apache.cordova.api.PluginResult;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
@@ -39,7 +42,7 @@ import java.util.HashMap;
|
||||
* android_asset: file name must start with /android_asset/sound.mp3
|
||||
* sdcard: file name is just sound.mp3
|
||||
*/
|
||||
public class AudioHandler extends Plugin {
|
||||
public class AudioHandler extends CordovaPlugin {
|
||||
|
||||
public static String TAG = "AudioHandler";
|
||||
HashMap<String, AudioPlayer> players; // Audio player object
|
||||
@@ -57,69 +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), args.getString(1));
|
||||
}
|
||||
else if (action.equals("stopRecordingAudio")) {
|
||||
this.stopRecordingAudio(args.getString(0));
|
||||
}
|
||||
else if (action.equals("startPlayingAudio")) {
|
||||
this.startPlayingAudio(args.getString(0), args.getString(1));
|
||||
}
|
||||
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("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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,6 +130,14 @@ public class AudioHandler extends Plugin {
|
||||
this.players.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop all audio players and recorders on navigate.
|
||||
*/
|
||||
@Override
|
||||
public void onReset() {
|
||||
onDestroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a message is sent to plugin.
|
||||
*
|
||||
@@ -149,7 +155,7 @@ public class AudioHandler extends Plugin {
|
||||
|
||||
// Get all audio players and pause them
|
||||
for (AudioPlayer audio : this.players.values()) {
|
||||
if (audio.getState() == AudioPlayer.MEDIA_RUNNING) {
|
||||
if (audio.getState() == AudioPlayer.STATE.MEDIA_RUNNING.ordinal()) {
|
||||
this.pausedForPhone.add(audio);
|
||||
audio.pausePlaying();
|
||||
}
|
||||
@@ -192,12 +198,11 @@ public class AudioHandler extends Plugin {
|
||||
* @param file The name of the file
|
||||
*/
|
||||
public void startRecordingAudio(String id, String file) {
|
||||
// If already recording, then just return;
|
||||
if (this.players.containsKey(id)) {
|
||||
return;
|
||||
AudioPlayer audio = this.players.get(id);
|
||||
if ( audio == null) {
|
||||
audio = new AudioPlayer(this, id, file);
|
||||
this.players.put(id, audio);
|
||||
}
|
||||
AudioPlayer audio = new AudioPlayer(this, id);
|
||||
this.players.put(id, audio);
|
||||
audio.startRecording(file);
|
||||
}
|
||||
|
||||
@@ -209,7 +214,6 @@ public class AudioHandler extends Plugin {
|
||||
AudioPlayer audio = this.players.get(id);
|
||||
if (audio != null) {
|
||||
audio.stopRecording();
|
||||
this.players.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +225,7 @@ public class AudioHandler extends Plugin {
|
||||
public void startPlayingAudio(String id, String file) {
|
||||
AudioPlayer audio = this.players.get(id);
|
||||
if (audio == null) {
|
||||
audio = new AudioPlayer(this, id);
|
||||
audio = new AudioPlayer(this, id, file);
|
||||
this.players.put(id, audio);
|
||||
}
|
||||
audio.startPlaying(file);
|
||||
@@ -230,7 +234,7 @@ public class AudioHandler extends Plugin {
|
||||
/**
|
||||
* Seek to a location.
|
||||
* @param id The id of the audio player
|
||||
* @param miliseconds int: number of milliseconds to skip 1000 = 1 second
|
||||
* @param milliseconds int: number of milliseconds to skip 1000 = 1 second
|
||||
*/
|
||||
public void seekToAudio(String id, int milliseconds) {
|
||||
AudioPlayer audio = this.players.get(id);
|
||||
@@ -292,7 +296,7 @@ public class AudioHandler extends Plugin {
|
||||
|
||||
// If not already open, then open the file
|
||||
else {
|
||||
audio = new AudioPlayer(this, id);
|
||||
audio = new AudioPlayer(this, id, file);
|
||||
this.players.put(id, audio);
|
||||
return (audio.getDuration(file));
|
||||
}
|
||||
|
||||
416
framework/src/org/apache/cordova/AudioPlayer.java
Executable file → Normal file
@@ -42,14 +42,19 @@ import java.io.IOException;
|
||||
*/
|
||||
public class AudioPlayer implements OnCompletionListener, OnPreparedListener, OnErrorListener {
|
||||
|
||||
private static final String LOG_TAG = "AudioPlayer";
|
||||
// AudioPlayer modes
|
||||
public enum MODE { NONE, PLAY, RECORD };
|
||||
|
||||
// AudioPlayer states
|
||||
public static int MEDIA_NONE = 0;
|
||||
public static int MEDIA_STARTING = 1;
|
||||
public static int MEDIA_RUNNING = 2;
|
||||
public static int MEDIA_PAUSED = 3;
|
||||
public static int MEDIA_STOPPED = 4;
|
||||
public enum STATE { MEDIA_NONE,
|
||||
MEDIA_STARTING,
|
||||
MEDIA_RUNNING,
|
||||
MEDIA_PAUSED,
|
||||
MEDIA_STOPPED,
|
||||
MEDIA_LOADING
|
||||
};
|
||||
|
||||
private static final String LOG_TAG = "AudioPlayer";
|
||||
|
||||
// AudioPlayer message ids
|
||||
private static int MEDIA_STATE = 1;
|
||||
@@ -63,33 +68,40 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
|
||||
private static int MEDIA_ERR_NETWORK = 2;
|
||||
private static int MEDIA_ERR_DECODE = 3;
|
||||
private static int MEDIA_ERR_NONE_SUPPORTED = 4;
|
||||
|
||||
private AudioHandler handler; // The AudioHandler object
|
||||
private String id; // The id of this player (used to identify Media object in JavaScript)
|
||||
private int state = MEDIA_NONE; // State of recording or playback
|
||||
private String audioFile = null; // File name to play or record to
|
||||
private float duration = -1; // Duration of audio
|
||||
|
||||
private MediaRecorder recorder = null; // Audio recording object
|
||||
private String tempFile = null; // Temporary recording file name
|
||||
|
||||
private MediaPlayer mPlayer = null; // Audio player object
|
||||
private boolean prepareOnly = false;
|
||||
private AudioHandler handler; // The AudioHandler object
|
||||
private String id; // The id of this player (used to identify Media object in JavaScript)
|
||||
private MODE mode = MODE.NONE; // Playback or Recording mode
|
||||
private STATE state = STATE.MEDIA_NONE; // State of recording or playback
|
||||
|
||||
private String audioFile = null; // File name to play or record to
|
||||
private float duration = -1; // Duration of audio
|
||||
|
||||
private MediaRecorder recorder = null; // Audio recording object
|
||||
private String tempFile = null; // Temporary recording file name
|
||||
|
||||
private MediaPlayer player = null; // Audio player object
|
||||
private boolean prepareOnly = true; // playback after file prepare flag
|
||||
private int seekOnPrepared = 0; // seek to this location once media is prepared
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
*
|
||||
* @param handler The audio handler object
|
||||
* @param id The id of this audio player
|
||||
*/
|
||||
public AudioPlayer(AudioHandler handler, String id) {
|
||||
public AudioPlayer(AudioHandler handler, String id, String file) {
|
||||
this.handler = handler;
|
||||
this.id = id;
|
||||
this.audioFile = file;
|
||||
this.recorder = new MediaRecorder();
|
||||
|
||||
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
||||
this.tempFile = Environment.getExternalStorageDirectory().getAbsolutePath() + "/tmprecording.mp3";
|
||||
this.tempFile = Environment.getExternalStorageDirectory().getAbsolutePath() + "/tmprecording.3gp";
|
||||
} else {
|
||||
this.tempFile = "/data/data/" + handler.cordova.getActivity().getPackageName() + "/cache/tmprecording.mp3";
|
||||
this.tempFile = "/data/data/" + handler.cordova.getActivity().getPackageName() + "/cache/tmprecording.3gp";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,13 +109,13 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
|
||||
*/
|
||||
public void destroy() {
|
||||
// Stop any play or record
|
||||
if (this.mPlayer != null) {
|
||||
if ((this.state == MEDIA_RUNNING) || (this.state == MEDIA_PAUSED)) {
|
||||
this.mPlayer.stop();
|
||||
this.setState(MEDIA_STOPPED);
|
||||
if (this.player != null) {
|
||||
if ((this.state == STATE.MEDIA_RUNNING) || (this.state == STATE.MEDIA_PAUSED)) {
|
||||
this.player.stop();
|
||||
this.setState(STATE.MEDIA_STOPPED);
|
||||
}
|
||||
this.mPlayer.release();
|
||||
this.mPlayer = null;
|
||||
this.player.release();
|
||||
this.player = null;
|
||||
}
|
||||
if (this.recorder != null) {
|
||||
this.stopRecording();
|
||||
@@ -114,18 +126,17 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
|
||||
|
||||
/**
|
||||
* Start recording the specified file.
|
||||
*
|
||||
*
|
||||
* @param file The name of the file
|
||||
*/
|
||||
public void startRecording(String file) {
|
||||
if (this.mPlayer != null) {
|
||||
switch (this.mode) {
|
||||
case PLAY:
|
||||
Log.d(LOG_TAG, "AudioPlayer Error: Can't record in play mode.");
|
||||
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});");
|
||||
}
|
||||
// Make sure we're not already recording
|
||||
else if (this.recorder == null) {
|
||||
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});");
|
||||
break;
|
||||
case NONE:
|
||||
this.audioFile = file;
|
||||
this.recorder = new MediaRecorder();
|
||||
this.recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
|
||||
this.recorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); // THREE_GPP);
|
||||
this.recorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT); //AMR_NB);
|
||||
@@ -133,18 +144,18 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
|
||||
try {
|
||||
this.recorder.prepare();
|
||||
this.recorder.start();
|
||||
this.setState(MEDIA_RUNNING);
|
||||
this.setState(STATE.MEDIA_RUNNING);
|
||||
return;
|
||||
} catch (IllegalStateException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});");
|
||||
}
|
||||
else {
|
||||
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});");
|
||||
break;
|
||||
case RECORD:
|
||||
Log.d(LOG_TAG, "AudioPlayer Error: Already recording.");
|
||||
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});");
|
||||
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,10 +182,11 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
|
||||
public void stopRecording() {
|
||||
if (this.recorder != null) {
|
||||
try{
|
||||
if (this.state == MEDIA_RUNNING) {
|
||||
if (this.state == STATE.MEDIA_RUNNING) {
|
||||
this.recorder.stop();
|
||||
this.setState(MEDIA_STOPPED);
|
||||
this.setState(STATE.MEDIA_STOPPED);
|
||||
}
|
||||
this.recorder.reset();
|
||||
this.moveFile(this.audioFile);
|
||||
}
|
||||
catch (Exception e) {
|
||||
@@ -183,81 +195,22 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
// Playback
|
||||
//==========================================================================
|
||||
|
||||
/**
|
||||
* Start or resume playing audio file.
|
||||
*
|
||||
*
|
||||
* @param file The name of the audio file.
|
||||
*/
|
||||
public void startPlaying(String file) {
|
||||
if (this.recorder != null) {
|
||||
Log.d(LOG_TAG, "AudioPlayer Error: Can't play in record mode.");
|
||||
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});");
|
||||
}
|
||||
|
||||
// If this is a new request to play audio, or stopped
|
||||
else if ((this.mPlayer == null) || (this.state == MEDIA_STOPPED)) {
|
||||
try {
|
||||
// If stopped, then reset player
|
||||
if (this.mPlayer != null) {
|
||||
this.mPlayer.reset();
|
||||
}
|
||||
// Otherwise, create a new one
|
||||
else {
|
||||
this.mPlayer = new MediaPlayer();
|
||||
}
|
||||
this.audioFile = file;
|
||||
|
||||
// If streaming file
|
||||
if (this.isStreaming(file)) {
|
||||
this.mPlayer.setDataSource(file);
|
||||
this.mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
|
||||
this.setState(MEDIA_STARTING);
|
||||
this.mPlayer.setOnPreparedListener(this);
|
||||
this.mPlayer.prepareAsync();
|
||||
}
|
||||
|
||||
// If local file
|
||||
else {
|
||||
if (file.startsWith("/android_asset/")) {
|
||||
String f = file.substring(15);
|
||||
android.content.res.AssetFileDescriptor fd = this.handler.cordova.getActivity().getAssets().openFd(f);
|
||||
this.mPlayer.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
|
||||
}
|
||||
else {
|
||||
File fp = new File(file);
|
||||
if (fp.exists()) {
|
||||
FileInputStream fileInputStream = new FileInputStream(file);
|
||||
this.mPlayer.setDataSource(fileInputStream.getFD());
|
||||
}
|
||||
else {
|
||||
this.mPlayer.setDataSource("/sdcard/" + file);
|
||||
}
|
||||
}
|
||||
this.setState(MEDIA_STARTING);
|
||||
this.mPlayer.setOnPreparedListener(this);
|
||||
this.mPlayer.prepare();
|
||||
|
||||
// Get duration
|
||||
this.duration = getDurationInSeconds();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});");
|
||||
}
|
||||
}
|
||||
|
||||
// If we have already have created an audio player
|
||||
else {
|
||||
|
||||
// If player has been paused, then resume playback
|
||||
if ((this.state == MEDIA_PAUSED) || (this.state == MEDIA_STARTING)) {
|
||||
this.mPlayer.start();
|
||||
this.setState(MEDIA_RUNNING);
|
||||
}
|
||||
else {
|
||||
Log.d(LOG_TAG, "AudioPlayer Error: startPlaying() called during invalid state: " + this.state);
|
||||
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});");
|
||||
}
|
||||
if (this.readyPlayer(file) && this.player != null) {
|
||||
this.player.start();
|
||||
this.setState(STATE.MEDIA_RUNNING);
|
||||
this.seekOnPrepared = 0; //insures this is always reset
|
||||
} else {
|
||||
this.prepareOnly = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,10 +218,13 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
|
||||
* Seek or jump to a new time in the track.
|
||||
*/
|
||||
public void seekToPlaying(int milliseconds) {
|
||||
if (this.mPlayer != null) {
|
||||
this.mPlayer.seekTo(milliseconds);
|
||||
if (this.readyPlayer(this.audioFile)) {
|
||||
this.player.seekTo(milliseconds);
|
||||
Log.d(LOG_TAG, "Send a onStatus update for the new seek");
|
||||
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_POSITION + ", " + milliseconds / 1000.0f + ");");
|
||||
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_POSITION + ", " + milliseconds / 1000.0f + ");");
|
||||
}
|
||||
else {
|
||||
this.seekOnPrepared = milliseconds;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,13 +234,13 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
|
||||
public void pausePlaying() {
|
||||
|
||||
// If playing, then pause
|
||||
if (this.state == MEDIA_RUNNING) {
|
||||
this.mPlayer.pause();
|
||||
this.setState(MEDIA_PAUSED);
|
||||
if (this.state == STATE.MEDIA_RUNNING && this.player != null) {
|
||||
this.player.pause();
|
||||
this.setState(STATE.MEDIA_PAUSED);
|
||||
}
|
||||
else {
|
||||
Log.d(LOG_TAG, "AudioPlayer Error: pausePlaying() called during invalid state: " + this.state);
|
||||
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_NONE_ACTIVE + "});");
|
||||
Log.d(LOG_TAG, "AudioPlayer Error: pausePlaying() called during invalid state: " + this.state.ordinal());
|
||||
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_NONE_ACTIVE + "});");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,34 +248,37 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
|
||||
* Stop playing the audio file.
|
||||
*/
|
||||
public void stopPlaying() {
|
||||
if ((this.state == MEDIA_RUNNING) || (this.state == MEDIA_PAUSED)) {
|
||||
this.mPlayer.stop();
|
||||
this.setState(MEDIA_STOPPED);
|
||||
if ((this.state == STATE.MEDIA_RUNNING) || (this.state == STATE.MEDIA_PAUSED)) {
|
||||
this.player.pause();
|
||||
this.player.seekTo(0);
|
||||
Log.d(LOG_TAG, "stopPlaying is calling stopped");
|
||||
this.setState(STATE.MEDIA_STOPPED);
|
||||
}
|
||||
else {
|
||||
Log.d(LOG_TAG, "AudioPlayer Error: stopPlaying() called during invalid state: " + this.state);
|
||||
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_NONE_ACTIVE + "});");
|
||||
Log.d(LOG_TAG, "AudioPlayer Error: stopPlaying() called during invalid state: " + this.state.ordinal());
|
||||
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_NONE_ACTIVE + "});");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to be invoked when playback of a media source has completed.
|
||||
*
|
||||
* @param mPlayer The MediaPlayer that reached the end of the file
|
||||
*
|
||||
* @param player The MediaPlayer that reached the end of the file
|
||||
*/
|
||||
public void onCompletion(MediaPlayer mPlayer) {
|
||||
this.setState(MEDIA_STOPPED);
|
||||
public void onCompletion(MediaPlayer player) {
|
||||
Log.d(LOG_TAG, "on completion is calling stopped");
|
||||
this.setState(STATE.MEDIA_STOPPED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current position of playback.
|
||||
*
|
||||
*
|
||||
* @return position in msec or -1 if not playing
|
||||
*/
|
||||
public long getCurrentPosition() {
|
||||
if ((this.state == MEDIA_RUNNING) || (this.state == MEDIA_PAUSED)) {
|
||||
int curPos = this.mPlayer.getCurrentPosition();
|
||||
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_POSITION + ", " + curPos / 1000.0f + ");");
|
||||
if ((this.state == STATE.MEDIA_RUNNING) || (this.state == STATE.MEDIA_PAUSED)) {
|
||||
int curPos = this.player.getCurrentPosition();
|
||||
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_POSITION + ", " + curPos / 1000.0f + ");");
|
||||
return curPos;
|
||||
}
|
||||
else {
|
||||
@@ -330,7 +289,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
|
||||
/**
|
||||
* Determine if playback file is streaming or local.
|
||||
* It is streaming if file name starts with "http://"
|
||||
*
|
||||
*
|
||||
* @param file The file name
|
||||
* @return T=streaming, F=local
|
||||
*/
|
||||
@@ -345,7 +304,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
|
||||
|
||||
/**
|
||||
* Get the duration of the audio file.
|
||||
*
|
||||
*
|
||||
* @param file The name of the audio file.
|
||||
* @return The duration in msec.
|
||||
* -1=can't be determined
|
||||
@@ -359,7 +318,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
|
||||
}
|
||||
|
||||
// If audio file already loaded and started, then return duration
|
||||
if (this.mPlayer != null) {
|
||||
if (this.player != null) {
|
||||
return this.duration;
|
||||
}
|
||||
|
||||
@@ -375,82 +334,93 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to be invoked when the media source is ready for playback.
|
||||
*
|
||||
* @param mPlayer The MediaPlayer that is ready for playback
|
||||
* Callback to be invoked when the media source is ready for playback.
|
||||
*
|
||||
* @param player The MediaPlayer that is ready for playback
|
||||
*/
|
||||
public void onPrepared(MediaPlayer mPlayer) {
|
||||
public void onPrepared(MediaPlayer player) {
|
||||
// Listen for playback completion
|
||||
this.mPlayer.setOnCompletionListener(this);
|
||||
|
||||
this.player.setOnCompletionListener(this);
|
||||
// seek to any location received while not prepared
|
||||
this.seekToPlaying(this.seekOnPrepared);
|
||||
// If start playing after prepared
|
||||
if (!this.prepareOnly) {
|
||||
|
||||
// Start playing
|
||||
this.mPlayer.start();
|
||||
|
||||
// Set player init flag
|
||||
this.setState(MEDIA_RUNNING);
|
||||
this.player.start();
|
||||
this.setState(STATE.MEDIA_RUNNING);
|
||||
this.seekOnPrepared = 0; //reset only when played
|
||||
} else {
|
||||
this.setState(STATE.MEDIA_STARTING);
|
||||
}
|
||||
|
||||
// Save off duration
|
||||
this.duration = getDurationInSeconds();
|
||||
this.prepareOnly = false;
|
||||
// reset prepare only flag
|
||||
this.prepareOnly = true;
|
||||
|
||||
// Send status notification to JavaScript
|
||||
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_DURATION + "," + this.duration + ");");
|
||||
|
||||
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_DURATION + "," + this.duration + ");");
|
||||
}
|
||||
|
||||
/**
|
||||
* By default Android returns the length of audio in mills but we want seconds
|
||||
*
|
||||
*
|
||||
* @return length of clip in seconds
|
||||
*/
|
||||
private float getDurationInSeconds() {
|
||||
return (this.mPlayer.getDuration() / 1000.0f);
|
||||
return (this.player.getDuration() / 1000.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to be invoked when there has been an error during an asynchronous operation
|
||||
* (other errors will throw exceptions at method call time).
|
||||
*
|
||||
* @param mPlayer the MediaPlayer the error pertains to
|
||||
*
|
||||
* @param player the MediaPlayer the error pertains to
|
||||
* @param arg1 the type of error that has occurred: (MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_SERVER_DIED)
|
||||
* @param arg2 an extra code, specific to the error.
|
||||
*/
|
||||
public boolean onError(MediaPlayer mPlayer, int arg1, int arg2) {
|
||||
public boolean onError(MediaPlayer player, int arg1, int arg2) {
|
||||
Log.d(LOG_TAG, "AudioPlayer.onError(" + arg1 + ", " + arg2 + ")");
|
||||
|
||||
// TODO: Not sure if this needs to be sent?
|
||||
this.mPlayer.stop();
|
||||
this.mPlayer.release();
|
||||
this.player.stop();
|
||||
this.player.release();
|
||||
|
||||
// Send error notification to JavaScript
|
||||
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', { \"code\":" + arg1 + "});");
|
||||
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', { \"code\":" + arg1 + "});");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the state and send it to JavaScript.
|
||||
*
|
||||
*
|
||||
* @param state
|
||||
*/
|
||||
private void setState(int state) {
|
||||
private void setState(STATE state) {
|
||||
if (this.state != state) {
|
||||
this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_STATE + ", " + state + ");");
|
||||
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_STATE + ", " + state.ordinal() + ");");
|
||||
}
|
||||
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the mode and send it to JavaScript.
|
||||
*
|
||||
* @param state
|
||||
*/
|
||||
private void setMode(MODE mode) {
|
||||
if (this.mode != mode) {
|
||||
//mode is not part of the expected behavior, so no notification
|
||||
//this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_STATE + ", " + mode + ");");
|
||||
}
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the audio state.
|
||||
*
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public int getState() {
|
||||
return this.state;
|
||||
return this.state.ordinal();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -459,6 +429,120 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On
|
||||
* @param volume
|
||||
*/
|
||||
public void setVolume(float volume) {
|
||||
this.mPlayer.setVolume(volume, volume);
|
||||
this.player.setVolume(volume, volume);
|
||||
}
|
||||
|
||||
/**
|
||||
* attempts to put the player in play mode
|
||||
* @return true if in playmode, false otherwise
|
||||
*/
|
||||
private boolean playMode() {
|
||||
switch(this.mode) {
|
||||
case NONE:
|
||||
this.setMode(MODE.PLAY);
|
||||
break;
|
||||
case PLAY:
|
||||
break;
|
||||
case RECORD:
|
||||
Log.d(LOG_TAG, "AudioPlayer Error: Can't play in record mode.");
|
||||
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});");
|
||||
return false; //player is not ready
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* attempts to initialize the media player for playback
|
||||
* @param file the file to play
|
||||
* @return false if player not ready, reports if in wrong mode or state
|
||||
*/
|
||||
private boolean readyPlayer(String file) {
|
||||
if (playMode()) {
|
||||
switch (this.state) {
|
||||
case MEDIA_NONE:
|
||||
if (this.player == null) {
|
||||
this.player = new MediaPlayer();
|
||||
}
|
||||
try {
|
||||
this.loadAudioFile(file);
|
||||
} catch (Exception e) {
|
||||
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', "+MEDIA_ERROR+", { \"code\":"+MEDIA_ERR_ABORTED+"});");
|
||||
}
|
||||
return false;
|
||||
case MEDIA_LOADING:
|
||||
//cordova js is not aware of MEDIA_LOADING, so we send MEDIA_STARTING instead
|
||||
Log.d(LOG_TAG, "AudioPlayer Loading: startPlaying() called during media preparation: " + STATE.MEDIA_STARTING.ordinal());
|
||||
this.prepareOnly = false;
|
||||
return false;
|
||||
case MEDIA_STARTING:
|
||||
case MEDIA_RUNNING:
|
||||
case MEDIA_PAUSED:
|
||||
return true;
|
||||
case MEDIA_STOPPED:
|
||||
//if we are readying the same file
|
||||
if (this.audioFile.compareTo(file) == 0) {
|
||||
//reset the audio file
|
||||
player.seekTo(0);
|
||||
player.pause();
|
||||
return true;
|
||||
} else {
|
||||
//reset the player
|
||||
this.player.reset();
|
||||
try {
|
||||
this.loadAudioFile(file);
|
||||
} catch (Exception e) {
|
||||
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});");
|
||||
}
|
||||
//if we had to prepare= the file, we won't be in the correct state for playback
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
Log.d(LOG_TAG, "AudioPlayer Error: startPlaying() called during invalid state: " + this.state);
|
||||
this.handler.webView.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_ERROR + ", { \"code\":" + MEDIA_ERR_ABORTED + "});");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* load audio file
|
||||
* @throws IOException
|
||||
* @throws IllegalStateException
|
||||
* @throws SecurityException
|
||||
* @throws IllegalArgumentException
|
||||
*/
|
||||
private void loadAudioFile(String file) throws IllegalArgumentException, SecurityException, IllegalStateException, IOException {
|
||||
if (this.isStreaming(file)) {
|
||||
this.player.setDataSource(file);
|
||||
this.player.setAudioStreamType(AudioManager.STREAM_MUSIC);
|
||||
//if it's a streaming file, play mode is implied
|
||||
this.setMode(MODE.PLAY);
|
||||
this.setState(STATE.MEDIA_STARTING);
|
||||
this.player.setOnPreparedListener(this);
|
||||
this.player.prepareAsync();
|
||||
}
|
||||
else {
|
||||
if (file.startsWith("/android_asset/")) {
|
||||
String f = file.substring(15);
|
||||
android.content.res.AssetFileDescriptor fd = this.handler.cordova.getActivity().getAssets().openFd(f);
|
||||
this.player.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
|
||||
}
|
||||
else {
|
||||
File fp = new File(file);
|
||||
if (fp.exists()) {
|
||||
FileInputStream fileInputStream = new FileInputStream(file);
|
||||
this.player.setDataSource(fileInputStream.getFD());
|
||||
}
|
||||
else {
|
||||
this.player.setDataSource("/sdcard/" + file);
|
||||
}
|
||||
}
|
||||
this.setState(STATE.MEDIA_STARTING);
|
||||
this.player.setOnPreparedListener(this);
|
||||
this.player.prepare();
|
||||
|
||||
// Get duration
|
||||
this.duration = getDurationInSeconds();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import org.apache.cordova.api.Plugin;
|
||||
import org.apache.cordova.api.CallbackContext;
|
||||
import org.apache.cordova.api.CordovaPlugin;
|
||||
import org.apache.cordova.api.PluginResult;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
@@ -30,13 +31,13 @@ import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.util.Log;
|
||||
|
||||
public class BatteryListener extends Plugin {
|
||||
public class BatteryListener extends CordovaPlugin {
|
||||
|
||||
private static final String LOG_TAG = "BatteryManager";
|
||||
|
||||
BroadcastReceiver receiver;
|
||||
|
||||
private String batteryCallbackId = null;
|
||||
private CallbackContext batteryCallbackContext = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@@ -46,22 +47,20 @@ public class BatteryListener extends Plugin {
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the request and returns PluginResult.
|
||||
* Executes the request.
|
||||
*
|
||||
* @param action The action to execute.
|
||||
* @param args JSONArry of arguments for the plugin.
|
||||
* @param callbackId The callback id used when calling back into JavaScript.
|
||||
* @return A PluginResult object with a status and message.
|
||||
* @param 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,426 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* This class provides a way for Java to run JavaScript in the web page that has loaded Cordova.
|
||||
* The CallbackServer class implements an XHR server and a polling server with a list of JavaScript
|
||||
* statements that are to be executed on the web page.
|
||||
*
|
||||
* The process flow for XHR is:
|
||||
* 1. JavaScript makes an async XHR call.
|
||||
* 2. The server holds the connection open until data is available.
|
||||
* 3. The server writes the data to the client and closes the connection.
|
||||
* 4. The server immediately starts listening for the next XHR call.
|
||||
* 5. The client receives this XHR response, processes it.
|
||||
* 6. The client sends a new async XHR request.
|
||||
*
|
||||
* The CallbackServer class requires the following permission in Android manifest file
|
||||
* <uses-permission android:name="android.permission.INTERNET" />
|
||||
*
|
||||
* If the device has a proxy set, then XHR cannot be used, so polling must be used instead.
|
||||
* This can be determined by the client by calling CallbackServer.usePolling().
|
||||
*
|
||||
* The process flow for polling is:
|
||||
* 1. The client calls CallbackServer.getJavascript() to retrieve next statement.
|
||||
* 2. If statement available, then client processes it.
|
||||
* 3. The client repeats #1 in loop.
|
||||
*/
|
||||
public class CallbackServer implements Runnable {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String LOG_TAG = "CallbackServer";
|
||||
|
||||
/**
|
||||
* The list of JavaScript statements to be sent to JavaScript.
|
||||
*/
|
||||
private LinkedList<String> javascript;
|
||||
|
||||
/**
|
||||
* The port to listen on.
|
||||
*/
|
||||
private int port;
|
||||
|
||||
/**
|
||||
* The server thread.
|
||||
*/
|
||||
private Thread serverThread;
|
||||
|
||||
/**
|
||||
* Indicates the server is running.
|
||||
*/
|
||||
private boolean active;
|
||||
|
||||
/**
|
||||
* Indicates that the JavaScript statements list is empty
|
||||
*/
|
||||
private boolean empty;
|
||||
|
||||
/**
|
||||
* Indicates that polling should be used instead of XHR.
|
||||
*/
|
||||
private boolean usePolling = true;
|
||||
|
||||
/**
|
||||
* Security token to prevent other apps from accessing this callback server via XHR
|
||||
*/
|
||||
private String token;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public CallbackServer() {
|
||||
//Log.d(LOG_TAG, "CallbackServer()");
|
||||
this.active = false;
|
||||
this.empty = true;
|
||||
this.port = 0;
|
||||
this.javascript = new LinkedList<String>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init callback server and start XHR if running local app.
|
||||
*
|
||||
* If Cordova app is loaded from file://, then we can use XHR
|
||||
* otherwise we have to use polling due to cross-domain security restrictions.
|
||||
*
|
||||
* @param url The URL of the Cordova app being loaded
|
||||
*/
|
||||
public void init(String url) {
|
||||
//System.out.println("CallbackServer.start("+url+")");
|
||||
this.active = false;
|
||||
this.empty = true;
|
||||
this.port = 0;
|
||||
this.javascript = new LinkedList<String>();
|
||||
|
||||
// Determine if XHR or polling is to be used
|
||||
if ((url != null) && !url.startsWith("file://")) {
|
||||
this.usePolling = true;
|
||||
this.stopServer();
|
||||
}
|
||||
else if (android.net.Proxy.getDefaultHost() != null) {
|
||||
this.usePolling = true;
|
||||
this.stopServer();
|
||||
}
|
||||
else {
|
||||
this.usePolling = false;
|
||||
this.startServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-init when loading a new HTML page into webview.
|
||||
*
|
||||
* @param url The URL of the Cordova app being loaded
|
||||
*/
|
||||
public void reinit(String url) {
|
||||
this.stopServer();
|
||||
this.init(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if polling is being used instead of XHR.
|
||||
* @return
|
||||
*/
|
||||
public boolean usePolling() {
|
||||
return this.usePolling;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the port that this server is running on.
|
||||
* @return
|
||||
*/
|
||||
public int getPort() {
|
||||
return this.port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the security token that this server requires when calling getJavascript().
|
||||
* @return
|
||||
*/
|
||||
public String getToken() {
|
||||
return this.token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the server on a new thread.
|
||||
*/
|
||||
public void startServer() {
|
||||
//Log.d(LOG_TAG, "CallbackServer.startServer()");
|
||||
this.active = false;
|
||||
|
||||
// Start server on new thread
|
||||
this.serverThread = new Thread(this);
|
||||
this.serverThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restart the server on a new thread.
|
||||
*/
|
||||
public void restartServer() {
|
||||
|
||||
// Stop server
|
||||
this.stopServer();
|
||||
|
||||
// Start server again
|
||||
this.startServer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start running the server.
|
||||
* This is called automatically when the server thread is started.
|
||||
*/
|
||||
public void run() {
|
||||
|
||||
// Start server
|
||||
try {
|
||||
this.active = true;
|
||||
String request;
|
||||
ServerSocket waitSocket = new ServerSocket(0);
|
||||
this.port = waitSocket.getLocalPort();
|
||||
//Log.d(LOG_TAG, "CallbackServer -- using port " +this.port);
|
||||
this.token = java.util.UUID.randomUUID().toString();
|
||||
//Log.d(LOG_TAG, "CallbackServer -- using token "+this.token);
|
||||
|
||||
while (this.active) {
|
||||
//Log.d(LOG_TAG, "CallbackServer: Waiting for data on socket");
|
||||
Socket connection = waitSocket.accept();
|
||||
BufferedReader xhrReader = new BufferedReader(new InputStreamReader(connection.getInputStream()), 40);
|
||||
DataOutputStream output = new DataOutputStream(connection.getOutputStream());
|
||||
request = xhrReader.readLine();
|
||||
String response = "";
|
||||
//Log.d(LOG_TAG, "CallbackServerRequest="+request);
|
||||
if (this.active && (request != null)) {
|
||||
if (request.contains("GET")) {
|
||||
|
||||
// Get requested file
|
||||
String[] requestParts = request.split(" ");
|
||||
|
||||
// Must have security token
|
||||
if ((requestParts.length == 3) && (requestParts[1].substring(1).equals(this.token))) {
|
||||
//Log.d(LOG_TAG, "CallbackServer -- Processing GET request");
|
||||
|
||||
// Wait until there is some data to send, or send empty data every 10 sec
|
||||
// to prevent XHR timeout on the client
|
||||
synchronized (this) {
|
||||
while (this.empty) {
|
||||
try {
|
||||
this.wait(10000); // prevent timeout from happening
|
||||
//Log.d(LOG_TAG, "CallbackServer>>> break <<<");
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If server is still running
|
||||
if (this.active) {
|
||||
|
||||
// If no data, then send 404 back to client before it times out
|
||||
if (this.empty) {
|
||||
//Log.d(LOG_TAG, "CallbackServer -- sending data 0");
|
||||
response = "HTTP/1.1 404 NO DATA\r\n\r\n "; // need to send content otherwise some Android devices fail, so send space
|
||||
}
|
||||
else {
|
||||
//Log.d(LOG_TAG, "CallbackServer -- sending item");
|
||||
response = "HTTP/1.1 200 OK\r\n\r\n";
|
||||
String js = this.getJavascript();
|
||||
if (js != null) {
|
||||
response += encode(js, "UTF-8");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
response = "HTTP/1.1 503 Service Unavailable\r\n\r\n ";
|
||||
}
|
||||
}
|
||||
else {
|
||||
response = "HTTP/1.1 403 Forbidden\r\n\r\n ";
|
||||
}
|
||||
}
|
||||
else {
|
||||
response = "HTTP/1.1 400 Bad Request\r\n\r\n ";
|
||||
}
|
||||
//Log.d(LOG_TAG, "CallbackServer: response="+response);
|
||||
//Log.d(LOG_TAG, "CallbackServer: closing output");
|
||||
output.writeBytes(response);
|
||||
output.flush();
|
||||
}
|
||||
output.close();
|
||||
xhrReader.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
this.active = false;
|
||||
//Log.d(LOG_TAG, "CallbackServer.startServer() - EXIT");
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop server.
|
||||
* This stops the thread that the server is running on.
|
||||
*/
|
||||
public void stopServer() {
|
||||
//Log.d(LOG_TAG, "CallbackServer.stopServer()");
|
||||
if (this.active) {
|
||||
this.active = false;
|
||||
|
||||
// Break out of server wait
|
||||
synchronized (this) {
|
||||
this.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy
|
||||
*/
|
||||
public void destroy() {
|
||||
this.stopServer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of JavaScript statements.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public int getSize() {
|
||||
synchronized (this) {
|
||||
int size = this.javascript.size();
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next JavaScript statement and remove from list.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String getJavascript() {
|
||||
synchronized (this) {
|
||||
if (this.javascript.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
String statement = this.javascript.remove(0);
|
||||
if (this.javascript.size() == 0) {
|
||||
this.empty = true;
|
||||
}
|
||||
return statement;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a JavaScript statement to the list.
|
||||
*
|
||||
* @param statement
|
||||
*/
|
||||
public void sendJavascript(String statement) {
|
||||
synchronized (this) {
|
||||
this.javascript.add(statement);
|
||||
this.empty = false;
|
||||
this.notify();
|
||||
}
|
||||
}
|
||||
|
||||
/* The Following code has been modified from original implementation of URLEncoder */
|
||||
|
||||
/* start */
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
static final String digits = "0123456789ABCDEF";
|
||||
|
||||
/**
|
||||
* This will encode the return value to JavaScript. We revert the encoding for
|
||||
* common characters that don't require encoding to reduce the size of the string
|
||||
* being passed to JavaScript.
|
||||
*
|
||||
* @param s to be encoded
|
||||
* @param enc encoding type
|
||||
* @return encoded string
|
||||
*/
|
||||
public static String encode(String s, String enc) throws UnsupportedEncodingException {
|
||||
if (s == null || enc == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
// check for UnsupportedEncodingException
|
||||
"".getBytes(enc);
|
||||
|
||||
// Guess a bit bigger for encoded form
|
||||
StringBuilder buf = new StringBuilder(s.length() + 16);
|
||||
int start = -1;
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char ch = s.charAt(i);
|
||||
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
|
||||
|| (ch >= '0' && ch <= '9')
|
||||
|| " .-*_'(),<>=?@[]{}:~\"\\/;!".indexOf(ch) > -1) {
|
||||
if (start >= 0) {
|
||||
convert(s.substring(start, i), buf, enc);
|
||||
start = -1;
|
||||
}
|
||||
if (ch != ' ') {
|
||||
buf.append(ch);
|
||||
} else {
|
||||
buf.append(' ');
|
||||
}
|
||||
} else {
|
||||
if (start < 0) {
|
||||
start = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (start >= 0) {
|
||||
convert(s.substring(start, s.length()), buf, enc);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static void convert(String s, StringBuilder buf, String enc) throws UnsupportedEncodingException {
|
||||
byte[] bytes = s.getBytes(enc);
|
||||
for (int j = 0; j < bytes.length; j++) {
|
||||
buf.append('%');
|
||||
buf.append(digits.charAt((bytes[j] & 0xf0) >> 4));
|
||||
buf.append(digits.charAt(bytes[j] & 0xf));
|
||||
}
|
||||
}
|
||||
|
||||
/* end */
|
||||
}
|
||||
@@ -27,19 +27,19 @@ import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
//import org.apache.cordova.api.CordovaInterface;
|
||||
import org.apache.cordova.api.CallbackContext;
|
||||
import org.apache.cordova.api.CordovaPlugin;
|
||||
import org.apache.cordova.api.LOG;
|
||||
import org.apache.cordova.api.Plugin;
|
||||
import org.apache.cordova.api.PluginResult;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ContentValues;
|
||||
//import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Bitmap.CompressFormat;
|
||||
import android.media.MediaScannerConnection;
|
||||
@@ -54,10 +54,11 @@ import android.util.Log;
|
||||
* and returns the captured image. When the camera view is closed, the screen displayed before
|
||||
* the camera view was shown is redisplayed.
|
||||
*/
|
||||
public class CameraLauncher extends Plugin implements MediaScannerConnectionClient {
|
||||
public class CameraLauncher extends CordovaPlugin implements MediaScannerConnectionClient {
|
||||
|
||||
private static final int DATA_URL = 0; // Return base64 encoded string
|
||||
private static final int FILE_URI = 1; // Return file uri (content://media/external/images/media/2 for Android)
|
||||
private static final int NATIVE_URI = 2; // On Android, this is the same as FILE_URI
|
||||
|
||||
private static final int PHOTOLIBRARY = 0; // Choose image from picture library (same as SAVEDPHOTOALBUM for Android)
|
||||
private static final int CAMERA = 1; // Take picture from camera
|
||||
@@ -82,11 +83,14 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
|
||||
private int encodingType; // Type of encoding to use
|
||||
private int mediaType; // What type of media to retrieve
|
||||
private boolean saveToPhotoAlbum; // Should the picture be saved to the device's photo album
|
||||
private boolean correctOrientation; // Should the pictures orientation be corrected
|
||||
//private boolean allowEdit; // Should we allow the user to crop the image. UNUSED.
|
||||
|
||||
public String callbackId;
|
||||
public CallbackContext callbackContext;
|
||||
private int numPics;
|
||||
|
||||
|
||||
private MediaScannerConnection conn; // Used to update gallery app with newly-written files
|
||||
private Uri scanMe; // Uri of image to be added to content store
|
||||
|
||||
//This should never be null!
|
||||
//private CordovaInterface cordova;
|
||||
@@ -108,51 +112,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.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 (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;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -181,13 +190,12 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
|
||||
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
|
||||
|
||||
// Specify file so that large image is captured and returned
|
||||
// TODO: What if there isn't any external storage?
|
||||
File photo = createCaptureFile(encodingType);
|
||||
intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(photo));
|
||||
this.imageUri = Uri.fromFile(photo);
|
||||
|
||||
if (this.cordova != null) {
|
||||
this.cordova.startActivityForResult((Plugin) this, intent, (CAMERA + 1) * 16 + returnType + 1);
|
||||
this.cordova.startActivityForResult((CordovaPlugin) this, intent, (CAMERA + 1) * 16 + returnType + 1);
|
||||
}
|
||||
// else
|
||||
// LOG.d(LOG_TAG, "ERROR: You must use the CordovaInterface for this to work correctly. Please implement it in your activity");
|
||||
@@ -239,26 +247,367 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
|
||||
intent.setAction(Intent.ACTION_GET_CONTENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
if (this.cordova != null) {
|
||||
this.cordova.startActivityForResult((Plugin) this, Intent.createChooser(intent,
|
||||
this.cordova.startActivityForResult((CordovaPlugin) this, Intent.createChooser(intent,
|
||||
new String(title)), (srcType + 1) * 16 + returnType + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scales the bitmap according to the requested size.
|
||||
* Called when the camera view exits.
|
||||
*
|
||||
* @param bitmap The bitmap to scale.
|
||||
* @return Bitmap A new Bitmap object of the same bitmap after scaling.
|
||||
* @param requestCode The request code originally supplied to startActivityForResult(),
|
||||
* allowing you to identify who this result came from.
|
||||
* @param resultCode The integer result code returned by the child activity through its setResult().
|
||||
* @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
|
||||
*/
|
||||
public Bitmap scaleBitmap(Bitmap bitmap) {
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||
|
||||
// Get src and dest types from request code
|
||||
int srcType = (requestCode / 16) - 1;
|
||||
int destType = (requestCode % 16) - 1;
|
||||
int rotate = 0;
|
||||
|
||||
// If CAMERA
|
||||
if (srcType == CAMERA) {
|
||||
// If image available
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
try {
|
||||
// Create an ExifHelper to save the exif data that is lost during compression
|
||||
ExifHelper exif = new ExifHelper();
|
||||
try {
|
||||
if (this.encodingType == JPEG) {
|
||||
exif.createInFile(DirectoryManager.getTempDirectoryPath(this.cordova.getActivity()) + "/.Pic.jpg");
|
||||
exif.readExifData();
|
||||
rotate = exif.getOrientation();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
Bitmap bitmap = null;
|
||||
Uri uri = null;
|
||||
|
||||
// If sending base64 image back
|
||||
if (destType == DATA_URL) {
|
||||
bitmap = getScaledBitmap(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);
|
||||
}
|
||||
|
||||
this.processPicture(bitmap);
|
||||
checkForDuplicateImage(DATA_URL);
|
||||
}
|
||||
|
||||
// If sending filename back
|
||||
else if (destType == FILE_URI || destType == NATIVE_URI) {
|
||||
if (!this.saveToPhotoAlbum) {
|
||||
uri = Uri.fromFile(new File(DirectoryManager.getTempDirectoryPath(this.cordova.getActivity()), System.currentTimeMillis() + ".jpg"));
|
||||
} else {
|
||||
uri = getUriFromMediaStore();
|
||||
}
|
||||
|
||||
if (uri == null) {
|
||||
this.failPicture("Error capturing image - no media storage found.");
|
||||
}
|
||||
|
||||
// If all this is true we shouldn't compress the image.
|
||||
if (this.targetHeight == -1 && this.targetWidth == -1 && this.mQuality == 100 &&
|
||||
!this.correctOrientation) {
|
||||
writeUncompressedImage(uri);
|
||||
|
||||
this.callbackContext.success(uri.toString());
|
||||
} else {
|
||||
bitmap = getScaledBitmap(FileUtils.stripFileProtocol(imageUri.toString()));
|
||||
|
||||
if (rotate != 0 && this.correctOrientation) {
|
||||
bitmap = getRotatedBitmap(rotate, bitmap, exif);
|
||||
}
|
||||
|
||||
// Add compressed version of captured image to returned media store Uri
|
||||
OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os);
|
||||
os.close();
|
||||
|
||||
// Restore exif data to file
|
||||
if (this.encodingType == JPEG) {
|
||||
String exifPath;
|
||||
if (this.saveToPhotoAlbum) {
|
||||
exifPath = FileUtils.getRealPathFromURI(uri, this.cordova);
|
||||
} else {
|
||||
exifPath = uri.getPath();
|
||||
}
|
||||
exif.createOutFile(exifPath);
|
||||
exif.writeExifData();
|
||||
}
|
||||
|
||||
}
|
||||
// Send Uri back to JavaScript for viewing image
|
||||
this.callbackContext.success(uri.toString());
|
||||
}
|
||||
|
||||
this.cleanup(FILE_URI, this.imageUri, uri, bitmap);
|
||||
bitmap = null;
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
this.failPicture("Error capturing image.");
|
||||
}
|
||||
}
|
||||
|
||||
// If cancelled
|
||||
else if (resultCode == Activity.RESULT_CANCELED) {
|
||||
this.failPicture("Camera cancelled.");
|
||||
}
|
||||
|
||||
// If something else
|
||||
else {
|
||||
this.failPicture("Did not complete!");
|
||||
}
|
||||
}
|
||||
|
||||
// If retrieving photo from library
|
||||
else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
Uri uri = intent.getData();
|
||||
|
||||
// If you ask for video or all media type you will automatically get back a file URI
|
||||
// and there will be no attempt to resize any returned data
|
||||
if (this.mediaType != PICTURE) {
|
||||
this.callbackContext.success(uri.toString());
|
||||
}
|
||||
else {
|
||||
// This is a special case to just return the path as no scaling,
|
||||
// rotating, nor compressing needs to be done
|
||||
if (this.targetHeight == -1 && this.targetWidth == -1 &&
|
||||
(destType == FILE_URI || destType == NATIVE_URI) && !this.correctOrientation) {
|
||||
this.callbackContext.success(uri.toString());
|
||||
} else {
|
||||
// Get the path to the image. Makes loading so much easier.
|
||||
String imagePath = FileUtils.getRealPathFromURI(uri, this.cordova);
|
||||
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 || mimeType == null ||
|
||||
!(mimeType.equalsIgnoreCase("image/jpeg") || mimeType.equalsIgnoreCase("image/png"))) {
|
||||
Log.d(LOG_TAG, "I either have a null image path or bitmap");
|
||||
this.failPicture("Unable to retrieve path to picture!");
|
||||
return;
|
||||
}
|
||||
Bitmap bitmap = getScaledBitmap(imagePath);
|
||||
if (bitmap == null) {
|
||||
Log.d(LOG_TAG, "I either have a null image path or bitmap");
|
||||
this.failPicture("Unable to create bitmap!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.correctOrientation) {
|
||||
String[] cols = { MediaStore.Images.Media.ORIENTATION };
|
||||
Cursor cursor = this.cordova.getActivity().getContentResolver().query(intent.getData(),
|
||||
cols, null, null, null);
|
||||
if (cursor != null) {
|
||||
cursor.moveToPosition(0);
|
||||
rotate = cursor.getInt(0);
|
||||
cursor.close();
|
||||
}
|
||||
if (rotate != 0) {
|
||||
Matrix matrix = new Matrix();
|
||||
matrix.setRotate(rotate);
|
||||
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
|
||||
}
|
||||
}
|
||||
|
||||
// If sending base64 image back
|
||||
if (destType == DATA_URL) {
|
||||
this.processPicture(bitmap);
|
||||
}
|
||||
|
||||
// If sending filename back
|
||||
else if (destType == FILE_URI || destType == NATIVE_URI) {
|
||||
// Do we need to scale the returned file
|
||||
if (this.targetHeight > 0 && this.targetWidth > 0) {
|
||||
try {
|
||||
// Create an ExifHelper to save the exif data that is lost during compression
|
||||
String resizePath = DirectoryManager.getTempDirectoryPath(this.cordova.getActivity()) + "/resize.jpg";
|
||||
ExifHelper exif = new ExifHelper();
|
||||
try {
|
||||
if (this.encodingType == JPEG) {
|
||||
exif.createInFile(resizePath);
|
||||
exif.readExifData();
|
||||
rotate = exif.getOrientation();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
OutputStream os = new FileOutputStream(resizePath);
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os);
|
||||
os.close();
|
||||
|
||||
// Restore exif data to file
|
||||
if (this.encodingType == JPEG) {
|
||||
exif.createOutFile(FileUtils.getRealPathFromURI(uri, this.cordova));
|
||||
exif.writeExifData();
|
||||
}
|
||||
|
||||
// The resized image is cached by the app in order to get around this and not have to delete you
|
||||
// application cache I'm adding the current system time to the end of the file url.
|
||||
this.callbackContext.success("file://" + resizePath + "?" + System.currentTimeMillis());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
this.failPicture("Error retrieving image.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.callbackContext.success(uri.toString());
|
||||
}
|
||||
}
|
||||
if (bitmap != null) {
|
||||
bitmap.recycle();
|
||||
bitmap = null;
|
||||
}
|
||||
System.gc();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (resultCode == Activity.RESULT_CANCELED) {
|
||||
this.failPicture("Selection cancelled.");
|
||||
}
|
||||
else {
|
||||
this.failPicture("Selection did not complete!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Figure out if the bitmap should be rotated. For instance if the picture was taken in
|
||||
* portrait mode
|
||||
*
|
||||
* @param rotate
|
||||
* @param bitmap
|
||||
* @return rotated bitmap
|
||||
*/
|
||||
private Bitmap getRotatedBitmap(int rotate, Bitmap bitmap, ExifHelper exif) {
|
||||
Matrix matrix = new Matrix();
|
||||
if (rotate == 180) {
|
||||
matrix.setRotate(rotate);
|
||||
} else {
|
||||
matrix.setRotate(rotate, (float) bitmap.getWidth() / 2, (float) bitmap.getHeight() / 2);
|
||||
}
|
||||
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
|
||||
exif.resetOrientation();
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
/**
|
||||
* In the special case where the default width, height and quality are unchanged
|
||||
* we just write the file out to disk saving the expensive Bitmap.compress function.
|
||||
*
|
||||
* @param uri
|
||||
* @throws FileNotFoundException
|
||||
* @throws IOException
|
||||
*/
|
||||
private void writeUncompressedImage(Uri uri) throws FileNotFoundException,
|
||||
IOException {
|
||||
FileInputStream fis = new FileInputStream(FileUtils.stripFileProtocol(imageUri.toString()));
|
||||
OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
|
||||
byte[] buffer = new byte[4096];
|
||||
int len;
|
||||
while ((len = fis.read(buffer)) != -1) {
|
||||
os.write(buffer, 0, len);
|
||||
}
|
||||
os.flush();
|
||||
os.close();
|
||||
fis.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create entry in media store for image
|
||||
*
|
||||
* @return uri
|
||||
*/
|
||||
private Uri getUriFromMediaStore() {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(android.provider.MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
|
||||
Uri uri;
|
||||
try {
|
||||
uri = this.cordova.getActivity().getContentResolver().insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
|
||||
} catch (UnsupportedOperationException e) {
|
||||
LOG.d(LOG_TAG, "Can't write to external media storage.");
|
||||
try {
|
||||
uri = this.cordova.getActivity().getContentResolver().insert(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, values);
|
||||
} catch (UnsupportedOperationException ex) {
|
||||
LOG.d(LOG_TAG, "Can't write to internal media storage.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a scaled bitmap based on the target width and height
|
||||
*
|
||||
* @param imagePath
|
||||
* @return
|
||||
*/
|
||||
private Bitmap getScaledBitmap(String imagePath) {
|
||||
// If no new width or height were specified return the original bitmap
|
||||
if (this.targetWidth <= 0 && this.targetHeight <= 0) {
|
||||
return BitmapFactory.decodeFile(imagePath);
|
||||
}
|
||||
|
||||
// figure out the original width and height of the image
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeFile(imagePath, options);
|
||||
|
||||
//CB-2292: WTF? Why is the width null?
|
||||
if(options.outWidth == 0 || options.outHeight == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// determine the correct aspect ratio
|
||||
int[] widthHeight = calculateAspectRatio(options.outWidth, options.outHeight);
|
||||
|
||||
// Load in the smallest bitmap possible that is closest to the size we want
|
||||
options.inJustDecodeBounds = false;
|
||||
options.inSampleSize = calculateSampleSize(options.outWidth, options.outHeight, this.targetWidth, this.targetHeight);
|
||||
Bitmap unscaledBitmap = BitmapFactory.decodeFile(imagePath, options);
|
||||
if (unscaledBitmap == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Bitmap.createScaledBitmap(unscaledBitmap, widthHeight[0], widthHeight[1], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maintain the aspect ratio so the resulting image does not look smooshed
|
||||
*
|
||||
* @param origWidth
|
||||
* @param origHeight
|
||||
* @return
|
||||
*/
|
||||
public int[] calculateAspectRatio(int origWidth, int origHeight) {
|
||||
int newWidth = this.targetWidth;
|
||||
int newHeight = this.targetHeight;
|
||||
int origWidth = bitmap.getWidth();
|
||||
int origHeight = bitmap.getHeight();
|
||||
|
||||
// If no new width or height were specified return the original bitmap
|
||||
if (newWidth <= 0 && newHeight <= 0) {
|
||||
return bitmap;
|
||||
newWidth = origWidth;
|
||||
newHeight = origHeight;
|
||||
}
|
||||
// Only the width was specified
|
||||
else if (newWidth > 0 && newHeight <= 0) {
|
||||
@@ -285,237 +634,32 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
|
||||
}
|
||||
}
|
||||
|
||||
Bitmap retval = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true);
|
||||
bitmap.recycle();
|
||||
System.gc();
|
||||
int[] retval = new int[2];
|
||||
retval[0] = newWidth;
|
||||
retval[1] = newHeight;
|
||||
return retval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the camera view exits.
|
||||
* Figure out what ratio we can load our image into memory at while still being bigger than
|
||||
* our desired width and height
|
||||
*
|
||||
* @param requestCode The request code originally supplied to startActivityForResult(),
|
||||
* allowing you to identify who this result came from.
|
||||
* @param resultCode The integer result code returned by the child activity through its setResult().
|
||||
* @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
|
||||
* @param srcWidth
|
||||
* @param srcHeight
|
||||
* @param dstWidth
|
||||
* @param dstHeight
|
||||
* @return
|
||||
*/
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||
public static int calculateSampleSize(int srcWidth, int srcHeight, int dstWidth, int dstHeight) {
|
||||
final float srcAspect = (float)srcWidth / (float)srcHeight;
|
||||
final float dstAspect = (float)dstWidth / (float)dstHeight;
|
||||
|
||||
// Get src and dest types from request code
|
||||
int srcType = (requestCode / 16) - 1;
|
||||
int destType = (requestCode % 16) - 1;
|
||||
int rotate = 0;
|
||||
|
||||
// Create an ExifHelper to save the exif data that is lost during compression
|
||||
ExifHelper exif = new ExifHelper();
|
||||
try {
|
||||
if (this.encodingType == JPEG) {
|
||||
exif.createInFile(DirectoryManager.getTempDirectoryPath(this.cordova.getActivity()) + "/.Pic.jpg");
|
||||
exif.readExifData();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
if (srcAspect > dstAspect) {
|
||||
return srcWidth / dstWidth;
|
||||
} else {
|
||||
return srcHeight / dstHeight;
|
||||
}
|
||||
// If CAMERA
|
||||
if (srcType == CAMERA) {
|
||||
// If image available
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
try {
|
||||
Bitmap bitmap = null;
|
||||
|
||||
// If sending base64 image back
|
||||
if (destType == DATA_URL) {
|
||||
bitmap = scaleBitmap(getBitmapFromResult(intent));
|
||||
|
||||
this.processPicture(bitmap);
|
||||
checkForDuplicateImage(DATA_URL);
|
||||
}
|
||||
|
||||
// If sending filename back
|
||||
else if (destType == FILE_URI) {
|
||||
Uri uri;
|
||||
if (!this.saveToPhotoAlbum) {
|
||||
uri = Uri.fromFile(new File("/data/data/" + this.cordova.getActivity().getPackageName() + "/", (new File(FileUtils.stripFileProtocol(this.imageUri.toString()))).getName()));
|
||||
} else {
|
||||
// Create entry in media store for image
|
||||
// (Don't use insertImage() because it uses default compression setting of 50 - no way to change it)
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(android.provider.MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
|
||||
|
||||
try {
|
||||
uri = this.cordova.getActivity().getContentResolver().insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
|
||||
} catch (UnsupportedOperationException e) {
|
||||
LOG.d(LOG_TAG, "Can't write to external media storage.");
|
||||
try {
|
||||
uri = this.cordova.getActivity().getContentResolver().insert(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, values);
|
||||
} catch (UnsupportedOperationException ex) {
|
||||
LOG.d(LOG_TAG, "Can't write to internal media storage.");
|
||||
this.failPicture("Error capturing image - no media storage found.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If all this is true we shouldn't compress the image.
|
||||
if (this.targetHeight == -1 && this.targetWidth == -1 && this.mQuality == 100) {
|
||||
FileInputStream fis = new FileInputStream(FileUtils.stripFileProtocol(imageUri.toString()));
|
||||
OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
|
||||
byte[] buffer = new byte[4096];
|
||||
int len;
|
||||
while ((len = fis.read(buffer)) != -1) {
|
||||
os.write(buffer, 0, len);
|
||||
}
|
||||
os.flush();
|
||||
os.close();
|
||||
fis.close();
|
||||
|
||||
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
|
||||
} else {
|
||||
|
||||
bitmap = scaleBitmap(getBitmapFromResult(intent));
|
||||
|
||||
// Add compressed version of captured image to returned media store Uri
|
||||
OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os);
|
||||
os.close();
|
||||
|
||||
// Restore exif data to file
|
||||
|
||||
if (this.encodingType == JPEG) {
|
||||
String exifPath;
|
||||
if (this.saveToPhotoAlbum) {
|
||||
exifPath = FileUtils.getRealPathFromURI(uri, this.cordova);
|
||||
} else {
|
||||
exifPath = uri.getPath();
|
||||
}
|
||||
exif.createOutFile(exifPath);
|
||||
exif.writeExifData();
|
||||
}
|
||||
|
||||
}
|
||||
// Send Uri back to JavaScript for viewing image
|
||||
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
|
||||
}
|
||||
|
||||
this.cleanup(FILE_URI, this.imageUri, bitmap);
|
||||
bitmap = null;
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
this.failPicture("Error capturing image.");
|
||||
}
|
||||
}
|
||||
|
||||
// If cancelled
|
||||
else if (resultCode == Activity.RESULT_CANCELED) {
|
||||
this.failPicture("Camera cancelled.");
|
||||
}
|
||||
|
||||
// If something else
|
||||
else {
|
||||
this.failPicture("Did not complete!");
|
||||
}
|
||||
}
|
||||
|
||||
// If retrieving photo from library
|
||||
else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
Uri uri = intent.getData();
|
||||
android.content.ContentResolver resolver = this.cordova.getActivity().getContentResolver();
|
||||
|
||||
// If you ask for video or all media type you will automatically get back a file URI
|
||||
// and there will be no attempt to resize any returned data
|
||||
if (this.mediaType != PICTURE) {
|
||||
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
|
||||
}
|
||||
else {
|
||||
// If sending base64 image back
|
||||
if (destType == DATA_URL) {
|
||||
try {
|
||||
Bitmap bitmap = android.graphics.BitmapFactory.decodeStream(resolver.openInputStream(uri));
|
||||
String[] cols = { MediaStore.Images.Media.ORIENTATION };
|
||||
Cursor cursor = this.cordova.getActivity().getContentResolver().query(intent.getData(),
|
||||
cols,
|
||||
null, null, null);
|
||||
if (cursor != null) {
|
||||
cursor.moveToPosition(0);
|
||||
rotate = cursor.getInt(0);
|
||||
cursor.close();
|
||||
}
|
||||
if (rotate != 0) {
|
||||
Matrix matrix = new Matrix();
|
||||
matrix.setRotate(rotate);
|
||||
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
|
||||
}
|
||||
bitmap = scaleBitmap(bitmap);
|
||||
this.processPicture(bitmap);
|
||||
bitmap.recycle();
|
||||
bitmap = null;
|
||||
System.gc();
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
this.failPicture("Error retrieving image.");
|
||||
}
|
||||
}
|
||||
|
||||
// If sending filename back
|
||||
else if (destType == FILE_URI) {
|
||||
// Do we need to scale the returned file
|
||||
if (this.targetHeight > 0 && this.targetWidth > 0) {
|
||||
try {
|
||||
Bitmap bitmap = android.graphics.BitmapFactory.decodeStream(resolver.openInputStream(uri));
|
||||
bitmap = scaleBitmap(bitmap);
|
||||
|
||||
String fileName = DirectoryManager.getTempDirectoryPath(this.cordova.getActivity()) + "/resize.jpg";
|
||||
OutputStream os = new FileOutputStream(fileName);
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os);
|
||||
os.close();
|
||||
|
||||
// Restore exif data to file
|
||||
if (this.encodingType == JPEG) {
|
||||
exif.createOutFile(FileUtils.getRealPathFromURI(uri, this.cordova));
|
||||
exif.writeExifData();
|
||||
}
|
||||
|
||||
bitmap.recycle();
|
||||
bitmap = null;
|
||||
|
||||
// The resized image is cached by the app in order to get around this and not have to delete you
|
||||
// application cache I'm adding the current system time to the end of the file url.
|
||||
this.success(new PluginResult(PluginResult.Status.OK, ("file://" + fileName + "?" + System.currentTimeMillis())), this.callbackId);
|
||||
System.gc();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
this.failPicture("Error retrieving image.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (resultCode == Activity.RESULT_CANCELED) {
|
||||
this.failPicture("Selection cancelled.");
|
||||
}
|
||||
else {
|
||||
this.failPicture("Selection did not complete!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Bitmap getBitmapFromResult(Intent intent)
|
||||
throws IOException, FileNotFoundException {
|
||||
Bitmap bitmap = null;
|
||||
try {
|
||||
bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getActivity().getContentResolver(), imageUri);
|
||||
} catch (FileNotFoundException e) {
|
||||
Uri uri = intent.getData();
|
||||
android.content.ContentResolver resolver = this.ctx.getActivity().getContentResolver();
|
||||
bitmap = android.graphics.BitmapFactory.decodeStream(resolver.openInputStream(uri));
|
||||
}
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a cursor that can be used to determine how many images we have.
|
||||
@@ -530,20 +674,25 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Cleans up after picture taking. Checking for duplicates and that kind of stuff.
|
||||
* @param newImage
|
||||
*/
|
||||
private void cleanup(int imageType, Uri oldImage, Bitmap bitmap) {
|
||||
bitmap.recycle();
|
||||
|
||||
private void cleanup(int imageType, Uri oldImage, Uri newImage, Bitmap bitmap) {
|
||||
if (bitmap != null) {
|
||||
bitmap.recycle();
|
||||
}
|
||||
|
||||
// Clean up initial camera-written image file.
|
||||
(new File(FileUtils.stripFileProtocol(oldImage.toString()))).delete();
|
||||
|
||||
|
||||
checkForDuplicateImage(imageType);
|
||||
// Scan for the gallery to update pic refs in gallery
|
||||
this.scanForGallery();
|
||||
|
||||
if (this.saveToPhotoAlbum && newImage != null) {
|
||||
this.scanForGallery(newImage);
|
||||
}
|
||||
|
||||
System.gc();
|
||||
}
|
||||
|
||||
@@ -560,14 +709,17 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
|
||||
Cursor cursor = queryImgDB(contentStore);
|
||||
int currentNumOfImages = cursor.getCount();
|
||||
|
||||
if (type == FILE_URI) {
|
||||
if (type == FILE_URI && this.saveToPhotoAlbum) {
|
||||
diff = 2;
|
||||
}
|
||||
|
||||
// delete the duplicate file if the difference is 2 for file URI or 1 for Data URL
|
||||
if ((currentNumOfImages - numPics) == diff) {
|
||||
cursor.moveToLast();
|
||||
int id = Integer.valueOf(cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media._ID))) - 1;
|
||||
int id = Integer.valueOf(cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media._ID)));
|
||||
if (diff == 2) {
|
||||
id--;
|
||||
}
|
||||
Uri uri = Uri.parse(contentStore + "/" + id);
|
||||
this.cordova.getActivity().getContentResolver().delete(uri, null, null);
|
||||
}
|
||||
@@ -597,7 +749,7 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie
|
||||
byte[] code = jpeg_data.toByteArray();
|
||||
byte[] output = Base64.encodeBase64(code);
|
||||
String js_out = new String(output);
|
||||
this.success(new PluginResult(PluginResult.Status.OK, js_out), this.callbackId);
|
||||
this.callbackContext.success(js_out);
|
||||
js_out = null;
|
||||
output = null;
|
||||
code = null;
|
||||
@@ -614,26 +766,28 @@ 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) {
|
||||
this.scanMe = newImage;
|
||||
if(this.conn != null) {
|
||||
this.conn.disconnect();
|
||||
}
|
||||
this.conn = new MediaScannerConnection(this.cordova.getActivity().getApplicationContext(), this);
|
||||
conn.connect();
|
||||
}
|
||||
|
||||
private void scanForGallery() {
|
||||
if(this.conn!=null) this.conn.disconnect();
|
||||
this.conn = new MediaScannerConnection(this.ctx.getActivity().getApplicationContext(), this);
|
||||
conn.connect();
|
||||
}
|
||||
|
||||
public void onMediaScannerConnected() {
|
||||
try{
|
||||
this.conn.scanFile(this.imageUri.toString(), "image/*");
|
||||
this.conn.scanFile(this.scanMe.toString(), "image/*");
|
||||
} catch (java.lang.IllegalStateException e){
|
||||
e.printStackTrace();
|
||||
LOG.d(LOG_TAG, "Can;t scan file in MediaScanner aftering taking picture");
|
||||
LOG.e(LOG_TAG, "Can't scan file in MediaScanner after taking picture");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void onScanCompleted(String path, Uri uri) {
|
||||
this.conn.disconnect();
|
||||
this.conn.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
273
framework/src/org/apache/cordova/Config.java
Normal file
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.cordova.api.LOG;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import android.content.res.XmlResourceParser;
|
||||
import android.graphics.Color;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
public class Config {
|
||||
|
||||
public static final String TAG = "Config";
|
||||
|
||||
private ArrayList<Pattern> whiteList = new ArrayList<Pattern>();
|
||||
private HashMap<String, Boolean> whiteListCache = new HashMap<String, Boolean>();
|
||||
private String startUrl;
|
||||
|
||||
private static Config self = null;
|
||||
|
||||
public static void init(Activity action) {
|
||||
if (self == null) {
|
||||
self = new Config(action);
|
||||
}
|
||||
}
|
||||
|
||||
// Intended to be used for testing only; creates an empty configuration.
|
||||
public static void init() {
|
||||
if (self == null) {
|
||||
self = new Config();
|
||||
}
|
||||
}
|
||||
|
||||
// Intended to be used for testing only; creates an empty configuration.
|
||||
private Config() {
|
||||
}
|
||||
|
||||
private Config(Activity action) {
|
||||
if (action == null) {
|
||||
LOG.i("CordovaLog", "There is no activity. Is this on the lock screen?");
|
||||
return;
|
||||
}
|
||||
|
||||
int id = action.getResources().getIdentifier("config", "xml", action.getPackageName());
|
||||
if (id == 0) {
|
||||
id = action.getResources().getIdentifier("cordova", "xml", action.getPackageName());
|
||||
LOG.i("CordovaLog", "config.xml missing, reverting to cordova.xml");
|
||||
}
|
||||
if (id == 0) {
|
||||
LOG.i("CordovaLog", "cordova.xml missing. Ignoring...");
|
||||
return;
|
||||
}
|
||||
|
||||
XmlResourceParser xml = action.getResources().getXml(id);
|
||||
int eventType = -1;
|
||||
while (eventType != XmlResourceParser.END_DOCUMENT) {
|
||||
if (eventType == XmlResourceParser.START_TAG) {
|
||||
String strNode = xml.getName();
|
||||
|
||||
if (strNode.equals("access")) {
|
||||
String origin = xml.getAttributeValue(null, "origin");
|
||||
String subdomains = xml.getAttributeValue(null, "subdomains");
|
||||
if (origin != null) {
|
||||
this._addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0));
|
||||
}
|
||||
}
|
||||
else if (strNode.equals("log")) {
|
||||
String level = xml.getAttributeValue(null, "level");
|
||||
LOG.i("CordovaLog", "Found log level %s", level);
|
||||
if (level != null) {
|
||||
LOG.setLogLevel(level);
|
||||
}
|
||||
}
|
||||
else if (strNode.equals("preference")) {
|
||||
String name = xml.getAttributeValue(null, "name");
|
||||
/* Java 1.6 does not support switch-based strings
|
||||
Java 7 does, but we're using Dalvik, which is apparently not Java.
|
||||
Since we're reading XML, this has to be an ugly if/else.
|
||||
|
||||
Also, due to cast issues, each of them has to call their separate putExtra!
|
||||
Wheee!!! Isn't Java FUN!?!?!?
|
||||
|
||||
Note: We should probably pass in the classname for the variable splash on splashscreen!
|
||||
*/
|
||||
if(name.equals("splashscreen")) {
|
||||
String value = xml.getAttributeValue(null, "value");
|
||||
int resource = 0;
|
||||
if (value != null)
|
||||
{
|
||||
value = "splash";
|
||||
}
|
||||
resource = action.getResources().getIdentifier(value, "drawable", action.getPackageName());
|
||||
|
||||
action.getIntent().putExtra(name, resource);
|
||||
LOG.i("CordovaLog", "Found preference for %s=%s", name, value);
|
||||
Log.d("CordovaLog", "Found preference for " + name + "=" + value);
|
||||
}
|
||||
else if(name.equals("backgroundColor")) {
|
||||
int value = xml.getAttributeIntValue(null, "value", Color.BLACK);
|
||||
action.getIntent().putExtra(name, value);
|
||||
LOG.i("CordovaLog", "Found preference for %s=%d", name, value);
|
||||
Log.d("CordovaLog", "Found preference for " + name + "=" + Integer.toString(value));
|
||||
}
|
||||
else if(name.equals("loadUrlTimeoutValue")) {
|
||||
int value = xml.getAttributeIntValue(null, "value", 20000);
|
||||
action.getIntent().putExtra(name, value);
|
||||
LOG.i("CordovaLog", "Found preference for %s=%d", name, value);
|
||||
Log.d("CordovaLog", "Found preference for " + name + "=" + Integer.toString(value));
|
||||
}
|
||||
else if(name.equals("keepRunning"))
|
||||
{
|
||||
boolean value = xml.getAttributeValue(null, "value").equals("true");
|
||||
action.getIntent().putExtra(name, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
String value = xml.getAttributeValue(null, "value");
|
||||
action.getIntent().putExtra(name, value);
|
||||
LOG.i("CordovaLog", "Found preference for %s=%s", name, value);
|
||||
Log.d("CordovaLog", "Found preference for " + name + "=" + value);
|
||||
}
|
||||
/*
|
||||
LOG.i("CordovaLog", "Found preference for %s=%s", name, value);
|
||||
Log.d("CordovaLog", "Found preference for " + name + "=" + value);
|
||||
*/
|
||||
}
|
||||
else if (strNode.equals("content")) {
|
||||
String src = xml.getAttributeValue(null, "src");
|
||||
|
||||
LOG.i("CordovaLog", "Found start page location: %s", src);
|
||||
|
||||
if (src != null) {
|
||||
Pattern schemeRegex = Pattern.compile("^[a-z]+://");
|
||||
Matcher matcher = schemeRegex.matcher(src);
|
||||
if (matcher.find()) {
|
||||
startUrl = src;
|
||||
} else {
|
||||
if (src.charAt(0) == '/') {
|
||||
src = src.substring(1);
|
||||
}
|
||||
startUrl = "file:///android_asset/www/" + src;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
eventType = xml.next();
|
||||
} catch (XmlPullParserException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add entry to approved list of URLs (whitelist)
|
||||
*
|
||||
* @param origin URL regular expression to allow
|
||||
* @param subdomains T=include all subdomains under origin
|
||||
*/
|
||||
public static void addWhiteListEntry(String origin, boolean subdomains) {
|
||||
if (self == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
self._addWhiteListEntry(origin, subdomains);
|
||||
}
|
||||
|
||||
|
||||
private void _addWhiteListEntry(String origin, boolean subdomains) {
|
||||
try {
|
||||
// Unlimited access to network resources
|
||||
if (origin.compareTo("*") == 0) {
|
||||
LOG.d(TAG, "Unlimited access to network resources");
|
||||
this.whiteList.add(Pattern.compile(".*"));
|
||||
} else { // specific access
|
||||
// check if subdomains should be included
|
||||
// TODO: we should not add more domains if * has already been added
|
||||
if (subdomains) {
|
||||
// XXX making it stupid friendly for people who forget to include protocol/SSL
|
||||
if (origin.startsWith("http")) {
|
||||
this.whiteList.add(Pattern.compile(origin.replaceFirst("https?://", "^https?://(.*\\.)?")));
|
||||
} else {
|
||||
this.whiteList.add(Pattern.compile("^https?://(.*\\.)?" + origin));
|
||||
}
|
||||
LOG.d(TAG, "Origin to allow with subdomains: %s", origin);
|
||||
} else {
|
||||
// XXX making it stupid friendly for people who forget to include protocol/SSL
|
||||
if (origin.startsWith("http")) {
|
||||
this.whiteList.add(Pattern.compile(origin.replaceFirst("https?://", "^https?://")));
|
||||
} else {
|
||||
this.whiteList.add(Pattern.compile("^https?://" + origin));
|
||||
}
|
||||
LOG.d(TAG, "Origin to allow: %s", origin);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.d(TAG, "Failed to add origin %s", origin);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if URL is in approved list of URLs to load.
|
||||
*
|
||||
* @param url
|
||||
* @return
|
||||
*/
|
||||
public static boolean isUrlWhiteListed(String url) {
|
||||
if (self == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check to see if we have matched url previously
|
||||
if (self.whiteListCache.get(url) != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Look for match in white list
|
||||
Iterator<Pattern> pit = self.whiteList.iterator();
|
||||
while (pit.hasNext()) {
|
||||
Pattern p = pit.next();
|
||||
Matcher m = p.matcher(url);
|
||||
|
||||
// If match found, then cache it to speed up subsequent comparisons
|
||||
if (m.find()) {
|
||||
self.whiteListCache.put(url, true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static String getStartUrl() {
|
||||
if (self == null || self.startUrl == null) {
|
||||
return "file:///android_asset/www/index.html";
|
||||
}
|
||||
return self.startUrl;
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ package org.apache.cordova;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.webkit.WebView;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
112
framework/src/org/apache/cordova/CordovaArgs.java
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
||||
import android.util.Base64;
|
||||
|
||||
public class CordovaArgs {
|
||||
private JSONArray baseArgs;
|
||||
|
||||
public CordovaArgs(JSONArray args) {
|
||||
this.baseArgs = args;
|
||||
}
|
||||
|
||||
|
||||
// Pass through the basics to the base args.
|
||||
public Object get(int index) throws JSONException {
|
||||
return baseArgs.get(index);
|
||||
}
|
||||
|
||||
public boolean getBoolean(int index) throws JSONException {
|
||||
return baseArgs.getBoolean(index);
|
||||
}
|
||||
|
||||
public double getDouble(int index) throws JSONException {
|
||||
return baseArgs.getDouble(index);
|
||||
}
|
||||
|
||||
public int getInt(int index) throws JSONException {
|
||||
return baseArgs.getInt(index);
|
||||
}
|
||||
|
||||
public JSONArray getJSONArray(int index) throws JSONException {
|
||||
return baseArgs.getJSONArray(index);
|
||||
}
|
||||
|
||||
public Object getJSONObject(int index) throws JSONException {
|
||||
return baseArgs.getJSONObject(index);
|
||||
}
|
||||
|
||||
public long getLong(int index) throws JSONException {
|
||||
return baseArgs.getLong(index);
|
||||
}
|
||||
|
||||
public String getString(int index) throws JSONException {
|
||||
return baseArgs.getString(index);
|
||||
}
|
||||
|
||||
|
||||
public Object opt(int index) {
|
||||
return baseArgs.opt(index);
|
||||
}
|
||||
|
||||
public boolean optBoolean(int index) {
|
||||
return baseArgs.optBoolean(index);
|
||||
}
|
||||
|
||||
public double optDouble(int index) {
|
||||
return baseArgs.optDouble(index);
|
||||
}
|
||||
|
||||
public int optInt(int index) {
|
||||
return baseArgs.optInt(index);
|
||||
}
|
||||
|
||||
public JSONArray optJSONArray(int index) {
|
||||
return baseArgs.optJSONArray(index);
|
||||
}
|
||||
|
||||
public Object optJSONObject(int index) {
|
||||
return baseArgs.optJSONObject(index);
|
||||
}
|
||||
|
||||
public long optLong(int index) {
|
||||
return baseArgs.optLong(index);
|
||||
}
|
||||
|
||||
public String optString(int index) {
|
||||
return baseArgs.optString(index);
|
||||
}
|
||||
|
||||
public boolean isNull(int index) {
|
||||
return baseArgs.isNull(index);
|
||||
}
|
||||
|
||||
|
||||
// The interesting custom helpers.
|
||||
public byte[] getArrayBuffer(int index) throws JSONException {
|
||||
String encoded = baseArgs.getString(index);
|
||||
return Base64.decode(encoded, Base64.DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,31 +23,46 @@ import org.apache.cordova.api.LOG;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
||||
//import android.app.Activity;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.AlertDialog;
|
||||
//import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
//import android.view.View;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
import android.webkit.ConsoleMessage;
|
||||
import android.webkit.JsPromptResult;
|
||||
import android.webkit.JsResult;
|
||||
import android.webkit.ValueCallback;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebStorage;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.GeolocationPermissions.Callback;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
/**
|
||||
* This class is the WebChromeClient that implements callbacks for our web view.
|
||||
*/
|
||||
public class CordovaChromeClient extends WebChromeClient {
|
||||
|
||||
public static final int FILECHOOSER_RESULTCODE = 5173;
|
||||
private static final String LOG_TAG = "CordovaChromeClient";
|
||||
private String TAG = "CordovaLog";
|
||||
private long MAX_QUOTA = 100 * 1024 * 1024;
|
||||
private CordovaInterface cordova;
|
||||
private CordovaWebView appView;
|
||||
|
||||
// the video progress view
|
||||
private View mVideoProgressView;
|
||||
|
||||
// File Chooser
|
||||
public ValueCallback<Uri> mUploadMessage;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
@@ -101,7 +116,7 @@ public class CordovaChromeClient extends WebChromeClient {
|
||||
dlg.setOnCancelListener(
|
||||
new DialogInterface.OnCancelListener() {
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
result.confirm();
|
||||
result.cancel();
|
||||
}
|
||||
});
|
||||
dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {
|
||||
@@ -190,7 +205,7 @@ public class CordovaChromeClient extends WebChromeClient {
|
||||
// Security check to make sure any requests are coming from the page initially
|
||||
// loaded in webview and not another loaded in an iframe.
|
||||
boolean reqOk = false;
|
||||
if (url.startsWith("file://") || url.indexOf(this.appView.baseUrl) == 0 || this.appView.isUrlWhiteListed(url)) {
|
||||
if (url.startsWith("file://") || url.indexOf(this.appView.baseUrl) == 0 || Config.isUrlWhiteListed(url)) {
|
||||
reqOk = true;
|
||||
}
|
||||
|
||||
@@ -203,18 +218,23 @@ public class CordovaChromeClient extends WebChromeClient {
|
||||
String service = array.getString(0);
|
||||
String action = array.getString(1);
|
||||
String callbackId = array.getString(2);
|
||||
boolean async = array.getBoolean(3);
|
||||
String r = this.appView.pluginManager.exec(service, action, callbackId, message, async);
|
||||
result.confirm(r);
|
||||
String r = this.appView.exposedJsApi.exec(service, action, callbackId, message);
|
||||
result.confirm(r == null ? "" : r);
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// Sets the native->JS bridge mode.
|
||||
else if (reqOk && defaultValue != null && defaultValue.equals("gap_bridge_mode:")) {
|
||||
this.appView.exposedJsApi.setNativeToJsBridgeMode(Integer.parseInt(message));
|
||||
result.confirm("");
|
||||
}
|
||||
|
||||
// Polling for JavaScript messages
|
||||
else if (reqOk && defaultValue != null && defaultValue.equals("gap_poll:")) {
|
||||
String r = this.appView.callbackServer.getJavascript();
|
||||
result.confirm(r);
|
||||
String r = this.appView.exposedJsApi.retrieveJsMessages();
|
||||
result.confirm(r == null ? "" : r);
|
||||
}
|
||||
|
||||
// Do NO-OP so older code doesn't display dialog
|
||||
@@ -222,24 +242,6 @@ public class CordovaChromeClient extends WebChromeClient {
|
||||
result.confirm("OK");
|
||||
}
|
||||
|
||||
// Calling into CallbackServer
|
||||
else if (reqOk && defaultValue != null && defaultValue.equals("gap_callbackServer:")) {
|
||||
String r = "";
|
||||
if (message.equals("usePolling")) {
|
||||
r = "" + this.appView.callbackServer.usePolling();
|
||||
}
|
||||
else if (message.equals("restartServer")) {
|
||||
this.appView.callbackServer.restartServer();
|
||||
}
|
||||
else if (message.equals("getPort")) {
|
||||
r = Integer.toString(this.appView.callbackServer.getPort());
|
||||
}
|
||||
else if (message.equals("getToken")) {
|
||||
r = this.appView.callbackServer.getToken();
|
||||
}
|
||||
result.confirm(r);
|
||||
}
|
||||
|
||||
// Show dialog
|
||||
else {
|
||||
final JsPromptResult res = result;
|
||||
@@ -302,14 +304,20 @@ public class CordovaChromeClient extends WebChromeClient {
|
||||
}
|
||||
|
||||
// console.log in api level 7: http://developer.android.com/guide/developing/debug-tasks.html
|
||||
// Expect this to not compile in a future Android release!
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void onConsoleMessage(String message, int lineNumber, String sourceID)
|
||||
{
|
||||
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)
|
||||
@Override
|
||||
public boolean onConsoleMessage(ConsoleMessage consoleMessage)
|
||||
{
|
||||
@@ -329,4 +337,66 @@ public class CordovaChromeClient extends WebChromeClient {
|
||||
super.onGeolocationPermissionsShowPrompt(origin, callback);
|
||||
callback.invoke(origin, true, false);
|
||||
}
|
||||
|
||||
// API level 7 is required for this, see if we could lower this using something else
|
||||
@Override
|
||||
public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
|
||||
this.appView.showCustomView(view, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHideCustomView() {
|
||||
this.appView.hideCustomView();
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* Ask the host application for a custom progress view to show while
|
||||
* a <video> is loading.
|
||||
* @return View The progress view.
|
||||
*/
|
||||
public View getVideoLoadingProgressView() {
|
||||
|
||||
if (mVideoProgressView == null) {
|
||||
// Create a new Loading view programmatically.
|
||||
|
||||
// create the linear layout
|
||||
LinearLayout layout = new LinearLayout(this.appView.getContext());
|
||||
layout.setOrientation(LinearLayout.VERTICAL);
|
||||
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
||||
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
|
||||
layout.setLayoutParams(layoutParams);
|
||||
// the proress bar
|
||||
ProgressBar bar = new ProgressBar(this.appView.getContext());
|
||||
LinearLayout.LayoutParams barLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
||||
barLayoutParams.gravity = Gravity.CENTER;
|
||||
bar.setLayoutParams(barLayoutParams);
|
||||
layout.addView(bar);
|
||||
|
||||
mVideoProgressView = layout;
|
||||
}
|
||||
return mVideoProgressView;
|
||||
}
|
||||
|
||||
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
|
||||
this.openFileChooser(uploadMsg, "*/*");
|
||||
}
|
||||
|
||||
public void openFileChooser( ValueCallback<Uri> uploadMsg, String acceptType ) {
|
||||
this.openFileChooser(uploadMsg, acceptType, null);
|
||||
}
|
||||
|
||||
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture)
|
||||
{
|
||||
mUploadMessage = uploadMsg;
|
||||
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
i.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
i.setType("*/*");
|
||||
this.cordova.getActivity().startActivityForResult(Intent.createChooser(i, "File Browser"),
|
||||
FILECHOOSER_RESULTCODE);
|
||||
}
|
||||
|
||||
public ValueCallback<Uri> getValueCallback() {
|
||||
return this.mUploadMessage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cordova.api.CallbackContext;
|
||||
|
||||
import android.location.Location;
|
||||
import android.location.LocationListener;
|
||||
@@ -39,8 +40,8 @@ public class CordovaLocationListener implements LocationListener {
|
||||
private GeoBroker owner;
|
||||
protected boolean running = false;
|
||||
|
||||
public HashMap<String, String> watches = new HashMap<String, String>();
|
||||
private List<String> callbacks = new ArrayList<String>();
|
||||
public HashMap<String, CallbackContext> watches = new HashMap<String, CallbackContext>();
|
||||
private List<CallbackContext> callbacks = new ArrayList<CallbackContext>();
|
||||
|
||||
private String TAG = "[Cordova Location Listener]";
|
||||
|
||||
@@ -51,30 +52,38 @@ public class CordovaLocationListener implements LocationListener {
|
||||
}
|
||||
|
||||
protected void fail(int code, String message) {
|
||||
for (String callbackId: this.callbacks)
|
||||
for (CallbackContext callbackContext: this.callbacks)
|
||||
{
|
||||
this.owner.fail(code, message, callbackId);
|
||||
this.owner.fail(code, message, callbackContext);
|
||||
}
|
||||
if(this.owner.isGlobalListener(this))
|
||||
{
|
||||
Log.d(TAG, "Stopping global listener");
|
||||
this.stop();
|
||||
}
|
||||
this.callbacks.clear();
|
||||
|
||||
Iterator it = this.watches.entrySet().iterator();
|
||||
Iterator<CallbackContext> it = this.watches.values().iterator();
|
||||
while (it.hasNext()) {
|
||||
Map.Entry pairs = (Map.Entry)it.next();
|
||||
this.owner.fail(code, message, (String)pairs.getValue());
|
||||
this.owner.fail(code, message, it.next());
|
||||
}
|
||||
}
|
||||
|
||||
private void win(Location loc) {
|
||||
for (String callbackId: this.callbacks)
|
||||
for (CallbackContext callbackContext: this.callbacks)
|
||||
{
|
||||
this.owner.win(loc, callbackId);
|
||||
this.owner.win(loc, callbackContext);
|
||||
}
|
||||
if(this.owner.isGlobalListener(this))
|
||||
{
|
||||
Log.d(TAG, "Stopping global listener");
|
||||
this.stop();
|
||||
}
|
||||
this.callbacks.clear();
|
||||
|
||||
Iterator it = this.watches.entrySet().iterator();
|
||||
Iterator<CallbackContext> it = this.watches.values().iterator();
|
||||
while (it.hasNext()) {
|
||||
Map.Entry pairs = (Map.Entry)it.next();
|
||||
this.owner.win(loc, (String)pairs.getValue());
|
||||
this.owner.win(loc, it.next());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,14 +149,14 @@ public class CordovaLocationListener implements LocationListener {
|
||||
return this.watches.size() + this.callbacks.size();
|
||||
}
|
||||
|
||||
public void addWatch(String timerId, String callbackId) {
|
||||
this.watches.put(timerId, callbackId);
|
||||
public void addWatch(String timerId, CallbackContext callbackContext) {
|
||||
this.watches.put(timerId, callbackContext);
|
||||
if (this.size() == 1) {
|
||||
this.start();
|
||||
}
|
||||
}
|
||||
public void addCallback(String callbackId) {
|
||||
this.callbacks.add(callbackId);
|
||||
public void addCallback(CallbackContext callbackContext) {
|
||||
this.callbacks.add(callbackContext);
|
||||
if (this.size() == 1) {
|
||||
this.start();
|
||||
}
|
||||
|
||||
@@ -19,71 +19,110 @@
|
||||
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Stack;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.cordova.Config;
|
||||
import org.apache.cordova.api.CordovaInterface;
|
||||
import org.apache.cordova.api.LOG;
|
||||
import org.apache.cordova.api.PluginManager;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import org.apache.cordova.api.PluginResult;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.XmlResourceParser;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.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;
|
||||
|
||||
|
||||
/** Actvities and other important classes **/
|
||||
public PluginManager pluginManager;
|
||||
private boolean paused;
|
||||
|
||||
private BroadcastReceiver receiver;
|
||||
|
||||
|
||||
/** 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 handleButton = false;
|
||||
|
||||
private long lastMenuEventTime = 0;
|
||||
|
||||
private boolean volumeupBound;
|
||||
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.
|
||||
*
|
||||
*
|
||||
* @param context
|
||||
*/
|
||||
public CordovaWebView(Context context) {
|
||||
@@ -102,7 +141,7 @@ public class CordovaWebView extends WebView {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
*
|
||||
* @param context
|
||||
* @param attrs
|
||||
*/
|
||||
@@ -117,18 +156,18 @@ public class CordovaWebView extends WebView {
|
||||
Log.d(TAG, "Your activity must implement CordovaInterface to work");
|
||||
}
|
||||
this.setWebChromeClient(new CordovaChromeClient(this.cordova, this));
|
||||
this.setWebViewClient(new CordovaWebViewClient(this.cordova, this));
|
||||
this.initWebViewClient(this.cordova);
|
||||
this.loadConfiguration();
|
||||
this.setup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
*
|
||||
* @param context
|
||||
* @param attrs
|
||||
* @param defStyle
|
||||
*
|
||||
*
|
||||
*/
|
||||
public CordovaWebView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
@@ -141,19 +180,19 @@ public class CordovaWebView extends WebView {
|
||||
Log.d(TAG, "Your activity must implement CordovaInterface to work");
|
||||
}
|
||||
this.setWebChromeClient(new CordovaChromeClient(this.cordova, this));
|
||||
this.setWebViewClient(new CordovaWebViewClient(this.cordova, this));
|
||||
this.loadConfiguration();
|
||||
this.setup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
*
|
||||
* @param context
|
||||
* @param attrs
|
||||
* @param defStyle
|
||||
* @param privateBrowsing
|
||||
*/
|
||||
@TargetApi(11)
|
||||
public CordovaWebView(Context context, AttributeSet attrs, int defStyle, boolean privateBrowsing) {
|
||||
super(context, attrs, defStyle, privateBrowsing);
|
||||
if (CordovaInterface.class.isInstance(context))
|
||||
@@ -165,17 +204,29 @@ public class CordovaWebView extends WebView {
|
||||
Log.d(TAG, "Your activity must implement CordovaInterface to work");
|
||||
}
|
||||
this.setWebChromeClient(new CordovaChromeClient(this.cordova));
|
||||
this.setWebViewClient(new CordovaWebViewClient(this.cordova));
|
||||
this.initWebViewClient(this.cordova);
|
||||
this.loadConfiguration();
|
||||
this.setup();
|
||||
}
|
||||
|
||||
|
||||
private void initWebViewClient(CordovaInterface cordova) {
|
||||
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB)
|
||||
{
|
||||
this.setWebViewClient(new CordovaWebViewClient(this.cordova, this));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.setWebViewClient(new IceCreamCordovaWebViewClient(this.cordova, this));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize webview.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
@SuppressLint("NewApi")
|
||||
private void setup() {
|
||||
|
||||
this.setInitialScale(0);
|
||||
this.setVerticalScrollBarEnabled(false);
|
||||
this.requestFocusFromTouch();
|
||||
@@ -185,33 +236,94 @@ 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, 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.");
|
||||
}
|
||||
|
||||
//Set the nav dump for HTC
|
||||
settings.setNavDump(true);
|
||||
|
||||
// Jellybean rightfully tried to lock this down. Too bad they didn't give us a whitelist
|
||||
// while we do this
|
||||
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
|
||||
Level16Apis.enableUniversalAccess(settings);
|
||||
// Enable database
|
||||
settings.setDatabaseEnabled(true);
|
||||
String databasePath = this.cordova.getActivity().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
|
||||
settings.setDatabasePath(databasePath);
|
||||
settings.setGeolocationDatabasePath(databasePath);
|
||||
|
||||
// Enable DOM storage
|
||||
settings.setDomStorageEnabled(true);
|
||||
|
||||
// Enable built-in geolocation
|
||||
settings.setGeolocationEnabled(true);
|
||||
|
||||
//Start up the plugin manager
|
||||
try {
|
||||
this.pluginManager = new PluginManager(this, this.cordova);
|
||||
} catch (Exception e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
|
||||
// Enable AppCache
|
||||
// Fix for CB-2282
|
||||
settings.setAppCacheMaxSize(5 * 1048576);
|
||||
String pathToCache = this.cordova.getActivity().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
|
||||
settings.setAppCachePath(pathToCache);
|
||||
settings.setAppCacheEnabled(true);
|
||||
|
||||
// Fix for CB-1405
|
||||
// Google issue 4641
|
||||
this.updateUserAgentString();
|
||||
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
|
||||
if (this.receiver == null) {
|
||||
this.receiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
updateUserAgentString();
|
||||
}
|
||||
};
|
||||
this.cordova.getActivity().registerReceiver(this.receiver, intentFilter);
|
||||
}
|
||||
// end CB-1405
|
||||
|
||||
pluginManager = new PluginManager(this, this.cordova);
|
||||
jsMessageQueue = new NativeToJsMessageQueue(this, cordova);
|
||||
exposedJsApi = new ExposedJsApi(pluginManager, jsMessageQueue);
|
||||
exposeJsInterface();
|
||||
}
|
||||
|
||||
private void updateUserAgentString() {
|
||||
this.getSettings().getUserAgentString();
|
||||
}
|
||||
|
||||
private void exposeJsInterface() {
|
||||
int SDK_INT = Build.VERSION.SDK_INT;
|
||||
boolean isHoneycomb = (SDK_INT >= Build.VERSION_CODES.HONEYCOMB && SDK_INT <= Build.VERSION_CODES.HONEYCOMB_MR2);
|
||||
if (isHoneycomb || (SDK_INT < Build.VERSION_CODES.GINGERBREAD)) {
|
||||
Log.i(TAG, "Disabled addJavascriptInterface() bridge since Android version is old.");
|
||||
// Bug being that Java Strings do not get converted to JS strings automatically.
|
||||
// This isn't hard to work-around on the JS side, but it's easier to just
|
||||
// use the prompt bridge instead.
|
||||
return;
|
||||
} else if (SDK_INT < Build.VERSION_CODES.HONEYCOMB && Build.MANUFACTURER.equals("unknown")) {
|
||||
// addJavascriptInterface crashes on the 2.3 emulator.
|
||||
Log.i(TAG, "Disabled addJavascriptInterface() bridge callback due to a bug on the 2.3 emulator");
|
||||
return;
|
||||
}
|
||||
this.addJavascriptInterface(exposedJsApi, "_cordovaNative");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the WebViewClient.
|
||||
*
|
||||
*
|
||||
* @param client
|
||||
*/
|
||||
public void setWebViewClient(CordovaWebViewClient client) {
|
||||
@@ -221,83 +333,21 @@ public class CordovaWebView extends WebView {
|
||||
|
||||
/**
|
||||
* Set the WebChromeClient.
|
||||
*
|
||||
*
|
||||
* @param client
|
||||
*/
|
||||
public void setWebChromeClient(CordovaChromeClient client) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the url into the webview.
|
||||
*
|
||||
*
|
||||
* @param url
|
||||
*/
|
||||
@Override
|
||||
@@ -323,7 +373,7 @@ public class CordovaWebView extends WebView {
|
||||
/**
|
||||
* Load the url into the webview after waiting for period of time.
|
||||
* This is used to display the splashscreen for certain amount of time.
|
||||
*
|
||||
*
|
||||
* @param url
|
||||
* @param time The number of ms to wait before loading webview
|
||||
*/
|
||||
@@ -342,7 +392,7 @@ public class CordovaWebView extends WebView {
|
||||
|
||||
/**
|
||||
* Load the url into the webview.
|
||||
*
|
||||
*
|
||||
* @param url
|
||||
*/
|
||||
public void loadUrlIntoView(final String url) {
|
||||
@@ -411,18 +461,22 @@ public class CordovaWebView extends WebView {
|
||||
|
||||
/**
|
||||
* Load URL in webview.
|
||||
*
|
||||
*
|
||||
* @param url
|
||||
*/
|
||||
private void loadUrlNow(String url) {
|
||||
LOG.d(TAG, ">>> loadUrlNow()");
|
||||
super.loadUrl(url);
|
||||
void loadUrlNow(String 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the url into the webview after waiting for period of time.
|
||||
* This is used to display the splashscreen for certain amount of time.
|
||||
*
|
||||
*
|
||||
* @param url
|
||||
* @param time The number of ms to wait before loading webview
|
||||
*/
|
||||
@@ -445,22 +499,30 @@ public class CordovaWebView extends WebView {
|
||||
// Load url
|
||||
this.loadUrlIntoView(url);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send JavaScript statement back to JavaScript.
|
||||
* (This is a convenience method)
|
||||
*
|
||||
*
|
||||
* @param message
|
||||
*/
|
||||
public void sendJavascript(String statement) {
|
||||
if (this.callbackServer != null) {
|
||||
this.callbackServer.sendJavascript(statement);
|
||||
}
|
||||
this.jsMessageQueue.addJavaScript(statement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to all plugins.
|
||||
*
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to all plugins.
|
||||
*
|
||||
* @param id The message id
|
||||
* @param data The message data
|
||||
*/
|
||||
@@ -470,8 +532,8 @@ public class CordovaWebView extends WebView {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the top url on the stack without removing it from
|
||||
/**
|
||||
* Returns the top url on the stack without removing it from
|
||||
* the stack.
|
||||
*/
|
||||
public String peekAtUrlStack() {
|
||||
@@ -483,7 +545,7 @@ public class CordovaWebView extends WebView {
|
||||
|
||||
/**
|
||||
* Add a url to the stack
|
||||
*
|
||||
*
|
||||
* @param url
|
||||
*/
|
||||
public void pushUrl(String url) {
|
||||
@@ -492,20 +554,21 @@ public class CordovaWebView extends WebView {
|
||||
|
||||
/**
|
||||
* Go to previous page in history. (We manage our own history)
|
||||
*
|
||||
*
|
||||
* @return true if we went back, false if we are already at top
|
||||
*/
|
||||
public boolean backHistory() {
|
||||
|
||||
// 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()) {
|
||||
if (super.canGoBack() && this.useBrowserHistory) {
|
||||
printBackForwardList();
|
||||
super.goBack();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// If our managed history has prev url
|
||||
if (this.urls.size() > 1) {
|
||||
else 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);
|
||||
@@ -517,14 +580,14 @@ public class CordovaWebView extends WebView {
|
||||
|
||||
/**
|
||||
* Return true if there is a history item.
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean canGoBack() {
|
||||
if (super.canGoBack()) {
|
||||
if (super.canGoBack() && this.useBrowserHistory) {
|
||||
return true;
|
||||
}
|
||||
if (this.urls.size() > 1) {
|
||||
else if (this.urls.size() > 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -532,7 +595,7 @@ public class CordovaWebView extends WebView {
|
||||
|
||||
/**
|
||||
* Load the specified URL in the Cordova webview or a new browser instance.
|
||||
*
|
||||
*
|
||||
* NOTE: If openExternal is false, only URLs listed in whitelist can be loaded.
|
||||
*
|
||||
* @param url The url to load.
|
||||
@@ -552,7 +615,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
|
||||
@@ -589,64 +652,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("cordova", "xml", this.cordova.getActivity().getPackageName());
|
||||
if (id == 0) {
|
||||
LOG.i("CordovaLog", "cordova.xml missing. Ignoring...");
|
||||
return;
|
||||
}
|
||||
XmlResourceParser xml = getResources().getXml(id);
|
||||
int eventType = -1;
|
||||
while (eventType != XmlResourceParser.END_DOCUMENT) {
|
||||
if (eventType == XmlResourceParser.START_TAG) {
|
||||
String strNode = xml.getName();
|
||||
if (strNode.equals("access")) {
|
||||
String origin = xml.getAttributeValue(null, "origin");
|
||||
String subdomains = xml.getAttributeValue(null, "subdomains");
|
||||
if (origin != null) {
|
||||
this.addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0));
|
||||
}
|
||||
}
|
||||
else if (strNode.equals("log")) {
|
||||
String level = xml.getAttributeValue(null, "level");
|
||||
LOG.i("CordovaLog", "Found log level %s", level);
|
||||
if (level != null) {
|
||||
LOG.setLogLevel(level);
|
||||
}
|
||||
}
|
||||
else if (strNode.equals("preference")) {
|
||||
String name = xml.getAttributeValue(null, "name");
|
||||
String value = xml.getAttributeValue(null, "value");
|
||||
|
||||
LOG.i("CordovaLog", "Found preference for %s=%s", name, value);
|
||||
|
||||
// Save preferences in Intent
|
||||
this.cordova.getActivity().getIntent().putExtra(name, value);
|
||||
}
|
||||
}
|
||||
try {
|
||||
eventType = xml.next();
|
||||
} catch (XmlPullParserException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// Init preferences
|
||||
if ("true".equals(this.getProperty("useBrowserHistory", "true"))) {
|
||||
this.useBrowserHistory = true;
|
||||
}
|
||||
else {
|
||||
// Config has already been loaded, and it stores these preferences on the Intent.
|
||||
if("false".equals(this.getProperty("useBrowserHistory", "true")))
|
||||
{
|
||||
//Switch back to the old browser history and state the six month policy
|
||||
this.useBrowserHistory = false;
|
||||
Log.w(TAG, "useBrowserHistory=false is deprecated as of Cordova 2.2.0 and will be removed six months after the 2.2.0 release. Please use the browser history and use history.back().");
|
||||
}
|
||||
|
||||
|
||||
if ("true".equals(this.getProperty("fullscreen", "false"))) {
|
||||
this.cordova.getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
|
||||
this.cordova.getActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
@@ -655,7 +675,7 @@ public class CordovaWebView extends WebView {
|
||||
|
||||
/**
|
||||
* Get string property for activity.
|
||||
*
|
||||
*
|
||||
* @param name
|
||||
* @param defaultValue
|
||||
* @return
|
||||
@@ -671,18 +691,17 @@ public class CordovaWebView extends WebView {
|
||||
}
|
||||
return p.toString();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* onKeyDown
|
||||
* onKeyDown
|
||||
*/
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event)
|
||||
{
|
||||
if(keyDownCodes.contains(keyCode))
|
||||
{
|
||||
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
|
||||
// only override default behaviour is event bound
|
||||
// only override default behavior is event bound
|
||||
LOG.d(TAG, "Down Key Hit");
|
||||
this.loadUrl("javascript:cordova.fireDocumentEvent('volumedownbutton');");
|
||||
return true;
|
||||
@@ -695,40 +714,60 @@ public class CordovaWebView extends WebView {
|
||||
}
|
||||
else
|
||||
{
|
||||
//Do some other stuff!
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event)
|
||||
{
|
||||
|
||||
Log.d(TAG, "KeyDown has been triggered on the view");
|
||||
|
||||
// If back key
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
// If back key is bound, then send event to JavaScript
|
||||
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;
|
||||
// If they hit back button when app is initializing, app should exit instead of hang until initilazation (CB2-458)
|
||||
this.cordova.getActivity().finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Legacy
|
||||
else if (keyCode == KeyEvent.KEYCODE_MENU) {
|
||||
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
|
||||
@@ -739,18 +778,19 @@ public class CordovaWebView extends WebView {
|
||||
else if(keyUpCodes.contains(keyCode))
|
||||
{
|
||||
//What the hell should this do?
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
|
||||
|
||||
Log.d(TAG, "KeyUp has been triggered on the view");
|
||||
return false;
|
||||
//Does webkit change this behavior?
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
|
||||
|
||||
public void bindButton(boolean override)
|
||||
{
|
||||
this.bound = override;
|
||||
}
|
||||
|
||||
|
||||
public void bindButton(String button, boolean override) {
|
||||
// TODO Auto-generated method stub
|
||||
if (button.compareTo("volumeup")==0) {
|
||||
@@ -760,7 +800,7 @@ public class CordovaWebView extends WebView {
|
||||
keyDownCodes.add(KeyEvent.KEYCODE_VOLUME_DOWN);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void bindButton(int keyCode, boolean keyDown, boolean override) {
|
||||
if(keyDown)
|
||||
{
|
||||
@@ -771,4 +811,187 @@ public class CordovaWebView extends WebView {
|
||||
keyUpCodes.add(keyCode);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isBackButtonBound()
|
||||
{
|
||||
return this.bound;
|
||||
}
|
||||
|
||||
public void handlePause(boolean keepRunning)
|
||||
{
|
||||
LOG.d(TAG, "Handle the pause");
|
||||
// Send pause event to JavaScript
|
||||
this.loadUrl("javascript:try{cordova.fireDocumentEvent('pause');}catch(e){console.log('exception firing pause event from native');};");
|
||||
|
||||
// Forward to plugins
|
||||
if (this.pluginManager != null) {
|
||||
this.pluginManager.onPause(keepRunning);
|
||||
}
|
||||
|
||||
// If app doesn't want to run in background
|
||||
if (!keepRunning) {
|
||||
// Pause JavaScript timers (including setInterval)
|
||||
this.pauseTimers();
|
||||
}
|
||||
paused = true;
|
||||
|
||||
}
|
||||
|
||||
public void handleResume(boolean keepRunning, boolean activityResultKeepRunning)
|
||||
{
|
||||
|
||||
this.loadUrl("javascript:try{cordova.fireDocumentEvent('resume');}catch(e){console.log('exception firing resume event from native');};");
|
||||
|
||||
// Forward to plugins
|
||||
if (this.pluginManager != null) {
|
||||
this.pluginManager.onResume(keepRunning);
|
||||
}
|
||||
|
||||
// Resume JavaScript timers (including setInterval)
|
||||
this.resumeTimers();
|
||||
paused = false;
|
||||
}
|
||||
|
||||
public void handleDestroy()
|
||||
{
|
||||
// Send destroy event to JavaScript
|
||||
// Since baseUrl is set in loadUrlIntoView, if user hit Back button before loadUrl was called, we'll get an NPE on baseUrl (CB-2458)
|
||||
this.loadUrlIntoView("javascript:try{cordova.require('cordova/channel').onDestroy.fire();}catch(e){console.log('exception firing destroy event from native');};");
|
||||
|
||||
// Load blank page so that JavaScript onunload is called
|
||||
this.loadUrl("about:blank");
|
||||
|
||||
// Forward to plugins
|
||||
if (this.pluginManager != null) {
|
||||
this.pluginManager.onDestroy();
|
||||
}
|
||||
|
||||
// unregister the receiver
|
||||
if (this.receiver != null) {
|
||||
try {
|
||||
this.cordova.getActivity().unregisterReceiver(this.receiver);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error unregistering configuration receiver: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onNewIntent(Intent intent)
|
||||
{
|
||||
//Forward to plugins
|
||||
if (this.pluginManager != null) {
|
||||
this.pluginManager.onNewIntent(intent);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isPaused()
|
||||
{
|
||||
return paused;
|
||||
}
|
||||
|
||||
public boolean hadKeyEvent() {
|
||||
return handleButton;
|
||||
}
|
||||
|
||||
// Wrapping these functions in their own class prevents warnings in adb like:
|
||||
// VFY: unable to resolve virtual method 285: Landroid/webkit/WebSettings;.setAllowUniversalAccessFromFileURLs
|
||||
@TargetApi(16)
|
||||
private static class Level16Apis {
|
||||
static void enableUniversalAccess(WebSettings settings) {
|
||||
settings.setAllowUniversalAccessFromFileURLs(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void printBackForwardList() {
|
||||
WebBackForwardList currentList = this.copyBackForwardList();
|
||||
int currentSize = currentList.getSize();
|
||||
for(int i = 0; i < currentSize; ++i)
|
||||
{
|
||||
WebHistoryItem item = currentList.getItemAtIndex(i);
|
||||
String url = item.getUrl();
|
||||
LOG.d(TAG, "The URL at index: " + Integer.toString(i) + "is " + url );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Can Go Back is BROKEN!
|
||||
public boolean startOfHistory()
|
||||
{
|
||||
WebBackForwardList currentList = this.copyBackForwardList();
|
||||
WebHistoryItem item = currentList.getItemAtIndex(0);
|
||||
if( item!=null){ // Null-fence in case they haven't called loadUrl yet (CB-2458)
|
||||
String url = item.getUrl();
|
||||
String currentUrl = this.getUrl();
|
||||
LOG.d(TAG, "The current URL is: " + currentUrl);
|
||||
LOG.d(TAG, "The URL at item 0 is:" + url);
|
||||
return currentUrl.equals(url);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void showCustomView(View view, WebChromeClient.CustomViewCallback callback) {
|
||||
// This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
|
||||
Log.d(TAG, "showing Custom View");
|
||||
// if a view already exists then immediately terminate the new one
|
||||
if (mCustomView != null) {
|
||||
callback.onCustomViewHidden();
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the view and its callback for later (to kill it properly)
|
||||
mCustomView = view;
|
||||
mCustomViewCallback = callback;
|
||||
|
||||
// Add the custom view to its container.
|
||||
ViewGroup parent = (ViewGroup) this.getParent();
|
||||
parent.addView(view, COVER_SCREEN_GRAVITY_CENTER);
|
||||
|
||||
// Hide the content view.
|
||||
this.setVisibility(View.GONE);
|
||||
|
||||
// Finally show the custom view container.
|
||||
parent.setVisibility(View.VISIBLE);
|
||||
parent.bringToFront();
|
||||
}
|
||||
|
||||
public void hideCustomView() {
|
||||
// This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
|
||||
Log.d(TAG, "Hidding Custom View");
|
||||
if (mCustomView == null) return;
|
||||
|
||||
// Hide the custom view.
|
||||
mCustomView.setVisibility(View.GONE);
|
||||
|
||||
// Remove the custom view from its container.
|
||||
ViewGroup parent = (ViewGroup) this.getParent();
|
||||
parent.removeView(mCustomView);
|
||||
mCustomView = null;
|
||||
mCustomViewCallback.onCustomViewHidden();
|
||||
|
||||
// Show the content view.
|
||||
this.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* if the video overlay is showing then we need to know
|
||||
* as it effects back button handling
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isCustomViewShowing() {
|
||||
return mCustomView != null;
|
||||
}
|
||||
|
||||
public WebBackForwardList restoreState(Bundle savedInstanceState)
|
||||
{
|
||||
WebBackForwardList myList = super.restoreState(savedInstanceState);
|
||||
Log.d(TAG, "WebView restoration crew now restoring!");
|
||||
//Initialize the plugin manager once more
|
||||
this.pluginManager.init();
|
||||
return myList;
|
||||
}
|
||||
|
||||
public void storeResult(int requestCode, int resultCode, Intent intent) {
|
||||
mResult = new ActivityResult(requestCode, resultCode, intent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,26 +21,23 @@ package org.apache.cordova;
|
||||
import java.util.Hashtable;
|
||||
|
||||
import org.apache.cordova.api.CordovaInterface;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.apache.cordova.api.LOG;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.content.Context;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.res.AssetManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.net.http.SslError;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.webkit.HttpAuthHandler;
|
||||
import android.webkit.SslErrorHandler;
|
||||
import android.webkit.WebResourceResponse;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
|
||||
@@ -49,7 +46,8 @@ import android.webkit.WebViewClient;
|
||||
*/
|
||||
public class CordovaWebViewClient extends WebViewClient {
|
||||
|
||||
private static final String TAG = "Cordova";
|
||||
private static final String TAG = "Cordova";
|
||||
private static final String CORDOVA_EXEC_URL_PREFIX = "http://cdv_exec/";
|
||||
CordovaInterface cordova;
|
||||
CordovaWebView appView;
|
||||
private boolean doClearHistory = false;
|
||||
@@ -86,6 +84,25 @@ public class CordovaWebViewClient extends WebViewClient {
|
||||
this.appView = view;
|
||||
}
|
||||
|
||||
|
||||
// Parses commands sent by setting the webView's URL to:
|
||||
// cdvbrg:service/action/callbackId#jsonArgs
|
||||
private void handleExecUrl(String url) {
|
||||
int idx1 = CORDOVA_EXEC_URL_PREFIX.length();
|
||||
int idx2 = url.indexOf('#', idx1 + 1);
|
||||
int idx3 = url.indexOf('#', idx2 + 1);
|
||||
int idx4 = url.indexOf('#', idx3 + 1);
|
||||
if (idx1 == -1 || idx2 == -1 || idx3 == -1 || idx4 == -1) {
|
||||
Log.e(TAG, "Could not decode URL command: " + url);
|
||||
return;
|
||||
}
|
||||
String service = url.substring(idx1, idx2);
|
||||
String action = url.substring(idx2 + 1, idx3);
|
||||
String callbackId = url.substring(idx3 + 1, idx4);
|
||||
String jsonArgs = url.substring(idx4 + 1);
|
||||
appView.pluginManager.exec(service, action, callbackId, jsonArgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Give the host application a chance to take over the control when a new url
|
||||
* is about to be loaded in the current WebView.
|
||||
@@ -94,11 +111,15 @@ public class CordovaWebViewClient extends WebViewClient {
|
||||
* @param url The url to be loaded.
|
||||
* @return true to override, false for default behavior
|
||||
*/
|
||||
@Override
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
// Check if it's an exec() bridge command message.
|
||||
if (NativeToJsMessageQueue.ENABLE_LOCATION_CHANGE_EXEC_MODE && url.startsWith(CORDOVA_EXEC_URL_PREFIX)) {
|
||||
handleExecUrl(url);
|
||||
}
|
||||
|
||||
// First give any plugins the chance to handle the url themselves
|
||||
if ((this.appView.pluginManager != null) && this.appView.pluginManager.onOverrideUrlLoading(url)) {
|
||||
// Give plugins the chance to handle the url
|
||||
else if ((this.appView.pluginManager != null) && this.appView.pluginManager.onOverrideUrlLoading(url)) {
|
||||
}
|
||||
|
||||
// If dialing phone (tel:5551212)
|
||||
@@ -171,9 +192,9 @@ public class CordovaWebViewClient extends WebViewClient {
|
||||
|
||||
// If our app or file:, then load into a new Cordova webview container by starting a new instance of our activity.
|
||||
// Our app continues to run. When BACK is pressed, our app is redisplayed.
|
||||
if (url.startsWith("file://") || url.indexOf(this.appView.baseUrl) == 0 || this.appView.isUrlWhiteListed(url)) {
|
||||
if (url.startsWith("file://") || url.startsWith("data:") || url.indexOf(this.appView.baseUrl) == 0 || Config.isUrlWhiteListed(url)) {
|
||||
//This will fix iFrames
|
||||
if (appView.useBrowserHistory)
|
||||
if (appView.useBrowserHistory || url.startsWith("data:"))
|
||||
return false;
|
||||
else
|
||||
this.appView.loadUrl(url);
|
||||
@@ -209,6 +230,10 @@ public class CordovaWebViewClient extends WebViewClient {
|
||||
AuthenticationToken token = this.getAuthenticationToken(host, realm);
|
||||
if (token != null) {
|
||||
handler.proceed(token.getUserName(), token.getPassword());
|
||||
}
|
||||
else {
|
||||
// Handle 401 like we'd normally do!
|
||||
super.onReceivedHttpAuthRequest(view, handler, host, realm);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,17 +255,16 @@ public class CordovaWebViewClient extends WebViewClient {
|
||||
this.doClearHistory = true;
|
||||
}
|
||||
|
||||
// Create callback server and plugin manager
|
||||
if (this.appView.callbackServer == null) {
|
||||
this.appView.callbackServer = new CallbackServer();
|
||||
this.appView.callbackServer.init(url);
|
||||
}
|
||||
else {
|
||||
this.appView.callbackServer.reinit(url);
|
||||
}
|
||||
// Flush stale messages.
|
||||
this.appView.jsMessageQueue.reset();
|
||||
|
||||
// Broadcast message that page has loaded
|
||||
this.appView.postMessage("onPageStarted", url);
|
||||
|
||||
// Notify all plugins of the navigation, so they can clean up if necessary.
|
||||
if (this.appView.pluginManager != null) {
|
||||
this.appView.pluginManager.onReset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -274,6 +298,7 @@ public class CordovaWebViewClient extends WebViewClient {
|
||||
// not loaded yet then just set a flag so that the onNativeReady can be fired
|
||||
// from the JS side when the JS gets to that code.
|
||||
if (!url.equals("about:blank")) {
|
||||
LOG.d(TAG, "Trying to fire onNativeReady");
|
||||
this.appView.loadUrl("javascript:try{ cordova.require('cordova/channel').onNativeReady.fire();}catch(e){_nativeReady = true;}");
|
||||
this.appView.postMessage("onNativeReady", null);
|
||||
}
|
||||
@@ -301,9 +326,6 @@ public class CordovaWebViewClient extends WebViewClient {
|
||||
|
||||
// Shutdown if blank loaded
|
||||
if (url.equals("about:blank")) {
|
||||
if (this.appView.callbackServer != null) {
|
||||
this.appView.callbackServer.destroy();
|
||||
}
|
||||
appView.postMessage("exit", null);
|
||||
}
|
||||
}
|
||||
@@ -346,6 +368,7 @@ public class CordovaWebViewClient extends WebViewClient {
|
||||
* @param handler An SslErrorHandler object that will handle the user's response.
|
||||
* @param error The SSL error object.
|
||||
*/
|
||||
@TargetApi(8)
|
||||
@Override
|
||||
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
|
||||
|
||||
@@ -459,43 +482,4 @@ public class CordovaWebViewClient extends WebViewClient {
|
||||
this.authenticationTokens.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
|
||||
if(url.contains("?") || url.contains("#")){
|
||||
return generateWebResourceResponse(url);
|
||||
} else {
|
||||
return super.shouldInterceptRequest(view, url);
|
||||
}
|
||||
}
|
||||
|
||||
private WebResourceResponse generateWebResourceResponse(String url) {
|
||||
final String ANDROID_ASSET = "file:///android_asset/";
|
||||
if (url.startsWith(ANDROID_ASSET)) {
|
||||
String niceUrl = url;
|
||||
niceUrl = url.replaceFirst(ANDROID_ASSET, "");
|
||||
if(niceUrl.contains("?")){
|
||||
niceUrl = niceUrl.split("\\?")[0];
|
||||
}
|
||||
else if(niceUrl.contains("#"))
|
||||
{
|
||||
niceUrl = niceUrl.split("#")[0];
|
||||
}
|
||||
|
||||
String mimetype = null;
|
||||
if(niceUrl.endsWith(".html")){
|
||||
mimetype = "text/html";
|
||||
}
|
||||
|
||||
try {
|
||||
AssetManager assets = cordova.getActivity().getAssets();
|
||||
Uri uri = Uri.parse(niceUrl);
|
||||
InputStream stream = assets.open(uri.getPath(), AssetManager.ACCESS_STREAMING);
|
||||
WebResourceResponse response = new WebResourceResponse(mimetype, "UTF-8", stream);
|
||||
return response;
|
||||
} catch (IOException e) {
|
||||
LOG.e("generateWebResourceResponse", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,10 +20,10 @@ package org.apache.cordova;
|
||||
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.apache.cordova.api.CallbackContext;
|
||||
import org.apache.cordova.api.CordovaPlugin;
|
||||
import org.apache.cordova.api.LOG;
|
||||
import org.apache.cordova.api.CordovaInterface;
|
||||
import org.apache.cordova.api.Plugin;
|
||||
import org.apache.cordova.api.PluginResult;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
@@ -35,10 +35,10 @@ import android.content.IntentFilter;
|
||||
import android.provider.Settings;
|
||||
import android.telephony.TelephonyManager;
|
||||
|
||||
public class Device extends Plugin {
|
||||
public class Device extends CordovaPlugin {
|
||||
public static final String TAG = "Device";
|
||||
|
||||
public static String cordovaVersion = "1.9.0rc1"; // Cordova version
|
||||
public static String cordovaVersion = "2.5.0rc1"; // Cordova version
|
||||
public static String platform = "Android"; // Device OS
|
||||
public static String uuid; // Device UUID
|
||||
|
||||
@@ -55,9 +55,10 @@ public class Device extends Plugin {
|
||||
* get file paths associated with the Activity.
|
||||
*
|
||||
* @param cordova The context of the main Activity.
|
||||
* @param webView The CordovaWebView Cordova is running in.
|
||||
*/
|
||||
public void setContext(CordovaInterface cordova) {
|
||||
super.setContext(cordova);
|
||||
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
|
||||
super.initialize(cordova, webView);
|
||||
Device.uuid = getUuid();
|
||||
this.initTelephonyReceiver();
|
||||
}
|
||||
@@ -65,45 +66,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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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,8 +38,10 @@ import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Color;
|
||||
import android.media.AudioManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
@@ -46,6 +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.WebViewClient;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
@@ -140,6 +145,8 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
protected LinearLayout root;
|
||||
protected boolean cancelLoadUrl = false;
|
||||
protected ProgressDialog spinnerDialog = null;
|
||||
private final ExecutorService threadPool = Executors.newCachedThreadPool();
|
||||
|
||||
|
||||
// The initial URL for our app
|
||||
// ie http://server/path/index.html#abc?query
|
||||
@@ -150,13 +157,8 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
private static int ACTIVITY_EXITING = 2;
|
||||
private int activityState = 0; // 0=starting, 1=running (after 1st resume), 2=shutting down
|
||||
|
||||
// The base of the initial URL for our app.
|
||||
// Does not include file name. Ends with /
|
||||
// ie http://server/path/
|
||||
String baseUrl = null;
|
||||
|
||||
// Plugin to call when activity result is received
|
||||
protected IPlugin activityResultCallback = null;
|
||||
protected CordovaPlugin activityResultCallback = null;
|
||||
protected boolean activityResultKeepRunning;
|
||||
|
||||
// Default background color for activity
|
||||
@@ -170,7 +172,7 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
// Draw a splash screen using an image located in the drawable resource directory.
|
||||
// This is not the same as calling super.loadSplashscreen(url)
|
||||
protected int splashscreen = 0;
|
||||
protected int splashscreenTime = 0;
|
||||
protected int splashscreenTime = 3000;
|
||||
|
||||
// LoadUrl timeout value in msec (default of 20 sec)
|
||||
protected int loadUrlTimeoutValue = 20000;
|
||||
@@ -180,6 +182,18 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
// when another application (activity) is started.
|
||||
protected boolean keepRunning = true;
|
||||
|
||||
private int lastRequestCode;
|
||||
|
||||
private Object responseCode;
|
||||
|
||||
private Intent lastIntent;
|
||||
|
||||
private Object lastResponseCode;
|
||||
|
||||
private String initCallbackClass;
|
||||
|
||||
private Object LOG_TAG;
|
||||
|
||||
/**
|
||||
* Sets the authentication token.
|
||||
*
|
||||
@@ -246,11 +260,15 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
//preferences = new PreferenceSet();
|
||||
|
||||
Config.init(this);
|
||||
LOG.d(TAG, "DroidGap.onCreate()");
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if(savedInstanceState != null)
|
||||
{
|
||||
initCallbackClass = savedInstanceState.getString("callbackClass");
|
||||
}
|
||||
|
||||
if(!this.getBooleanProperty("showTitle", false))
|
||||
{
|
||||
getWindow().requestFeature(Window.FEATURE_NO_TITLE);
|
||||
@@ -295,7 +313,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));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -329,6 +356,7 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
|
||||
// Clear cancel flag
|
||||
this.cancelLoadUrl = false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -404,6 +432,8 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
}
|
||||
|
||||
this.splashscreenTime = time;
|
||||
this.splashscreen = this.getIntegerProperty("splashscreen", 0);
|
||||
this.showSplashScreen(this.splashscreenTime);
|
||||
this.appView.loadUrl(url, time);
|
||||
}
|
||||
|
||||
@@ -560,6 +590,7 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
* @param value
|
||||
*/
|
||||
public void setBooleanProperty(String name, boolean value) {
|
||||
Log.d(TAG, "Setting boolean properties in DroidGap will be deprecated in 3.0 on July 2013, please use config.xml");
|
||||
this.getIntent().putExtra(name, value);
|
||||
}
|
||||
|
||||
@@ -570,6 +601,7 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
* @param value
|
||||
*/
|
||||
public void setIntegerProperty(String name, int value) {
|
||||
Log.d(TAG, "Setting integer properties in DroidGap will be deprecated in 3.1 on August 2013, please use config.xml");
|
||||
this.getIntent().putExtra(name, value);
|
||||
}
|
||||
|
||||
@@ -580,6 +612,7 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
* @param value
|
||||
*/
|
||||
public void setStringProperty(String name, String value) {
|
||||
Log.d(TAG, "Setting string properties in DroidGap will be deprecated in 3.0 on July 2013, please use config.xml");
|
||||
this.getIntent().putExtra(name, value);
|
||||
}
|
||||
|
||||
@@ -590,6 +623,7 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
* @param value
|
||||
*/
|
||||
public void setDoubleProperty(String name, double value) {
|
||||
Log.d(TAG, "Setting double properties in DroidGap will be deprecated in 3.0 on July 2013, please use config.xml");
|
||||
this.getIntent().putExtra(name, value);
|
||||
}
|
||||
|
||||
@@ -600,6 +634,8 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
LOG.d(TAG, "Paused the application!");
|
||||
|
||||
// Don't process pause if shutting down, since onDestroy() will be called
|
||||
if (this.activityState == ACTIVITY_EXITING) {
|
||||
return;
|
||||
@@ -608,21 +644,13 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
if (this.appView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Send pause event to JavaScript
|
||||
this.appView.loadUrl("javascript:try{cordova.fireDocumentEvent('pause');}catch(e){console.log('exception firing pause event from native');};");
|
||||
|
||||
// Forward to plugins
|
||||
if (this.appView.pluginManager != null) {
|
||||
this.appView.pluginManager.onPause(this.keepRunning);
|
||||
else
|
||||
{
|
||||
this.appView.handlePause(this.keepRunning);
|
||||
}
|
||||
|
||||
// If app doesn't want to run in background
|
||||
if (!this.keepRunning) {
|
||||
|
||||
// Pause JavaScript timers (including setInterval)
|
||||
this.appView.pauseTimers();
|
||||
}
|
||||
// hide the splash screen to avoid leaking a window
|
||||
this.removeSplashScreen();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -631,11 +659,9 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
**/
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
|
||||
//Forward to plugins
|
||||
if ((this.appView != null) && (this.appView.pluginManager != null)) {
|
||||
this.appView.pluginManager.onNewIntent(intent);
|
||||
}
|
||||
if (this.appView != null)
|
||||
this.appView.onNewIntent(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -645,6 +671,7 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
LOG.d(TAG, "Resuming the App");
|
||||
if (this.activityState == ACTIVITY_STARTING) {
|
||||
this.activityState = ACTIVITY_RUNNING;
|
||||
return;
|
||||
@@ -654,13 +681,7 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
return;
|
||||
}
|
||||
|
||||
// Send resume event to JavaScript
|
||||
this.appView.loadUrl("javascript:try{cordova.fireDocumentEvent('resume');}catch(e){console.log('exception firing resume event from native');};");
|
||||
|
||||
// Forward to plugins
|
||||
if (this.appView.pluginManager != null) {
|
||||
this.appView.pluginManager.onResume(this.keepRunning || this.activityResultKeepRunning);
|
||||
}
|
||||
this.appView.handleResume(this.keepRunning, this.activityResultKeepRunning);
|
||||
|
||||
// If app doesn't want to run in background
|
||||
if (!this.keepRunning || this.activityResultKeepRunning) {
|
||||
@@ -670,9 +691,6 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
this.keepRunning = this.activityResultKeepRunning;
|
||||
this.activityResultKeepRunning = false;
|
||||
}
|
||||
|
||||
// Resume JavaScript timers (including setInterval)
|
||||
this.appView.resumeTimers();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -684,18 +702,11 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
LOG.d(TAG, "onDestroy()");
|
||||
super.onDestroy();
|
||||
|
||||
// hide the splash screen to avoid leaking a window
|
||||
this.removeSplashScreen();
|
||||
|
||||
if (this.appView != null) {
|
||||
|
||||
// Send destroy event to JavaScript
|
||||
this.appView.loadUrl("javascript:try{cordova.require('cordova/channel').onDestroy.fire();}catch(e){console.log('exception firing destroy event from native');};");
|
||||
|
||||
// Load blank page so that JavaScript onunload is called
|
||||
this.appView.loadUrl("about:blank");
|
||||
|
||||
// Forward to plugins
|
||||
if (this.appView.pluginManager != null) {
|
||||
this.appView.pluginManager.onDestroy();
|
||||
}
|
||||
appView.handleDestroy();
|
||||
}
|
||||
else {
|
||||
this.endActivity();
|
||||
@@ -736,8 +747,8 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
* @param message
|
||||
*/
|
||||
public void sendJavascript(String statement) {
|
||||
if (this.appView != null && this.appView.callbackServer != null) {
|
||||
this.appView.callbackServer.sendJavascript(statement);
|
||||
if (this.appView != null) {
|
||||
this.appView.jsMessageQueue.addJavaScript(statement);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -762,10 +773,10 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop spinner.
|
||||
* Stop spinner - Must be called from UI thread
|
||||
*/
|
||||
public void spinnerStop() {
|
||||
if (this.spinnerDialog != null) {
|
||||
if (this.spinnerDialog != null && this.spinnerDialog.isShowing()) {
|
||||
this.spinnerDialog.dismiss();
|
||||
this.spinnerDialog = null;
|
||||
}
|
||||
@@ -779,7 +790,7 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
super.finish();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Launch an activity for which you would like a result when it finished. When this activity exits,
|
||||
* your onActivityResult() method will be called.
|
||||
@@ -788,7 +799,7 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
* @param intent The intent to start
|
||||
* @param requestCode The request code that is passed to callback to identify the activity
|
||||
*/
|
||||
public void startActivityForResult(IPlugin command, Intent intent, int requestCode) {
|
||||
public void startActivityForResult(CordovaPlugin command, Intent intent, int requestCode) {
|
||||
this.activityResultCallback = command;
|
||||
this.activityResultKeepRunning = this.keepRunning;
|
||||
|
||||
@@ -812,14 +823,40 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
* @param data An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
|
||||
*/
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||
LOG.d(TAG, "Incoming Result");
|
||||
super.onActivityResult(requestCode, resultCode, intent);
|
||||
IPlugin callback = this.activityResultCallback;
|
||||
if (callback != null) {
|
||||
Log.d(TAG, "Request code = " + requestCode);
|
||||
ValueCallback<Uri> mUploadMessage = this.appView.getWebChromeClient().getValueCallback();
|
||||
if (requestCode == CordovaChromeClient.FILECHOOSER_RESULTCODE) {
|
||||
Log.d(TAG, "did we get here?");
|
||||
if (null == mUploadMessage)
|
||||
return;
|
||||
Uri result = intent == null || resultCode != Activity.RESULT_OK ? null : intent.getData();
|
||||
Log.d(TAG, "result = " + result);
|
||||
// Uri filepath = Uri.parse("file://" + FileUtils.getRealPathFromURI(result, this));
|
||||
// Log.d(TAG, "result = " + filepath);
|
||||
mUploadMessage.onReceiveValue(result);
|
||||
mUploadMessage = null;
|
||||
}
|
||||
CordovaPlugin callback = this.activityResultCallback;
|
||||
if(callback == null)
|
||||
{
|
||||
if(initCallbackClass != null)
|
||||
{
|
||||
this.activityResultCallback = appView.pluginManager.getPlugin(initCallbackClass);
|
||||
callback = activityResultCallback;
|
||||
LOG.d(TAG, "We have a callback to send this result to");
|
||||
callback.onActivityResult(requestCode, resultCode, intent);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.d(TAG, "We have a callback to send this result to");
|
||||
callback.onActivityResult(requestCode, resultCode, intent);
|
||||
}
|
||||
}
|
||||
|
||||
public void setActivityResultCallback(IPlugin plugin) {
|
||||
public void setActivityResultCallback(CordovaPlugin plugin) {
|
||||
this.activityResultCallback = plugin;
|
||||
}
|
||||
|
||||
@@ -834,16 +871,15 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
public void onReceivedError(final int errorCode, final String description, final String failingUrl) {
|
||||
final DroidGap me = this;
|
||||
|
||||
// Stop "app loading" spinner if showing
|
||||
this.spinnerStop();
|
||||
|
||||
// If errorUrl specified, then load it
|
||||
final String errorUrl = me.getStringProperty("errorUrl", null);
|
||||
if ((errorUrl != null) && (errorUrl.startsWith("file://") || errorUrl.indexOf(me.baseUrl) == 0 || this.appView.isUrlWhiteListed(errorUrl)) && (!failingUrl.equals(errorUrl))) {
|
||||
if ((errorUrl != null) && (errorUrl.startsWith("file://") || Config.isUrlWhiteListed(errorUrl)) && (!failingUrl.equals(errorUrl))) {
|
||||
|
||||
// Load URL on UI thread
|
||||
me.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
// Stop "app loading" spinner if showing
|
||||
me.spinnerStop();
|
||||
me.appView.showWebPage(errorUrl, false, true, null);
|
||||
}
|
||||
});
|
||||
@@ -853,8 +889,7 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
final boolean exit = !(errorCode == WebViewClient.ERROR_HOST_LOOKUP);
|
||||
me.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
if (exit)
|
||||
{
|
||||
if (exit) {
|
||||
me.appView.setVisibility(View.GONE);
|
||||
me.displayError("Application Error", description + " (" + failingUrl + ")", "OK", exit);
|
||||
}
|
||||
@@ -905,11 +940,7 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
* @return
|
||||
*/
|
||||
public boolean isUrlWhiteListed(String url) {
|
||||
// Check to see if we have matched url previously
|
||||
if (this.appView != null) {
|
||||
return this.appView.isUrlWhiteListed(url);
|
||||
}
|
||||
return false;
|
||||
return Config.isUrlWhiteListed(url);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -925,7 +956,7 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
this.postMessage("onPrepareOptionsMenu", menu);
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -940,7 +971,8 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
* @return
|
||||
*/
|
||||
public Context getContext() {
|
||||
return this.getContext();
|
||||
LOG.d(TAG, "This will be deprecated December 2012");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -965,7 +997,7 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
* Removes the Dialog that displays the splash screen
|
||||
*/
|
||||
public void removeSplashScreen() {
|
||||
if (splashDialog != null) {
|
||||
if (splashDialog != null && splashDialog.isShowing()) {
|
||||
splashDialog.dismiss();
|
||||
splashDialog = null;
|
||||
}
|
||||
@@ -1017,6 +1049,39 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
this.runOnUiThread(runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event)
|
||||
{
|
||||
//Get whatever has focus!
|
||||
View childView = appView.getFocusedChild();
|
||||
if ((appView.isCustomViewShowing() || childView != null ) && keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
return appView.onKeyUp(keyCode, event);
|
||||
} else {
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Android 2.x needs to be able to check where the cursor is. Android 4.x does not
|
||||
*
|
||||
* (non-Javadoc)
|
||||
* @see android.app.Activity#onKeyDown(int, android.view.KeyEvent)
|
||||
*/
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event)
|
||||
{
|
||||
//Get whatever has focus!
|
||||
View childView = appView.getFocusedChild();
|
||||
//Determine if the focus is on the current view or not
|
||||
if (childView != null && keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
return appView.onKeyDown(keyCode, event);
|
||||
}
|
||||
else
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when a message is sent to plugin.
|
||||
*
|
||||
@@ -1031,8 +1096,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)) {
|
||||
@@ -1055,4 +1123,18 @@ public class DroidGap extends Activity implements CordovaInterface {
|
||||
return null;
|
||||
}
|
||||
|
||||
public ExecutorService getThreadPool() {
|
||||
return threadPool;
|
||||
}
|
||||
|
||||
protected void onSaveInstanceState(Bundle outState)
|
||||
{
|
||||
super.onSaveInstanceState(outState);
|
||||
if(this.activityResultCallback != null)
|
||||
{
|
||||
String cClass = this.activityResultCallback.getClass().getName();
|
||||
outState.putString("callbackClass", cClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
48
framework/src/org/apache/cordova/Echo.java
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import org.apache.cordova.api.CallbackContext;
|
||||
import org.apache.cordova.api.CordovaPlugin;
|
||||
import org.json.JSONException;
|
||||
|
||||
public class Echo extends CordovaPlugin {
|
||||
|
||||
@Override
|
||||
public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext) throws JSONException {
|
||||
if ("echo".equals(action)) {
|
||||
final String result = args.isNull(0) ? null : args.getString(0);
|
||||
callbackContext.success(result);
|
||||
return true;
|
||||
} else if ("echoAsync".equals(action)) {
|
||||
final String result = args.isNull(0) ? null : args.getString(0);
|
||||
cordova.getThreadPool().execute(new Runnable() {
|
||||
public void run() {
|
||||
callbackContext.success(result);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
} else if ("echoArrayBuffer".equals(action)) {
|
||||
final byte[] result = args.getArrayBuffer(0);
|
||||
callbackContext.success(result);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ import java.io.IOException;
|
||||
import android.media.ExifInterface;
|
||||
|
||||
public class ExifHelper {
|
||||
private String aperature = null;
|
||||
private String aperture = null;
|
||||
private String datetime = null;
|
||||
private String exposureTime = null;
|
||||
private String flash = null;
|
||||
@@ -70,7 +70,7 @@ public class ExifHelper {
|
||||
* Reads all the EXIF data from the input file.
|
||||
*/
|
||||
public void readExifData() {
|
||||
this.aperature = inFile.getAttribute(ExifInterface.TAG_APERTURE);
|
||||
this.aperture = inFile.getAttribute(ExifInterface.TAG_APERTURE);
|
||||
this.datetime = inFile.getAttribute(ExifInterface.TAG_DATETIME);
|
||||
this.exposureTime = inFile.getAttribute(ExifInterface.TAG_EXPOSURE_TIME);
|
||||
this.flash = inFile.getAttribute(ExifInterface.TAG_FLASH);
|
||||
@@ -102,8 +102,8 @@ public class ExifHelper {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.aperature != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_APERTURE, this.aperature);
|
||||
if (this.aperture != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_APERTURE, this.aperture);
|
||||
}
|
||||
if (this.datetime != null) {
|
||||
this.outFile.setAttribute(ExifInterface.TAG_DATETIME, this.datetime);
|
||||
@@ -162,4 +162,24 @@ public class ExifHelper {
|
||||
|
||||
this.outFile.saveAttributes();
|
||||
}
|
||||
|
||||
public int getOrientation() {
|
||||
int o = Integer.parseInt(this.orientation);
|
||||
|
||||
if (o == ExifInterface.ORIENTATION_NORMAL) {
|
||||
return 0;
|
||||
} else if (o == ExifInterface.ORIENTATION_ROTATE_90) {
|
||||
return 90;
|
||||
} else if (o == ExifInterface.ORIENTATION_ROTATE_180) {
|
||||
return 180;
|
||||
} else if (o == ExifInterface.ORIENTATION_ROTATE_270) {
|
||||
return 270;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void resetOrientation() {
|
||||
this.orientation = "" + ExifInterface.ORIENTATION_NORMAL;
|
||||
}
|
||||
}
|
||||
|
||||
64
framework/src/org/apache/cordova/ExposedJsApi.java
Executable file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import android.webkit.JavascriptInterface;
|
||||
import org.apache.cordova.api.PluginManager;
|
||||
import org.json.JSONException;
|
||||
|
||||
/**
|
||||
* Contains APIs that the JS can call. All functions in here should also have
|
||||
* an equivalent entry in CordovaChromeClient.java, and be added to
|
||||
* cordova-js/lib/android/plugin/android/promptbasednativeapi.js
|
||||
*/
|
||||
/* package */ class ExposedJsApi {
|
||||
|
||||
private PluginManager pluginManager;
|
||||
private NativeToJsMessageQueue jsMessageQueue;
|
||||
|
||||
public ExposedJsApi(PluginManager pluginManager, NativeToJsMessageQueue jsMessageQueue) {
|
||||
this.pluginManager = pluginManager;
|
||||
this.jsMessageQueue = jsMessageQueue;
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public String exec(String service, String action, String callbackId, String arguments) throws JSONException {
|
||||
jsMessageQueue.setPaused(true);
|
||||
try {
|
||||
boolean wasSync = pluginManager.exec(service, action, callbackId, arguments);
|
||||
String ret = "";
|
||||
if (!NativeToJsMessageQueue.DISABLE_EXEC_CHAINING || wasSync) {
|
||||
ret = jsMessageQueue.popAndEncode();
|
||||
}
|
||||
return ret;
|
||||
} finally {
|
||||
jsMessageQueue.setPaused(false);
|
||||
}
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void setNativeToJsBridgeMode(int value) {
|
||||
jsMessageQueue.setBridgeMode(value);
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public String retrieveJsMessages() {
|
||||
return jsMessageQueue.popAndEncode();
|
||||
}
|
||||
}
|
||||
63
framework/src/org/apache/cordova/FileProgressResult.java
Normal 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") + "}");
|
||||
}
|
||||
}
|
||||
@@ -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) + "}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -50,7 +51,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 +85,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 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 = "";
|
||||
//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 +183,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -222,10 +237,16 @@ public class FileUtils extends Plugin {
|
||||
* @param filePath the path to check
|
||||
*/
|
||||
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 });
|
||||
String newFilePath = getRealPathFromURI(Uri.parse(filePath), cordova);
|
||||
try {
|
||||
this.cordova.getActivity().getContentResolver().delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||
MediaStore.Images.Media.DATA + " = ?",
|
||||
new String[] { newFilePath });
|
||||
} catch (UnsupportedOperationException t) {
|
||||
// Was seeing this on the File mobile-spec tests on 4.0.3 x86 emulator.
|
||||
// The ContentResolver applies only when the file was registered in the
|
||||
// first case, which is generally only the case with images.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -298,7 +319,9 @@ public class FileUtils extends Plugin {
|
||||
if (fp.isDirectory()) {
|
||||
File[] files = fp.listFiles();
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
entries.put(getEntry(files[i]));
|
||||
if (files[i].canRead()) {
|
||||
entries.put(getEntry(files[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,17 +341,18 @@ 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 {
|
||||
fileName = stripFileProtocol(fileName);
|
||||
newParent = stripFileProtocol(newParent);
|
||||
private JSONObject transferTo(String fileName, String newParent, String newName, boolean move) throws JSONException, NoModificationAllowedException, IOException, InvalidModificationException, EncodingException, FileExistsException {
|
||||
String newFileName = getRealPathFromURI(Uri.parse(fileName), cordova);
|
||||
newParent = getRealPathFromURI(Uri.parse(newParent), cordova);
|
||||
|
||||
// Check for invalid file name
|
||||
if (newName != null && newName.contains(":")) {
|
||||
throw new EncodingException("Bad file name");
|
||||
}
|
||||
|
||||
File source = new File(fileName);
|
||||
File source = new File(newFileName);
|
||||
|
||||
if (!source.exists()) {
|
||||
// The file/directory we are copying doesn't exist so we should fail.
|
||||
@@ -360,7 +384,14 @@ public class FileUtils extends Plugin {
|
||||
}
|
||||
} else {
|
||||
if (move) {
|
||||
return moveFile(source, destination);
|
||||
JSONObject newFileEntry = moveFile(source, destination);
|
||||
|
||||
// If we've moved a file given its content URI, we need to clean up.
|
||||
if (fileName.startsWith("content://")) {
|
||||
notifyDelete(fileName);
|
||||
}
|
||||
|
||||
return newFileEntry;
|
||||
} else {
|
||||
return copyFile(source, destination);
|
||||
}
|
||||
@@ -407,23 +438,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
|
||||
*
|
||||
@@ -450,7 +489,7 @@ public class FileUtils extends Plugin {
|
||||
if (!destinationDir.exists()) {
|
||||
if (!destinationDir.mkdir()) {
|
||||
// If we can't create the directory then fail
|
||||
throw new NoModificationAllowedException("Couldn't create the destination direcotry");
|
||||
throw new NoModificationAllowedException("Couldn't create the destination directory");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -478,7 +517,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;
|
||||
}
|
||||
@@ -496,7 +535,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");
|
||||
@@ -508,6 +547,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);
|
||||
@@ -522,8 +567,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");
|
||||
@@ -547,6 +594,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);
|
||||
@@ -695,7 +748,7 @@ public class FileUtils extends Plugin {
|
||||
if (fileName.startsWith("/")) {
|
||||
fp = new File(fileName);
|
||||
} else {
|
||||
dirPath = stripFileProtocol(dirPath);
|
||||
dirPath = getRealPathFromURI(Uri.parse(dirPath), cordova);
|
||||
fp = new File(dirPath + File.separator + fileName);
|
||||
}
|
||||
return fp;
|
||||
@@ -710,7 +763,7 @@ public class FileUtils extends Plugin {
|
||||
* @throws JSONException
|
||||
*/
|
||||
private JSONObject getParent(String filePath) throws JSONException {
|
||||
filePath = stripFileProtocol(filePath);
|
||||
filePath = getRealPathFromURI(Uri.parse(filePath), cordova);
|
||||
|
||||
if (atRootDirectory(filePath)) {
|
||||
return getEntry(filePath);
|
||||
@@ -726,7 +779,7 @@ public class FileUtils extends Plugin {
|
||||
* @return true if we are at the root, false otherwise.
|
||||
*/
|
||||
private boolean atRootDirectory(String filePath) {
|
||||
filePath = stripFileProtocol(filePath);
|
||||
filePath = getRealPathFromURI(Uri.parse(filePath), cordova);
|
||||
|
||||
if (filePath.equals(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/" + cordova.getActivity().getPackageName() + "/cache") ||
|
||||
filePath.equals(Environment.getExternalStorageDirectory().getAbsolutePath()) ||
|
||||
@@ -756,7 +809,7 @@ public class FileUtils extends Plugin {
|
||||
* @return
|
||||
*/
|
||||
private File createFileObject(String filePath) {
|
||||
filePath = stripFileProtocol(filePath);
|
||||
filePath = getRealPathFromURI(Uri.parse(filePath), cordova);
|
||||
|
||||
File file = new File(filePath);
|
||||
return file;
|
||||
@@ -798,7 +851,7 @@ public class FileUtils extends Plugin {
|
||||
metadata.put("size", file.length());
|
||||
metadata.put("type", getMimeType(filePath));
|
||||
metadata.put("name", file.getName());
|
||||
metadata.put("fullPath", file.getAbsolutePath());
|
||||
metadata.put("fullPath", filePath);
|
||||
metadata.put("lastModifiedDate", file.lastModified());
|
||||
|
||||
return metadata;
|
||||
@@ -909,17 +962,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);
|
||||
}
|
||||
|
||||
@@ -930,12 +993,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);
|
||||
}
|
||||
|
||||
@@ -961,8 +1031,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 "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -972,10 +1056,15 @@ public class FileUtils extends Plugin {
|
||||
* @param data The contents of the file.
|
||||
* @param offset The position to begin writing the file.
|
||||
* @throws FileNotFoundException, IOException
|
||||
* @throws NoModificationAllowedException
|
||||
*/
|
||||
/**/
|
||||
public long write(String filename, String data, int offset) throws FileNotFoundException, IOException {
|
||||
filename = stripFileProtocol(filename);
|
||||
public long write(String filename, String data, int offset) throws FileNotFoundException, IOException, NoModificationAllowedException {
|
||||
if (filename.startsWith("content://")) {
|
||||
throw new NoModificationAllowedException("Couldn't write to file given its content URI");
|
||||
}
|
||||
|
||||
filename = getRealPathFromURI(Uri.parse(filename), cordova);
|
||||
|
||||
boolean append = false;
|
||||
if (offset > 0) {
|
||||
@@ -1001,19 +1090,27 @@ public class FileUtils extends Plugin {
|
||||
* @param filename
|
||||
* @param size
|
||||
* @throws FileNotFoundException, IOException
|
||||
* @throws NoModificationAllowedException
|
||||
*/
|
||||
private long truncateFile(String filename, long size) throws FileNotFoundException, IOException {
|
||||
filename = stripFileProtocol(filename);
|
||||
private long truncateFile(String filename, long size) throws FileNotFoundException, IOException, NoModificationAllowedException {
|
||||
if (filename.startsWith("content://")) {
|
||||
throw new NoModificationAllowedException("Couldn't truncate file given its content URI");
|
||||
}
|
||||
|
||||
filename = getRealPathFromURI(Uri.parse(filename), cordova);
|
||||
|
||||
RandomAccessFile raf = new RandomAccessFile(filename, "rw");
|
||||
try {
|
||||
if (raf.length() >= size) {
|
||||
FileChannel channel = raf.getChannel();
|
||||
channel.truncate(size);
|
||||
return size;
|
||||
}
|
||||
|
||||
if (raf.length() >= size) {
|
||||
FileChannel channel = raf.getChannel();
|
||||
channel.truncate(size);
|
||||
return size;
|
||||
return raf.length();
|
||||
} finally {
|
||||
raf.close();
|
||||
}
|
||||
|
||||
return raf.length();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1029,7 +1126,7 @@ public class FileUtils extends Plugin {
|
||||
return cordova.getActivity().getContentResolver().openInputStream(uri);
|
||||
}
|
||||
else {
|
||||
path = stripFileProtocol(path);
|
||||
path = getRealPathFromURI(Uri.parse(path), cordova);
|
||||
return new FileInputStream(path);
|
||||
}
|
||||
}
|
||||
@@ -1038,15 +1135,25 @@ 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 == null) {
|
||||
return contentUri.toString();
|
||||
} else 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,46 +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 = "";
|
||||
PluginResult result = new PluginResult(status, message);
|
||||
result.setKeepCallback(true);
|
||||
|
||||
try {
|
||||
if ( locationManager.isProviderEnabled( LocationManager.GPS_PROVIDER ) ||
|
||||
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 ((System.currentTimeMillis() - last.getTime()) <= maximumAge) {
|
||||
result = new PluginResult(PluginResult.Status.OK, this.returnLocationJSON(last));
|
||||
if (last != null && (System.currentTimeMillis() - last.getTime()) <= maximumAge) {
|
||||
PluginResult result = new PluginResult(PluginResult.Status.OK, this.returnLocationJSON(last));
|
||||
callbackContext.sendPluginResult(result);
|
||||
} else {
|
||||
this.getCurrentLocation(callbackId, enableHighAccuracy);
|
||||
this.getCurrentLocation(callbackContext, enableHighAccuracy);
|
||||
}
|
||||
}
|
||||
else if (action.equals("addWatch")) {
|
||||
String id = args.getString(0);
|
||||
boolean enableHighAccuracy = args.getBoolean(1);
|
||||
this.addWatch(id, callbackId, enableHighAccuracy);
|
||||
this.addWatch(id, callbackContext, 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());
|
||||
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) {
|
||||
@@ -96,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) {
|
||||
@@ -153,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,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 {
|
||||
@@ -182,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;
|
||||
}
|
||||
}
|
||||
|
||||
577
framework/src/org/apache/cordova/Globalization.java
Normal 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;
|
||||
}
|
||||
}
|
||||
108
framework/src/org/apache/cordova/GlobalizationError.java
Normal 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;
|
||||
}
|
||||
}
|
||||