mirror of
https://github.com/apache/cordova-android.git
synced 2026-01-30 00:05:28 +08:00
Compare commits
573 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6cda55b35f | ||
|
|
da3f026974 | ||
|
|
7427b15f5f | ||
|
|
28580a2f4c | ||
|
|
31295566f3 | ||
|
|
20d2964be7 | ||
|
|
d40f43c144 | ||
|
|
5fb913d000 | ||
|
|
5fa4728ebe | ||
|
|
4a7cbb5eb4 | ||
|
|
bc91c554e6 | ||
|
|
dc9413258e | ||
|
|
4b574a2863 | ||
|
|
4b3cc67353 | ||
|
|
32b72756f3 | ||
|
|
2fc86e2833 | ||
|
|
fab472859d | ||
|
|
92caa3a186 | ||
|
|
26c7a96255 | ||
|
|
e170e463fe | ||
|
|
899daa9ea7 | ||
|
|
6d334c05e9 | ||
|
|
5ac0cc51d3 | ||
|
|
f93c2b161d | ||
|
|
6b071c0fb2 | ||
|
|
d3245a43d3 | ||
|
|
90a51c2cc1 | ||
|
|
61df5e0a37 | ||
|
|
c0312f9b50 | ||
|
|
eb70f05168 | ||
|
|
505db38232 | ||
|
|
096e1e3caa | ||
|
|
b5d8b51310 | ||
|
|
c9e7201058 | ||
|
|
4bf705a3d3 | ||
|
|
ce42568721 | ||
|
|
eb956b2449 | ||
|
|
1bf4e93da1 | ||
|
|
aba0a8421b | ||
|
|
b5a58e6ca0 | ||
|
|
44aa7464e1 | ||
|
|
4ea684dd7a | ||
|
|
215b7e08f8 | ||
|
|
754911f346 | ||
|
|
9873106785 | ||
|
|
d005359f89 | ||
|
|
1ce52a2845 | ||
|
|
7e480d1ff9 | ||
|
|
85877d259c | ||
|
|
0b86db8748 | ||
|
|
bca7f62efd | ||
|
|
4953ae84cd | ||
|
|
e96a5a0b3e | ||
|
|
e4678f4709 | ||
|
|
1def13deb3 | ||
|
|
bce4283239 | ||
|
|
9ff786d021 | ||
|
|
ee14a67795 | ||
|
|
b63a2e37be | ||
|
|
84274b4259 | ||
|
|
b6bf5298e6 | ||
|
|
b0d5ffec8f | ||
|
|
09ff81c411 | ||
|
|
a0293578b1 | ||
|
|
4595403a99 | ||
|
|
0f73884c8d | ||
|
|
2e9cbdcb0d | ||
|
|
a652d892ca | ||
|
|
581252febc | ||
|
|
b27d283f21 | ||
|
|
f2d7c49acf | ||
|
|
a397a23a9c | ||
|
|
9f7e179288 | ||
|
|
ad1c3d2438 | ||
|
|
51adf81918 | ||
|
|
97718a0a25 | ||
|
|
1aaba440b5 | ||
|
|
b8f2b8948f | ||
|
|
d96d49329b | ||
|
|
4db421ca36 | ||
|
|
c3991c8164 | ||
|
|
e904bab206 | ||
|
|
500ccd8e80 | ||
|
|
7cf7311a9d | ||
|
|
0669edddae | ||
|
|
38a8d7742e | ||
|
|
32e84d2316 | ||
|
|
151b86cb7b | ||
|
|
e4c9bebe34 | ||
|
|
8d5cb00bec | ||
|
|
15530a4820 | ||
|
|
f6e56b345d | ||
|
|
56d61eb44f | ||
|
|
2103da7b9d | ||
|
|
679069729c | ||
|
|
f764448ccc | ||
|
|
e1828696f7 | ||
|
|
5b87380749 | ||
|
|
917d0dfc49 | ||
|
|
191839f764 | ||
|
|
316cf057f3 | ||
|
|
55be212594 | ||
|
|
489e63f8e7 | ||
|
|
62c081dc85 | ||
|
|
023ad9ddf8 | ||
|
|
eccf486162 | ||
|
|
a6da46a00e | ||
|
|
747d2c97cd | ||
|
|
af2969dec5 | ||
|
|
53dba8678c | ||
|
|
afdac9b413 | ||
|
|
1ad280db98 | ||
|
|
035c3ad319 | ||
|
|
c237a1c0d2 | ||
|
|
f1d093548e | ||
|
|
beab74adf5 | ||
|
|
2a49e8a931 | ||
|
|
395857c37c | ||
|
|
9a34f25edc | ||
|
|
0af02fb9ae | ||
|
|
dcff8794ad | ||
|
|
1b4f5b13f1 | ||
|
|
3950818030 | ||
|
|
d6da2ef096 | ||
|
|
455298d736 | ||
|
|
d99856c52b | ||
|
|
087ec11e6a | ||
|
|
00c0a84e4e | ||
|
|
be229b1ac6 | ||
|
|
8106981bb6 | ||
|
|
de4d7cd10d | ||
|
|
804dcac12f | ||
|
|
fb0987b824 | ||
|
|
88f50a66ff | ||
|
|
7be600d8e9 | ||
|
|
11d6b8029f | ||
|
|
f1d4c01190 | ||
|
|
c12d93e77f | ||
|
|
204130a598 | ||
|
|
dbd45d4173 | ||
|
|
7e0bfbbad2 | ||
|
|
db18e1480e | ||
|
|
9baa27508a | ||
|
|
c3267def97 | ||
|
|
390927772e | ||
|
|
a8bec4ec9c | ||
|
|
167e283450 | ||
|
|
0c3254fd48 | ||
|
|
0faf2f0461 | ||
|
|
dd6e42aacc | ||
|
|
18e5e9dcc5 | ||
|
|
c8f44ab460 | ||
|
|
ac1f9c790a | ||
|
|
7533996fac | ||
|
|
1721571012 | ||
|
|
4358a04730 | ||
|
|
c552d912a0 | ||
|
|
ad7ce085f7 | ||
|
|
828edb3a43 | ||
|
|
4cb64580fd | ||
|
|
5b2fa128a4 | ||
|
|
b7abb64661 | ||
|
|
66424b7ed5 | ||
|
|
81dafb7b3f | ||
|
|
cea81c2dc1 | ||
|
|
4b1e99ef93 | ||
|
|
83120a5bea | ||
|
|
20723896e1 | ||
|
|
aed4859642 | ||
|
|
d0ade1d190 | ||
|
|
fb8e35bb44 | ||
|
|
ce351f5c38 | ||
|
|
26ee1c4547 | ||
|
|
bf327f3916 | ||
|
|
e3dd6d8c88 | ||
|
|
137fe12c43 | ||
|
|
a2fed200fe | ||
|
|
efeeef214b | ||
|
|
37617c67f8 | ||
|
|
56f675f188 | ||
|
|
7e7dc7694c | ||
|
|
8cf8da5776 | ||
|
|
b59705bed4 | ||
|
|
3b909253bb | ||
|
|
98f90340f3 | ||
|
|
a4c9bf7d30 | ||
|
|
f459eaa5ea | ||
|
|
8d8b874c20 | ||
|
|
ccceaeaca2 | ||
|
|
076e93184b | ||
|
|
c352b296da | ||
|
|
9e04eec9dd | ||
|
|
0e19f88a04 | ||
|
|
e788e8fa0f | ||
|
|
a56c406aa3 | ||
|
|
a3457d9408 | ||
|
|
b69fed18e2 | ||
|
|
2964aea447 | ||
|
|
587488a1b1 | ||
|
|
623b394c83 | ||
|
|
e671ffdab1 | ||
|
|
92d1080b2f | ||
|
|
893c0e9b67 | ||
|
|
af60f71ea3 | ||
|
|
9a952f1004 | ||
|
|
3ec7dfff8b | ||
|
|
d30a5e0388 | ||
|
|
fcece7e189 | ||
|
|
3949d9633c | ||
|
|
62c1c5f38b | ||
|
|
56204c5748 | ||
|
|
34c163be88 | ||
|
|
11002d4a56 | ||
|
|
240f27ce97 | ||
|
|
5295be1c25 | ||
|
|
238a67af3a | ||
|
|
4382234676 | ||
|
|
8e5c93a31f | ||
|
|
0e5d72dc5d | ||
|
|
4b8069f5ec | ||
|
|
a816a48416 | ||
|
|
5415440829 | ||
|
|
9668272b80 | ||
|
|
15e19489e3 | ||
|
|
2083f683ad | ||
|
|
c3610aa43c | ||
|
|
df4fbc272a | ||
|
|
9698a995fb | ||
|
|
c1ac3aa483 | ||
|
|
311bdbd360 | ||
|
|
291f111913 | ||
|
|
95e10bdb9e | ||
|
|
c2a6dcb6bd | ||
|
|
61c4bb9888 | ||
|
|
3439746645 | ||
|
|
b10fe465ab | ||
|
|
c6b171ba95 | ||
|
|
5a17d6cd5f | ||
|
|
480af2644c | ||
|
|
ecd2e06883 | ||
|
|
4f3ae23170 | ||
|
|
7cfb33d0ef | ||
|
|
9224ab1592 | ||
|
|
a696ff37f1 | ||
|
|
931a996dab | ||
|
|
68c03090a3 | ||
|
|
98fe46757f | ||
|
|
cefd137634 | ||
|
|
6b6e887c2f | ||
|
|
20cd4f806a | ||
|
|
b92303b1c9 | ||
|
|
59d23e05b1 | ||
|
|
731a36d3a0 | ||
|
|
162fc0720e | ||
|
|
342bbaa3ae | ||
|
|
f086ef5cad | ||
|
|
56a3ee5fe6 | ||
|
|
d80d532a2a | ||
|
|
3aca14d530 | ||
|
|
aa2d3962bf | ||
|
|
f7c717e393 | ||
|
|
268fea58ee | ||
|
|
87cdc5ad1c | ||
|
|
ba140a8a84 | ||
|
|
27f1181d53 | ||
|
|
f953e6adb8 | ||
|
|
ffd14fe7d9 | ||
|
|
3206c2100d | ||
|
|
66fa12a091 | ||
|
|
b1bdf23d9c | ||
|
|
12bf07d560 | ||
|
|
132650df28 | ||
|
|
81a77949fc | ||
|
|
e597f98c62 | ||
|
|
7fbb2b195f | ||
|
|
1feaa7fed7 | ||
|
|
9b82ae19b0 | ||
|
|
9d3c13065b | ||
|
|
4859f8f759 | ||
|
|
ac284fd39c | ||
|
|
fdef0db87c | ||
|
|
e78db000c6 | ||
|
|
032ea8a8d3 | ||
|
|
fc63f66e89 | ||
|
|
832e626573 | ||
|
|
cc7d352209 | ||
|
|
ce5d9a2ee8 | ||
|
|
77c51d3ae7 | ||
|
|
53dae45430 | ||
|
|
16343ffe70 | ||
|
|
b37498d5f6 | ||
|
|
7ad16e5b0c | ||
|
|
9f41906895 | ||
|
|
2af8daff1d | ||
|
|
fbeb379f1b | ||
|
|
9577735ff7 | ||
|
|
2dcd50c11b | ||
|
|
862c223e11 | ||
|
|
7f4d5aeb0e | ||
|
|
30681eb772 | ||
|
|
52e575e1e7 | ||
|
|
890e12c306 | ||
|
|
6cbf6b7875 | ||
|
|
c255a84941 | ||
|
|
ce7d6d69d9 | ||
|
|
d5538b7076 | ||
|
|
cdfa13b265 | ||
|
|
e5efc91ef4 | ||
|
|
e31c911c30 | ||
|
|
a658ea1573 | ||
|
|
6d5b88d7b9 | ||
|
|
a986e72338 | ||
|
|
162d9b6c2e | ||
|
|
f7f49d27c5 | ||
|
|
9e3ccf4b3e | ||
|
|
6b71c2f392 | ||
|
|
0d313a3964 | ||
|
|
75a0a6752a | ||
|
|
363fc8deb5 | ||
|
|
ddac192c4a | ||
|
|
69a03c2e16 | ||
|
|
2b128b85f7 | ||
|
|
b09f973231 | ||
|
|
95815a558c | ||
|
|
879da03438 | ||
|
|
d022be547b | ||
|
|
3f83fdbfc1 | ||
|
|
949152532c | ||
|
|
215adab1f9 | ||
|
|
7ce46ed60c | ||
|
|
c32bcca67b | ||
|
|
cb442364ca | ||
|
|
6bdc01290d | ||
|
|
ac34bf1e54 | ||
|
|
6fb164d200 | ||
|
|
a5d300c6ff | ||
|
|
6eb4409a72 | ||
|
|
533677df8b | ||
|
|
8f27b2ab56 | ||
|
|
25be42d385 | ||
|
|
00f6d30e08 | ||
|
|
090822eb41 | ||
|
|
d9900a725d | ||
|
|
a10106c61a | ||
|
|
5cb01f2ae9 | ||
|
|
4c1efe7ad4 | ||
|
|
4be92f285a | ||
|
|
f9b89e98c2 | ||
|
|
be01ce03d0 | ||
|
|
f221441877 | ||
|
|
18fda7ec68 | ||
|
|
f2e8c00f49 | ||
|
|
30e8b818f5 | ||
|
|
525ce0e0ad | ||
|
|
3cd567dc95 | ||
|
|
2f7ffa3636 | ||
|
|
d99386ef1e | ||
|
|
9ae3d2c074 | ||
|
|
dd5a337a49 | ||
|
|
51e634ccb4 | ||
|
|
31b1a821ca | ||
|
|
bf13fd48ce | ||
|
|
3b99760a42 | ||
|
|
0e78dc35d8 | ||
|
|
c8bbdb23de | ||
|
|
7ee8117186 | ||
|
|
8237c41143 | ||
|
|
d52ca93ba6 | ||
|
|
8354651059 | ||
|
|
81cc3c260f | ||
|
|
4dc32e194b | ||
|
|
5a82dd5110 | ||
|
|
f20708a5e7 | ||
|
|
91cf78f183 | ||
|
|
0cde8819cf | ||
|
|
07632b0eeb | ||
|
|
4a7f825cfe | ||
|
|
0b6b068097 | ||
|
|
4bc2051f44 | ||
|
|
623b2306ca | ||
|
|
34dde53506 | ||
|
|
7a09182446 | ||
|
|
233e513860 | ||
|
|
eb8cf56e8e | ||
|
|
12a27643db | ||
|
|
c6ccde0558 | ||
|
|
16e3ebd87b | ||
|
|
94c096dd5b | ||
|
|
2e3e4ec3b2 | ||
|
|
6e222c3938 | ||
|
|
3b3bd9b6c9 | ||
|
|
4e3331ba66 | ||
|
|
7caa96abcd | ||
|
|
b2776269cf | ||
|
|
b6c5a5fc9a | ||
|
|
94943a9a84 | ||
|
|
4c1942e3fe | ||
|
|
71e72f215d | ||
|
|
58cdfd86d0 | ||
|
|
dfa66b9dd4 | ||
|
|
d56ea25816 | ||
|
|
c91b272648 | ||
|
|
ca8bb75b40 | ||
|
|
36eab713a1 | ||
|
|
7133576fe9 | ||
|
|
effffcba1d | ||
|
|
404ce8bc3e | ||
|
|
a91bd095b0 | ||
|
|
fd6a1e5ed0 | ||
|
|
7d6ac87033 | ||
|
|
8aa813b862 | ||
|
|
95aa5c9f1c | ||
|
|
4319447cb5 | ||
|
|
50ea162251 | ||
|
|
9c239804d3 | ||
|
|
a7ccb9243d | ||
|
|
320e31bb10 | ||
|
|
8b55a16986 | ||
|
|
f9b8f9a45f | ||
|
|
41125ea1e2 | ||
|
|
73219bf2d2 | ||
|
|
d6eb723b7f | ||
|
|
993d73762c | ||
|
|
48b51c451a | ||
|
|
3d191d5884 | ||
|
|
955133f173 | ||
|
|
5054b714e2 | ||
|
|
c2cafb4b45 | ||
|
|
05868b541b | ||
|
|
67f474ef42 | ||
|
|
a40424e75c | ||
|
|
cd6c0e1de9 | ||
|
|
a99c8219bd | ||
|
|
a03fdaba39 | ||
|
|
92be0033a8 | ||
|
|
6f301576eb | ||
|
|
e2b3f76a10 | ||
|
|
b934c1be6a | ||
|
|
145b50a320 | ||
|
|
a33cdc9c7b | ||
|
|
62101e85ff | ||
|
|
0a3714e5e0 | ||
|
|
86a2830d75 | ||
|
|
b277202838 | ||
|
|
a4f6d9f6e7 | ||
|
|
9300e97d2b | ||
|
|
1d4aa44d3d | ||
|
|
3792f75281 | ||
|
|
b52fcb8aa9 | ||
|
|
a14c794255 | ||
|
|
aef96e95e8 | ||
|
|
f0da63a8ff | ||
|
|
cc860804f6 | ||
|
|
f38c460588 | ||
|
|
d8a19b5565 | ||
|
|
1c5b5e2ce6 | ||
|
|
9b9c59766f | ||
|
|
fc2a202afa | ||
|
|
4b4b71ff32 | ||
|
|
9358838dab | ||
|
|
2f24e42dc1 | ||
|
|
0c12aa163e | ||
|
|
a4d9f702e4 | ||
|
|
ec47274fbd | ||
|
|
efcedabee0 | ||
|
|
25a7b66296 | ||
|
|
ac194cd34f | ||
|
|
04ccb06e3f | ||
|
|
d31ee20ba5 | ||
|
|
9b25d45b93 | ||
|
|
eca05e6bad | ||
|
|
84bf20152b | ||
|
|
d51abdd73e | ||
|
|
200e9f1a8e | ||
|
|
7dc09b4019 | ||
|
|
dbb196a17e | ||
|
|
05a95c699f | ||
|
|
9ea8b2237a | ||
|
|
9c5e340fb8 | ||
|
|
e86c2e5970 | ||
|
|
67006add53 | ||
|
|
caeb86843d | ||
|
|
1571b26a65 | ||
|
|
0f15608175 | ||
|
|
705991e5b0 | ||
|
|
b636874bd9 | ||
|
|
965e4e9b19 | ||
|
|
af77977fda | ||
|
|
e74baf188f | ||
|
|
663a71255f | ||
|
|
bdf2f22f81 | ||
|
|
79aa3e159d | ||
|
|
95118398dd | ||
|
|
4d18a8e55f | ||
|
|
3bab41f138 | ||
|
|
a8330773ca | ||
|
|
4ca2305693 | ||
|
|
f577af0886 | ||
|
|
aab47bd453 | ||
|
|
445ddd89fb | ||
|
|
6f21a96238 | ||
|
|
c47bcb2f54 | ||
|
|
b0b628ffc2 | ||
|
|
428e1bc14d | ||
|
|
d66bb84924 | ||
|
|
4ce5123a12 | ||
|
|
4b4a2e9f9e | ||
|
|
58afd0b604 | ||
|
|
4352456129 | ||
|
|
bb141a70e8 | ||
|
|
ff260c03ca | ||
|
|
96a1192474 | ||
|
|
297f862ccc | ||
|
|
c052f40ef8 | ||
|
|
98246c0e35 | ||
|
|
8ac067da89 | ||
|
|
0ffb5d253a | ||
|
|
3a9898a6a6 | ||
|
|
693ec14df5 | ||
|
|
fa189b3234 | ||
|
|
3b27cd093b | ||
|
|
141bbfb051 | ||
|
|
663a919ed1 | ||
|
|
483babe3bc | ||
|
|
6abb9da88a | ||
|
|
b407641049 | ||
|
|
d5e8807756 | ||
|
|
7e9fdb3555 | ||
|
|
32e07c22d0 | ||
|
|
d427c52aac | ||
|
|
eb623a84d5 | ||
|
|
07290277ba | ||
|
|
743541218f | ||
|
|
94de0a7ce2 | ||
|
|
36e9fb292b | ||
|
|
2661e010d9 | ||
|
|
7687becfee | ||
|
|
1641f09dc9 | ||
|
|
b42faea2eb | ||
|
|
1505673393 | ||
|
|
635a6279a9 | ||
|
|
404d3e0959 | ||
|
|
f77b20bbca | ||
|
|
1d0a1664e6 | ||
|
|
410afbf9a1 | ||
|
|
aaddfa6f3a | ||
|
|
2d9a16e857 | ||
|
|
1dcba51092 | ||
|
|
7c63b30de1 | ||
|
|
c0eae1ad52 | ||
|
|
c012b98223 | ||
|
|
559493babd | ||
|
|
990ab2c7ef | ||
|
|
437003de29 | ||
|
|
629e05b7b1 | ||
|
|
22b1959333 | ||
|
|
e98f6ae570 | ||
|
|
0b5bf0c098 | ||
|
|
1deefa48ee | ||
|
|
97008305ff | ||
|
|
1a17083e8c | ||
|
|
b6664cc859 | ||
|
|
50c4aef873 | ||
|
|
cf42d31214 | ||
|
|
00caa1c0a0 | ||
|
|
93c8ba920a | ||
|
|
8702c04d39 | ||
|
|
e595c313a1 | ||
|
|
955da2e360 | ||
|
|
c349892c5b | ||
|
|
3d4b8ce99b | ||
|
|
64fd87134f | ||
|
|
82bba44538 |
38
.gitignore
vendored
38
.gitignore
vendored
@@ -2,28 +2,29 @@
|
||||
default.properties
|
||||
gen
|
||||
assets/www/cordova.js
|
||||
framework/assets/www/.tmp*
|
||||
local.properties
|
||||
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
|
||||
framework/javadoc-public
|
||||
framework/javadoc-private
|
||||
test/libs
|
||||
/framework/lib
|
||||
/framework/build
|
||||
/framework/bin
|
||||
/framework/assets/www/.DS_Store
|
||||
/framework/assets/www/cordova-*.js
|
||||
/framework/assets/www/phonegap-*.js
|
||||
/framework/libs
|
||||
/framework/javadoc-public
|
||||
/framework/javadoc-private
|
||||
/test/libs
|
||||
example
|
||||
./test
|
||||
test/bin
|
||||
test/assets/www/.tmp*
|
||||
test/assets/www/cordova.js
|
||||
test/cordova/plugins/org.apache.cordova.device/www/device.js
|
||||
test/cordova/plugins/org.apache.cordova.device/src/android/Device.java
|
||||
/test/bin
|
||||
/test/assets/www/.tmp*
|
||||
/test/assets/www/cordova.js
|
||||
/test/gradle
|
||||
/test/gradlew
|
||||
/test/gradlew.bat
|
||||
/test/build
|
||||
.gradle
|
||||
tmp/**
|
||||
.metadata
|
||||
tmp/**/*
|
||||
@@ -37,3 +38,6 @@ Desktop.ini
|
||||
# IntelliJ IDEA files
|
||||
*.iml
|
||||
.idea
|
||||
npm-debug.log
|
||||
/node_modules
|
||||
/framework/build
|
||||
|
||||
2
.jshintignore
Normal file
2
.jshintignore
Normal file
@@ -0,0 +1,2 @@
|
||||
bin/node_modules/*
|
||||
bin/templates/project/*
|
||||
10
.jshintrc
Normal file
10
.jshintrc
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"node": true
|
||||
, "bitwise": true
|
||||
, "undef": true
|
||||
, "trailing": true
|
||||
, "quotmark": true
|
||||
, "indent": 4
|
||||
, "unused": "vars"
|
||||
, "latedef": "nofunc"
|
||||
}
|
||||
4
.ratignore
Normal file
4
.ratignore
Normal file
@@ -0,0 +1,4 @@
|
||||
*.properties
|
||||
bin
|
||||
gen
|
||||
proguard-project.txt
|
||||
8
.travis.yml
Normal file
8
.travis.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
language: android
|
||||
sudo: false
|
||||
install:
|
||||
- npm install
|
||||
- echo y | android update sdk -u --filter android-22
|
||||
script:
|
||||
- npm test
|
||||
- npm run test-build
|
||||
38
CONTRIBUTING.md
Normal file
38
CONTRIBUTING.md
Normal file
@@ -0,0 +1,38 @@
|
||||
<!--
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
-->
|
||||
|
||||
# Contributing to Apache Cordova
|
||||
|
||||
Anyone can contribute to Cordova. And we need your contributions.
|
||||
|
||||
There are multiple ways to contribute: report bugs, improve the docs, and
|
||||
contribute code.
|
||||
|
||||
For instructions on this, start with the
|
||||
[contribution overview](http://cordova.apache.org/#contribute).
|
||||
|
||||
The details are explained there, but the important items are:
|
||||
- Sign and submit an Apache ICLA (Contributor License Agreement).
|
||||
- Have a Jira issue open that corresponds to your contribution.
|
||||
- Run the tests so your patch doesn't break existing functionality.
|
||||
|
||||
We look forward to your contributions!
|
||||
|
||||
118
LICENSE
118
LICENSE
@@ -1,4 +1,4 @@
|
||||
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
@@ -187,7 +187,7 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
Copyright 2015 Apache Cordova
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -199,4 +199,116 @@
|
||||
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.
|
||||
limitations under the License.
|
||||
|
||||
ADDITIONAL LICENSES:
|
||||
|
||||
================================================================================
|
||||
bin/node_modules/q
|
||||
================================================================================
|
||||
|
||||
Copyright 2009–2012 Kristopher Michael Kowal. All rights reserved.
|
||||
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.
|
||||
|
||||
================================================================================
|
||||
bin/node_modules/shelljs
|
||||
================================================================================
|
||||
Copyright (c) 2012, Artur Adib <aadib@mozilla.com>
|
||||
All rights reserved.
|
||||
|
||||
You may use this project under the terms of the New BSD license as follows:
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Artur Adib nor the
|
||||
names of the contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL ARTUR ADIB BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
================================================================================
|
||||
bin/node_modules/nopt
|
||||
================================================================================
|
||||
Copyright 2009, 2010, 2011 Isaac Z. Schlueter.
|
||||
All rights reserved.
|
||||
|
||||
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.
|
||||
|
||||
================================================================================
|
||||
bin/node_modules/which
|
||||
================================================================================
|
||||
|
||||
Copyright 2009, 2010, 2011 Isaac Z. Schlueter.
|
||||
All rights reserved.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
|
||||
14
NOTICE
14
NOTICE
@@ -1,5 +1,15 @@
|
||||
Apache Cordova
|
||||
Copyright 2012 The Apache Software Foundation
|
||||
Copyright 2014 The Apache Software Foundation
|
||||
|
||||
This product includes software developed at
|
||||
The Apache Software Foundation (http://www.apache.org/).
|
||||
The Apache Software Foundation (http://www.apache.org)
|
||||
|
||||
=========================================================================
|
||||
== NOTICE file corresponding to the section 4 d of ==
|
||||
== the Apache License, Version 2.0, ==
|
||||
== in this case for the Android-specific code. ==
|
||||
=========================================================================
|
||||
|
||||
This product includes software developed as part of
|
||||
The Android Open Source Project (http://source.android.com).
|
||||
|
||||
|
||||
53
README.md
Executable file → Normal file
53
README.md
Executable file → Normal file
@@ -18,37 +18,31 @@
|
||||
# under the License.
|
||||
#
|
||||
-->
|
||||
Cordova Android
|
||||
===
|
||||
# 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, applications written with web technology: HTML, CSS and JavaScript.
|
||||
|
||||
[Apache Cordova](http://cordova.io) is a project of The Apache Software Foundation (ASF).
|
||||
[Apache Cordova](https://cordova.apache.org) is a project of The Apache Software Foundation (ASF).
|
||||
|
||||
|
||||
Requires
|
||||
---
|
||||
## Requires
|
||||
|
||||
- Java JDK 1.5 or greater
|
||||
- Apache Ant 1.8.0 or greater
|
||||
- Java JDK 1.6 or greater
|
||||
- Android SDK [http://developer.android.com](http://developer.android.com)
|
||||
|
||||
|
||||
Cordova Android Developer Tools
|
||||
---
|
||||
## Cordova Android Developer Tools
|
||||
|
||||
The Cordova developer tooling is split between general tooling and project level tooling.
|
||||
We recommend using the [Cordova command-line tool](https://www.npmjs.com/package/cordova) to create projects and be able to easily install plugins.
|
||||
|
||||
General Commands
|
||||
However, the following scripts can be used instead:
|
||||
|
||||
./bin/create [path package activity] ... creates the ./example app or a cordova android project
|
||||
./bin/check_reqs ....................... checks that your environment is set up for cordova-android development
|
||||
./bin/update [path] .................... updates an existing cordova-android project to the version of the framework
|
||||
|
||||
Project Commands
|
||||
|
||||
These commands live in a generated Cordova Android project. Any interactions with the emulator require you to have an AVD defined.
|
||||
|
||||
./cordova/clean ........................ cleans the project
|
||||
@@ -57,35 +51,8 @@ These commands live in a generated Cordova Android project. Any interactions wit
|
||||
./cordova/run ........................ calls `build` then deploys to a connected Android device. If no Android device is detected, will launch an emulator and deploy to it.
|
||||
./cordova/version ...................... returns the cordova-android version of the current project
|
||||
|
||||
Importing a Cordova Android Project into Eclipse
|
||||
----
|
||||
## Using Android Studio
|
||||
|
||||
1. File > New > Project...
|
||||
2. Android > Android Project
|
||||
3. Create project from existing source (point to the generated app found in tmp/android)
|
||||
4. Right click on libs/cordova.jar and add to build path
|
||||
5. Right click on the project root: Run as > Run Configurations
|
||||
6. Click on the Target tab and select Manual (this way you can choose the emulator or device to build to)
|
||||
1. Create a project
|
||||
2. Import it via "Non-Android Studio Project"
|
||||
|
||||
Building without the Tooling
|
||||
---
|
||||
Note: The Developer Tools handle this. This is only to be done if the tooling fails, or if
|
||||
you are developing directly against the framework.
|
||||
|
||||
|
||||
To create your `cordova.jar` file, run in the framework directory:
|
||||
|
||||
android update project -p . -t android-19
|
||||
ant jar
|
||||
|
||||
|
||||
Running Tests
|
||||
----
|
||||
Please see details under test/README.md.
|
||||
|
||||
Further Reading
|
||||
---
|
||||
|
||||
- [http://developer.android.com](http://developer.android.com)
|
||||
- [http://cordova.apache.org/](http://cordova.apache.org)
|
||||
- [http://wiki.apache.org/cordova/](http://wiki.apache.org/cordova/)
|
||||
|
||||
324
RELEASENOTES.md
324
RELEASENOTES.md
@@ -19,6 +19,330 @@
|
||||
#
|
||||
-->
|
||||
## Release Notes for Cordova (Android) ##
|
||||
Update these notes using: git log --pretty=format:'* %s' --topo-order --no-merges *remote*/4.1.x...HEAD
|
||||
|
||||
### Release 4.1.1 (Aug 2015) ###
|
||||
|
||||
* CB-9428 update script now bumps up minSdkVersion to 14 if it is less than that
|
||||
* CB-9430 Fixes check_reqs failure when javac returns an extra line
|
||||
|
||||
### Release 4.1.0 (Jul 2015) ###
|
||||
|
||||
* CB-9185 Fixed an issue when unsigned apks couldn't be found. This closes #202
|
||||
* CB-9397 Fixes minor issues with `cordova requirements android`
|
||||
* CB-9389 Fixes build/check_reqs hang
|
||||
* CB-9392 Fixed printing flavored versions. This closes #184.
|
||||
* CB-9382 [Android] Fix KeepRunning setting when Plugin activity is showed. This closes #200
|
||||
* CB-9391 Fixes cdvBuildMultipleApks option casting
|
||||
* CB-9343 Split the Content-Type to obtain a clean mimetype
|
||||
* CB-9255 Make getUriType case insensitive.
|
||||
* CB-9149 Fixes JSHint issue introduced by 899daa9
|
||||
* CB-9372: Remove unused files: 'main.js' & 'master.css'. This closes #198
|
||||
* CB-9149 Make gradle alias subprojects in order to handle libs that depend on libs. This closes #182
|
||||
* Update min SDK version to 14
|
||||
* Update licenses. This closes #190
|
||||
* CB-9185 Fix signed release build exception. This closes #193.
|
||||
* CB-9286 Fixes build failure when ANDROID_HOME is not set.
|
||||
* CB-9284 Fix for handling absolute path for keystore in build.json
|
||||
* CB-9260 Install Android-22 on Travis-CI
|
||||
* Adding .ratignore file.
|
||||
* CB-9119 Adding lib/retry.js for retrying promise-returning functions. Retrying 'adb install' in emulator.js because it sometimes hangs.
|
||||
* CB-9115 android: Grant Lollipop permission req
|
||||
* Remove extra console message
|
||||
* CB-8898 Report expected gradle location properly
|
||||
* CB-8898 Fixes gradle check failure due to missing quotes
|
||||
* CB-9080: -d option is not supported on Android 4.1.1 and lower, removing
|
||||
* CB-8954 Adds `requirements` command support to check_reqs module
|
||||
* Update JS snapshot to version 4.1.0-dev (via coho)
|
||||
* CB-8417 updated platform specific files from cordova.js repo
|
||||
* Adding tests to confirm that preferences aren't changed by Intents
|
||||
* Forgot to remove the method that copied over the intent data
|
||||
* Getting around to removing this old Intent code
|
||||
* Update JS snapshot to version 4.1.0-dev (via coho)
|
||||
* Fix CordovaPluginTest on KitKat (start-up events seem to change)
|
||||
* CB-3360 Allow setting a custom User-Agent (close #162)
|
||||
* CB-8902 Use immersive mode when available when going fullscreen (close #175)
|
||||
* Make BridgeMode methods public (they were always supposed to be)
|
||||
* Simplify: EncodingUtils.getBytes(str) -> str.getBytes()
|
||||
* Don't show warning when gradlew file is read-only
|
||||
* Don't show warning when prepEnv copies gradlew and it's read-only
|
||||
* Make gradle wrapper prepEnv code work even when android-sdk is read-only
|
||||
* CB-8897 Delete drawable/icon.png since it duplicates drawable-mdpi/icon.png
|
||||
* Updating the template to target mininumSdkTarget=14
|
||||
* CB-8894: Updating the template to target mininumSdkTarget=14
|
||||
* CB-8891 Add a note about when the gradle helpers were added
|
||||
* CB-8891 Add a gradle helper for retrieving config.xml preference values
|
||||
* CB-8884 Delete Eclipse tweaks from create script
|
||||
* CB-8834 Don't fail to install on VERSION_DOWNGRADE
|
||||
* Automated tools fail, and you have to remember all four places where this is set.
|
||||
* Update the package.json
|
||||
* CB-9042 coho failed to update version, so here we are
|
||||
* CB9042 - Updating Release Notes
|
||||
* Adding tests to confirm that preferences aren't changed by Intents
|
||||
* updating existing test code
|
||||
* Forgot to remove the method that copied over the intent data
|
||||
* Getting around to removing this old Intent code
|
||||
* CB-8834 Don't fail to install on VERSION_DOWNGRADE
|
||||
|
||||
### Release 4.0.2 (May 2015) ###
|
||||
|
||||
* Removed Intent Functionality from Preferences - Preferences can no longer be set by intents
|
||||
|
||||
### Release 4.0.1 (April 2015) ###
|
||||
|
||||
* Bug fixed where platform failed to install on a version downgrade
|
||||
|
||||
### Release 4.0.0 (March 2015) ###
|
||||
|
||||
This release adds significant functionality, and also introduces a number
|
||||
of breaking changes. Some of the changes to the code base will be of
|
||||
particular interest to plugin developers.
|
||||
|
||||
#### Major Changes ####
|
||||
* Support for pluggable WebViews
|
||||
* The system WebView can be replaced in your app, via a plugin
|
||||
* Core WebView functionality is encapsulated, with extension points exposed
|
||||
via interfaces
|
||||
* Support for Crosswalk to bring the modern Chromium WebView to older devices
|
||||
* Uses the pluggable WebView framework
|
||||
* You will need to add the new [cordova-crosswalk-engine](https://github.com/MobileChromeApps/cordova-crosswalk-engine) plugin
|
||||
* Splash screen functionality is now provided via plugin
|
||||
* You will need to add the new [cordova-plugin-splashscreen](https://github.com/apache/cordova-plugin-splashscreen) plugin to continue using a splash screen
|
||||
* Whitelist functionality is now provided via plugin (CB-7747)
|
||||
* The whitelist has been enhanced to be more secure and configurable
|
||||
* Setting of Content-Security-Policy is now supported by the framework (see details in plugin readme)
|
||||
* You will need to add the new [cordova-plugin-whitelist](https://github.com/apache/cordova-plugin-whitelist) plugin
|
||||
* Legacy whitelist behaviour is still available via plugin (although not recommended).
|
||||
|
||||
Changes For Plugin Developers:
|
||||
|
||||
* Develop in Android Studio
|
||||
* Android Studio is now fully supported, and recommended over Eclipse
|
||||
* Build using Gradle
|
||||
* All builds [use Gradle by default](Android%20Shell%20Tool%20Guide_building_with_gradle), instead of Ant
|
||||
* Plugins can add their own gradle build steps!
|
||||
* Plugins can depend on Maven libraries using `<framework>` tags
|
||||
* New APIs: `onStart`, `onStop`, `onConfigurationChanged`
|
||||
* `"onScrollChanged"` message removed. Use `view.getViewTreeObserver().addOnScrollChangedListener(...)` instead
|
||||
* CB-8702 New API for plugins to override `shouldInterceptRequest` with a stream
|
||||
|
||||
#### Other Changes ####
|
||||
* CB-8378 Removed `hidekeyboard` and `showkeyboard` events (apps should use a plugin instead)
|
||||
* CB-8735 `bin/create` regex relaxed / better support for numbers
|
||||
* CB-8699 Fix CordovaResourceApi `copyResource` creating zero-length files when src=uncompressed asset
|
||||
* CB-8693 CordovaLib should not contain icons / splashscreens
|
||||
* CB-8592 Fix NPE if lifecycle events reach CordovaWebView before `init()` has been called
|
||||
* CB-8588 Add CATEGORY_BROWSABLE to intents from showWebPage openExternal=true
|
||||
* CB-8587 Don't allow WebView navigations within showWebPage that are not whitelisted
|
||||
* CB-7827 Add `--activity-name` for `bin/create`
|
||||
* CB-8548 Use debug-signing.properties and release-signing.properties when they exist
|
||||
* CB-8545 Don't add a layout as a parent of the WebView
|
||||
* CB-7159 BackgroundColor not used when `<html style="opacity:0">`, nor during screen rotation
|
||||
* CB-6630 Removed OkHttp from core library. It's now available as a plugin: [cordova-plugin-okhttp](https://www.npmjs.com/package/cordova-plugin-okhttp)
|
||||
|
||||
### Release 3.7.1 (January 2015) ###
|
||||
* CB-8411 Initialize plugins only after `createViews()` is called (regression in 3.7.0)
|
||||
|
||||
### Release 3.7.0 (January 2015) ###
|
||||
|
||||
* CB-8328 Allow plugins to handle certificate challenges (close #150)
|
||||
* CB-8201 Add support for auth dialogs into Cordova Android
|
||||
* CB-8017 Add support for `<input type=file>` for Lollipop
|
||||
* CB-8143 Loads of gradle improvements (try it with cordova/build --gradle)
|
||||
* CB-8329 Cancel outstanding ActivityResult requests when a new startActivityForResult occurs
|
||||
* CB-8026 Bumping up Android Version and setting it up to allow third-party cookies. This might change later.
|
||||
* CB-8210 Use PluginResult for various events from native so that content-security-policy <meta> can be used
|
||||
* CB-8168 Add support for `cordova/run --list` (closes #139)
|
||||
* CB-8176 Vastly better auto-detection of SDK & JDK locations
|
||||
* CB-8079 Use activity class package name, but fallback to application package name when looking for splash screen drawable
|
||||
* CB-8147 Have corodva/build warn about unrecognized flags rather than fail
|
||||
* CB-7881 Android tooling shouldn't lock application directory
|
||||
* CB-8112 Turn off mediaPlaybackRequiresUserGesture
|
||||
* CB-6153 Add a preference for controlling hardware button audio stream (DefaultVolumeStream)
|
||||
* CB-8031 Fix race condition that shows as ConcurrentModificationException
|
||||
* CB-7974 Cancel timeout timer if view is destroyed
|
||||
* CB-7940 Disable exec bridge if bridgeSecret is wrong
|
||||
* CB-7758 Allow content-url-hosted pages to access the bridge
|
||||
* CB-6511 Fixes build for android when app name contains unicode characters.
|
||||
* CB-7707 Added multipart PluginResult
|
||||
* CB-6837 Fix leaked window when hitting back button while alert being rendered
|
||||
* CB-7674 Move preference activation back into onCreate()
|
||||
* CB-7499 Support RTL text direction
|
||||
* CB-7330 Don't run check_reqs for bin/create.
|
||||
|
||||
### 3.6.4 (Sept 30, 2014) ###
|
||||
|
||||
* Set VERSION to 3.6.4 (via coho)
|
||||
* Update JS snapshot to version 3.6.4 (via coho)
|
||||
* CB-7634 Detect JAVA_HOME properly on Ubuntu
|
||||
* CB-7579 Fix run script's ability to use non-arch-specific APKs
|
||||
* CB-6511 Fixes build for android when app name contains unicode characters.
|
||||
* CB-7463: Adding licences. I don't know what the gradle syntax is for comments, that still needs to be done.
|
||||
* CB-7463: Looked at the Apache BigTop git, gradle uses C-style comments
|
||||
* CB-7460: Fixing bug with KitKat where the background colour would override the CSS colours on the application
|
||||
|
||||
### 3.6.0 (Sept 2014) ###
|
||||
|
||||
* Set VERSION to 3.6.0 (via coho)
|
||||
* CB-7410 fix the menu test
|
||||
* CB-7410 Fix the errorUrl test
|
||||
* CB-7410 Fix Basic Authentication test
|
||||
* CB-3445: Allow build and run scripts to select APK by architecture
|
||||
* CB-3445: Add environment variable 'BUILD_MULTIPLE_APKS' for splitting APKs based on architecture
|
||||
* CB-3445: Ensure that JAR files in libs directory are included
|
||||
* CB-7267 update RELEASENOTES for 3.5.1
|
||||
* CB-7410 clarify the title
|
||||
* CB-7385 update cordova.js for testing prior to branch/tag
|
||||
* CB-7410 add whitelist entries to get iframe/GoogleMaps working
|
||||
* CB-7291 propogate change in method signature to the native tests
|
||||
* CB-7291: Restrict meaning of "\*" in internal whitelist to just http and https
|
||||
* CB-7291: Only add file, content and data URLs to internal whitelist
|
||||
* CB-7291: Add defaults to external whitelist
|
||||
* CB-7291: Add external-launch-whitelist and use it for filtering intent launches
|
||||
* CB-3445: Read project.properties to configure gradle libraries
|
||||
* CB-7325 Fix error message in android_sdk_version.js when missing SDK on windows
|
||||
* CB-7335 Add a .gitignore to android project template
|
||||
* CB-7330 Fix dangling function call in last commit (broke gradle builds)
|
||||
* CB-7330 Don't run "android update" during creation
|
||||
* CB-3445 Add gradle support clean command (plus some code cleanup)
|
||||
* CB-7044 Fix typo in prev commit causing check_reqs to always fail.
|
||||
* CB-3445 Copy gradle wrapper in build instead of create
|
||||
* CB-3445 Add .gradle template files for "update" as well as "create"
|
||||
* CB-7044 Add JAVA_HOME when not set. Be stricter about ANDROID_HOME
|
||||
* CB-3445 Speed up gradle building (incremental builds go from 10s -> 1.5s for me)
|
||||
* CB-3445: android: Copy Gradle wrapper from Android SDK rather than bundling a JAR
|
||||
* CB-3445: Add which to checked-in node_modules
|
||||
* CB-3445: Add option to build and install with gradle
|
||||
* CB-3445: Add an initial set of Gradle build scripts
|
||||
* CB-7321 Don't require ant for create script
|
||||
* CB-7044, CB-7299 Fix up PATH problems when possible.
|
||||
* Change in test's AndroidManifest.xml needed for the test to run properly. Forgot the manifest.
|
||||
* Change in test's AndroidManifest.xml needed for the test to run properly
|
||||
* Adding tests related to 3.5.1
|
||||
* CB-7261 Fix setNativeToJsBridgeMode sometimes crashing when switching to ONLINE_EVENT
|
||||
* CB-7265 Fix crash when navigating to custom protocol (introduced in 3.5.1)
|
||||
* Filter out non-launchable intents
|
||||
* Handle unsupported protocol errors in webview better
|
||||
* CB-7238: I should have collapsed this, but Config.init() must go before the creation of CordovaWebView
|
||||
* CB-7238: Minor band-aid to get tests running again, this has to go away before 3.6.0 is released, since this is an API change.
|
||||
* Extend whitelist to handle URLs without // chars
|
||||
* CB-7172 Force window to have focus after resume
|
||||
* CB-7159 Set background color of webView as well as its parent
|
||||
* CB-7018 Fix setButtonPlumbedToJs never un-listening
|
||||
* Undeprecate some just-deprecated symbols in PluginManager.
|
||||
* @Deprecate methods of PluginManager that were never meant to be public
|
||||
* Move plugin instantiation and instance storing logic PluginEntry->PluginManager
|
||||
* Fix broken unit test due to missing Config.init() call
|
||||
* Update to check for Google Glass APIs
|
||||
* Fix for `android` not being in PATH check on Windows
|
||||
* Displaying error when regex does not match.
|
||||
* Fix broken compile due to previous commit :(
|
||||
* Tweak CordovaPlugin.initialize method to be less deprecated.
|
||||
* Un-deprecate CordovaActivity.init() - it's needed to tweak prefs in onCreate
|
||||
* Tweak log messages in CordovaBridge with bridgeSecret is wrong
|
||||
* Backport CordovaBridge from 4.0.x -> master
|
||||
* Update unit tests to not use most deprecated things (e.g. DroidGap)
|
||||
* Add non-String overloades for CordovaPreferences.set()
|
||||
* Make CordovaWebview resilient to init() not being called (for backwards-compatibility)
|
||||
* Add node_module licenses to LICENSE
|
||||
* Update cordova.js snapshot to work with bridge changes
|
||||
* Provide CordovaPlugin with CordovaPreferences. Add new Plugin.initialize()
|
||||
* Convert usages of Config.\* to use the non-static versions
|
||||
* Change getProperty -> prefs.get\* within CordovaActivity
|
||||
* Make CordovaUriHelper class package-private
|
||||
* Fix PluginManager.setPluginEntries not removing old entries
|
||||
* Move registration of App plugin from config.xml -> code
|
||||
* Make setWebViewClient an override instead of an overload. Delete Location-change JS->Native bridge mode (missed some of it).
|
||||
* CB-4404 Revert setting android:windowSoftInputMode to "adjustPan"
|
||||
* Refactor: Use ConfigXmlParser in activity. Adds CordovaWebView.init()
|
||||
* Deprecate some convenience methods on CordovaActivity
|
||||
* Fix CordovaPreferences not correctly parsing hex values (valueOf->decode)
|
||||
* Refactor: Move url-filter information into PluginEntry.
|
||||
* Don't re-parse config.xml in onResume.
|
||||
* Move handling of Fullscreen preference to CordovaActivity
|
||||
* Delete dead code from CordovaActivity
|
||||
* Update .classpath to make Eclipse happy (just re-orders one line)
|
||||
* Delete "CB-3064: The errorUrl is..." Log message left over from debugging presumably
|
||||
* Refactor Config into ConfigXmlParser, CordovaPreferences
|
||||
* Delete Location-change JS->Native bridge mode
|
||||
* CB-5988 Allow exec() only from file: or start-up URL's domain
|
||||
* CB-6761 Fix native->JS bridge ceasing to fire when page changes and online is set to false and the JS loads quickly
|
||||
* Update the errorurl to no longer use intents
|
||||
* This breaks running the JUnit tests, we'll bring it back soon
|
||||
* Refactoring the URI handling on Cordova, removing dead code
|
||||
* CB-7018 Clean up and deprecation of some button-related functions
|
||||
* CB-7017 Fix onload=true being set on all subsequent plugins
|
||||
* CB-5971: Fix package / project validation
|
||||
* CB-5971: Add unit tests to cordova-android
|
||||
* CB-5971: Factor out package/project name validation logic
|
||||
* Delete explicit activity.finish() in back button handling. No change in behaviour.
|
||||
* CB-5971: This would have been a good first bug, too bad
|
||||
* CB-4404: Changing where android:windowSoftInputMode is in the manifest so it works
|
||||
* Add documentation referencing other implementation.
|
||||
* CB-6851 Deprecate WebView.sendJavascript()
|
||||
* CB-6876 Show the correct executable name
|
||||
* CB-6876 Fix the "print usage"
|
||||
* Trivial spelling fix in comments when reading CordovaResourceApi
|
||||
* CB-6818: I want to remove this code, because Square didn't do their headers properly
|
||||
* CB-6860 Add activity_name and launcher_name to AndroidManifest.xml & strings.xml
|
||||
* Add a comment to custom_rules.xml saying why we move AndroidManifest.xml
|
||||
* Remove +x from README.md
|
||||
* CB-6784 Add missing licenses
|
||||
* CB-6784 Add license to CONTRIBUTING.md
|
||||
* Revert "defaults.xml: Add AndroidLaunchMode preference"
|
||||
* updated RELEASENOTES
|
||||
* CB-6315: Wrapping this so it runs on the UI thread
|
||||
* CB-6723 Update package name for Robotium
|
||||
* CB-6707 Update minSdkVersion to 10 consistently
|
||||
* CB-5652 make visible cordova version
|
||||
* Update JS snapshot to version 3.6.0-dev (via coho)
|
||||
* Update JS snapshot to version 3.6.0-dev (via coho)
|
||||
* Set VERSION to 3.6.0-dev (via coho)
|
||||
|
||||
### 3.5.1 (August 2014) ###
|
||||
|
||||
This was a security update to address CVE-2014-3500, CVE-2014-3501,
|
||||
and CVE-2014-3502. For more information, see
|
||||
http://cordova.apache.org/announcements/2014/08/04/android-351.html
|
||||
|
||||
* Filter out non-launchable intents
|
||||
* Handle unsupported protocol errors in webview better
|
||||
* Update the errorurl to no longer use intents
|
||||
* Refactoring the URI handling on Cordova, removing dead code
|
||||
|
||||
### 3.5.0 (May 2014) ###
|
||||
|
||||
* OkHttp has broken headers. Updating for ASF compliance.
|
||||
* Revert accidentally removed lines from NOTICE
|
||||
* CB-6552: added top level package.json
|
||||
* CB-6491 add CONTRIBUTING.md
|
||||
* CB-6543 Fix cordova/run failure when no custom_rules.xml available
|
||||
* defaults.xml: Add AndroidLaunchMode preference
|
||||
* Add JavaDoc for CordovaResourceApi
|
||||
* CB-6388: Handle binary data correctly in LOAD_URL bridge
|
||||
* Fix CB-6048: Set launchMode=singleTop so tapping app icon does not always restart app
|
||||
* Remove incorrect usage of AlertDialog.Builder.create
|
||||
* Catch uncaught exceptions in from plugins and turn them into error responses.
|
||||
* Add NOTICE file
|
||||
* CB-6047 Fix online sometimes getting in a bad state on page transitions.
|
||||
* Add another convenience overload for CordovaResourceApi.copyResource
|
||||
* Update framework's .classpath to what Eclipse wants it to be.
|
||||
* README.md: `android update` to `android-19`.
|
||||
* Fix NPE when POLLING bridge mode is used.
|
||||
* Updating NOTICE to include Square for OkHttp
|
||||
* CB-5398 Apply KitKat content URI fix to all content URIs
|
||||
* CB-5398 Work-around for KitKat content: URLs not rendering in <img> tags
|
||||
* CB-5908: add splascreen images to template
|
||||
* CB-5395: Make scheme and host (but not path) case-insensitive in whitelist
|
||||
* Ignore multiple onPageFinished() callbacks & onReceivedError due to stopLoading()
|
||||
* Removing addJavascriptInterface support from all Android versions lower than 4.2 due to security vu
|
||||
* CB-4984 Don't create on CordovaActivity name
|
||||
* CB-5917 Add a loadUrlIntoView overload that doesn't recreate plugins.
|
||||
* Use thread pool for load timeout.
|
||||
* CB-5715 For CLI, hide assets/www and res/xml/config.xml by default
|
||||
* CB-5793 ant builds: Rename AndroidManifest during -post-build to avoid Eclipse detecting ant-build/
|
||||
* CB-5889 Make update script find project name instead of using "null" for CordovaLib
|
||||
* CB-5889 Add a message in the update script about needing to import CordovaLib when using an IDE.
|
||||
|
||||
### 3.4.0 (Feb 2014) ###
|
||||
|
||||
|
||||
23
bin/create
23
bin/create
@@ -20,17 +20,30 @@
|
||||
*/
|
||||
var path = require('path');
|
||||
var create = require('./lib/create');
|
||||
var args = require('./lib/simpleargs').getArgs(process.argv);
|
||||
var argv = require('nopt')({
|
||||
'help' : Boolean,
|
||||
'cli' : Boolean,
|
||||
'shared' : Boolean,
|
||||
'link' : Boolean,
|
||||
'activity-name' : [String, undefined]
|
||||
});
|
||||
|
||||
if (args['--help'] || args._.length === 0) {
|
||||
console.log('Usage: ' + path.relative(process.cwd(), path.join(__dirname, 'create')) + ' <path_to_new_project> <package_name> <project_name> [<template_path>] [--shared]');
|
||||
if (argv.help || argv.argv.remain.length === 0) {
|
||||
console.log('Usage: ' + path.relative(process.cwd(), path.join(__dirname, 'create')) + ' <path_to_new_project> <package_name> <project_name> [<template_path>] [--activity-name <activity_name>] [--link]');
|
||||
console.log(' <path_to_new_project>: Path to your new Cordova Android project');
|
||||
console.log(' <package_name>: Package name, following reverse-domain style convention');
|
||||
console.log(' <project_name>: Project name');
|
||||
console.log(' <template_path>: Path to a custom application template to use');
|
||||
console.log(' --shared will use the CordovaLib project directly instead of making a copy.');
|
||||
console.log(' --activity-name <activity_name>: Activity name');
|
||||
console.log(' --link will use the CordovaLib project directly instead of making a copy.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
create.createProject(args._[0], args._[1], args._[2], args._[3], args['--shared'], args['--cli']).done();
|
||||
var project_path = argv.argv.remain[0];
|
||||
var package_name = argv.argv.remain[1];
|
||||
var project_name = argv.argv.remain[2];
|
||||
var template_path = argv.argv.remain[3];
|
||||
var activity_name = argv['activity-name'];
|
||||
|
||||
create.createProject(project_path, package_name, project_name, activity_name, template_path, argv.link || argv.shared, argv.cli).done();
|
||||
|
||||
|
||||
@@ -19,21 +19,20 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
var shell = require('shelljs'),
|
||||
child_process = require('child_process'),
|
||||
var child_process = require('child_process'),
|
||||
Q = require('q');
|
||||
|
||||
get_highest_sdk = function(results){
|
||||
var get_highest_sdk = function(results){
|
||||
var reg = /\d+/;
|
||||
var apiLevels = [];
|
||||
for(var i=0;i<results.length;i++){
|
||||
apiLevels[i] = parseInt(results[i].match(reg)[0]);
|
||||
}
|
||||
apiLevels.sort(function(a,b){return b-a});
|
||||
apiLevels.sort(function(a,b){return b-a;});
|
||||
console.log(apiLevels[0]);
|
||||
}
|
||||
};
|
||||
|
||||
get_sdks = function() {
|
||||
var get_sdks = function() {
|
||||
var d = Q.defer();
|
||||
child_process.exec('android list targets', function(err, stdout, stderr) {
|
||||
if (err) d.reject(stderr);
|
||||
@@ -51,15 +50,15 @@ get_sdks = function() {
|
||||
|
||||
return Q();
|
||||
}, function(stderr) {
|
||||
if (stderr.match(/command\snot\sfound/)) {
|
||||
if (stderr.match(/command\snot\sfound/) || stderr.match(/'android' is not recognized/)) {
|
||||
return Q.reject(new Error('The command \"android\" failed. Make sure you have the latest Android SDK installed, and the \"android\" command (inside the tools/ folder) is added to your path.'));
|
||||
} else {
|
||||
return Q.reject(new Error('An error occurred while listing Android targets'));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.run = function() {
|
||||
return Q.all([get_sdks()]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -19,78 +19,309 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
var shell = require('shelljs'),
|
||||
/* jshint sub:true */
|
||||
|
||||
var shelljs = require('shelljs'),
|
||||
child_process = require('child_process'),
|
||||
Q = require('q'),
|
||||
path = require('path'),
|
||||
fs = require('fs'),
|
||||
which = require('which'),
|
||||
ROOT = path.join(__dirname, '..', '..');
|
||||
|
||||
// Get valid target from framework/project.properties
|
||||
module.exports.get_target = function() {
|
||||
if(fs.existsSync(path.join(ROOT, 'framework', 'project.properties'))) {
|
||||
var target = shell.grep(/target=android-[\d+]/, path.join(ROOT, 'framework', 'project.properties'));
|
||||
return target.split('=')[1].replace('\n', '').replace('\r', '').replace(' ', '');
|
||||
} else if (fs.existsSync(path.join(ROOT, 'project.properties'))) {
|
||||
// if no target found, we're probably in a project and project.properties is in ROOT.
|
||||
// this is called on the project itself, and can support Google APIs AND Vanilla Android
|
||||
var target = shell.grep(/target=android-[\d+]/, path.join(ROOT, 'project.properties')) ||
|
||||
shell.grep(/target=Google Inc.:Google APIs:[\d+]/, path.join(ROOT, 'project.properties'));
|
||||
return target.split('=')[1].replace('\n', '').replace('\r', '');
|
||||
var isWindows = process.platform == 'win32';
|
||||
|
||||
function forgivingWhichSync(cmd) {
|
||||
try {
|
||||
// TODO: Should use shelljs.which() here to have one less dependency.
|
||||
return fs.realpathSync(which.sync(cmd));
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a promise.
|
||||
module.exports.check_ant = function() {
|
||||
function tryCommand(cmd, errMsg, catchStderr) {
|
||||
var d = Q.defer();
|
||||
child_process.exec('ant -version', function(err, stdout, stderr) {
|
||||
if (err) d.reject(new Error('ERROR : executing command \'ant\', make sure you have ant installed and added to your path.'));
|
||||
else d.resolve();
|
||||
child_process.exec(cmd, function(err, stdout, stderr) {
|
||||
if (err) d.reject(new Error(errMsg));
|
||||
// Sometimes it is necessary to return an stderr instead of stdout in case of success, since
|
||||
// some commands prints theirs output to stderr instead of stdout. 'javac' is the example
|
||||
else d.resolve((catchStderr ? stderr : stdout).trim());
|
||||
});
|
||||
return d.promise;
|
||||
}
|
||||
|
||||
// Get valid target from framework/project.properties
|
||||
module.exports.get_target = function() {
|
||||
function extractFromFile(filePath) {
|
||||
var target = shelljs.grep(/\btarget=/, filePath);
|
||||
if (!target) {
|
||||
throw new Error('Could not find android target within: ' + filePath);
|
||||
}
|
||||
return target.split('=')[1].trim();
|
||||
}
|
||||
if (fs.existsSync(path.join(ROOT, 'framework', 'project.properties'))) {
|
||||
return extractFromFile(path.join(ROOT, 'framework', 'project.properties'));
|
||||
}
|
||||
if (fs.existsSync(path.join(ROOT, 'project.properties'))) {
|
||||
// if no target found, we're probably in a project and project.properties is in ROOT.
|
||||
return extractFromFile(path.join(ROOT, 'project.properties'));
|
||||
}
|
||||
throw new Error('Could not find android target. File missing: ' + path.join(ROOT, 'project.properties'));
|
||||
};
|
||||
|
||||
// Returns a promise. Called only by build and clean commands.
|
||||
module.exports.check_ant = function() {
|
||||
return tryCommand('ant -version', 'Failed to run "ant -version", make sure you have ant installed and added to your PATH.')
|
||||
.then(function (output) {
|
||||
// Parse Ant version from command output
|
||||
return /version ((?:\d+\.)+(?:\d+))/i.exec(output)[1];
|
||||
});
|
||||
};
|
||||
|
||||
// Returns a promise. Called only by build and clean commands.
|
||||
module.exports.check_gradle = function() {
|
||||
var sdkDir = process.env['ANDROID_HOME'];
|
||||
if (!sdkDir)
|
||||
return Q.reject('Could not find gradle wrapper within Android SDK. Could not find Android SDK directory.\n' +
|
||||
'Might need to install Android SDK or set up \'ANDROID_HOME\' env variable.');
|
||||
|
||||
var wrapperDir = path.join(sdkDir, 'tools', 'templates', 'gradle', 'wrapper');
|
||||
if (!fs.existsSync(wrapperDir)) {
|
||||
return Q.reject(new Error('Could not find gradle wrapper within Android SDK. Might need to update your Android SDK.\n' +
|
||||
'Looked here: ' + wrapperDir));
|
||||
}
|
||||
return Q.when();
|
||||
};
|
||||
|
||||
// Returns a promise.
|
||||
module.exports.check_java = function() {
|
||||
var d = Q.defer();
|
||||
child_process.exec('java -version', function(err, stdout, stderr) {
|
||||
if(err) {
|
||||
var msg =
|
||||
'Failed to run \'java -version\', make sure your java environment is set up\n' +
|
||||
'including JDK and JRE.\n' +
|
||||
'Your JAVA_HOME variable is ' + process.env.JAVA_HOME + '\n';
|
||||
d.reject(new Error(msg + err));
|
||||
var javacPath = forgivingWhichSync('javac');
|
||||
var hasJavaHome = !!process.env['JAVA_HOME'];
|
||||
return Q().then(function() {
|
||||
if (hasJavaHome) {
|
||||
// Windows java installer doesn't add javac to PATH, nor set JAVA_HOME (ugh).
|
||||
if (!javacPath) {
|
||||
process.env['PATH'] += path.delimiter + path.join(process.env['JAVA_HOME'], 'bin');
|
||||
}
|
||||
} else {
|
||||
if (javacPath) {
|
||||
var msg = 'Failed to find \'JAVA_HOME\' environment variable. Try setting setting it manually.';
|
||||
// OS X has a command for finding JAVA_HOME.
|
||||
if (fs.existsSync('/usr/libexec/java_home')) {
|
||||
return tryCommand('/usr/libexec/java_home', msg)
|
||||
.then(function(stdout) {
|
||||
process.env['JAVA_HOME'] = stdout.trim();
|
||||
});
|
||||
} else {
|
||||
// See if we can derive it from javac's location.
|
||||
// fs.realpathSync is require on Ubuntu, which symplinks from /usr/bin -> JDK
|
||||
var maybeJavaHome = path.dirname(path.dirname(javacPath));
|
||||
if (fs.existsSync(path.join(maybeJavaHome, 'lib', 'tools.jar'))) {
|
||||
process.env['JAVA_HOME'] = maybeJavaHome;
|
||||
} else {
|
||||
throw new Error(msg);
|
||||
}
|
||||
}
|
||||
} else if (isWindows) {
|
||||
// Try to auto-detect java in the default install paths.
|
||||
var oldSilent = shelljs.config.silent;
|
||||
shelljs.config.silent = true;
|
||||
var firstJdkDir =
|
||||
shelljs.ls(process.env['ProgramFiles'] + '\\java\\jdk*')[0] ||
|
||||
shelljs.ls('C:\\Program Files\\java\\jdk*')[0] ||
|
||||
shelljs.ls('C:\\Program Files (x86)\\java\\jdk*')[0];
|
||||
shelljs.config.silent = oldSilent;
|
||||
if (firstJdkDir) {
|
||||
// shelljs always uses / in paths.
|
||||
firstJdkDir = firstJdkDir.replace(/\//g, path.sep);
|
||||
if (!javacPath) {
|
||||
process.env['PATH'] += path.delimiter + path.join(firstJdkDir, 'bin');
|
||||
}
|
||||
process.env['JAVA_HOME'] = firstJdkDir;
|
||||
}
|
||||
}
|
||||
}
|
||||
else d.resolve();
|
||||
}).then(function() {
|
||||
var msg =
|
||||
'Failed to run "java -version", make sure that you have a JDK installed.\n' +
|
||||
'You can get it from: http://www.oracle.com/technetwork/java/javase/downloads.\n';
|
||||
if (process.env['JAVA_HOME']) {
|
||||
msg += 'Your JAVA_HOME is invalid: ' + process.env['JAVA_HOME'] + '\n';
|
||||
}
|
||||
return tryCommand('java -version', msg)
|
||||
.then(function() {
|
||||
// We use tryCommand with catchStderr = true, because
|
||||
// javac writes version info to stderr instead of stdout
|
||||
return tryCommand('javac -version', msg, true);
|
||||
}).then(function (output) {
|
||||
var match = /javac ((?:\d+\.)+(?:\d+))/i.exec(output)[1];
|
||||
return match && match[1];
|
||||
});
|
||||
});
|
||||
return d.promise;
|
||||
}
|
||||
};
|
||||
|
||||
// Returns a promise.
|
||||
module.exports.check_android = function() {
|
||||
var valid_target = this.get_target();
|
||||
var d = Q.defer();
|
||||
child_process.exec('android list targets', function(err, stdout, stderr) {
|
||||
if (err) d.reject(stderr);
|
||||
else d.resolve(stdout);
|
||||
return Q().then(function() {
|
||||
var androidCmdPath = forgivingWhichSync('android');
|
||||
var adbInPath = !!forgivingWhichSync('adb');
|
||||
var hasAndroidHome = !!process.env['ANDROID_HOME'] && fs.existsSync(process.env['ANDROID_HOME']);
|
||||
function maybeSetAndroidHome(value) {
|
||||
if (!hasAndroidHome && fs.existsSync(value)) {
|
||||
hasAndroidHome = true;
|
||||
process.env['ANDROID_HOME'] = value;
|
||||
}
|
||||
}
|
||||
if (!hasAndroidHome && !androidCmdPath) {
|
||||
if (isWindows) {
|
||||
// Android Studio 1.0 installer
|
||||
maybeSetAndroidHome(path.join(process.env['LOCALAPPDATA'], 'Android', 'sdk'));
|
||||
maybeSetAndroidHome(path.join(process.env['ProgramFiles'], 'Android', 'sdk'));
|
||||
// Android Studio pre-1.0 installer
|
||||
maybeSetAndroidHome(path.join(process.env['LOCALAPPDATA'], 'Android', 'android-studio', 'sdk'));
|
||||
maybeSetAndroidHome(path.join(process.env['ProgramFiles'], 'Android', 'android-studio', 'sdk'));
|
||||
// Stand-alone installer
|
||||
maybeSetAndroidHome(path.join(process.env['LOCALAPPDATA'], 'Android', 'android-sdk'));
|
||||
maybeSetAndroidHome(path.join(process.env['ProgramFiles'], 'Android', 'android-sdk'));
|
||||
} else if (process.platform == 'darwin') {
|
||||
// Android Studio 1.0 installer
|
||||
maybeSetAndroidHome(path.join(process.env['HOME'], 'Library', 'Android', 'sdk'));
|
||||
// Android Studio pre-1.0 installer
|
||||
maybeSetAndroidHome('/Applications/Android Studio.app/sdk');
|
||||
// Stand-alone zip file that user might think to put under /Applications
|
||||
maybeSetAndroidHome('/Applications/android-sdk-macosx');
|
||||
maybeSetAndroidHome('/Applications/android-sdk');
|
||||
}
|
||||
if (process.env['HOME']) {
|
||||
// Stand-alone zip file that user might think to put under their home directory
|
||||
maybeSetAndroidHome(path.join(process.env['HOME'], 'android-sdk-macosx'));
|
||||
maybeSetAndroidHome(path.join(process.env['HOME'], 'android-sdk'));
|
||||
}
|
||||
}
|
||||
if (hasAndroidHome && !androidCmdPath) {
|
||||
process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'tools');
|
||||
}
|
||||
if (androidCmdPath && !hasAndroidHome) {
|
||||
var parentDir = path.dirname(androidCmdPath);
|
||||
var grandParentDir = path.dirname(parentDir);
|
||||
if (path.basename(parentDir) == 'tools') {
|
||||
process.env['ANDROID_HOME'] = path.dirname(parentDir);
|
||||
hasAndroidHome = true;
|
||||
} else if (fs.existsSync(path.join(grandParentDir, 'tools', 'android'))) {
|
||||
process.env['ANDROID_HOME'] = grandParentDir;
|
||||
hasAndroidHome = true;
|
||||
} else {
|
||||
throw new Error('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' +
|
||||
'Detected \'android\' command at ' + parentDir + ' but no \'tools\' directory found near.\n' +
|
||||
'Try reinstall Android SDK or update your PATH to include path to valid SDK directory.');
|
||||
}
|
||||
}
|
||||
if (hasAndroidHome && !adbInPath) {
|
||||
process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'platform-tools');
|
||||
}
|
||||
if (!process.env['ANDROID_HOME']) {
|
||||
throw new Error('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' +
|
||||
'Failed to find \'android\' command in your \'PATH\'. Try update your \'PATH\' to include path to valid SDK directory.');
|
||||
}
|
||||
if (!fs.existsSync(process.env['ANDROID_HOME'])) {
|
||||
throw new Error('\'ANDROID_HOME\' environment variable is set to non-existent path: ' + process.env['ANDROID_HOME'] +
|
||||
'\nTry update it manually to point to valid SDK directory.');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return d.promise.then(function(output) {
|
||||
if (!output.match(valid_target)) {
|
||||
return Q.reject(new Error('Please install Android target ' + valid_target.split('-')[1] + ' (the Android newest SDK). Make sure you have the latest Android tools installed as well. Run \"android\" from your command-line to install/update any missing SDKs or tools.'));
|
||||
}
|
||||
return Q();
|
||||
}, function(stderr) {
|
||||
if (stderr.match(/command\snot\sfound/)) {
|
||||
return Q.reject(new Error('The command \"android\" failed. Make sure you have the latest Android SDK installed, and the \"android\" command (inside the tools/ folder) is added to your path.'));
|
||||
} else {
|
||||
return Q.reject(new Error('An error occurred while listing Android targets'));
|
||||
module.exports.getAbsoluteAndroidCmd = function() {
|
||||
return forgivingWhichSync('android').replace(/(\s)/g, '\\$1');
|
||||
};
|
||||
|
||||
module.exports.check_android_target = function(valid_target) {
|
||||
// valid_target can look like:
|
||||
// android-19
|
||||
// android-L
|
||||
// Google Inc.:Google APIs:20
|
||||
// Google Inc.:Glass Development Kit Preview:20
|
||||
if (!valid_target) valid_target = module.exports.get_target();
|
||||
var msg = 'Android SDK not found. Make sure that it is installed. If it is not at the default location, set the ANDROID_HOME environment variable.';
|
||||
return tryCommand('android list targets --compact', msg)
|
||||
.then(function(output) {
|
||||
var targets = output.split('\n');
|
||||
if (targets.indexOf(valid_target) >= 0) {
|
||||
return targets;
|
||||
}
|
||||
|
||||
var androidCmd = module.exports.getAbsoluteAndroidCmd();
|
||||
throw new Error('Please install Android target: "' + valid_target + '".\n\n' +
|
||||
'Hint: Open the SDK manager by running: ' + androidCmd + '\n' +
|
||||
'You will require:\n' +
|
||||
'1. "SDK Platform" for ' + valid_target + '\n' +
|
||||
'2. "Android SDK Platform-tools (latest)\n' +
|
||||
'3. "Android SDK Build-tools" (latest)');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Returns a promise.
|
||||
module.exports.run = function() {
|
||||
return Q.all([this.check_ant(), this.check_java(), this.check_android()]);
|
||||
}
|
||||
return Q.all([this.check_java(), this.check_android().then(this.check_android_target)])
|
||||
.then(function() {
|
||||
console.log('ANDROID_HOME=' + process.env['ANDROID_HOME']);
|
||||
console.log('JAVA_HOME=' + process.env['JAVA_HOME']);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Object thar represents one of requirements for current platform.
|
||||
* @param {String} id The unique identifier for this requirements.
|
||||
* @param {String} name The name of requirements. Human-readable field.
|
||||
* @param {String} version The version of requirement installed. In some cases could be an array of strings
|
||||
* (for example, check_android_target returns an array of android targets installed)
|
||||
* @param {Boolean} installed Indicates whether the requirement is installed or not
|
||||
*/
|
||||
var Requirement = function (id, name, version, installed) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.installed = installed || false;
|
||||
this.metadata = {
|
||||
version: version,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Methods that runs all checks one by one and returns a result of checks
|
||||
* as an array of Requirement objects. This method intended to be used by cordova-lib check_reqs method
|
||||
*
|
||||
* @return Promise<Requirement[]> Array of requirements. Due to implementation, promise is always fulfilled.
|
||||
*/
|
||||
module.exports.check_all = function() {
|
||||
|
||||
var requirements = [
|
||||
new Requirement('java', 'Java JDK'),
|
||||
new Requirement('androidSdk', 'Android SDK'),
|
||||
new Requirement('androidTarget', 'Android target'),
|
||||
new Requirement('gradle', 'Gradle')
|
||||
];
|
||||
|
||||
var checkFns = [
|
||||
this.check_java,
|
||||
this.check_android,
|
||||
this.check_android_target,
|
||||
this.check_gradle
|
||||
];
|
||||
|
||||
// Then execute requirement checks one-by-one
|
||||
return checkFns.reduce(function (promise, checkFn, idx) {
|
||||
// Update each requirement with results
|
||||
var requirement = requirements[idx];
|
||||
return promise.then(checkFn)
|
||||
.then(function (version) {
|
||||
requirement.installed = true;
|
||||
requirement.metadata.version = version;
|
||||
}, function (err) {
|
||||
requirement.metadata.reason = err instanceof Error ? err.message : err;
|
||||
});
|
||||
}, Q())
|
||||
.then(function () {
|
||||
// When chain is completed, return requirements array to upstream API
|
||||
return requirements;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -18,27 +18,14 @@
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
var shell = require('shelljs'),
|
||||
child_process = require('child_process'),
|
||||
Q = require('q'),
|
||||
path = require('path'),
|
||||
fs = require('fs'),
|
||||
check_reqs = require('./check_reqs'),
|
||||
ROOT = path.join(__dirname, '..', '..');
|
||||
|
||||
// Returns a promise.
|
||||
function exec(command, opt_cwd) {
|
||||
var d = Q.defer();
|
||||
console.log('Running: ' + command);
|
||||
child_process.exec(command, { cwd: opt_cwd }, function(err, stdout, stderr) {
|
||||
stdout && console.log(stdout);
|
||||
stderr && console.error(stderr);
|
||||
if (err) d.reject(err);
|
||||
else d.resolve(stdout);
|
||||
});
|
||||
return d.promise;
|
||||
}
|
||||
|
||||
function setShellFatal(value, func) {
|
||||
var oldVal = shell.config.fatal;
|
||||
shell.config.fatal = value;
|
||||
@@ -52,44 +39,85 @@ function getFrameworkDir(projectPath, shared) {
|
||||
|
||||
function copyJsAndLibrary(projectPath, shared, projectName) {
|
||||
var nestedCordovaLibPath = getFrameworkDir(projectPath, false);
|
||||
shell.cp('-f', path.join(ROOT, 'framework', 'assets', 'www', 'cordova.js'), path.join(projectPath, 'assets', 'www', 'cordova.js'));
|
||||
var srcCordovaJsPath = path.join(ROOT, 'bin', 'templates', 'project', 'assets', 'www', 'cordova.js');
|
||||
shell.cp('-f', srcCordovaJsPath, path.join(projectPath, 'assets', 'www', 'cordova.js'));
|
||||
// Don't fail if there are no old jars.
|
||||
setShellFatal(false, function() {
|
||||
shell.ls(path.join(projectPath, 'libs', 'cordova-*.jar')).forEach(function(oldJar) {
|
||||
console.log("Deleting " + oldJar);
|
||||
console.log('Deleting ' + oldJar);
|
||||
shell.rm('-f', oldJar);
|
||||
});
|
||||
var wasSymlink = true;
|
||||
try {
|
||||
// Delete the symlink if it was one.
|
||||
fs.unlinkSync(nestedCordovaLibPath);
|
||||
} catch (e) {
|
||||
wasSymlink = false;
|
||||
}
|
||||
// Delete old library project if it existed.
|
||||
if (shared) {
|
||||
shell.rm('-rf', nestedCordovaLibPath);
|
||||
} else {
|
||||
// Delete only the src, since eclipse can't handle its .project file being deleted.
|
||||
} else if (!wasSymlink) {
|
||||
// Delete only the src, since Eclipse / Android Studio can't handle their project files being deleted.
|
||||
shell.rm('-rf', path.join(nestedCordovaLibPath, 'src'));
|
||||
}
|
||||
});
|
||||
if (!shared) {
|
||||
if (shared) {
|
||||
var relativeFrameworkPath = path.relative(projectPath, getFrameworkDir(projectPath, true));
|
||||
fs.symlinkSync(relativeFrameworkPath, nestedCordovaLibPath, 'dir');
|
||||
} else {
|
||||
shell.mkdir('-p', nestedCordovaLibPath);
|
||||
shell.cp('-f', path.join(ROOT, 'framework', 'AndroidManifest.xml'), nestedCordovaLibPath);
|
||||
shell.cp('-f', path.join(ROOT, 'framework', 'project.properties'), nestedCordovaLibPath);
|
||||
shell.cp('-f', path.join(ROOT, 'framework', 'build.gradle'), nestedCordovaLibPath);
|
||||
shell.cp('-f', path.join(ROOT, 'framework', 'cordova.gradle'), nestedCordovaLibPath);
|
||||
shell.cp('-r', path.join(ROOT, 'framework', 'src'), nestedCordovaLibPath);
|
||||
// Create an eclipse project file and set the name of it to something unique.
|
||||
// Without this, you can't import multiple CordovaLib projects into the same workspace.
|
||||
var eclipseProjectFilePath = path.join(nestedCordovaLibPath, '.project');
|
||||
if (!fs.existsSync(eclipseProjectFilePath)) {
|
||||
var data = '<?xml version="1.0" encoding="UTF-8"?><projectDescription><name>' + projectName + '-' + 'CordovaLib</name></projectDescription>';
|
||||
fs.writeFileSync(eclipseProjectFilePath, data, 'utf8');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function runAndroidUpdate(projectPath, target_api, shared) {
|
||||
var targetFrameworkDir = getFrameworkDir(projectPath, shared);
|
||||
return exec('android update project --subprojects --path "' + projectPath + '" --target ' + target_api + ' --library "' + path.relative(projectPath, targetFrameworkDir) + '"');
|
||||
function extractSubProjectPaths(data) {
|
||||
var ret = {};
|
||||
var r = /^\s*android\.library\.reference\.\d+=(.*)(?:\s|$)/mg;
|
||||
var m;
|
||||
while ((m = r.exec(data))) {
|
||||
ret[m[1]] = 1;
|
||||
}
|
||||
return Object.keys(ret);
|
||||
}
|
||||
|
||||
function copyAntRules(projectPath) {
|
||||
function writeProjectProperties(projectPath, target_api) {
|
||||
var dstPath = path.join(projectPath, 'project.properties');
|
||||
var templatePath = path.join(ROOT, 'bin', 'templates', 'project', 'project.properties');
|
||||
var srcPath = fs.existsSync(dstPath) ? dstPath : templatePath;
|
||||
var data = fs.readFileSync(srcPath, 'utf8');
|
||||
data = data.replace(/^target=.*/m, 'target=' + target_api);
|
||||
var subProjects = extractSubProjectPaths(data);
|
||||
subProjects = subProjects.filter(function(p) {
|
||||
return !(/^CordovaLib$/m.exec(p) ||
|
||||
/[\\\/]cordova-android[\\\/]framework$/m.exec(p) ||
|
||||
/^(\.\.[\\\/])+framework$/m.exec(p)
|
||||
);
|
||||
});
|
||||
subProjects.unshift('CordovaLib');
|
||||
data = data.replace(/^\s*android\.library\.reference\.\d+=.*\n/mg, '');
|
||||
if (!/\n$/.exec(data)) {
|
||||
data += '\n';
|
||||
}
|
||||
for (var i = 0; i < subProjects.length; ++i) {
|
||||
data += 'android.library.reference.' + (i+1) + '=' + subProjects[i] + '\n';
|
||||
}
|
||||
fs.writeFileSync(dstPath, data);
|
||||
}
|
||||
|
||||
function prepBuildFiles(projectPath) {
|
||||
var buildModule = require(path.join(path.resolve(projectPath), 'cordova', 'lib', 'build'));
|
||||
buildModule.prepBuildFiles();
|
||||
}
|
||||
|
||||
function copyBuildRules(projectPath) {
|
||||
var srcDir = path.join(ROOT, 'bin', 'templates', 'project');
|
||||
shell.cp('-f', path.join(srcDir, 'custom_rules.xml'), projectPath);
|
||||
|
||||
shell.cp('-f', path.join(srcDir, 'build.gradle'), projectPath);
|
||||
}
|
||||
|
||||
function copyScripts(projectPath) {
|
||||
@@ -100,12 +128,57 @@ function copyScripts(projectPath) {
|
||||
// Copy in the new ones.
|
||||
shell.cp('-r', srcScriptsDir, projectPath);
|
||||
shell.cp('-r', path.join(ROOT, 'bin', 'node_modules'), destScriptsDir);
|
||||
shell.cp(path.join(ROOT, 'bin', 'check_reqs'), path.join(destScriptsDir, 'check_reqs'));
|
||||
shell.cp(path.join(ROOT, 'bin', 'check_reqs*'), destScriptsDir);
|
||||
shell.cp(path.join(ROOT, 'bin', 'lib', 'check_reqs.js'), path.join(projectPath, 'cordova', 'lib', 'check_reqs.js'));
|
||||
shell.cp(path.join(ROOT, 'bin', 'android_sdk_version'), path.join(destScriptsDir, 'android_sdk_version'));
|
||||
shell.cp(path.join(ROOT, 'bin', 'lib', 'android_sdk_version.js'), path.join(projectPath, 'cordova', 'lib', 'android_sdk_version.js'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether a package name is acceptable for use as an android project.
|
||||
* Returns a promise, fulfilled if the package name is acceptable; rejected
|
||||
* otherwise.
|
||||
*/
|
||||
function validatePackageName(package_name) {
|
||||
//Make the package conform to Java package types
|
||||
//http://developer.android.com/guide/topics/manifest/manifest-element.html#package
|
||||
//Enforce underscore limitation
|
||||
if (!/^[a-zA-Z][a-zA-Z0-9_]+(\.[a-zA-Z][a-zA-Z0-9_]*)+$/.test(package_name)) {
|
||||
return Q.reject('Package name must look like: com.company.Name');
|
||||
}
|
||||
|
||||
//Class is a reserved word
|
||||
if(/\b[Cc]lass\b/.test(package_name)) {
|
||||
return Q.reject('class is a reserved word');
|
||||
}
|
||||
|
||||
return Q.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether a project name is acceptable for use as an android class.
|
||||
* Returns a promise, fulfilled if the project name is acceptable; rejected
|
||||
* otherwise.
|
||||
*/
|
||||
function validateProjectName(project_name) {
|
||||
//Make sure there's something there
|
||||
if (project_name === '') {
|
||||
return Q.reject('Project name cannot be empty');
|
||||
}
|
||||
|
||||
//Enforce stupid name error
|
||||
if (project_name === 'CordovaActivity') {
|
||||
return Q.reject('Project name cannot be CordovaActivity');
|
||||
}
|
||||
|
||||
//Classes in Java don't begin with numbers
|
||||
if (/^[0-9]/.test(project_name)) {
|
||||
return Q.reject('Project name must not begin with a number');
|
||||
}
|
||||
|
||||
return Q.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* $ create [options]
|
||||
*
|
||||
@@ -116,16 +189,15 @@ function copyScripts(projectPath) {
|
||||
* - `project_path` {String} Path to the new Cordova android project.
|
||||
* - `package_name`{String} Package name, following reverse-domain style convention.
|
||||
* - `project_name` {String} Project name.
|
||||
* - `activity_name` {String} Name for the activity
|
||||
* - 'project_template_dir' {String} Path to project template (override).
|
||||
*
|
||||
* Returns a promise.
|
||||
*/
|
||||
|
||||
exports.createProject = function(project_path, package_name, project_name, project_template_dir, use_shared_project, use_cli_template) {
|
||||
var VERSION = fs.readFileSync(path.join(ROOT, 'VERSION'), 'utf-8').trim();
|
||||
|
||||
exports.createProject = function(project_path, package_name, project_name, activity_name, project_template_dir, use_shared_project, use_cli_template) {
|
||||
// Set default values for path, package and name
|
||||
project_path = typeof project_path !== 'undefined' ? project_path : "CordovaExample";
|
||||
project_path = typeof project_path !== 'undefined' ? project_path : 'CordovaExample';
|
||||
project_path = path.relative(process.cwd(), project_path);
|
||||
package_name = typeof package_name !== 'undefined' ? package_name : 'my.cordova.project';
|
||||
project_name = typeof project_name !== 'undefined' ? project_name : 'CordovaExample';
|
||||
@@ -133,9 +205,9 @@ exports.createProject = function(project_path, package_name, project_name, proje
|
||||
project_template_dir :
|
||||
path.join(ROOT, 'bin', 'templates', 'project');
|
||||
|
||||
var safe_activity_name = project_name.replace(/\W/g, '');
|
||||
var package_as_path = package_name.replace(/\./g, path.sep);
|
||||
var activity_dir = path.join(project_path, 'src', package_as_path);
|
||||
var safe_activity_name = typeof activity_name !== 'undefined' ? activity_name : 'MainActivity';
|
||||
var activity_path = path.join(activity_dir, safe_activity_name + '.java');
|
||||
var target_api = check_reqs.get_target();
|
||||
var manifest_path = path.join(project_path, 'AndroidManifest.xml');
|
||||
@@ -145,22 +217,17 @@ exports.createProject = function(project_path, package_name, project_name, proje
|
||||
return Q.reject('Project already exists! Delete and recreate');
|
||||
}
|
||||
|
||||
if (!/[a-zA-Z0-9_]+\.[a-zA-Z0-9_](.[a-zA-Z0-9_])*/.test(package_name)) {
|
||||
return Q.reject('Package name must look like: com.company.Name');
|
||||
}
|
||||
|
||||
if (project_name === 'CordovaActivity') {
|
||||
return Q.reject('Project name cannot be CordovaActivity');
|
||||
}
|
||||
|
||||
// Check that requirements are met and proper targets are installed
|
||||
return check_reqs.run()
|
||||
//Make the package conform to Java package types
|
||||
return validatePackageName(package_name)
|
||||
.then(function() {
|
||||
validateProjectName(project_name);
|
||||
}).then(function() {
|
||||
// Log the given values for the project
|
||||
console.log('Creating Cordova project for the Android platform:');
|
||||
console.log('\tPath: ' + project_path);
|
||||
console.log('\tPackage: ' + package_name);
|
||||
console.log('\tName: ' + project_name);
|
||||
console.log('\tActivity: ' + safe_activity_name);
|
||||
console.log('\tAndroid target: ' + target_api);
|
||||
|
||||
console.log('Copying template files...');
|
||||
@@ -169,22 +236,11 @@ exports.createProject = function(project_path, package_name, project_name, proje
|
||||
// copy project template
|
||||
shell.cp('-r', path.join(project_template_dir, 'assets'), project_path);
|
||||
shell.cp('-r', path.join(project_template_dir, 'res'), project_path);
|
||||
shell.cp('-r', path.join(ROOT, 'framework', 'res', 'xml'), path.join(project_path, 'res'));
|
||||
shell.cp(path.join(project_template_dir, 'gitignore'), path.join(project_path, '.gitignore'));
|
||||
|
||||
// Manually create directories that would be empty within the template (since git doesn't track directories).
|
||||
shell.mkdir(path.join(project_path, 'libs'));
|
||||
|
||||
// Add in the proper eclipse project file.
|
||||
if (use_cli_template) {
|
||||
var note = 'To show `assets/www` or `res/xml/config.xml`, go to:\n' +
|
||||
' Project -> Properties -> Resource -> Resource Filters\n' +
|
||||
'And delete the exclusion filter.\n';
|
||||
shell.cp(path.join(project_template_dir, 'eclipse-project-CLI'), path.join(project_path, '.project'));
|
||||
fs.writeFileSync(path.join(project_path, 'assets', '_where-is-www.txt'), note);
|
||||
} else {
|
||||
shell.cp(path.join(project_template_dir, 'eclipse-project'), path.join(project_path, '.project'));
|
||||
}
|
||||
|
||||
// copy cordova.js, cordova.jar
|
||||
copyJsAndLibrary(project_path, use_shared_project, safe_activity_name);
|
||||
|
||||
@@ -193,7 +249,6 @@ exports.createProject = function(project_path, package_name, project_name, proje
|
||||
shell.cp('-f', path.join(project_template_dir, 'Activity.java'), activity_path);
|
||||
shell.sed('-i', /__ACTIVITY__/, safe_activity_name, activity_path);
|
||||
shell.sed('-i', /__NAME__/, project_name, path.join(project_path, 'res', 'values', 'strings.xml'));
|
||||
shell.sed('-i', /__NAME__/, project_name, path.join(project_path, '.project'));
|
||||
shell.sed('-i', /__ID__/, package_name, activity_path);
|
||||
|
||||
shell.cp('-f', path.join(project_template_dir, 'AndroidManifest.xml'), manifest_path);
|
||||
@@ -201,13 +256,22 @@ exports.createProject = function(project_path, package_name, project_name, proje
|
||||
shell.sed('-i', /__PACKAGE__/, package_name, manifest_path);
|
||||
shell.sed('-i', /__APILEVEL__/, target_api.split('-')[1], manifest_path);
|
||||
copyScripts(project_path);
|
||||
copyAntRules(project_path);
|
||||
copyBuildRules(project_path);
|
||||
});
|
||||
// Link it to local android install.
|
||||
return runAndroidUpdate(project_path, target_api, use_shared_project);
|
||||
}).then(function() {
|
||||
console.log('Project successfully created.');
|
||||
writeProjectProperties(project_path, target_api);
|
||||
prepBuildFiles(project_path);
|
||||
console.log(generateDoneMessage('create', use_shared_project));
|
||||
});
|
||||
};
|
||||
|
||||
function generateDoneMessage(type, link) {
|
||||
var pkg = require('../../package');
|
||||
var msg = 'Android project ' + (type == 'update' ? 'updated ' : 'created ') + 'with ' + pkg.name + '@' + pkg.version;
|
||||
if (link) {
|
||||
msg += ' and has a linked CordovaLib';
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
// Attribute removed in Cordova 4.4 (CB-5447).
|
||||
@@ -226,23 +290,42 @@ function extractProjectNameFromManifest(projectPath) {
|
||||
return m[1];
|
||||
}
|
||||
|
||||
// Cordova-android updates sometimes drop support for older versions. Need to update minSDK in existing projects.
|
||||
function updateMinSDKInManifest(projectPath) {
|
||||
var manifestPath = path.join(projectPath, 'AndroidManifest.xml');
|
||||
var manifestData = fs.readFileSync(manifestPath, 'utf8');
|
||||
var minSDKVersion = 14;
|
||||
|
||||
//grab minSdkVersion from Android.
|
||||
var m = /android:minSdkVersion\s*=\s*"(.*?)"/i.exec(manifestData);
|
||||
if (!m) {
|
||||
throw new Error('Could not find minSDKVersion in ' + manifestPath);
|
||||
}
|
||||
//if minSDKVersion in Android.manifest is less than our current min, replace it
|
||||
if(Number(m[1]) < minSDKVersion) {
|
||||
console.log('Updating minSdkVersion from ' + m[1] + ' to ' + minSDKVersion + ' in AndroidManifest.xml');
|
||||
shell.sed('-i', /android:minSdkVersion\s*=\s*"(.*?)"/, 'android:minSdkVersion="'+minSDKVersion+'"', manifestPath);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a promise.
|
||||
exports.updateProject = function(projectPath) {
|
||||
var version = fs.readFileSync(path.join(ROOT, 'VERSION'), 'utf-8').trim();
|
||||
// Check that requirements are met and proper targets are installed
|
||||
return check_reqs.run()
|
||||
exports.updateProject = function(projectPath, shared) {
|
||||
return Q()
|
||||
.then(function() {
|
||||
var projectName = extractProjectNameFromManifest(projectPath);
|
||||
var target_api = check_reqs.get_target();
|
||||
copyJsAndLibrary(projectPath, false, projectName);
|
||||
updateMinSDKInManifest(projectPath);
|
||||
copyJsAndLibrary(projectPath, shared, projectName);
|
||||
copyScripts(projectPath);
|
||||
copyAntRules(projectPath);
|
||||
copyBuildRules(projectPath);
|
||||
removeDebuggableFromManifest(projectPath);
|
||||
return runAndroidUpdate(projectPath, target_api, false)
|
||||
.then(function() {
|
||||
console.log('Android project is now at version ' + version);
|
||||
console.log('If you updated from a pre-3.2.0 version and use an IDE, we now require that you import the "CordovaLib" library project.');
|
||||
});
|
||||
writeProjectProperties(projectPath, target_api);
|
||||
prepBuildFiles(projectPath);
|
||||
console.log(generateDoneMessage('update', shared));
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// For testing
|
||||
exports.validatePackageName = validatePackageName;
|
||||
exports.validateProjectName = validateProjectName;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
exports.getArgs = function(argv) {
|
||||
var ret = {};
|
||||
var posArgs = [];
|
||||
for (var i = 2, arg; arg = argv[i] || i < argv.length; ++i) {
|
||||
for (var i = 2, arg; (arg = argv[i] || i < argv.length); ++i) {
|
||||
if (/^--/.exec(arg)) {
|
||||
ret[arg] = true;
|
||||
} else {
|
||||
|
||||
23
bin/node_modules/nopt/LICENSE
generated
vendored
Normal file
23
bin/node_modules/nopt/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
Copyright 2009, 2010, 2011 Isaac Z. Schlueter.
|
||||
All rights reserved.
|
||||
|
||||
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.
|
||||
414
bin/node_modules/nopt/lib/nopt.js
generated
vendored
Normal file
414
bin/node_modules/nopt/lib/nopt.js
generated
vendored
Normal file
@@ -0,0 +1,414 @@
|
||||
// info about each config option.
|
||||
|
||||
var debug = process.env.DEBUG_NOPT || process.env.NOPT_DEBUG
|
||||
? function () { console.error.apply(console, arguments) }
|
||||
: function () {}
|
||||
|
||||
var url = require("url")
|
||||
, path = require("path")
|
||||
, Stream = require("stream").Stream
|
||||
, abbrev = require("abbrev")
|
||||
|
||||
module.exports = exports = nopt
|
||||
exports.clean = clean
|
||||
|
||||
exports.typeDefs =
|
||||
{ String : { type: String, validate: validateString }
|
||||
, Boolean : { type: Boolean, validate: validateBoolean }
|
||||
, url : { type: url, validate: validateUrl }
|
||||
, Number : { type: Number, validate: validateNumber }
|
||||
, path : { type: path, validate: validatePath }
|
||||
, Stream : { type: Stream, validate: validateStream }
|
||||
, Date : { type: Date, validate: validateDate }
|
||||
}
|
||||
|
||||
function nopt (types, shorthands, args, slice) {
|
||||
args = args || process.argv
|
||||
types = types || {}
|
||||
shorthands = shorthands || {}
|
||||
if (typeof slice !== "number") slice = 2
|
||||
|
||||
debug(types, shorthands, args, slice)
|
||||
|
||||
args = args.slice(slice)
|
||||
var data = {}
|
||||
, key
|
||||
, remain = []
|
||||
, cooked = args
|
||||
, original = args.slice(0)
|
||||
|
||||
parse(args, data, remain, types, shorthands)
|
||||
// now data is full
|
||||
clean(data, types, exports.typeDefs)
|
||||
data.argv = {remain:remain,cooked:cooked,original:original}
|
||||
Object.defineProperty(data.argv, 'toString', { value: function () {
|
||||
return this.original.map(JSON.stringify).join(" ")
|
||||
}, enumerable: false })
|
||||
return data
|
||||
}
|
||||
|
||||
function clean (data, types, typeDefs) {
|
||||
typeDefs = typeDefs || exports.typeDefs
|
||||
var remove = {}
|
||||
, typeDefault = [false, true, null, String, Array]
|
||||
|
||||
Object.keys(data).forEach(function (k) {
|
||||
if (k === "argv") return
|
||||
var val = data[k]
|
||||
, isArray = Array.isArray(val)
|
||||
, type = types[k]
|
||||
if (!isArray) val = [val]
|
||||
if (!type) type = typeDefault
|
||||
if (type === Array) type = typeDefault.concat(Array)
|
||||
if (!Array.isArray(type)) type = [type]
|
||||
|
||||
debug("val=%j", val)
|
||||
debug("types=", type)
|
||||
val = val.map(function (val) {
|
||||
// if it's an unknown value, then parse false/true/null/numbers/dates
|
||||
if (typeof val === "string") {
|
||||
debug("string %j", val)
|
||||
val = val.trim()
|
||||
if ((val === "null" && ~type.indexOf(null))
|
||||
|| (val === "true" &&
|
||||
(~type.indexOf(true) || ~type.indexOf(Boolean)))
|
||||
|| (val === "false" &&
|
||||
(~type.indexOf(false) || ~type.indexOf(Boolean)))) {
|
||||
val = JSON.parse(val)
|
||||
debug("jsonable %j", val)
|
||||
} else if (~type.indexOf(Number) && !isNaN(val)) {
|
||||
debug("convert to number", val)
|
||||
val = +val
|
||||
} else if (~type.indexOf(Date) && !isNaN(Date.parse(val))) {
|
||||
debug("convert to date", val)
|
||||
val = new Date(val)
|
||||
}
|
||||
}
|
||||
|
||||
if (!types.hasOwnProperty(k)) {
|
||||
return val
|
||||
}
|
||||
|
||||
// allow `--no-blah` to set 'blah' to null if null is allowed
|
||||
if (val === false && ~type.indexOf(null) &&
|
||||
!(~type.indexOf(false) || ~type.indexOf(Boolean))) {
|
||||
val = null
|
||||
}
|
||||
|
||||
var d = {}
|
||||
d[k] = val
|
||||
debug("prevalidated val", d, val, types[k])
|
||||
if (!validate(d, k, val, types[k], typeDefs)) {
|
||||
if (exports.invalidHandler) {
|
||||
exports.invalidHandler(k, val, types[k], data)
|
||||
} else if (exports.invalidHandler !== false) {
|
||||
debug("invalid: "+k+"="+val, types[k])
|
||||
}
|
||||
return remove
|
||||
}
|
||||
debug("validated val", d, val, types[k])
|
||||
return d[k]
|
||||
}).filter(function (val) { return val !== remove })
|
||||
|
||||
if (!val.length) delete data[k]
|
||||
else if (isArray) {
|
||||
debug(isArray, data[k], val)
|
||||
data[k] = val
|
||||
} else data[k] = val[0]
|
||||
|
||||
debug("k=%s val=%j", k, val, data[k])
|
||||
})
|
||||
}
|
||||
|
||||
function validateString (data, k, val) {
|
||||
data[k] = String(val)
|
||||
}
|
||||
|
||||
function validatePath (data, k, val) {
|
||||
if (val === true) return false
|
||||
if (val === null) return true
|
||||
|
||||
val = String(val)
|
||||
var homePattern = process.platform === 'win32' ? /^~(\/|\\)/ : /^~\//
|
||||
if (val.match(homePattern) && process.env.HOME) {
|
||||
val = path.resolve(process.env.HOME, val.substr(2))
|
||||
}
|
||||
data[k] = path.resolve(String(val))
|
||||
return true
|
||||
}
|
||||
|
||||
function validateNumber (data, k, val) {
|
||||
debug("validate Number %j %j %j", k, val, isNaN(val))
|
||||
if (isNaN(val)) return false
|
||||
data[k] = +val
|
||||
}
|
||||
|
||||
function validateDate (data, k, val) {
|
||||
debug("validate Date %j %j %j", k, val, Date.parse(val))
|
||||
var s = Date.parse(val)
|
||||
if (isNaN(s)) return false
|
||||
data[k] = new Date(val)
|
||||
}
|
||||
|
||||
function validateBoolean (data, k, val) {
|
||||
if (val instanceof Boolean) val = val.valueOf()
|
||||
else if (typeof val === "string") {
|
||||
if (!isNaN(val)) val = !!(+val)
|
||||
else if (val === "null" || val === "false") val = false
|
||||
else val = true
|
||||
} else val = !!val
|
||||
data[k] = val
|
||||
}
|
||||
|
||||
function validateUrl (data, k, val) {
|
||||
val = url.parse(String(val))
|
||||
if (!val.host) return false
|
||||
data[k] = val.href
|
||||
}
|
||||
|
||||
function validateStream (data, k, val) {
|
||||
if (!(val instanceof Stream)) return false
|
||||
data[k] = val
|
||||
}
|
||||
|
||||
function validate (data, k, val, type, typeDefs) {
|
||||
// arrays are lists of types.
|
||||
if (Array.isArray(type)) {
|
||||
for (var i = 0, l = type.length; i < l; i ++) {
|
||||
if (type[i] === Array) continue
|
||||
if (validate(data, k, val, type[i], typeDefs)) return true
|
||||
}
|
||||
delete data[k]
|
||||
return false
|
||||
}
|
||||
|
||||
// an array of anything?
|
||||
if (type === Array) return true
|
||||
|
||||
// NaN is poisonous. Means that something is not allowed.
|
||||
if (type !== type) {
|
||||
debug("Poison NaN", k, val, type)
|
||||
delete data[k]
|
||||
return false
|
||||
}
|
||||
|
||||
// explicit list of values
|
||||
if (val === type) {
|
||||
debug("Explicitly allowed %j", val)
|
||||
// if (isArray) (data[k] = data[k] || []).push(val)
|
||||
// else data[k] = val
|
||||
data[k] = val
|
||||
return true
|
||||
}
|
||||
|
||||
// now go through the list of typeDefs, validate against each one.
|
||||
var ok = false
|
||||
, types = Object.keys(typeDefs)
|
||||
for (var i = 0, l = types.length; i < l; i ++) {
|
||||
debug("test type %j %j %j", k, val, types[i])
|
||||
var t = typeDefs[types[i]]
|
||||
if (t && type === t.type) {
|
||||
var d = {}
|
||||
ok = false !== t.validate(d, k, val)
|
||||
val = d[k]
|
||||
if (ok) {
|
||||
// if (isArray) (data[k] = data[k] || []).push(val)
|
||||
// else data[k] = val
|
||||
data[k] = val
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
debug("OK? %j (%j %j %j)", ok, k, val, types[i])
|
||||
|
||||
if (!ok) delete data[k]
|
||||
return ok
|
||||
}
|
||||
|
||||
function parse (args, data, remain, types, shorthands) {
|
||||
debug("parse", args, data, remain)
|
||||
|
||||
var key = null
|
||||
, abbrevs = abbrev(Object.keys(types))
|
||||
, shortAbbr = abbrev(Object.keys(shorthands))
|
||||
|
||||
for (var i = 0; i < args.length; i ++) {
|
||||
var arg = args[i]
|
||||
debug("arg", arg)
|
||||
|
||||
if (arg.match(/^-{2,}$/)) {
|
||||
// done with keys.
|
||||
// the rest are args.
|
||||
remain.push.apply(remain, args.slice(i + 1))
|
||||
args[i] = "--"
|
||||
break
|
||||
}
|
||||
var hadEq = false
|
||||
if (arg.charAt(0) === "-" && arg.length > 1) {
|
||||
if (arg.indexOf("=") !== -1) {
|
||||
hadEq = true
|
||||
var v = arg.split("=")
|
||||
arg = v.shift()
|
||||
v = v.join("=")
|
||||
args.splice.apply(args, [i, 1].concat([arg, v]))
|
||||
}
|
||||
|
||||
// see if it's a shorthand
|
||||
// if so, splice and back up to re-parse it.
|
||||
var shRes = resolveShort(arg, shorthands, shortAbbr, abbrevs)
|
||||
debug("arg=%j shRes=%j", arg, shRes)
|
||||
if (shRes) {
|
||||
debug(arg, shRes)
|
||||
args.splice.apply(args, [i, 1].concat(shRes))
|
||||
if (arg !== shRes[0]) {
|
||||
i --
|
||||
continue
|
||||
}
|
||||
}
|
||||
arg = arg.replace(/^-+/, "")
|
||||
var no = null
|
||||
while (arg.toLowerCase().indexOf("no-") === 0) {
|
||||
no = !no
|
||||
arg = arg.substr(3)
|
||||
}
|
||||
|
||||
if (abbrevs[arg]) arg = abbrevs[arg]
|
||||
|
||||
var isArray = types[arg] === Array ||
|
||||
Array.isArray(types[arg]) && types[arg].indexOf(Array) !== -1
|
||||
|
||||
// allow unknown things to be arrays if specified multiple times.
|
||||
if (!types.hasOwnProperty(arg) && data.hasOwnProperty(arg)) {
|
||||
if (!Array.isArray(data[arg]))
|
||||
data[arg] = [data[arg]]
|
||||
isArray = true
|
||||
}
|
||||
|
||||
var val
|
||||
, la = args[i + 1]
|
||||
|
||||
var isBool = typeof no === 'boolean' ||
|
||||
types[arg] === Boolean ||
|
||||
Array.isArray(types[arg]) && types[arg].indexOf(Boolean) !== -1 ||
|
||||
(typeof types[arg] === 'undefined' && !hadEq) ||
|
||||
(la === "false" &&
|
||||
(types[arg] === null ||
|
||||
Array.isArray(types[arg]) && ~types[arg].indexOf(null)))
|
||||
|
||||
if (isBool) {
|
||||
// just set and move along
|
||||
val = !no
|
||||
// however, also support --bool true or --bool false
|
||||
if (la === "true" || la === "false") {
|
||||
val = JSON.parse(la)
|
||||
la = null
|
||||
if (no) val = !val
|
||||
i ++
|
||||
}
|
||||
|
||||
// also support "foo":[Boolean, "bar"] and "--foo bar"
|
||||
if (Array.isArray(types[arg]) && la) {
|
||||
if (~types[arg].indexOf(la)) {
|
||||
// an explicit type
|
||||
val = la
|
||||
i ++
|
||||
} else if ( la === "null" && ~types[arg].indexOf(null) ) {
|
||||
// null allowed
|
||||
val = null
|
||||
i ++
|
||||
} else if ( !la.match(/^-{2,}[^-]/) &&
|
||||
!isNaN(la) &&
|
||||
~types[arg].indexOf(Number) ) {
|
||||
// number
|
||||
val = +la
|
||||
i ++
|
||||
} else if ( !la.match(/^-[^-]/) && ~types[arg].indexOf(String) ) {
|
||||
// string
|
||||
val = la
|
||||
i ++
|
||||
}
|
||||
}
|
||||
|
||||
if (isArray) (data[arg] = data[arg] || []).push(val)
|
||||
else data[arg] = val
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if (types[arg] === String && la === undefined)
|
||||
la = ""
|
||||
|
||||
if (la && la.match(/^-{2,}$/)) {
|
||||
la = undefined
|
||||
i --
|
||||
}
|
||||
|
||||
val = la === undefined ? true : la
|
||||
if (isArray) (data[arg] = data[arg] || []).push(val)
|
||||
else data[arg] = val
|
||||
|
||||
i ++
|
||||
continue
|
||||
}
|
||||
remain.push(arg)
|
||||
}
|
||||
}
|
||||
|
||||
function resolveShort (arg, shorthands, shortAbbr, abbrevs) {
|
||||
// handle single-char shorthands glommed together, like
|
||||
// npm ls -glp, but only if there is one dash, and only if
|
||||
// all of the chars are single-char shorthands, and it's
|
||||
// not a match to some other abbrev.
|
||||
arg = arg.replace(/^-+/, '')
|
||||
|
||||
// if it's an exact known option, then don't go any further
|
||||
if (abbrevs[arg] === arg)
|
||||
return null
|
||||
|
||||
// if it's an exact known shortopt, same deal
|
||||
if (shorthands[arg]) {
|
||||
// make it an array, if it's a list of words
|
||||
if (shorthands[arg] && !Array.isArray(shorthands[arg]))
|
||||
shorthands[arg] = shorthands[arg].split(/\s+/)
|
||||
|
||||
return shorthands[arg]
|
||||
}
|
||||
|
||||
// first check to see if this arg is a set of single-char shorthands
|
||||
var singles = shorthands.___singles
|
||||
if (!singles) {
|
||||
singles = Object.keys(shorthands).filter(function (s) {
|
||||
return s.length === 1
|
||||
}).reduce(function (l,r) {
|
||||
l[r] = true
|
||||
return l
|
||||
}, {})
|
||||
shorthands.___singles = singles
|
||||
debug('shorthand singles', singles)
|
||||
}
|
||||
|
||||
var chrs = arg.split("").filter(function (c) {
|
||||
return singles[c]
|
||||
})
|
||||
|
||||
if (chrs.join("") === arg) return chrs.map(function (c) {
|
||||
return shorthands[c]
|
||||
}).reduce(function (l, r) {
|
||||
return l.concat(r)
|
||||
}, [])
|
||||
|
||||
|
||||
// if it's an arg abbrev, and not a literal shorthand, then prefer the arg
|
||||
if (abbrevs[arg] && !shorthands[arg])
|
||||
return null
|
||||
|
||||
// if it's an abbr for a shorthand, then use that
|
||||
if (shortAbbr[arg])
|
||||
arg = shortAbbr[arg]
|
||||
|
||||
// make it an array, if it's a list of words
|
||||
if (shorthands[arg] && !Array.isArray(shorthands[arg]))
|
||||
shorthands[arg] = shorthands[arg].split(/\s+/)
|
||||
|
||||
return shorthands[arg]
|
||||
}
|
||||
23
bin/node_modules/nopt/node_modules/abbrev/LICENSE
generated
vendored
Normal file
23
bin/node_modules/nopt/node_modules/abbrev/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
Copyright 2009, 2010, 2011 Isaac Z. Schlueter.
|
||||
All rights reserved.
|
||||
|
||||
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.
|
||||
62
bin/node_modules/nopt/node_modules/abbrev/abbrev.js
generated
vendored
Normal file
62
bin/node_modules/nopt/node_modules/abbrev/abbrev.js
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
|
||||
module.exports = exports = abbrev.abbrev = abbrev
|
||||
|
||||
abbrev.monkeyPatch = monkeyPatch
|
||||
|
||||
function monkeyPatch () {
|
||||
Object.defineProperty(Array.prototype, 'abbrev', {
|
||||
value: function () { return abbrev(this) },
|
||||
enumerable: false, configurable: true, writable: true
|
||||
})
|
||||
|
||||
Object.defineProperty(Object.prototype, 'abbrev', {
|
||||
value: function () { return abbrev(Object.keys(this)) },
|
||||
enumerable: false, configurable: true, writable: true
|
||||
})
|
||||
}
|
||||
|
||||
function abbrev (list) {
|
||||
if (arguments.length !== 1 || !Array.isArray(list)) {
|
||||
list = Array.prototype.slice.call(arguments, 0)
|
||||
}
|
||||
for (var i = 0, l = list.length, args = [] ; i < l ; i ++) {
|
||||
args[i] = typeof list[i] === "string" ? list[i] : String(list[i])
|
||||
}
|
||||
|
||||
// sort them lexicographically, so that they're next to their nearest kin
|
||||
args = args.sort(lexSort)
|
||||
|
||||
// walk through each, seeing how much it has in common with the next and previous
|
||||
var abbrevs = {}
|
||||
, prev = ""
|
||||
for (var i = 0, l = args.length ; i < l ; i ++) {
|
||||
var current = args[i]
|
||||
, next = args[i + 1] || ""
|
||||
, nextMatches = true
|
||||
, prevMatches = true
|
||||
if (current === next) continue
|
||||
for (var j = 0, cl = current.length ; j < cl ; j ++) {
|
||||
var curChar = current.charAt(j)
|
||||
nextMatches = nextMatches && curChar === next.charAt(j)
|
||||
prevMatches = prevMatches && curChar === prev.charAt(j)
|
||||
if (!nextMatches && !prevMatches) {
|
||||
j ++
|
||||
break
|
||||
}
|
||||
}
|
||||
prev = current
|
||||
if (j === cl) {
|
||||
abbrevs[current] = current
|
||||
continue
|
||||
}
|
||||
for (var a = current.substr(0, j) ; j <= cl ; j ++) {
|
||||
abbrevs[a] = current
|
||||
a += current.charAt(j)
|
||||
}
|
||||
}
|
||||
return abbrevs
|
||||
}
|
||||
|
||||
function lexSort (a, b) {
|
||||
return a === b ? 0 : a > b ? 1 : -1
|
||||
}
|
||||
31
bin/node_modules/nopt/node_modules/abbrev/package.json
generated
vendored
Normal file
31
bin/node_modules/nopt/node_modules/abbrev/package.json
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "abbrev",
|
||||
"version": "1.0.5",
|
||||
"description": "Like ruby's abbrev module, but in js",
|
||||
"author": {
|
||||
"name": "Isaac Z. Schlueter",
|
||||
"email": "i@izs.me"
|
||||
},
|
||||
"main": "abbrev.js",
|
||||
"scripts": {
|
||||
"test": "node test.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/isaacs/abbrev-js"
|
||||
},
|
||||
"license": {
|
||||
"type": "MIT",
|
||||
"url": "https://github.com/isaacs/abbrev-js/raw/master/LICENSE"
|
||||
},
|
||||
"readme": "# abbrev-js\n\nJust like [ruby's Abbrev](http://apidock.com/ruby/Abbrev).\n\nUsage:\n\n var abbrev = require(\"abbrev\");\n abbrev(\"foo\", \"fool\", \"folding\", \"flop\");\n \n // returns:\n { fl: 'flop'\n , flo: 'flop'\n , flop: 'flop'\n , fol: 'folding'\n , fold: 'folding'\n , foldi: 'folding'\n , foldin: 'folding'\n , folding: 'folding'\n , foo: 'foo'\n , fool: 'fool'\n }\n\nThis is handy for command-line scripts, or other cases where you want to be able to accept shorthands.\n",
|
||||
"readmeFilename": "README.md",
|
||||
"bugs": {
|
||||
"url": "https://github.com/isaacs/abbrev-js/issues"
|
||||
},
|
||||
"homepage": "https://github.com/isaacs/abbrev-js",
|
||||
"_id": "abbrev@1.0.5",
|
||||
"_shasum": "5d8257bd9ebe435e698b2fa431afde4fe7b10b03",
|
||||
"_from": "abbrev@1",
|
||||
"_resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.5.tgz"
|
||||
}
|
||||
41
bin/node_modules/nopt/package.json
generated
vendored
Normal file
41
bin/node_modules/nopt/package.json
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
23
bin/node_modules/which/LICENSE
generated
vendored
Normal file
23
bin/node_modules/which/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
Copyright 2009, 2010, 2011 Isaac Z. Schlueter.
|
||||
All rights reserved.
|
||||
|
||||
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.
|
||||
5
bin/node_modules/which/README.md
generated
vendored
Normal file
5
bin/node_modules/which/README.md
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
The "which" util from npm's guts.
|
||||
|
||||
Finds the first instance of a specified executable in the PATH
|
||||
environment variable. Does not cache the results, so `hash -r` is not
|
||||
needed when the PATH changes.
|
||||
14
bin/node_modules/which/bin/which
generated
vendored
Executable file
14
bin/node_modules/which/bin/which
generated
vendored
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env node
|
||||
var which = require("../")
|
||||
if (process.argv.length < 3) {
|
||||
console.error("Usage: which <thing>")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
which(process.argv[2], function (er, thing) {
|
||||
if (er) {
|
||||
console.error(er.message)
|
||||
process.exit(er.errno || 127)
|
||||
}
|
||||
console.log(thing)
|
||||
})
|
||||
31
bin/node_modules/which/package.json
generated
vendored
Normal file
31
bin/node_modules/which/package.json
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"author": {
|
||||
"name": "Isaac Z. Schlueter",
|
||||
"email": "i@izs.me",
|
||||
"url": "http://blog.izs.me"
|
||||
},
|
||||
"name": "which",
|
||||
"description": "Like which(1) unix command. Find the first instance of an executable in the PATH.",
|
||||
"version": "1.0.5",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/isaacs/node-which.git"
|
||||
},
|
||||
"main": "which.js",
|
||||
"bin": {
|
||||
"which": "./bin/which"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {},
|
||||
"readme": "The \"which\" util from npm's guts.\n\nFinds the first instance of a specified executable in the PATH\nenvironment variable. Does not cache the results, so `hash -r` is not\nneeded when the PATH changes.\n",
|
||||
"readmeFilename": "README.md",
|
||||
"bugs": {
|
||||
"url": "https://github.com/isaacs/node-which/issues"
|
||||
},
|
||||
"homepage": "https://github.com/isaacs/node-which",
|
||||
"_id": "which@1.0.5",
|
||||
"_from": "which@"
|
||||
}
|
||||
104
bin/node_modules/which/which.js
generated
vendored
Normal file
104
bin/node_modules/which/which.js
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
module.exports = which
|
||||
which.sync = whichSync
|
||||
|
||||
var path = require("path")
|
||||
, fs
|
||||
, COLON = process.platform === "win32" ? ";" : ":"
|
||||
, isExe
|
||||
|
||||
try {
|
||||
fs = require("graceful-fs")
|
||||
} catch (ex) {
|
||||
fs = require("fs")
|
||||
}
|
||||
|
||||
if (process.platform == "win32") {
|
||||
// On windows, there is no good way to check that a file is executable
|
||||
isExe = function isExe () { return true }
|
||||
} else {
|
||||
isExe = function isExe (mod, uid, gid) {
|
||||
//console.error(mod, uid, gid);
|
||||
//console.error("isExe?", (mod & 0111).toString(8))
|
||||
var ret = (mod & 0001)
|
||||
|| (mod & 0010) && process.getgid && gid === process.getgid()
|
||||
|| (mod & 0100) && process.getuid && uid === process.getuid()
|
||||
//console.error("isExe?", ret)
|
||||
return ret
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function which (cmd, cb) {
|
||||
if (isAbsolute(cmd)) return cb(null, cmd)
|
||||
var pathEnv = (process.env.PATH || "").split(COLON)
|
||||
, pathExt = [""]
|
||||
if (process.platform === "win32") {
|
||||
pathEnv.push(process.cwd())
|
||||
pathExt = (process.env.PATHEXT || ".EXE").split(COLON)
|
||||
if (cmd.indexOf(".") !== -1) pathExt.unshift("")
|
||||
}
|
||||
//console.error("pathEnv", pathEnv)
|
||||
;(function F (i, l) {
|
||||
if (i === l) return cb(new Error("not found: "+cmd))
|
||||
var p = path.resolve(pathEnv[i], cmd)
|
||||
;(function E (ii, ll) {
|
||||
if (ii === ll) return F(i + 1, l)
|
||||
var ext = pathExt[ii]
|
||||
//console.error(p + ext)
|
||||
fs.stat(p + ext, function (er, stat) {
|
||||
if (!er &&
|
||||
stat &&
|
||||
stat.isFile() &&
|
||||
isExe(stat.mode, stat.uid, stat.gid)) {
|
||||
//console.error("yes, exe!", p + ext)
|
||||
return cb(null, p + ext)
|
||||
}
|
||||
return E(ii + 1, ll)
|
||||
})
|
||||
})(0, pathExt.length)
|
||||
})(0, pathEnv.length)
|
||||
}
|
||||
|
||||
function whichSync (cmd) {
|
||||
if (isAbsolute(cmd)) return cmd
|
||||
var pathEnv = (process.env.PATH || "").split(COLON)
|
||||
, pathExt = [""]
|
||||
if (process.platform === "win32") {
|
||||
pathEnv.push(process.cwd())
|
||||
pathExt = (process.env.PATHEXT || ".EXE").split(COLON)
|
||||
if (cmd.indexOf(".") !== -1) pathExt.unshift("")
|
||||
}
|
||||
for (var i = 0, l = pathEnv.length; i < l; i ++) {
|
||||
var p = path.join(pathEnv[i], cmd)
|
||||
for (var j = 0, ll = pathExt.length; j < ll; j ++) {
|
||||
var cur = p + pathExt[j]
|
||||
var stat
|
||||
try { stat = fs.statSync(cur) } catch (ex) {}
|
||||
if (stat &&
|
||||
stat.isFile() &&
|
||||
isExe(stat.mode, stat.uid, stat.gid)) return cur
|
||||
}
|
||||
}
|
||||
throw new Error("not found: "+cmd)
|
||||
}
|
||||
|
||||
var isAbsolute = process.platform === "win32" ? absWin : absUnix
|
||||
|
||||
function absWin (p) {
|
||||
if (absUnix(p)) return true
|
||||
// pull off the device/UNC bit from a windows path.
|
||||
// from node's lib/path.js
|
||||
var splitDeviceRe =
|
||||
/^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/][^\\\/]+)?([\\\/])?/
|
||||
, result = splitDeviceRe.exec(p)
|
||||
, device = result[1] || ''
|
||||
, isUnc = device && device.charAt(1) !== ':'
|
||||
, isAbsolute = !!result[2] || isUnc // UNC paths are always absolute
|
||||
|
||||
return isAbsolute
|
||||
}
|
||||
|
||||
function absUnix (p) {
|
||||
return p.charAt(0) === "/" || p === ""
|
||||
}
|
||||
@@ -24,13 +24,17 @@ var build = require('./lib/build'),
|
||||
args = process.argv;
|
||||
|
||||
// Support basic help commands
|
||||
if(args[2] == '--help' || args[2] == '/?' || args[2] == '-h' ||
|
||||
args[2] == 'help' || args[2] == '-help' || args[2] == '/help') {
|
||||
if(args[2] == '--help' ||
|
||||
args[2] == '/?' ||
|
||||
args[2] == '-h' ||
|
||||
args[2] == 'help' ||
|
||||
args[2] == '-help' ||
|
||||
args[2] == '/help') {
|
||||
build.help();
|
||||
} else {
|
||||
reqs.run().then(function() {
|
||||
return build.run(args[2]);
|
||||
}).done(null, function(err) {
|
||||
reqs.run().done(function() {
|
||||
return build.run(args.slice(2));
|
||||
}, function(err) {
|
||||
console.error(err);
|
||||
process.exit(2);
|
||||
});
|
||||
|
||||
@@ -19,18 +19,26 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
var clean = require('./lib/clean'),
|
||||
var build = require('./lib/build'),
|
||||
reqs = require('./lib/check_reqs'),
|
||||
args = process.argv;
|
||||
var path = require('path');
|
||||
|
||||
// Usage support for when args are given
|
||||
if(args.length > 2) {
|
||||
clean.help();
|
||||
// Support basic help commands
|
||||
if(args[2] == '--help' ||
|
||||
args[2] == '/?' ||
|
||||
args[2] == '-h' ||
|
||||
args[2] == 'help' ||
|
||||
args[2] == '-help' ||
|
||||
args[2] == '/help') {
|
||||
console.log('Usage: ' + path.relative(process.cwd(), process.argv[1]));
|
||||
console.log('Cleans the project directory.');
|
||||
process.exit(0);
|
||||
} else {
|
||||
reqs.run().done(function() {
|
||||
return clean.run();
|
||||
return build.runClean(args.slice(2));
|
||||
}, function(err) {
|
||||
console.error('ERROR: ' + err);
|
||||
console.error(err);
|
||||
process.exit(2);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -23,10 +23,4 @@
|
||||
|
||||
<!-- Preferences for Android -->
|
||||
<preference name="loglevel" value="DEBUG" />
|
||||
<preference name="AndroidLaunchMode" value="singleTop" />
|
||||
|
||||
<!-- This is required for native Android hooks -->
|
||||
<feature name="App">
|
||||
<param name="android-package" value="org.apache.cordova.App" />
|
||||
</feature>
|
||||
</widget>
|
||||
|
||||
2
bin/templates/cordova/lib/appinfo.js
vendored
2
bin/templates/cordova/lib/appinfo.js
vendored
@@ -37,5 +37,5 @@ function readAppInfoFromManifest() {
|
||||
}
|
||||
|
||||
exports.getActivityName = function() {
|
||||
return cachedAppInfo = cachedAppInfo || readAppInfoFromManifest();
|
||||
return (cachedAppInfo = cachedAppInfo || readAppInfoFromManifest());
|
||||
};
|
||||
|
||||
732
bin/templates/cordova/lib/build.js
vendored
732
bin/templates/cordova/lib/build.js
vendored
@@ -19,95 +19,699 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
/* jshint sub:true */
|
||||
|
||||
var shell = require('shelljs'),
|
||||
spawn = require('./spawn'),
|
||||
Q = require('q'),
|
||||
path = require('path'),
|
||||
fs = require('fs'),
|
||||
os = require('os'),
|
||||
ROOT = path.join(__dirname, '..', '..');
|
||||
var check_reqs = require('./check_reqs');
|
||||
var exec = require('./exec');
|
||||
|
||||
|
||||
var SIGNING_PROPERTIES = '-signing.properties';
|
||||
var MARKER = 'YOUR CHANGES WILL BE ERASED!';
|
||||
var TEMPLATE =
|
||||
'# This file is automatically generated.\n' +
|
||||
'# Do not modify this file -- ' + MARKER + '\n';
|
||||
|
||||
function findApks(directory) {
|
||||
var ret = [];
|
||||
if (fs.existsSync(directory)) {
|
||||
fs.readdirSync(directory).forEach(function(p) {
|
||||
if (path.extname(p) == '.apk') {
|
||||
ret.push(path.join(directory, p));
|
||||
}
|
||||
});
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function sortFilesByDate(files) {
|
||||
return files.map(function(p) {
|
||||
return { p: p, t: fs.statSync(p).mtime };
|
||||
}).sort(function(a, b) {
|
||||
var timeDiff = b.t - a.t;
|
||||
return timeDiff === 0 ? a.p.length - b.p.length : timeDiff;
|
||||
}).map(function(p) { return p.p; });
|
||||
}
|
||||
|
||||
function isAutoGenerated(file) {
|
||||
if(fs.existsSync(file)) {
|
||||
var fileContents = fs.readFileSync(file, 'utf8');
|
||||
return fileContents.indexOf(MARKER) > 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function findOutputApksHelper(dir, build_type, arch) {
|
||||
var ret = findApks(dir).filter(function(candidate) {
|
||||
// Need to choose between release and debug .apk.
|
||||
if (build_type === 'debug') {
|
||||
return /-debug/.exec(candidate) && !/-unaligned|-unsigned/.exec(candidate);
|
||||
}
|
||||
if (build_type === 'release') {
|
||||
return /-release/.exec(candidate) && !/-unaligned/.exec(candidate);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
ret = sortFilesByDate(ret);
|
||||
if (ret.length === 0) {
|
||||
return ret;
|
||||
}
|
||||
// Assume arch-specific build if newest apk has -x86 or -arm.
|
||||
var archSpecific = !!/-x86|-arm/.exec(ret[0]);
|
||||
// And show only arch-specific ones (or non-arch-specific)
|
||||
ret = ret.filter(function(p) {
|
||||
/*jshint -W018 */
|
||||
return !!/-x86|-arm/.exec(p) == archSpecific;
|
||||
/*jshint +W018 */
|
||||
});
|
||||
if (archSpecific && ret.length > 1) {
|
||||
ret = ret.filter(function(p) {
|
||||
return p.indexOf('-' + arch) != -1;
|
||||
});
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
function hasCustomRules() {
|
||||
return fs.existsSync(path.join(ROOT, 'custom_rules.xml'));
|
||||
}
|
||||
module.exports.getAntArgs = function(cmd) {
|
||||
var args = [cmd, '-f', path.join(ROOT, 'build.xml')];
|
||||
// custom_rules.xml is required for incremental builds.
|
||||
if (hasCustomRules()) {
|
||||
args.push('-Dout.dir=ant-build', '-Dgen.absolute.dir=ant-gen');
|
||||
|
||||
function extractRealProjectNameFromManifest(projectPath) {
|
||||
var manifestPath = path.join(projectPath, 'AndroidManifest.xml');
|
||||
var manifestData = fs.readFileSync(manifestPath, 'utf8');
|
||||
var m = /<manifest[\s\S]*?package\s*=\s*"(.*?)"/i.exec(manifestData);
|
||||
if (!m) {
|
||||
throw new Error('Could not find package name in ' + manifestPath);
|
||||
}
|
||||
return args;
|
||||
|
||||
var packageName=m[1];
|
||||
var lastDotIndex = packageName.lastIndexOf('.');
|
||||
return packageName.substring(lastDotIndex + 1);
|
||||
}
|
||||
|
||||
function extractProjectNameFromManifest(projectPath) {
|
||||
var manifestPath = path.join(projectPath, 'AndroidManifest.xml');
|
||||
var manifestData = fs.readFileSync(manifestPath, 'utf8');
|
||||
var m = /<activity[\s\S]*?android:name\s*=\s*"(.*?)"/i.exec(manifestData);
|
||||
if (!m) {
|
||||
throw new Error('Could not find activity name in ' + manifestPath);
|
||||
}
|
||||
return m[1];
|
||||
}
|
||||
|
||||
function findAllUniq(data, r) {
|
||||
var s = {};
|
||||
var m;
|
||||
while ((m = r.exec(data))) {
|
||||
s[m[1]] = 1;
|
||||
}
|
||||
return Object.keys(s);
|
||||
}
|
||||
|
||||
function readProjectProperties() {
|
||||
var data = fs.readFileSync(path.join(ROOT, 'project.properties'), 'utf8');
|
||||
return {
|
||||
libs: findAllUniq(data, /^\s*android\.library\.reference\.\d+=(.*)(?:\s|$)/mg),
|
||||
gradleIncludes: findAllUniq(data, /^\s*cordova\.gradle\.include\.\d+=(.*)(?:\s|$)/mg),
|
||||
systemLibs: findAllUniq(data, /^\s*cordova\.system\.library\.\d+=(.*)(?:\s|$)/mg)
|
||||
};
|
||||
}
|
||||
|
||||
var builders = {
|
||||
ant: {
|
||||
getArgs: function(cmd, opts) {
|
||||
var args = [cmd, '-f', path.join(ROOT, 'build.xml')];
|
||||
// custom_rules.xml is required for incremental builds.
|
||||
if (hasCustomRules()) {
|
||||
args.push('-Dout.dir=ant-build', '-Dgen.absolute.dir=ant-gen');
|
||||
}
|
||||
if(opts.packageInfo) {
|
||||
args.push('-propertyfile=' + path.join(ROOT, opts.buildType + SIGNING_PROPERTIES));
|
||||
}
|
||||
return args;
|
||||
},
|
||||
|
||||
prepEnv: function(opts) {
|
||||
return check_reqs.check_ant()
|
||||
.then(function() {
|
||||
// Copy in build.xml on each build so that:
|
||||
// A) we don't require the Android SDK at project creation time, and
|
||||
// B) we always use the SDK's latest version of it.
|
||||
var sdkDir = process.env['ANDROID_HOME'];
|
||||
var buildTemplate = fs.readFileSync(path.join(sdkDir, 'tools', 'lib', 'build.template'), 'utf8');
|
||||
function writeBuildXml(projectPath) {
|
||||
var newData = buildTemplate.replace('PROJECT_NAME', extractProjectNameFromManifest(ROOT));
|
||||
fs.writeFileSync(path.join(projectPath, 'build.xml'), newData);
|
||||
if (!fs.existsSync(path.join(projectPath, 'local.properties'))) {
|
||||
fs.writeFileSync(path.join(projectPath, 'local.properties'), TEMPLATE);
|
||||
}
|
||||
}
|
||||
writeBuildXml(ROOT);
|
||||
var propertiesObj = readProjectProperties();
|
||||
var subProjects = propertiesObj.libs;
|
||||
for (var i = 0; i < subProjects.length; ++i) {
|
||||
writeBuildXml(path.join(ROOT, subProjects[i]));
|
||||
}
|
||||
if (propertiesObj.systemLibs.length > 0) {
|
||||
throw new Error('Project contains at least one plugin that requires a system library. This is not supported with ANT. Please build using gradle.');
|
||||
}
|
||||
|
||||
var propertiesFile = opts.buildType + SIGNING_PROPERTIES;
|
||||
var propertiesFilePath = path.join(ROOT, propertiesFile);
|
||||
if (opts.packageInfo) {
|
||||
fs.writeFileSync(propertiesFilePath, TEMPLATE + opts.packageInfo.toProperties());
|
||||
} else if(isAutoGenerated(propertiesFilePath)) {
|
||||
shell.rm('-f', propertiesFilePath);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Builds the project with ant.
|
||||
* Returns a promise.
|
||||
*/
|
||||
build: function(opts) {
|
||||
// Without our custom_rules.xml, we need to clean before building.
|
||||
var ret = Q();
|
||||
if (!hasCustomRules()) {
|
||||
// clean will call check_ant() for us.
|
||||
ret = this.clean(opts);
|
||||
}
|
||||
|
||||
var args = this.getArgs(opts.buildType == 'debug' ? 'debug' : 'release', opts);
|
||||
return check_reqs.check_ant()
|
||||
.then(function() {
|
||||
console.log('Executing: ant ' + args.join(' '));
|
||||
return spawn('ant', args);
|
||||
});
|
||||
},
|
||||
|
||||
clean: function(opts) {
|
||||
var args = this.getArgs('clean', opts);
|
||||
return check_reqs.check_ant()
|
||||
.then(function() {
|
||||
return spawn('ant', args);
|
||||
});
|
||||
},
|
||||
|
||||
findOutputApks: function(build_type) {
|
||||
var binDir = path.join(ROOT, hasCustomRules() ? 'ant-build' : 'bin');
|
||||
return findOutputApksHelper(binDir, build_type, null);
|
||||
}
|
||||
},
|
||||
gradle: {
|
||||
getArgs: function(cmd, opts) {
|
||||
if (cmd == 'release') {
|
||||
cmd = 'cdvBuildRelease';
|
||||
} else if (cmd == 'debug') {
|
||||
cmd = 'cdvBuildDebug';
|
||||
}
|
||||
var args = [cmd, '-b', path.join(ROOT, 'build.gradle')];
|
||||
if (opts.arch) {
|
||||
args.push('-PcdvBuildArch=' + opts.arch);
|
||||
}
|
||||
|
||||
// 10 seconds -> 6 seconds
|
||||
args.push('-Dorg.gradle.daemon=true');
|
||||
args.push.apply(args, opts.extraArgs);
|
||||
// Shaves another 100ms, but produces a "try at own risk" warning. Not worth it (yet):
|
||||
// args.push('-Dorg.gradle.parallel=true');
|
||||
return args;
|
||||
},
|
||||
|
||||
// Makes the project buildable, minus the gradle wrapper.
|
||||
prepBuildFiles: function() {
|
||||
var projectPath = ROOT;
|
||||
// Update the version of build.gradle in each dependent library.
|
||||
var pluginBuildGradle = path.join(projectPath, 'cordova', 'lib', 'plugin-build.gradle');
|
||||
var propertiesObj = readProjectProperties();
|
||||
var subProjects = propertiesObj.libs;
|
||||
for (var i = 0; i < subProjects.length; ++i) {
|
||||
if (subProjects[i] !== 'CordovaLib') {
|
||||
shell.cp('-f', pluginBuildGradle, path.join(ROOT, subProjects[i], 'build.gradle'));
|
||||
}
|
||||
}
|
||||
|
||||
var name = extractRealProjectNameFromManifest(ROOT);
|
||||
//Remove the proj.id/name- prefix from projects: https://issues.apache.org/jira/browse/CB-9149
|
||||
var settingsGradlePaths = subProjects.map(function(p){
|
||||
var realDir=p.replace(/[/\\]/g, ':');
|
||||
var libName=realDir.replace(name+'-','');
|
||||
var str='include ":'+libName+'"\n';
|
||||
if(realDir.indexOf(name+'-')!==-1)
|
||||
str+='project(":'+libName+'").projectDir = new File("'+p+'")\n';
|
||||
return str;
|
||||
});
|
||||
|
||||
// Write the settings.gradle file.
|
||||
fs.writeFileSync(path.join(projectPath, 'settings.gradle'),
|
||||
'// GENERATED FILE - DO NOT EDIT\n' +
|
||||
'include ":"\n' + settingsGradlePaths.join(''));
|
||||
// Update dependencies within build.gradle.
|
||||
var buildGradle = fs.readFileSync(path.join(projectPath, 'build.gradle'), 'utf8');
|
||||
var depsList = '';
|
||||
subProjects.forEach(function(p) {
|
||||
var libName=p.replace(/[/\\]/g, ':').replace(name+'-','');
|
||||
depsList += ' debugCompile project(path: "' + libName + '", configuration: "debug")\n';
|
||||
depsList += ' releaseCompile project(path: "' + libName + '", configuration: "release")\n';
|
||||
});
|
||||
// For why we do this mapping: https://issues.apache.org/jira/browse/CB-8390
|
||||
var SYSTEM_LIBRARY_MAPPINGS = [
|
||||
[/^\/?extras\/android\/support\/(.*)$/, 'com.android.support:support-$1:+'],
|
||||
[/^\/?google\/google_play_services\/libproject\/google-play-services_lib\/?$/, 'com.google.android.gms:play-services:+']
|
||||
];
|
||||
propertiesObj.systemLibs.forEach(function(p) {
|
||||
var mavenRef;
|
||||
// It's already in gradle form if it has two ':'s
|
||||
if (/:.*:/.exec(p)) {
|
||||
mavenRef = p;
|
||||
} else {
|
||||
for (var i = 0; i < SYSTEM_LIBRARY_MAPPINGS.length; ++i) {
|
||||
var pair = SYSTEM_LIBRARY_MAPPINGS[i];
|
||||
if (pair[0].exec(p)) {
|
||||
mavenRef = p.replace(pair[0], pair[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!mavenRef) {
|
||||
throw new Error('Unsupported system library (does not work with gradle): ' + p);
|
||||
}
|
||||
}
|
||||
depsList += ' compile "' + mavenRef + '"\n';
|
||||
});
|
||||
buildGradle = buildGradle.replace(/(SUB-PROJECT DEPENDENCIES START)[\s\S]*(\/\/ SUB-PROJECT DEPENDENCIES END)/, '$1\n' + depsList + ' $2');
|
||||
var includeList = '';
|
||||
propertiesObj.gradleIncludes.forEach(function(includePath) {
|
||||
includeList += 'apply from: "' + includePath + '"\n';
|
||||
});
|
||||
buildGradle = buildGradle.replace(/(PLUGIN GRADLE EXTENSIONS START)[\s\S]*(\/\/ PLUGIN GRADLE EXTENSIONS END)/, '$1\n' + includeList + '$2');
|
||||
fs.writeFileSync(path.join(projectPath, 'build.gradle'), buildGradle);
|
||||
},
|
||||
|
||||
prepEnv: function(opts) {
|
||||
var self = this;
|
||||
return check_reqs.check_gradle()
|
||||
.then(function() {
|
||||
return self.prepBuildFiles();
|
||||
}).then(function() {
|
||||
// Copy the gradle wrapper on each build so that:
|
||||
// A) we don't require the Android SDK at project creation time, and
|
||||
// B) we always use the SDK's latest version of it.
|
||||
var projectPath = ROOT;
|
||||
// check_reqs ensures that this is set.
|
||||
var sdkDir = process.env['ANDROID_HOME'];
|
||||
var wrapperDir = path.join(sdkDir, 'tools', 'templates', 'gradle', 'wrapper');
|
||||
if (process.platform == 'win32') {
|
||||
shell.rm('-f', path.join(projectPath, 'gradlew.bat'));
|
||||
shell.cp(path.join(wrapperDir, 'gradlew.bat'), projectPath);
|
||||
} else {
|
||||
shell.rm('-f', path.join(projectPath, 'gradlew'));
|
||||
shell.cp(path.join(wrapperDir, 'gradlew'), projectPath);
|
||||
}
|
||||
shell.rm('-rf', path.join(projectPath, 'gradle', 'wrapper'));
|
||||
shell.mkdir('-p', path.join(projectPath, 'gradle'));
|
||||
shell.cp('-r', path.join(wrapperDir, 'gradle', 'wrapper'), path.join(projectPath, 'gradle'));
|
||||
|
||||
// If the gradle distribution URL is set, make sure it points to version we want.
|
||||
// If it's not set, do nothing, assuming that we're using a future version of gradle that we don't want to mess with.
|
||||
// For some reason, using ^ and $ don't work. This does the job, though.
|
||||
var distributionUrlRegex = /distributionUrl.*zip/;
|
||||
var distributionUrl = 'distributionUrl=http\\://services.gradle.org/distributions/gradle-2.2.1-all.zip';
|
||||
var gradleWrapperPropertiesPath = path.join(projectPath, 'gradle', 'wrapper', 'gradle-wrapper.properties');
|
||||
shell.chmod('u+w', gradleWrapperPropertiesPath);
|
||||
shell.sed('-i', distributionUrlRegex, distributionUrl, gradleWrapperPropertiesPath);
|
||||
|
||||
var propertiesFile = opts.buildType + SIGNING_PROPERTIES;
|
||||
var propertiesFilePath = path.join(ROOT, propertiesFile);
|
||||
if (opts.packageInfo) {
|
||||
fs.writeFileSync(propertiesFilePath, TEMPLATE + opts.packageInfo.toProperties());
|
||||
} else if (isAutoGenerated(propertiesFilePath)) {
|
||||
shell.rm('-f', propertiesFilePath);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Builds the project with gradle.
|
||||
* Returns a promise.
|
||||
*/
|
||||
build: function(opts) {
|
||||
var wrapper = path.join(ROOT, 'gradlew');
|
||||
var args = this.getArgs(opts.buildType == 'debug' ? 'debug' : 'release', opts);
|
||||
return Q().then(function() {
|
||||
console.log('Running: ' + wrapper + ' ' + args.join(' '));
|
||||
return spawn(wrapper, args);
|
||||
});
|
||||
},
|
||||
|
||||
clean: function(opts) {
|
||||
var builder = this;
|
||||
var wrapper = path.join(ROOT, 'gradlew');
|
||||
var args = builder.getArgs('clean', opts);
|
||||
return Q().then(function() {
|
||||
console.log('Running: ' + wrapper + ' ' + args.join(' '));
|
||||
return spawn(wrapper, args);
|
||||
});
|
||||
},
|
||||
|
||||
findOutputApks: function(build_type, arch) {
|
||||
var binDir = path.join(ROOT, 'build', 'outputs', 'apk');
|
||||
return findOutputApksHelper(binDir, build_type, arch);
|
||||
}
|
||||
},
|
||||
|
||||
none: {
|
||||
prepEnv: function() {
|
||||
return Q();
|
||||
},
|
||||
build: function() {
|
||||
console.log('Skipping build...');
|
||||
return Q(null);
|
||||
},
|
||||
clean: function() {
|
||||
return Q();
|
||||
},
|
||||
findOutputApks: function(build_type, arch) {
|
||||
return sortFilesByDate(builders.ant.findOutputApks(build_type, arch).concat(builders.gradle.findOutputApks(build_type, arch)));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.isBuildFlag = function(flag) {
|
||||
return /^--(debug|release|ant|gradle|nobuild|versionCode=|minSdkVersion=|gradleArg=|keystore=|alias=|password=|storePassword=|keystoreType=|buildConfig=)/.exec(flag);
|
||||
};
|
||||
|
||||
function parseOpts(options, resolvedTarget) {
|
||||
// Backwards-compatibility: Allow a single string argument
|
||||
if (typeof options == 'string') options = [options];
|
||||
|
||||
var ret = {
|
||||
buildType: 'debug',
|
||||
buildMethod: process.env['ANDROID_BUILD'] || 'gradle',
|
||||
arch: null,
|
||||
extraArgs: []
|
||||
};
|
||||
|
||||
var multiValueArgs = {
|
||||
'versionCode': true,
|
||||
'minSdkVersion': true,
|
||||
'gradleArg': true,
|
||||
'keystore' : true,
|
||||
'alias' : true,
|
||||
'password' : true,
|
||||
'storePassword' : true,
|
||||
'keystoreType' : true,
|
||||
'buildConfig' : true
|
||||
};
|
||||
var packageArgs = {};
|
||||
var buildConfig;
|
||||
// Iterate through command line options
|
||||
for (var i=0; options && (i < options.length); ++i) {
|
||||
if (/^--/.exec(options[i])) {
|
||||
var keyValue = options[i].substring(2).split('=');
|
||||
var flagName = keyValue.shift();
|
||||
var flagValue = keyValue.join('=');
|
||||
if (multiValueArgs[flagName] && !flagValue) {
|
||||
flagValue = options[i + 1];
|
||||
++i;
|
||||
}
|
||||
switch(flagName) {
|
||||
case 'debug':
|
||||
case 'release':
|
||||
ret.buildType = flagName;
|
||||
break;
|
||||
case 'ant':
|
||||
case 'gradle':
|
||||
ret.buildMethod = flagName;
|
||||
break;
|
||||
case 'device':
|
||||
case 'emulator':
|
||||
// Don't need to do anything special to when building for device vs emulator.
|
||||
// iOS uses this flag to switch on architecture.
|
||||
break;
|
||||
case 'prepenv' :
|
||||
ret.prepEnv = true;
|
||||
break;
|
||||
case 'nobuild' :
|
||||
ret.buildMethod = 'none';
|
||||
break;
|
||||
case 'versionCode':
|
||||
ret.extraArgs.push('-PcdvVersionCode=' + flagValue);
|
||||
break;
|
||||
case 'minSdkVersion':
|
||||
ret.extraArgs.push('-PcdvMinSdkVersion=' + flagValue);
|
||||
break;
|
||||
case 'gradleArg':
|
||||
ret.extraArgs.push(flagValue);
|
||||
break;
|
||||
case 'keystore':
|
||||
packageArgs.keystore = path.relative(ROOT, path.resolve(flagValue));
|
||||
break;
|
||||
case 'alias':
|
||||
case 'storePassword':
|
||||
case 'password':
|
||||
case 'keystoreType':
|
||||
packageArgs[flagName] = flagValue;
|
||||
break;
|
||||
case 'buildConfig':
|
||||
buildConfig = flagValue;
|
||||
break;
|
||||
default :
|
||||
console.warn('Build option --\'' + flagName + '\' not recognized (ignoring).');
|
||||
}
|
||||
} else {
|
||||
console.warn('Build option \'' + options[i] + '\' not recognized (ignoring).');
|
||||
}
|
||||
}
|
||||
|
||||
// If some values are not specified as command line arguments - use build config to supplement them.
|
||||
// Command line arguemnts have precedence over build config.
|
||||
if (buildConfig) {
|
||||
if (!fs.existsSync(buildConfig)) {
|
||||
throw new Error('Specified build config file does not exist: ' + buildConfig);
|
||||
}
|
||||
console.log('Reading build config file: '+ path.resolve(buildConfig));
|
||||
var config = JSON.parse(fs.readFileSync(buildConfig, 'utf8'));
|
||||
if (config.android && config.android[ret.buildType]) {
|
||||
var androidInfo = config.android[ret.buildType];
|
||||
if(androidInfo.keystore && !packageArgs.keystore) {
|
||||
if(path.isAbsolute(androidInfo.keystore)) {
|
||||
packageArgs.keystore = androidInfo.keystore;
|
||||
} else {
|
||||
packageArgs.keystore = path.relative(ROOT, path.join(path.dirname(buildConfig), androidInfo.keystore));
|
||||
}
|
||||
}
|
||||
|
||||
['alias', 'storePassword', 'password','keystoreType'].forEach(function (key){
|
||||
packageArgs[key] = packageArgs[key] || androidInfo[key];
|
||||
});
|
||||
}
|
||||
}
|
||||
if (packageArgs.keystore && packageArgs.alias) {
|
||||
ret.packageInfo = new PackageInfo(packageArgs.keystore, packageArgs.alias, packageArgs.storePassword,
|
||||
packageArgs.password, packageArgs.keystoreType);
|
||||
}
|
||||
|
||||
if(!ret.packageInfo) {
|
||||
if(Object.keys(packageArgs).length > 0) {
|
||||
console.warn('\'keystore\' and \'alias\' need to be specified to generate a signed archive.');
|
||||
}
|
||||
}
|
||||
ret.arch = resolvedTarget && resolvedTarget.arch;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Builds the project with the specifed options
|
||||
* Returns a promise.
|
||||
*/
|
||||
module.exports.runClean = function(options) {
|
||||
var opts = parseOpts(options);
|
||||
var builder = builders[opts.buildMethod];
|
||||
return builder.prepEnv(opts)
|
||||
.then(function() {
|
||||
return builder.clean(opts);
|
||||
}).then(function() {
|
||||
shell.rm('-rf', path.join(ROOT, 'out'));
|
||||
|
||||
['debug', 'release'].forEach(function(config) {
|
||||
var propertiesFilePath = path.join(ROOT, config + SIGNING_PROPERTIES);
|
||||
if(isAutoGenerated(propertiesFilePath)){
|
||||
shell.rm('-f', propertiesFilePath);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* Builds the project with ant.
|
||||
* Builds the project with the specifed options
|
||||
* Returns a promise.
|
||||
*/
|
||||
module.exports.run = function(build_type) {
|
||||
//default build type
|
||||
build_type = typeof build_type !== 'undefined' ? build_type : "--debug";
|
||||
var args = module.exports.getAntArgs('debug');
|
||||
switch(build_type) {
|
||||
case '--debug' :
|
||||
break;
|
||||
case '--release' :
|
||||
args[0] = 'release';
|
||||
break;
|
||||
case '--nobuild' :
|
||||
console.log('Skipping build...');
|
||||
return Q();
|
||||
default :
|
||||
return Q.reject('Build option \'' + build_type + '\' not recognized.');
|
||||
}
|
||||
// Without our custom_rules.xml, we need to clean before building.
|
||||
var ret = Q();
|
||||
if (!hasCustomRules()) {
|
||||
ret = require('./clean').run();
|
||||
}
|
||||
return ret.then(function() {
|
||||
return spawn('ant', args);
|
||||
module.exports.run = function(options, optResolvedTarget) {
|
||||
var opts = parseOpts(options, optResolvedTarget);
|
||||
var builder = builders[opts.buildMethod];
|
||||
return builder.prepEnv(opts)
|
||||
.then(function() {
|
||||
if (opts.prepEnv) {
|
||||
console.log('Build file successfully prepared.');
|
||||
return;
|
||||
}
|
||||
return builder.build(opts)
|
||||
.then(function() {
|
||||
var apkPaths = builder.findOutputApks(opts.buildType, opts.arch);
|
||||
console.log('Built the following apk(s):');
|
||||
console.log(' ' + apkPaths.join('\n '));
|
||||
return {
|
||||
apkPaths: apkPaths,
|
||||
buildType: opts.buildType,
|
||||
buildMethod: opts.buildMethod
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Called by plugman after installing plugins, and by create script after creating project.
|
||||
module.exports.prepBuildFiles = function() {
|
||||
var builder = builders['gradle'];
|
||||
return builder.prepBuildFiles();
|
||||
};
|
||||
|
||||
/*
|
||||
* Gets the path to the apk file, if not such file exists then
|
||||
* the script will error out. (should we error or just return undefined?)
|
||||
* Detects the architecture of a device/emulator
|
||||
* Returns "arm" or "x86".
|
||||
*/
|
||||
module.exports.get_apk = function() {
|
||||
var binDir = '';
|
||||
if(!hasCustomRules()) {
|
||||
binDir = path.join(ROOT, 'bin');
|
||||
} else {
|
||||
binDir = path.join(ROOT, 'ant-build');
|
||||
}
|
||||
if (fs.existsSync(binDir)) {
|
||||
var candidates = fs.readdirSync(binDir).filter(function(p) {
|
||||
// Need to choose between release and debug .apk.
|
||||
return path.extname(p) == '.apk';
|
||||
}).map(function(p) {
|
||||
p = path.join(binDir, p);
|
||||
return { p: p, t: fs.statSync(p).mtime };
|
||||
}).sort(function(a,b) {
|
||||
return a.t > b.t ? -1 :
|
||||
a.t < b.t ? 1 : 0;
|
||||
module.exports.detectArchitecture = function(target) {
|
||||
function helper() {
|
||||
return exec('adb -s ' + target + ' shell cat /proc/cpuinfo', os.tmpdir())
|
||||
.then(function(output) {
|
||||
if (/intel/i.exec(output)) {
|
||||
return 'x86';
|
||||
}
|
||||
return 'arm';
|
||||
});
|
||||
if (candidates.length === 0) {
|
||||
console.error('ERROR : No .apk found in ' + binDir + ' directory');
|
||||
process.exit(2);
|
||||
}
|
||||
// It sometimes happens (at least on OS X), that this command will hang forever.
|
||||
// To fix it, either unplug & replug device, or restart adb server.
|
||||
return helper().timeout(1000, 'Device communication timed out. Try unplugging & replugging the device.')
|
||||
.then(null, function(err) {
|
||||
if (/timed out/.exec('' + err)) {
|
||||
// adb kill-server doesn't seem to do the trick.
|
||||
// Could probably find a x-platform version of killall, but I'm not actually
|
||||
// sure that this scenario even happens on non-OSX machines.
|
||||
return exec('killall adb')
|
||||
.then(function() {
|
||||
console.log('adb seems hung. retrying.');
|
||||
return helper()
|
||||
.then(null, function() {
|
||||
// The double kill is sadly often necessary, at least on mac.
|
||||
console.log('Now device not found... restarting adb again.');
|
||||
return exec('killall adb')
|
||||
.then(function() {
|
||||
return helper()
|
||||
.then(null, function() {
|
||||
return Q.reject('USB is flakey. Try unplugging & replugging the device.');
|
||||
});
|
||||
});
|
||||
});
|
||||
}, function() {
|
||||
// For non-killall OS's.
|
||||
return Q.reject(err);
|
||||
});
|
||||
}
|
||||
console.log('Using apk: ' + candidates[0].p);
|
||||
return candidates[0].p;
|
||||
} else {
|
||||
console.error('ERROR : unable to find project ' + binDir + ' directory, could not locate .apk');
|
||||
process.exit(2);
|
||||
throw err;
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.findBestApkForArchitecture = function(buildResults, arch) {
|
||||
var paths = buildResults.apkPaths.filter(function(p) {
|
||||
if (buildResults.buildType == 'debug') {
|
||||
return /-debug/.exec(p);
|
||||
}
|
||||
return !/-debug/.exec(p);
|
||||
});
|
||||
var archPattern = new RegExp('-' + arch);
|
||||
var hasArchPattern = /-x86|-arm/;
|
||||
for (var i = 0; i < paths.length; ++i) {
|
||||
if (hasArchPattern.exec(paths[i])) {
|
||||
if (archPattern.exec(paths[i])) {
|
||||
return paths[i];
|
||||
}
|
||||
} else {
|
||||
return paths[i];
|
||||
}
|
||||
}
|
||||
throw new Error('Could not find apk architecture: ' + arch + ' build-type: ' + buildResults.buildType);
|
||||
};
|
||||
|
||||
function PackageInfo(keystore, alias, storePassword, password, keystoreType) {
|
||||
this.keystore = {
|
||||
'name': 'key.store',
|
||||
'value': keystore
|
||||
};
|
||||
this.alias = {
|
||||
'name': 'key.alias',
|
||||
'value': alias
|
||||
};
|
||||
if (storePassword) {
|
||||
this.storePassword = {
|
||||
'name': 'key.store.password',
|
||||
'value': storePassword
|
||||
};
|
||||
}
|
||||
if (password) {
|
||||
this.password = {
|
||||
'name': 'key.alias.password',
|
||||
'value': password
|
||||
};
|
||||
}
|
||||
if (keystoreType) {
|
||||
this.keystoreType = {
|
||||
'name': 'key.store.type',
|
||||
'value': keystoreType
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
PackageInfo.prototype = {
|
||||
toProperties: function() {
|
||||
var self = this;
|
||||
var result = '';
|
||||
Object.keys(self).forEach(function(key) {
|
||||
result += self[key].name;
|
||||
result += '=';
|
||||
result += self[key].value.replace(/\\/g, '\\\\');
|
||||
result += '\n';
|
||||
});
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.help = function() {
|
||||
console.log('Usage: ' + path.relative(process.cwd(), path.join(ROOT, 'cordova', 'build')) + ' [build_type]');
|
||||
console.log('Build Types : ');
|
||||
console.log(' \'--debug\': Default build, will build project in using ant debug');
|
||||
console.log(' \'--release\': will build project using ant release');
|
||||
console.log(' \'--nobuild\': will skip build process (can be used with run command)');
|
||||
console.log('Usage: ' + path.relative(process.cwd(), path.join(ROOT, 'cordova', 'build')) + ' [flags] [Signed APK flags]');
|
||||
console.log('Flags:');
|
||||
console.log(' \'--debug\': will build project in debug mode (default)');
|
||||
console.log(' \'--release\': will build project for release');
|
||||
console.log(' \'--ant\': will build project with ant');
|
||||
console.log(' \'--gradle\': will build project with gradle (default)');
|
||||
console.log(' \'--nobuild\': will skip build process (useful when using run command)');
|
||||
console.log(' \'--prepenv\': don\'t build, but copy in build scripts where necessary');
|
||||
console.log(' \'--versionCode=#\': Override versionCode for this build. Useful for uploading multiple APKs. Requires --gradle.');
|
||||
console.log(' \'--minSdkVersion=#\': Override minSdkVersion for this build. Useful for uploading multiple APKs. Requires --gradle.');
|
||||
console.log(' \'--gradleArg=<gradle command line arg>\': Extra args to pass to the gradle command. Use one flag per arg. Ex. --gradleArg=-PcdvBuildMultipleApks=true');
|
||||
console.log('');
|
||||
console.log('Signed APK flags (overwrites debug/release-signing.proprties) :');
|
||||
console.log(' \'--keystore=<path to keystore>\': Key store used to build a signed archive. (Required)');
|
||||
console.log(' \'--alias=\': Alias for the key store. (Required)');
|
||||
console.log(' \'--storePassword=\': Password for the key store. (Optional - prompted)');
|
||||
console.log(' \'--password=\': Password for the key. (Optional - prompted)');
|
||||
console.log(' \'--keystoreType\': Type of the keystore. (Optional)');
|
||||
process.exit(0);
|
||||
}
|
||||
};
|
||||
|
||||
39
bin/templates/cordova/lib/clean.js
vendored
39
bin/templates/cordova/lib/clean.js
vendored
@@ -1,39 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/*
|
||||
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 = require('./build'),
|
||||
spawn = require('./spawn'),
|
||||
path = require('path');
|
||||
|
||||
/*
|
||||
* Cleans the project using ant
|
||||
* Returns a promise.
|
||||
*/
|
||||
module.exports.run = function() {
|
||||
var args = build.getAntArgs('clean');
|
||||
return spawn('ant', args);
|
||||
}
|
||||
|
||||
module.exports.help = function() {
|
||||
console.log('Usage: ' + path.relative(process.cwd(), process.argv[1]));
|
||||
console.log('Cleans the project directory.');
|
||||
process.exit(0);
|
||||
}
|
||||
127
bin/templates/cordova/lib/device.js
vendored
127
bin/templates/cordova/lib/device.js
vendored
@@ -21,66 +21,101 @@
|
||||
|
||||
var exec = require('./exec'),
|
||||
Q = require('q'),
|
||||
path = require('path'),
|
||||
os = require('os'),
|
||||
build = require('./build'),
|
||||
appinfo = require('./appinfo'),
|
||||
ROOT = path.join(__dirname, '..', '..');
|
||||
appinfo = require('./appinfo');
|
||||
|
||||
/**
|
||||
* Returns a promise for the list of the device ID's found
|
||||
* @param lookHarder When true, try restarting adb if no devices are found.
|
||||
*/
|
||||
module.exports.list = function() {
|
||||
return exec('adb devices')
|
||||
.then(function(output) {
|
||||
var response = output.split('\n');
|
||||
var device_list = [];
|
||||
for (var i = 1; i < response.length; i++) {
|
||||
if (response[i].match(/\w+\tdevice/) && !response[i].match(/emulator/)) {
|
||||
device_list.push(response[i].replace(/\tdevice/, '').replace('\r', ''));
|
||||
module.exports.list = function(lookHarder) {
|
||||
function helper() {
|
||||
return exec('adb devices', os.tmpdir())
|
||||
.then(function(output) {
|
||||
var response = output.split('\n');
|
||||
var device_list = [];
|
||||
for (var i = 1; i < response.length; i++) {
|
||||
if (response[i].match(/\w+\tdevice/) && !response[i].match(/emulator/)) {
|
||||
device_list.push(response[i].replace(/\tdevice/, '').replace('\r', ''));
|
||||
}
|
||||
}
|
||||
return device_list;
|
||||
});
|
||||
}
|
||||
return helper()
|
||||
.then(function(list) {
|
||||
if (list.length === 0 && lookHarder) {
|
||||
// adb kill-server doesn't seem to do the trick.
|
||||
// Could probably find a x-platform version of killall, but I'm not actually
|
||||
// sure that this scenario even happens on non-OSX machines.
|
||||
return exec('killall adb')
|
||||
.then(function() {
|
||||
console.log('Restarting adb to see if more devices are detected.');
|
||||
return helper();
|
||||
}, function() {
|
||||
// For non-killall OS's.
|
||||
return list;
|
||||
});
|
||||
}
|
||||
return device_list;
|
||||
return list;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.resolveTarget = function(target) {
|
||||
return this.list(true)
|
||||
.then(function(device_list) {
|
||||
if (!device_list || !device_list.length) {
|
||||
return Q.reject('ERROR: Failed to deploy to device, no devices found.');
|
||||
}
|
||||
// default device
|
||||
target = target || device_list[0];
|
||||
|
||||
if (device_list.indexOf(target) < 0) {
|
||||
return Q.reject('ERROR: Unable to find target \'' + target + '\'.');
|
||||
}
|
||||
|
||||
return build.detectArchitecture(target)
|
||||
.then(function(arch) {
|
||||
return { target: target, arch: arch, isEmulator: false };
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* Installs a previously built application on the device
|
||||
* and launches it.
|
||||
* Returns a promise.
|
||||
*/
|
||||
module.exports.install = function(target) {
|
||||
var launchName;
|
||||
return this.list()
|
||||
.then(function(device_list) {
|
||||
if (!device_list || !device_list.length)
|
||||
return Q.reject('ERROR: Failed to deploy to device, no devices found.');
|
||||
|
||||
// default device
|
||||
target = typeof target !== 'undefined' ? target : device_list[0];
|
||||
|
||||
if (device_list.indexOf(target) < 0)
|
||||
return Q.reject('ERROR: Unable to find target \'' + target + '\'.');
|
||||
|
||||
var apk_path = build.get_apk();
|
||||
launchName = appinfo.getActivityName();
|
||||
module.exports.install = function(target, buildResults) {
|
||||
return Q().then(function() {
|
||||
if (target && typeof target == 'object') {
|
||||
return target;
|
||||
}
|
||||
return module.exports.resolveTarget(target);
|
||||
}).then(function(resolvedTarget) {
|
||||
var apk_path = build.findBestApkForArchitecture(buildResults, resolvedTarget.arch);
|
||||
var launchName = appinfo.getActivityName();
|
||||
console.log('Using apk: ' + apk_path);
|
||||
console.log('Installing app on device...');
|
||||
var cmd = 'adb -s ' + target + ' install -r "' + apk_path + '"';
|
||||
return exec(cmd);
|
||||
}).then(function(output) {
|
||||
if (output.match(/Failure/)) return Q.reject('ERROR: Failed to install apk to device: ' + output);
|
||||
var cmd = 'adb -s ' + resolvedTarget.target + ' install -r "' + apk_path + '"';
|
||||
return exec(cmd, os.tmpdir())
|
||||
.then(function(output) {
|
||||
if (output.match(/Failure/)) return Q.reject('ERROR: Failed to install apk to device: ' + output);
|
||||
|
||||
//unlock screen
|
||||
var cmd = 'adb -s ' + target + ' shell input keyevent 82';
|
||||
return exec(cmd);
|
||||
}, function(err) { return Q.reject('ERROR: Failed to install apk to device: ' + err); })
|
||||
.then(function() {
|
||||
// launch the application
|
||||
console.log('Launching application...');
|
||||
var cmd = 'adb -s ' + target + ' shell am start -W -a android.intent.action.MAIN -n ' + launchName;
|
||||
return exec(cmd);
|
||||
}).then(function() {
|
||||
console.log('LAUNCH SUCCESS');
|
||||
}, function(err) {
|
||||
return Q.reject('ERROR: Failed to launch application on device: ' + err);
|
||||
//unlock screen
|
||||
var cmd = 'adb -s ' + resolvedTarget.target + ' shell input keyevent 82';
|
||||
return exec(cmd, os.tmpdir());
|
||||
}, function(err) { return Q.reject('ERROR: Failed to install apk to device: ' + err); })
|
||||
.then(function() {
|
||||
// launch the application
|
||||
console.log('Launching application...');
|
||||
var cmd = 'adb -s ' + resolvedTarget.target + ' shell am start -W -a android.intent.action.MAIN -n ' + launchName;
|
||||
return exec(cmd, os.tmpdir());
|
||||
}).then(function() {
|
||||
console.log('LAUNCH SUCCESS');
|
||||
}, function(err) {
|
||||
return Q.reject('ERROR: Failed to launch application on device: ' + err);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
178
bin/templates/cordova/lib/emulator.js
vendored
178
bin/templates/cordova/lib/emulator.js
vendored
@@ -19,15 +19,23 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
var shell = require('shelljs'),
|
||||
exec = require('./exec'),
|
||||
Q = require('q'),
|
||||
path = require('path'),
|
||||
appinfo = require('./appinfo'),
|
||||
build = require('./build'),
|
||||
ROOT = path.join(__dirname, '..', '..'),
|
||||
child_process = require('child_process'),
|
||||
new_emulator = 'cordova_emulator';
|
||||
/* jshint sub:true */
|
||||
|
||||
var exec = require('./exec');
|
||||
var appinfo = require('./appinfo');
|
||||
var retry = require('./retry');
|
||||
var build = require('./build');
|
||||
var check_reqs = require('./check_reqs');
|
||||
|
||||
var Q = require('q');
|
||||
var os = require('os');
|
||||
var child_process = require('child_process');
|
||||
|
||||
// constants
|
||||
var ONE_SECOND = 1000; // in milliseconds
|
||||
var INSTALL_COMMAND_TIMEOUT = 120 * ONE_SECOND; // in milliseconds
|
||||
var NUM_INSTALL_RETRIES = 3;
|
||||
var EXEC_KILL_SIGNAL = 'SIGKILL';
|
||||
|
||||
/**
|
||||
* Returns a Promise for a list of emulator images in the form of objects
|
||||
@@ -76,7 +84,7 @@ module.exports.list_images = function() {
|
||||
}
|
||||
return emulator_list;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Will return the closest avd to the projects target
|
||||
@@ -84,30 +92,30 @@ module.exports.list_images = function() {
|
||||
* Returns a promise.
|
||||
*/
|
||||
module.exports.best_image = function() {
|
||||
var project_target = this.get_target().replace('android-', '');
|
||||
var project_target = check_reqs.get_target().replace('android-', '');
|
||||
return this.list_images()
|
||||
.then(function(images) {
|
||||
var closest = 9999;
|
||||
var best = images[0];
|
||||
for (i in images) {
|
||||
for (var i in images) {
|
||||
var target = images[i].target;
|
||||
if(target) {
|
||||
var num = target.split('(API level ')[1].replace(')', '');
|
||||
if (num == project_target) {
|
||||
return images[i];
|
||||
} else if (project_target - num < closest && project_target > num) {
|
||||
var closest = project_target - num;
|
||||
closest = project_target - num;
|
||||
best = images[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
return best;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Returns a promise.
|
||||
module.exports.list_started = function() {
|
||||
return exec('adb devices')
|
||||
return exec('adb devices', os.tmpdir())
|
||||
.then(function(output) {
|
||||
var response = output.split('\n');
|
||||
var started_emulator_list = [];
|
||||
@@ -118,16 +126,11 @@ module.exports.list_started = function() {
|
||||
}
|
||||
return started_emulator_list;
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.get_target = function() {
|
||||
var target = shell.grep(/target=android-[\d+]/, path.join(ROOT, 'project.properties'));
|
||||
return target.split('=')[1].replace('\n', '').replace('\r', '').replace(' ', '');
|
||||
}
|
||||
};
|
||||
|
||||
// Returns a promise.
|
||||
module.exports.list_targets = function() {
|
||||
return exec('android list targets')
|
||||
return exec('android list targets', os.tmpdir())
|
||||
.then(function(output) {
|
||||
var target_out = output.split('\n');
|
||||
var targets = [];
|
||||
@@ -138,7 +141,7 @@ module.exports.list_targets = function() {
|
||||
}
|
||||
return targets;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Starts an emulator with the given ID,
|
||||
@@ -156,7 +159,7 @@ module.exports.start = function(emulator_ID) {
|
||||
.then(function(list) {
|
||||
started_emulators = list;
|
||||
num_started = started_emulators.length;
|
||||
if (typeof emulator_ID === 'undefined') {
|
||||
if (!emulator_ID) {
|
||||
return self.list_images()
|
||||
.then(function(emulator_list) {
|
||||
if (emulator_list.length > 0) {
|
||||
@@ -167,11 +170,11 @@ module.exports.start = function(emulator_ID) {
|
||||
return emulator_ID;
|
||||
});
|
||||
} else {
|
||||
return Q.reject('ERROR : No emulator images (avds) found, if you would like to create an\n' +
|
||||
' avd follow the instructions provided here:\n' +
|
||||
' http://developer.android.com/tools/devices/index.html\n' +
|
||||
' Or run \'android create avd --name <name> --target <targetID>\'\n' +
|
||||
' in on the command line.');
|
||||
var androidCmd = check_reqs.getAbsoluteAndroidCmd();
|
||||
return Q.reject('ERROR : No emulator images (avds) found.\n' +
|
||||
'1. Download desired System Image by running: ' + androidCmd + ' sdk\n' +
|
||||
'2. Create an AVD by running: ' + androidCmd + ' avd\n' +
|
||||
'HINT: For a faster emulator, use an Intel System Image and install the HAXM device driver\n');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -188,7 +191,7 @@ module.exports.start = function(emulator_ID) {
|
||||
return self.wait_for_emulator(num_started);
|
||||
}).then(function(new_started) {
|
||||
if (new_started.length > 1) {
|
||||
for (i in new_started) {
|
||||
for (var i in new_started) {
|
||||
if (started_emulators.indexOf(new_started[i]) < 0) {
|
||||
emulator_id = new_started[i];
|
||||
}
|
||||
@@ -205,12 +208,12 @@ module.exports.start = function(emulator_ID) {
|
||||
console.log('BOOT COMPLETE');
|
||||
|
||||
//unlock screen
|
||||
return exec('adb -s ' + emulator_id + ' shell input keyevent 82');
|
||||
return exec('adb -s ' + emulator_id + ' shell input keyevent 82', os.tmpdir());
|
||||
}).then(function() {
|
||||
//return the new emulator id for the started emulators
|
||||
return emulator_id;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Waits for the new emulator to apear on the started-emulator list.
|
||||
@@ -228,14 +231,14 @@ module.exports.wait_for_emulator = function(num_running) {
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Waits for the boot animation property of the emulator to switch to 'stopped'
|
||||
*/
|
||||
module.exports.wait_for_boot = function(emulator_id) {
|
||||
var self = this;
|
||||
return exec('adb -s ' + emulator_id + ' shell getprop init.svc.bootanim')
|
||||
return exec('adb -s ' + emulator_id + ' shell getprop init.svc.bootanim', os.tmpdir())
|
||||
.then(function(output) {
|
||||
if (output.match(/stopped/)) {
|
||||
return;
|
||||
@@ -246,7 +249,7 @@ module.exports.wait_for_boot = function(emulator_id) {
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Create avd
|
||||
@@ -260,7 +263,7 @@ module.exports.create_image = function(name, target) {
|
||||
.then(null, function(error) {
|
||||
console.error('ERROR : Failed to create emulator image : ');
|
||||
console.error(' Do you have the latest android targets including ' + target + '?');
|
||||
console.error(create.output);
|
||||
console.error(error);
|
||||
});
|
||||
} else {
|
||||
console.log('WARNING : Project target not found, creating avd with a different target but the project may fail to install.');
|
||||
@@ -275,16 +278,9 @@ module.exports.create_image = function(name, target) {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Installs a previously built application on the emulator and launches it.
|
||||
* If no target is specified, then it picks one.
|
||||
* If no started emulators are found, error out.
|
||||
* Returns a promise.
|
||||
*/
|
||||
module.exports.install = function(target) {
|
||||
var self = this;
|
||||
module.exports.resolveTarget = function(target) {
|
||||
return this.list_started()
|
||||
.then(function(emulator_list) {
|
||||
if (emulator_list.length < 1) {
|
||||
@@ -292,33 +288,85 @@ module.exports.install = function(target) {
|
||||
}
|
||||
|
||||
// default emulator
|
||||
target = typeof target !== 'undefined' ? target : emulator_list[0];
|
||||
target = target || emulator_list[0];
|
||||
if (emulator_list.indexOf(target) < 0) {
|
||||
return Q.reject('Unable to find target \'' + target + '\'. Failed to deploy to emulator.');
|
||||
}
|
||||
|
||||
console.log('Installing app on emulator...');
|
||||
var apk_path = build.get_apk();
|
||||
return exec('adb -s ' + target + ' install -r "' + apk_path + '"');
|
||||
}).then(function(output) {
|
||||
if (output.match(/Failure/)) {
|
||||
return Q.reject('Failed to install apk to emulator: ' + output);
|
||||
return build.detectArchitecture(target)
|
||||
.then(function(arch) {
|
||||
return {target:target, arch:arch, isEmulator:true};
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* Installs a previously built application on the emulator and launches it.
|
||||
* If no target is specified, then it picks one.
|
||||
* If no started emulators are found, error out.
|
||||
* Returns a promise.
|
||||
*/
|
||||
module.exports.install = function(givenTarget, buildResults) {
|
||||
|
||||
var target;
|
||||
|
||||
// resolve the target emulator
|
||||
return Q().then(function () {
|
||||
if (givenTarget && typeof givenTarget == 'object') {
|
||||
return givenTarget;
|
||||
} else {
|
||||
return module.exports.resolveTarget(givenTarget);
|
||||
}
|
||||
return Q();
|
||||
}, function(err) {
|
||||
return Q.reject('Failed to install apk to emulator: ' + err);
|
||||
}).then(function() {
|
||||
//unlock screen
|
||||
return exec('adb -s ' + target + ' shell input keyevent 82');
|
||||
}).then(function() {
|
||||
// launch the application
|
||||
|
||||
// set the resolved target
|
||||
}).then(function (resolvedTarget) {
|
||||
target = resolvedTarget;
|
||||
|
||||
// install the app
|
||||
}).then(function () {
|
||||
|
||||
var apk_path = build.findBestApkForArchitecture(buildResults, target.arch);
|
||||
var execOptions = {
|
||||
timeout: INSTALL_COMMAND_TIMEOUT, // in milliseconds
|
||||
killSignal: EXEC_KILL_SIGNAL
|
||||
};
|
||||
|
||||
console.log('Installing app on emulator...');
|
||||
console.log('Using apk: ' + apk_path);
|
||||
|
||||
var retriedInstall = retry.retryPromise(
|
||||
NUM_INSTALL_RETRIES,
|
||||
exec, 'adb -s ' + target.target + ' install -r -d "' + apk_path + '"', os.tmpdir(), execOptions
|
||||
);
|
||||
|
||||
return retriedInstall.then(function (output) {
|
||||
if (output.match(/Failure/)) {
|
||||
return Q.reject('Failed to install apk to emulator: ' + output);
|
||||
} else {
|
||||
console.log('INSTALL SUCCESS');
|
||||
}
|
||||
}, function (err) {
|
||||
return Q.reject('Failed to install apk to emulator: ' + err);
|
||||
});
|
||||
|
||||
// unlock screen
|
||||
}).then(function () {
|
||||
|
||||
console.log('Unlocking screen...');
|
||||
return exec('adb -s ' + target.target + ' shell input keyevent 82', os.tmpdir());
|
||||
|
||||
// launch the application
|
||||
}).then(function () {
|
||||
|
||||
console.log('Launching application...');
|
||||
var launchName = appinfo.getActivityName();
|
||||
cmd = 'adb -s ' + target + ' shell am start -W -a android.intent.action.MAIN -n ' + launchName;
|
||||
return exec(cmd);
|
||||
}).then(function(output) {
|
||||
var cmd = 'adb -s ' + target.target + ' shell am start -W -a android.intent.action.MAIN -n ' + launchName;
|
||||
return exec(cmd, os.tmpdir());
|
||||
|
||||
// report success or failure
|
||||
}).then(function (output) {
|
||||
console.log('LAUNCH SUCCESS');
|
||||
}, function(err) {
|
||||
}, function (err) {
|
||||
return Q.reject('Failed to launch app on emulator: ' + err);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
43
bin/templates/cordova/lib/exec.js
vendored
43
bin/templates/cordova/lib/exec.js
vendored
@@ -19,23 +19,50 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
var child_process = require('child_process'),
|
||||
Q = require('q');
|
||||
var child_process = require("child_process");
|
||||
var Q = require("q");
|
||||
|
||||
// constants
|
||||
var DEFAULT_MAX_BUFFER = 1024000;
|
||||
|
||||
// Takes a command and optional current working directory.
|
||||
// Returns a promise that either resolves with the stdout, or
|
||||
// rejects with an error message and the stderr.
|
||||
module.exports = function(cmd, opt_cwd) {
|
||||
//
|
||||
// WARNING:
|
||||
// opt_cwd is an artifact of an old design, and must
|
||||
// be removed in the future; the correct solution is
|
||||
// to pass the options object the same way that
|
||||
// child_process.exec expects
|
||||
//
|
||||
// NOTE:
|
||||
// exec documented here - https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback
|
||||
module.exports = function(cmd, opt_cwd, options) {
|
||||
|
||||
var d = Q.defer();
|
||||
|
||||
if (typeof options === "undefined") {
|
||||
options = {};
|
||||
}
|
||||
|
||||
// override cwd to preserve old opt_cwd behavior
|
||||
options.cwd = opt_cwd;
|
||||
|
||||
// set maxBuffer
|
||||
if (typeof options.maxBuffer === "undefined") {
|
||||
options.maxBuffer = DEFAULT_MAX_BUFFER;
|
||||
}
|
||||
|
||||
try {
|
||||
child_process.exec(cmd, {cwd: opt_cwd, maxBuffer: 1024000}, function(err, stdout, stderr) {
|
||||
if (err) d.reject('Error executing "' + cmd + '": ' + stderr);
|
||||
child_process.exec(cmd, options, function(err, stdout, stderr) {
|
||||
if (err) d.reject("Error executing \"" + cmd + "\": " + stderr);
|
||||
else d.resolve(stdout);
|
||||
});
|
||||
} catch(e) {
|
||||
console.error('error caught: ' + e);
|
||||
console.error("error caught: " + e);
|
||||
d.reject(e);
|
||||
}
|
||||
return d.promise;
|
||||
}
|
||||
|
||||
return d.promise;
|
||||
};
|
||||
|
||||
|
||||
11
bin/templates/cordova/lib/log.js
vendored
11
bin/templates/cordova/lib/log.js
vendored
@@ -19,8 +19,8 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
var shell = require('shelljs'),
|
||||
path = require('path'),
|
||||
var path = require('path'),
|
||||
os = require('os'),
|
||||
Q = require('q'),
|
||||
child_process = require('child_process'),
|
||||
ROOT = path.join(__dirname, '..', '..');
|
||||
@@ -30,9 +30,8 @@ var shell = require('shelljs'),
|
||||
* Returns a promise.
|
||||
*/
|
||||
module.exports.run = function() {
|
||||
var cmd = 'adb logcat | grep -v nativeGetEnabledTags';
|
||||
var d = Q.defer();
|
||||
var adb = child_process.spawn('adb', ['logcat']);
|
||||
var adb = child_process.spawn('adb', ['logcat'], {cwd: os.tmpdir()});
|
||||
|
||||
adb.stdout.on('data', function(data) {
|
||||
var lines = data ? data.toString().split('\n') : [];
|
||||
@@ -48,10 +47,10 @@ module.exports.run = function() {
|
||||
});
|
||||
|
||||
return d.promise;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.help = function() {
|
||||
console.log('Usage: ' + path.relative(process.cwd(), path.join(ROOT, 'cordova', 'log')));
|
||||
console.log('Gives the logcat output on the command line.');
|
||||
process.exit(0);
|
||||
}
|
||||
};
|
||||
|
||||
79
bin/templates/cordova/lib/plugin-build.gradle
Normal file
79
bin/templates/cordova/lib/plugin-build.gradle
Normal file
@@ -0,0 +1,79 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
// GENERATED FILE! DO NOT EDIT!
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
// Switch the Android Gradle plugin version requirement depending on the
|
||||
// installed version of Gradle. This dependency is documented at
|
||||
// http://tools.android.com/tech-docs/new-build-system/version-compatibility
|
||||
// and https://issues.apache.org/jira/browse/CB-8143
|
||||
if (gradle.gradleVersion >= "2.2") {
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:1.0.0+'
|
||||
}
|
||||
} else if (gradle.gradleVersion >= "2.1") {
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:0.14.0+'
|
||||
}
|
||||
} else {
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:0.12.0+'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'android-library'
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: '*.jar')
|
||||
debugCompile project(path: ":CordovaLib", configuration: "debug")
|
||||
releaseCompile project(path: ":CordovaLib", configuration: "release")
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion cdvCompileSdkVersion
|
||||
buildToolsVersion cdvBuildToolsVersion
|
||||
publishNonDefault true
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_6
|
||||
targetCompatibility JavaVersion.VERSION_1_6
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
java.srcDirs = ['src']
|
||||
resources.srcDirs = ['src']
|
||||
aidl.srcDirs = ['src']
|
||||
renderscript.srcDirs = ['src']
|
||||
res.srcDirs = ['res']
|
||||
assets.srcDirs = ['assets']
|
||||
jniLibs.srcDirs = ['libs']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (file('build-extras.gradle').exists()) {
|
||||
apply from: 'build-extras.gradle'
|
||||
}
|
||||
66
bin/templates/cordova/lib/retry.js
vendored
Normal file
66
bin/templates/cordova/lib/retry.js
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
/* jshint node: true */
|
||||
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
* Retry a promise-returning function a number of times, propagating its
|
||||
* results on success or throwing its error on a failed final attempt.
|
||||
*
|
||||
* @arg {Number} attemts_left - The number of times to retry the passed call.
|
||||
* @arg {Function} promiseFunction - A function that returns a promise.
|
||||
* @arg {...} - Arguments to pass to promiseFunction.
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
module.exports.retryPromise = function (attemts_left, promiseFunction) {
|
||||
|
||||
// NOTE:
|
||||
// get all trailing arguments, by skipping the first two (attemts_left and
|
||||
// promiseFunction) because they shouldn't get passed to promiseFunction
|
||||
var promiseFunctionArguments = Array.prototype.slice.call(arguments, 2);
|
||||
|
||||
return promiseFunction.apply(undefined, promiseFunctionArguments).then(
|
||||
|
||||
// on success pass results through
|
||||
function onFulfilled(value) {
|
||||
return value;
|
||||
},
|
||||
|
||||
// on rejection either retry, or throw the error
|
||||
function onRejected(error) {
|
||||
|
||||
attemts_left -= 1;
|
||||
|
||||
if (attemts_left < 1) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
console.log("A retried call failed. Retrying " + attemts_left + " more time(s).");
|
||||
|
||||
// retry call self again with the same arguments, except attemts_left is now lower
|
||||
var fullArguments = [attemts_left, promiseFunction].concat(promiseFunctionArguments);
|
||||
return module.exports.retryPromise.apply(undefined, fullArguments);
|
||||
}
|
||||
);
|
||||
};
|
||||
165
bin/templates/cordova/lib/run.js
vendored
165
bin/templates/cordova/lib/run.js
vendored
@@ -19,114 +19,135 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
/* jshint loopfunc:true */
|
||||
|
||||
var path = require('path'),
|
||||
build = require('./build'),
|
||||
emulator = require('./emulator'),
|
||||
device = require('./device'),
|
||||
shell = require('shelljs'),
|
||||
Q = require('q');
|
||||
|
||||
/*
|
||||
* Runs the application on a device if available.
|
||||
* If not device is found, it will use a started emulator.
|
||||
* If no device is found, it will use a started emulator.
|
||||
* If no started emulators are found it will attempt to start an avd.
|
||||
* If no avds are found it will error out.
|
||||
* Returns a promise.
|
||||
*/
|
||||
module.exports.run = function(args) {
|
||||
var build_type;
|
||||
var buildFlags = [];
|
||||
var install_target;
|
||||
var list = false;
|
||||
|
||||
for (var i=2; i<args.length; i++) {
|
||||
if (args[i] == '--debug') {
|
||||
build_type = '--debug';
|
||||
} else if (args[i] == '--release') {
|
||||
build_type = '--release';
|
||||
} else if (args[i] == '--nobuild') {
|
||||
build_type = '--nobuild';
|
||||
if (build.isBuildFlag(args[i])) {
|
||||
buildFlags.push(args[i]);
|
||||
} else if (args[i] == '--device') {
|
||||
install_target = '--device';
|
||||
} else if (args[i] == '--emulator') {
|
||||
install_target = '--emulator';
|
||||
} else if (args[i].substring(0, 9) == '--target=') {
|
||||
} else if (/^--target=/.exec(args[i])) {
|
||||
install_target = args[i].substring(9, args[i].length);
|
||||
} else if (args[i] == '--list') {
|
||||
list = true;
|
||||
} else {
|
||||
console.error('ERROR : Run option \'' + args[i] + '\' not recognized.');
|
||||
process.exit(2);
|
||||
console.warn('Option \'' + args[i] + '\' not recognized (ignoring).');
|
||||
}
|
||||
}
|
||||
|
||||
return build.run(build_type).then(function() {
|
||||
if (install_target == '--device') {
|
||||
return device.install();
|
||||
if (list) {
|
||||
var output = '';
|
||||
var temp = '';
|
||||
if (!install_target) {
|
||||
output += 'Available Android Devices:\n';
|
||||
temp = shell.exec(path.join(__dirname, 'list-devices'), {silent:true}).output;
|
||||
temp = temp.replace(/^(?=[^\s])/gm, '\t');
|
||||
output += temp;
|
||||
output += 'Available Android Virtual Devices:\n';
|
||||
temp = shell.exec(path.join(__dirname, 'list-emulator-images'), {silent:true}).output;
|
||||
temp = temp.replace(/^(?=[^\s])/gm, '\t');
|
||||
output += temp;
|
||||
} else if (install_target == '--emulator') {
|
||||
return emulator.list_started().then(function(started) {
|
||||
var p = started && started.length > 0 ? Q() : emulator.start();
|
||||
return p.then(function() { emulator.install(); });
|
||||
});
|
||||
} else if (install_target) {
|
||||
var devices, started_emulators, avds;
|
||||
return device.list()
|
||||
.then(function(res) {
|
||||
devices = res;
|
||||
return emulator.list_started();
|
||||
}).then(function(res) {
|
||||
started_emulators = res;
|
||||
return emulator.list_images();
|
||||
}).then(function(res) {
|
||||
avds = res;
|
||||
if (devices.indexOf(install_target) > -1) {
|
||||
return device.install(install_target);
|
||||
} else if (started_emulators.indexOf(install_target) > -1) {
|
||||
return emulator.install(install_target);
|
||||
} else {
|
||||
// if target emulator isn't started, then start it.
|
||||
var emulator_ID;
|
||||
for(avd in avds) {
|
||||
if(avds[avd].name == install_target) {
|
||||
return emulator.start(install_target)
|
||||
.then(function() { emulator.install(emulator_ID); });
|
||||
}
|
||||
}
|
||||
return Q.reject('Target \'' + install_target + '\' not found, unable to run project');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
output += 'Available Android Virtual Devices:\n';
|
||||
temp = shell.exec(path.join(__dirname, 'list-emulator-images'), {silent:true}).output;
|
||||
temp = temp.replace(/^(?=[^\s])/gm, '\t');
|
||||
output += temp;
|
||||
} else if (install_target == '--device') {
|
||||
output += 'Available Android Devices:\n';
|
||||
temp = shell.exec(path.join(__dirname, 'list-devices'), {silent:true}).output;
|
||||
temp = temp.replace(/^(?=[^\s])/gm, '\t');
|
||||
output += temp;
|
||||
}
|
||||
console.log(output);
|
||||
return;
|
||||
}
|
||||
|
||||
return Q()
|
||||
.then(function() {
|
||||
if (!install_target) {
|
||||
// no target given, deploy to device if available, otherwise use the emulator.
|
||||
return device.list()
|
||||
.then(function(device_list) {
|
||||
if (device_list.length > 0) {
|
||||
console.log('WARNING : No target specified, deploying to device \'' + device_list[0] + '\'.');
|
||||
return device.install(device_list[0]);
|
||||
install_target = device_list[0];
|
||||
} else {
|
||||
return emulator.list_started()
|
||||
.then(function(emulator_list) {
|
||||
if (emulator_list.length > 0) {
|
||||
console.log('WARNING : No target specified, deploying to emulator \'' + emulator_list[0] + '\'.');
|
||||
return emulator.install(emulator_list[0]);
|
||||
} else {
|
||||
console.log('WARNING : No started emulators found, starting an emulator.');
|
||||
return emulator.best_image()
|
||||
.then(function(best_avd) {
|
||||
if(best_avd) {
|
||||
return emulator.start(best_avd.name)
|
||||
.then(function(emulator_ID) {
|
||||
console.log('WARNING : No target specified, deploying to emulator \'' + emulator_ID + '\'.');
|
||||
return emulator.install(emulator_ID);
|
||||
});
|
||||
} else {
|
||||
return emulator.start();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
console.log('WARNING : No target specified, deploying to emulator');
|
||||
install_target = '--emulator';
|
||||
}
|
||||
});
|
||||
}
|
||||
}).then(function() {
|
||||
if (install_target == '--device') {
|
||||
return device.resolveTarget(null);
|
||||
} else if (install_target == '--emulator') {
|
||||
// Give preference to any already started emulators. Else, start one.
|
||||
return emulator.list_started()
|
||||
.then(function(started) {
|
||||
return started && started.length > 0 ? started[0] : emulator.start();
|
||||
}).then(function(emulatorId) {
|
||||
return emulator.resolveTarget(emulatorId);
|
||||
});
|
||||
}
|
||||
// They specified a specific device/emulator ID.
|
||||
return device.list()
|
||||
.then(function(devices) {
|
||||
if (devices.indexOf(install_target) > -1) {
|
||||
return device.resolveTarget(install_target);
|
||||
}
|
||||
return emulator.list_started()
|
||||
.then(function(started_emulators) {
|
||||
if (started_emulators.indexOf(install_target) > -1) {
|
||||
return emulator.resolveTarget(install_target);
|
||||
}
|
||||
return emulator.list_images()
|
||||
.then(function(avds) {
|
||||
// if target emulator isn't started, then start it.
|
||||
for (var avd in avds) {
|
||||
if (avds[avd].name == install_target) {
|
||||
return emulator.start(install_target)
|
||||
.then(function(emulatorId) {
|
||||
return emulator.resolveTarget(emulatorId);
|
||||
});
|
||||
}
|
||||
}
|
||||
return Q.reject('Target \'' + install_target + '\' not found, unable to run project');
|
||||
});
|
||||
});
|
||||
});
|
||||
}).then(function(resolvedTarget) {
|
||||
return build.run(buildFlags, resolvedTarget).then(function(buildResults) {
|
||||
if (resolvedTarget.isEmulator) {
|
||||
return emulator.install(resolvedTarget, buildResults);
|
||||
}
|
||||
return device.install(resolvedTarget, buildResults);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.help = function() {
|
||||
console.log('Usage: ' + path.relative(process.cwd(), args[0]) + ' [options]');
|
||||
module.exports.help = function(args) {
|
||||
console.log('Usage: ' + path.relative(process.cwd(), args[1]) + ' [options]');
|
||||
console.log('Build options :');
|
||||
console.log(' --debug : Builds project in debug mode');
|
||||
console.log(' --release : Builds project in release mode');
|
||||
@@ -136,4 +157,4 @@ module.exports.help = function() {
|
||||
console.log(' --emulator : Will deploy the built project to an emulator if one exists');
|
||||
console.log(' --target=<target_id> : Installs to the target with the specified id.');
|
||||
process.exit(0);
|
||||
}
|
||||
};
|
||||
|
||||
11
bin/templates/cordova/lib/spawn.js
vendored
11
bin/templates/cordova/lib/spawn.js
vendored
@@ -26,13 +26,15 @@ var isWindows = process.platform.slice(0, 3) == 'win';
|
||||
// Takes a command and optional current working directory.
|
||||
module.exports = function(cmd, args, opt_cwd) {
|
||||
var d = Q.defer();
|
||||
var opts = { cwd: opt_cwd, stdio: 'inherit' };
|
||||
try {
|
||||
// Work around spawn not being able to find .bat files.
|
||||
if (isWindows) {
|
||||
args.unshift('/s', '/c', cmd);
|
||||
cmd = 'cmd';
|
||||
args = [['/s', '/c', '"' + [cmd].concat(args).map(function(a){if (/^[^"].* .*[^"]/.test(a)) return '"' + a + '"'; return a;}).join(' ')+'"'].join(' ')];
|
||||
cmd = 'cmd';
|
||||
opts.windowsVerbatimArguments = true;
|
||||
}
|
||||
var child = child_process.spawn(cmd, args, {cwd: opt_cwd, stdio: 'inherit'});
|
||||
var child = child_process.spawn(cmd, args, opts);
|
||||
child.on('exit', function(code) {
|
||||
if (code) {
|
||||
d.reject('Error code ' + code + ' for command: ' + cmd + ' with args: ' + args);
|
||||
@@ -45,5 +47,4 @@ module.exports = function(cmd, args, opt_cwd) {
|
||||
d.reject(e);
|
||||
}
|
||||
return d.promise;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -26,7 +26,7 @@ var run = require('./lib/run'),
|
||||
// Support basic help commands
|
||||
if (args[2] == '--help' || args[2] == '/?' || args[2] == '-h' ||
|
||||
args[2] == 'help' || args[2] == '-help' || args[2] == '/help') {
|
||||
run.help();
|
||||
run.help(args);
|
||||
} else {
|
||||
reqs.run().done(function() {
|
||||
return run.run(args);
|
||||
|
||||
@@ -20,6 +20,6 @@
|
||||
*/
|
||||
|
||||
// Coho updates this line:
|
||||
var VERSION = "3.5.0-dev";
|
||||
var VERSION = "4.1.1";
|
||||
|
||||
console.log(VERSION);
|
||||
|
||||
@@ -22,16 +22,13 @@ package __ID__;
|
||||
import android.os.Bundle;
|
||||
import org.apache.cordova.*;
|
||||
|
||||
public class __ACTIVITY__ extends CordovaActivity
|
||||
public class __ACTIVITY__ extends CordovaActivity
|
||||
{
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
super.init();
|
||||
// Set by <content src="index.html" /> in config.xml
|
||||
super.loadUrl(Config.getStartUrl());
|
||||
//super.loadUrl("file:///android_asset/www/index.html");
|
||||
loadUrl(launchUrl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:windowSoftInputMode="adjustPan"
|
||||
package="__PACKAGE__" android:versionName="1.0" android:versionCode="1" android:hardwareAccelerated="true">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="__PACKAGE__" android:versionName="1.0" android:versionCode="1" android:hardwareAccelerated="true">
|
||||
<supports-screens
|
||||
android:largeScreens="true"
|
||||
android:normalScreens="true"
|
||||
@@ -33,16 +33,19 @@
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
|
||||
<application android:icon="@drawable/icon" android:label="@string/app_name"
|
||||
android:hardwareAccelerated="true">
|
||||
<activity android:name="__ACTIVITY__" android:label="@string/app_name" android:launchMode="singleTop"
|
||||
android:hardwareAccelerated="true" android:supportsRtl="true">
|
||||
<activity android:name="__ACTIVITY__"
|
||||
android:label="@string/activity_name"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@android:style/Theme.Black.NoTitleBar"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale">
|
||||
<intent-filter>
|
||||
<intent-filter android:label="@string/launcher_name">
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
<uses-sdk android:minSdkVersion="10" android:targetSdkVersion="__APILEVEL__"/>
|
||||
</manifest>
|
||||
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="__APILEVEL__"/>
|
||||
</manifest>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Platform: android
|
||||
// 3.5.0-dev-81f9a00
|
||||
// 2c29e187e4206a6a77fba940ef6f77aef5c7eb8c
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
@@ -19,7 +19,7 @@
|
||||
under the License.
|
||||
*/
|
||||
;(function() {
|
||||
var CORDOVA_JS_BUILD_LABEL = '3.5.0-dev-81f9a00';
|
||||
var PLATFORM_VERSION_BUILD_LABEL = '4.1.1';
|
||||
// file: src/scripts/require.js
|
||||
|
||||
/*jshint -W079 */
|
||||
@@ -101,10 +101,15 @@ if (typeof module === "object" && typeof require === "function") {
|
||||
// file: src/cordova.js
|
||||
define("cordova", function(require, exports, module) {
|
||||
|
||||
if(window.cordova){
|
||||
throw new Error("cordova already defined");
|
||||
}
|
||||
|
||||
|
||||
var channel = require('cordova/channel');
|
||||
var platform = require('cordova/platform');
|
||||
|
||||
|
||||
/**
|
||||
* Intercept calls to addEventListener + removeEventListener and handle deviceready,
|
||||
* resume, and pause events.
|
||||
@@ -175,7 +180,8 @@ function createEvent(type, data) {
|
||||
var cordova = {
|
||||
define:define,
|
||||
require:require,
|
||||
version:CORDOVA_JS_BUILD_LABEL,
|
||||
version:PLATFORM_VERSION_BUILD_LABEL,
|
||||
platformVersion:PLATFORM_VERSION_BUILD_LABEL,
|
||||
platformId:platform.id,
|
||||
/**
|
||||
* Methods to add/remove your own addEventListener hijacking on document + window.
|
||||
@@ -262,11 +268,7 @@ var cordova = {
|
||||
* Called by native code when returning successful result from an action.
|
||||
*/
|
||||
callbackSuccess: function(callbackId, args) {
|
||||
try {
|
||||
cordova.callbackFromNative(callbackId, true, args.status, [args.message], args.keepCallback);
|
||||
} catch (e) {
|
||||
console.log("Error in error callback: " + callbackId + " = "+e);
|
||||
}
|
||||
cordova.callbackFromNative(callbackId, true, args.status, [args.message], args.keepCallback);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -275,30 +277,40 @@ var cordova = {
|
||||
callbackError: function(callbackId, args) {
|
||||
// TODO: Deprecate callbackSuccess and callbackError in favour of callbackFromNative.
|
||||
// Derive success from status.
|
||||
try {
|
||||
cordova.callbackFromNative(callbackId, false, args.status, [args.message], args.keepCallback);
|
||||
} catch (e) {
|
||||
console.log("Error in error callback: " + callbackId + " = "+e);
|
||||
}
|
||||
cordova.callbackFromNative(callbackId, false, args.status, [args.message], args.keepCallback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called by native code when returning the result from an action.
|
||||
*/
|
||||
callbackFromNative: function(callbackId, success, status, args, keepCallback) {
|
||||
var callback = cordova.callbacks[callbackId];
|
||||
if (callback) {
|
||||
if (success && status == cordova.callbackStatus.OK) {
|
||||
callback.success && callback.success.apply(null, args);
|
||||
} else if (!success) {
|
||||
callback.fail && callback.fail.apply(null, args);
|
||||
}
|
||||
|
||||
// Clear callback if not expecting any more results
|
||||
if (!keepCallback) {
|
||||
delete cordova.callbacks[callbackId];
|
||||
callbackFromNative: function(callbackId, isSuccess, status, args, keepCallback) {
|
||||
try {
|
||||
var callback = cordova.callbacks[callbackId];
|
||||
if (callback) {
|
||||
if (isSuccess && status == cordova.callbackStatus.OK) {
|
||||
callback.success && callback.success.apply(null, args);
|
||||
} else if (!isSuccess) {
|
||||
callback.fail && callback.fail.apply(null, args);
|
||||
}
|
||||
/*
|
||||
else
|
||||
Note, this case is intentionally not caught.
|
||||
this can happen if isSuccess is true, but callbackStatus is NO_RESULT
|
||||
which is used to remove a callback from the list without calling the callbacks
|
||||
typically keepCallback is false in this case
|
||||
*/
|
||||
// Clear callback if not expecting any more results
|
||||
if (!keepCallback) {
|
||||
delete cordova.callbacks[callbackId];
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
var msg = "Error in " + (isSuccess ? "Success" : "Error") + " callbackId: " + callbackId + " : " + err;
|
||||
console && console.log && console.log(msg);
|
||||
cordova.fireWindowEvent("cordovacallbackerror", { 'message': msg });
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
addConstructor: function(func) {
|
||||
channel.onCordovaReady.subscribe(function() {
|
||||
@@ -316,7 +328,7 @@ module.exports = cordova;
|
||||
|
||||
});
|
||||
|
||||
// file: src/android/android/nativeapiprovider.js
|
||||
// file: /Users/steveng/repo/cordova/cordova-android/cordova-js-src/android/nativeapiprovider.js
|
||||
define("cordova/android/nativeapiprovider", function(require, exports, module) {
|
||||
|
||||
/**
|
||||
@@ -339,23 +351,23 @@ module.exports = {
|
||||
|
||||
});
|
||||
|
||||
// file: src/android/android/promptbasednativeapi.js
|
||||
// file: /Users/steveng/repo/cordova/cordova-android/cordova-js-src/android/promptbasednativeapi.js
|
||||
define("cordova/android/promptbasednativeapi", function(require, exports, module) {
|
||||
|
||||
/**
|
||||
* Implements the API of ExposedJsApi.java, but uses prompt() to communicate.
|
||||
* This is used only on the 2.3 simulator, where addJavascriptInterface() is broken.
|
||||
* This is used pre-JellyBean, where addJavascriptInterface() is disabled.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
exec: function(service, action, callbackId, argsJson) {
|
||||
return prompt(argsJson, 'gap:'+JSON.stringify([service, action, callbackId]));
|
||||
exec: function(bridgeSecret, service, action, callbackId, argsJson) {
|
||||
return prompt(argsJson, 'gap:'+JSON.stringify([bridgeSecret, service, action, callbackId]));
|
||||
},
|
||||
setNativeToJsBridgeMode: function(value) {
|
||||
prompt(value, 'gap_bridge_mode:');
|
||||
setNativeToJsBridgeMode: function(bridgeSecret, value) {
|
||||
prompt(value, 'gap_bridge_mode:' + bridgeSecret);
|
||||
},
|
||||
retrieveJsMessages: function(fromOnlineEvent) {
|
||||
return prompt(+fromOnlineEvent, 'gap_poll:');
|
||||
retrieveJsMessages: function(bridgeSecret, fromOnlineEvent) {
|
||||
return prompt(+fromOnlineEvent, 'gap_poll:' + bridgeSecret);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -364,7 +376,6 @@ module.exports = {
|
||||
// file: src/common/argscheck.js
|
||||
define("cordova/argscheck", function(require, exports, module) {
|
||||
|
||||
var exec = require('cordova/exec');
|
||||
var utils = require('cordova/utils');
|
||||
|
||||
var moduleExports = module.exports;
|
||||
@@ -508,9 +519,14 @@ function each(objects, func, context) {
|
||||
|
||||
function clobber(obj, key, value) {
|
||||
exports.replaceHookForTesting(obj, key);
|
||||
obj[key] = value;
|
||||
var needsProperty = false;
|
||||
try {
|
||||
obj[key] = value;
|
||||
} catch (e) {
|
||||
needsProperty = true;
|
||||
}
|
||||
// Getters can only be overridden by getters.
|
||||
if (obj[key] !== value) {
|
||||
if (needsProperty || obj[key] !== value) {
|
||||
utils.defineGetter(obj, key, function() {
|
||||
return value;
|
||||
});
|
||||
@@ -625,7 +641,6 @@ var utils = require('cordova/utils'),
|
||||
* onDeviceReady* User event fired to indicate that Cordova is ready
|
||||
* onResume User event fired to indicate a start/resume lifecycle event
|
||||
* onPause User event fired to indicate a pause lifecycle event
|
||||
* onDestroy* Internal event fired when app is being destroyed (User should use window.onunload event, not this one).
|
||||
*
|
||||
* The events marked with an * are sticky. Once they have fired, they will stay in the fired state.
|
||||
* All listeners that subscribe after the event is fired will be executed right away.
|
||||
@@ -825,6 +840,7 @@ channel.createSticky('onNativeReady');
|
||||
channel.createSticky('onCordovaReady');
|
||||
|
||||
// Event to indicate that all automatically loaded JS plugins are loaded and ready.
|
||||
// FIXME remove this
|
||||
channel.createSticky('onPluginsReady');
|
||||
|
||||
// Event to indicate that Cordova is ready
|
||||
@@ -836,9 +852,6 @@ channel.create('onResume');
|
||||
// Event to indicate a pause lifecycle event
|
||||
channel.create('onPause');
|
||||
|
||||
// Event to indicate a destroy lifecycle event
|
||||
channel.createSticky('onDestroy');
|
||||
|
||||
// Channels that must fire before "deviceready" is fired.
|
||||
channel.waitForInitialization('onCordovaReady');
|
||||
channel.waitForInitialization('onDOMContentLoaded');
|
||||
@@ -847,7 +860,7 @@ module.exports = channel;
|
||||
|
||||
});
|
||||
|
||||
// file: src/android/exec.js
|
||||
// file: /Users/steveng/repo/cordova/cordova-android/cordova-js-src/exec.js
|
||||
define("cordova/exec", function(require, exports, module) {
|
||||
|
||||
/**
|
||||
@@ -868,13 +881,10 @@ var cordova = require('cordova'),
|
||||
nativeApiProvider = require('cordova/android/nativeapiprovider'),
|
||||
utils = require('cordova/utils'),
|
||||
base64 = require('cordova/base64'),
|
||||
channel = require('cordova/channel'),
|
||||
jsToNativeModes = {
|
||||
PROMPT: 0,
|
||||
JS_OBJECT: 1,
|
||||
// This mode is currently for benchmarking purposes only. It must be enabled
|
||||
// on the native side through the ENABLE_LOCATION_CHANGE_EXEC_MODE
|
||||
// constant within CordovaWebViewClient.java before it will work.
|
||||
LOCATION_CHANGE: 2
|
||||
JS_OBJECT: 1
|
||||
},
|
||||
nativeToJsModes = {
|
||||
// Polls for messages using the JS->Native bridge.
|
||||
@@ -885,18 +895,26 @@ var cordova = require('cordova'),
|
||||
// For the ONLINE_EVENT to be viable, it would need to intercept all event
|
||||
// listeners (both through addEventListener and window.ononline) as well
|
||||
// as set the navigator property itself.
|
||||
ONLINE_EVENT: 2,
|
||||
// Uses reflection to access private APIs of the WebView that can send JS
|
||||
// to be executed.
|
||||
// Requires Android 3.2.4 or above.
|
||||
PRIVATE_API: 3
|
||||
ONLINE_EVENT: 2
|
||||
},
|
||||
jsToNativeBridgeMode, // Set lazily.
|
||||
nativeToJsBridgeMode = nativeToJsModes.ONLINE_EVENT,
|
||||
pollEnabled = false,
|
||||
messagesFromNative = [];
|
||||
bridgeSecret = -1;
|
||||
|
||||
var messagesFromNative = [];
|
||||
var isProcessing = false;
|
||||
var resolvedPromise = typeof Promise == 'undefined' ? null : Promise.resolve();
|
||||
var nextTick = resolvedPromise ? function(fn) { resolvedPromise.then(fn); } : function(fn) { setTimeout(fn); };
|
||||
|
||||
function androidExec(success, fail, service, action, args) {
|
||||
if (bridgeSecret < 0) {
|
||||
// If we ever catch this firing, we'll need to queue up exec()s
|
||||
// and fire them once we get a secret. For now, I don't think
|
||||
// it's possible for exec() to be called since plugins are parsed but
|
||||
// not run until until after onNativeReady.
|
||||
throw new Error('exec() called without bridgeSecret');
|
||||
}
|
||||
// Set default bridge modes if they have not already been set.
|
||||
// By default, we use the failsafe, since addJavascriptInterface breaks too often
|
||||
if (jsToNativeBridgeMode === undefined) {
|
||||
@@ -917,30 +935,41 @@ function androidExec(success, fail, service, action, args) {
|
||||
cordova.callbacks[callbackId] = {success:success, fail:fail};
|
||||
}
|
||||
|
||||
if (jsToNativeBridgeMode == jsToNativeModes.LOCATION_CHANGE) {
|
||||
window.location = 'http://cdv_exec/' + service + '#' + action + '#' + callbackId + '#' + argsJson;
|
||||
} else {
|
||||
var messages = nativeApiProvider.get().exec(service, action, callbackId, argsJson);
|
||||
// If argsJson was received by Java as null, try again with the PROMPT bridge mode.
|
||||
// This happens in rare circumstances, such as when certain Unicode characters are passed over the bridge on a Galaxy S2. See CB-2666.
|
||||
if (jsToNativeBridgeMode == jsToNativeModes.JS_OBJECT && messages === "@Null arguments.") {
|
||||
androidExec.setJsToNativeBridgeMode(jsToNativeModes.PROMPT);
|
||||
androidExec(success, fail, service, action, args);
|
||||
androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
|
||||
return;
|
||||
} else {
|
||||
androidExec.processMessages(messages, true);
|
||||
}
|
||||
var msgs = nativeApiProvider.get().exec(bridgeSecret, service, action, callbackId, argsJson);
|
||||
// If argsJson was received by Java as null, try again with the PROMPT bridge mode.
|
||||
// This happens in rare circumstances, such as when certain Unicode characters are passed over the bridge on a Galaxy S2. See CB-2666.
|
||||
if (jsToNativeBridgeMode == jsToNativeModes.JS_OBJECT && msgs === "@Null arguments.") {
|
||||
androidExec.setJsToNativeBridgeMode(jsToNativeModes.PROMPT);
|
||||
androidExec(success, fail, service, action, args);
|
||||
androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
|
||||
} else if (msgs) {
|
||||
messagesFromNative.push(msgs);
|
||||
// Always process async to avoid exceptions messing up stack.
|
||||
nextTick(processMessages);
|
||||
}
|
||||
}
|
||||
|
||||
androidExec.init = function() {
|
||||
bridgeSecret = +prompt('', 'gap_init:' + nativeToJsBridgeMode);
|
||||
channel.onNativeReady.fire();
|
||||
};
|
||||
|
||||
function pollOnceFromOnlineEvent() {
|
||||
pollOnce(true);
|
||||
}
|
||||
|
||||
function pollOnce(opt_fromOnlineEvent) {
|
||||
var msg = nativeApiProvider.get().retrieveJsMessages(!!opt_fromOnlineEvent);
|
||||
androidExec.processMessages(msg);
|
||||
if (bridgeSecret < 0) {
|
||||
// This can happen when the NativeToJsMessageQueue resets the online state on page transitions.
|
||||
// We know there's nothing to retrieve, so no need to poll.
|
||||
return;
|
||||
}
|
||||
var msgs = nativeApiProvider.get().retrieveJsMessages(bridgeSecret, !!opt_fromOnlineEvent);
|
||||
if (msgs) {
|
||||
messagesFromNative.push(msgs);
|
||||
// Process sync since we know we're already top-of-stack.
|
||||
processMessages();
|
||||
}
|
||||
}
|
||||
|
||||
function pollingTimerFunc() {
|
||||
@@ -989,7 +1018,10 @@ androidExec.setNativeToJsBridgeMode = function(mode) {
|
||||
|
||||
nativeToJsBridgeMode = mode;
|
||||
// Tell the native side to switch modes.
|
||||
nativeApiProvider.get().setNativeToJsBridgeMode(mode);
|
||||
// Otherwise, it will be set by androidExec.init()
|
||||
if (bridgeSecret >= 0) {
|
||||
nativeApiProvider.get().setNativeToJsBridgeMode(bridgeSecret, mode);
|
||||
}
|
||||
|
||||
if (mode == nativeToJsModes.POLLING) {
|
||||
pollEnabled = true;
|
||||
@@ -997,87 +1029,84 @@ androidExec.setNativeToJsBridgeMode = function(mode) {
|
||||
}
|
||||
};
|
||||
|
||||
// Processes a single message, as encoded by NativeToJsMessageQueue.java.
|
||||
function processMessage(message) {
|
||||
try {
|
||||
var firstChar = message.charAt(0);
|
||||
if (firstChar == 'J') {
|
||||
eval(message.slice(1));
|
||||
} else if (firstChar == 'S' || firstChar == 'F') {
|
||||
var success = firstChar == 'S';
|
||||
var keepCallback = message.charAt(1) == '1';
|
||||
var spaceIdx = message.indexOf(' ', 2);
|
||||
var status = +message.slice(2, spaceIdx);
|
||||
var nextSpaceIdx = message.indexOf(' ', spaceIdx + 1);
|
||||
var callbackId = message.slice(spaceIdx + 1, nextSpaceIdx);
|
||||
var payloadKind = message.charAt(nextSpaceIdx + 1);
|
||||
var payload;
|
||||
if (payloadKind == 's') {
|
||||
payload = message.slice(nextSpaceIdx + 2);
|
||||
} else if (payloadKind == 't') {
|
||||
payload = true;
|
||||
} else if (payloadKind == 'f') {
|
||||
payload = false;
|
||||
} else if (payloadKind == 'N') {
|
||||
payload = null;
|
||||
} else if (payloadKind == 'n') {
|
||||
payload = +message.slice(nextSpaceIdx + 2);
|
||||
} else if (payloadKind == 'A') {
|
||||
var data = message.slice(nextSpaceIdx + 2);
|
||||
var bytes = window.atob(data);
|
||||
var arraybuffer = new Uint8Array(bytes.length);
|
||||
for (var i = 0; i < bytes.length; i++) {
|
||||
arraybuffer[i] = bytes.charCodeAt(i);
|
||||
}
|
||||
payload = arraybuffer.buffer;
|
||||
} else if (payloadKind == 'S') {
|
||||
payload = window.atob(message.slice(nextSpaceIdx + 2));
|
||||
} else {
|
||||
payload = JSON.parse(message.slice(nextSpaceIdx + 1));
|
||||
}
|
||||
cordova.callbackFromNative(callbackId, success, status, [payload], keepCallback);
|
||||
} else {
|
||||
console.log("processMessage failed: invalid message: " + JSON.stringify(message));
|
||||
function buildPayload(payload, message) {
|
||||
var payloadKind = message.charAt(0);
|
||||
if (payloadKind == 's') {
|
||||
payload.push(message.slice(1));
|
||||
} else if (payloadKind == 't') {
|
||||
payload.push(true);
|
||||
} else if (payloadKind == 'f') {
|
||||
payload.push(false);
|
||||
} else if (payloadKind == 'N') {
|
||||
payload.push(null);
|
||||
} else if (payloadKind == 'n') {
|
||||
payload.push(+message.slice(1));
|
||||
} else if (payloadKind == 'A') {
|
||||
var data = message.slice(1);
|
||||
payload.push(base64.toArrayBuffer(data));
|
||||
} else if (payloadKind == 'S') {
|
||||
payload.push(window.atob(message.slice(1)));
|
||||
} else if (payloadKind == 'M') {
|
||||
var multipartMessages = message.slice(1);
|
||||
while (multipartMessages !== "") {
|
||||
var spaceIdx = multipartMessages.indexOf(' ');
|
||||
var msgLen = +multipartMessages.slice(0, spaceIdx);
|
||||
var multipartMessage = multipartMessages.substr(spaceIdx + 1, msgLen);
|
||||
multipartMessages = multipartMessages.slice(spaceIdx + msgLen + 1);
|
||||
buildPayload(payload, multipartMessage);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("processMessage failed: Error: " + e);
|
||||
console.log("processMessage failed: Stack: " + e.stack);
|
||||
console.log("processMessage failed: Message: " + message);
|
||||
} else {
|
||||
payload.push(JSON.parse(message));
|
||||
}
|
||||
}
|
||||
|
||||
var isProcessing = false;
|
||||
|
||||
// This is called from the NativeToJsMessageQueue.java.
|
||||
androidExec.processMessages = function(messages, opt_useTimeout) {
|
||||
if (messages) {
|
||||
messagesFromNative.push(messages);
|
||||
// Processes a single message, as encoded by NativeToJsMessageQueue.java.
|
||||
function processMessage(message) {
|
||||
var firstChar = message.charAt(0);
|
||||
if (firstChar == 'J') {
|
||||
// This is deprecated on the .java side. It doesn't work with CSP enabled.
|
||||
eval(message.slice(1));
|
||||
} else if (firstChar == 'S' || firstChar == 'F') {
|
||||
var success = firstChar == 'S';
|
||||
var keepCallback = message.charAt(1) == '1';
|
||||
var spaceIdx = message.indexOf(' ', 2);
|
||||
var status = +message.slice(2, spaceIdx);
|
||||
var nextSpaceIdx = message.indexOf(' ', spaceIdx + 1);
|
||||
var callbackId = message.slice(spaceIdx + 1, nextSpaceIdx);
|
||||
var payloadMessage = message.slice(nextSpaceIdx + 1);
|
||||
var payload = [];
|
||||
buildPayload(payload, payloadMessage);
|
||||
cordova.callbackFromNative(callbackId, success, status, payload, keepCallback);
|
||||
} else {
|
||||
console.log("processMessage failed: invalid message: " + JSON.stringify(message));
|
||||
}
|
||||
}
|
||||
|
||||
function processMessages() {
|
||||
// Check for the reentrant case.
|
||||
if (isProcessing) {
|
||||
return;
|
||||
}
|
||||
if (opt_useTimeout) {
|
||||
window.setTimeout(androidExec.processMessages, 0);
|
||||
if (messagesFromNative.length === 0) {
|
||||
return;
|
||||
}
|
||||
isProcessing = true;
|
||||
try {
|
||||
// TODO: add setImmediate polyfill and process only one message at a time.
|
||||
while (messagesFromNative.length) {
|
||||
var msg = popMessageFromQueue();
|
||||
// The Java side can send a * message to indicate that it
|
||||
// still has messages waiting to be retrieved.
|
||||
if (msg == '*' && messagesFromNative.length === 0) {
|
||||
setTimeout(pollOnce, 0);
|
||||
return;
|
||||
}
|
||||
processMessage(msg);
|
||||
var msg = popMessageFromQueue();
|
||||
// The Java side can send a * message to indicate that it
|
||||
// still has messages waiting to be retrieved.
|
||||
if (msg == '*' && messagesFromNative.length === 0) {
|
||||
nextTick(pollOnce);
|
||||
return;
|
||||
}
|
||||
processMessage(msg);
|
||||
} finally {
|
||||
isProcessing = false;
|
||||
if (messagesFromNative.length > 0) {
|
||||
nextTick(processMessages);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function popMessageFromQueue() {
|
||||
var messageBatch = messagesFromNative.shift();
|
||||
@@ -1137,6 +1166,7 @@ var cordova = require('cordova');
|
||||
var modulemapper = require('cordova/modulemapper');
|
||||
var platform = require('cordova/platform');
|
||||
var pluginloader = require('cordova/pluginloader');
|
||||
var utils = require('cordova/utils');
|
||||
|
||||
var platformInitChannelsArray = [channel.onNativeReady, channel.onPluginsReady];
|
||||
|
||||
@@ -1169,10 +1199,18 @@ function replaceNavigator(origNavigator) {
|
||||
if (typeof origNavigator[key] == 'function') {
|
||||
newNavigator[key] = origNavigator[key].bind(origNavigator);
|
||||
}
|
||||
else {
|
||||
(function(k) {
|
||||
utils.defineGetterSetter(newNavigator,key,function() {
|
||||
return origNavigator[k];
|
||||
});
|
||||
})(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
return newNavigator;
|
||||
}
|
||||
|
||||
if (window.navigator) {
|
||||
window.navigator = replaceNavigator(window.navigator);
|
||||
}
|
||||
@@ -1191,6 +1229,7 @@ if (!window.console.warn) {
|
||||
// Register pause, resume and deviceready channels as events on document.
|
||||
channel.onPause = cordova.addDocumentEventHandler('pause');
|
||||
channel.onResume = cordova.addDocumentEventHandler('resume');
|
||||
channel.onActivated = cordova.addDocumentEventHandler('activated');
|
||||
channel.onDeviceReady = cordova.addStickyDocumentEventHandler('deviceready');
|
||||
|
||||
// Listen for DOMContentLoaded and notify our channel subscribers.
|
||||
@@ -1245,6 +1284,120 @@ channel.join(function() {
|
||||
}, platformInitChannelsArray);
|
||||
|
||||
|
||||
});
|
||||
|
||||
// file: src/common/init_b.js
|
||||
define("cordova/init_b", function(require, exports, module) {
|
||||
|
||||
var channel = require('cordova/channel');
|
||||
var cordova = require('cordova');
|
||||
var platform = require('cordova/platform');
|
||||
var utils = require('cordova/utils');
|
||||
|
||||
var platformInitChannelsArray = [channel.onDOMContentLoaded, channel.onNativeReady];
|
||||
|
||||
// setting exec
|
||||
cordova.exec = require('cordova/exec');
|
||||
|
||||
function logUnfiredChannels(arr) {
|
||||
for (var i = 0; i < arr.length; ++i) {
|
||||
if (arr[i].state != 2) {
|
||||
console.log('Channel not fired: ' + arr[i].type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.setTimeout(function() {
|
||||
if (channel.onDeviceReady.state != 2) {
|
||||
console.log('deviceready has not fired after 5 seconds.');
|
||||
logUnfiredChannels(platformInitChannelsArray);
|
||||
logUnfiredChannels(channel.deviceReadyChannelsArray);
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
// Replace navigator before any modules are required(), to ensure it happens as soon as possible.
|
||||
// We replace it so that properties that can't be clobbered can instead be overridden.
|
||||
function replaceNavigator(origNavigator) {
|
||||
var CordovaNavigator = function() {};
|
||||
CordovaNavigator.prototype = origNavigator;
|
||||
var newNavigator = new CordovaNavigator();
|
||||
// This work-around really only applies to new APIs that are newer than Function.bind.
|
||||
// Without it, APIs such as getGamepads() break.
|
||||
if (CordovaNavigator.bind) {
|
||||
for (var key in origNavigator) {
|
||||
if (typeof origNavigator[key] == 'function') {
|
||||
newNavigator[key] = origNavigator[key].bind(origNavigator);
|
||||
}
|
||||
else {
|
||||
(function(k) {
|
||||
utils.defineGetterSetter(newNavigator,key,function() {
|
||||
return origNavigator[k];
|
||||
});
|
||||
})(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
return newNavigator;
|
||||
}
|
||||
if (window.navigator) {
|
||||
window.navigator = replaceNavigator(window.navigator);
|
||||
}
|
||||
|
||||
if (!window.console) {
|
||||
window.console = {
|
||||
log: function(){}
|
||||
};
|
||||
}
|
||||
if (!window.console.warn) {
|
||||
window.console.warn = function(msg) {
|
||||
this.log("warn: " + msg);
|
||||
};
|
||||
}
|
||||
|
||||
// Register pause, resume and deviceready channels as events on document.
|
||||
channel.onPause = cordova.addDocumentEventHandler('pause');
|
||||
channel.onResume = cordova.addDocumentEventHandler('resume');
|
||||
channel.onActivated = cordova.addDocumentEventHandler('activated');
|
||||
channel.onDeviceReady = cordova.addStickyDocumentEventHandler('deviceready');
|
||||
|
||||
// Listen for DOMContentLoaded and notify our channel subscribers.
|
||||
if (document.readyState == 'complete' || document.readyState == 'interactive') {
|
||||
channel.onDOMContentLoaded.fire();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
channel.onDOMContentLoaded.fire();
|
||||
}, false);
|
||||
}
|
||||
|
||||
// _nativeReady is global variable that the native side can set
|
||||
// to signify that the native code is ready. It is a global since
|
||||
// it may be called before any cordova JS is ready.
|
||||
if (window._nativeReady) {
|
||||
channel.onNativeReady.fire();
|
||||
}
|
||||
|
||||
// Call the platform-specific initialization.
|
||||
platform.bootstrap && platform.bootstrap();
|
||||
|
||||
/**
|
||||
* Create all cordova objects once native side is ready.
|
||||
*/
|
||||
channel.join(function() {
|
||||
|
||||
platform.initialize && platform.initialize();
|
||||
|
||||
// Fire event to notify that all objects are created
|
||||
channel.onCordovaReady.fire();
|
||||
|
||||
// Fire onDeviceReady event once page has fully loaded, all
|
||||
// constructors have run and cordova info has been received from native
|
||||
// side.
|
||||
channel.join(function() {
|
||||
require('cordova').fireDocumentEvent('deviceready');
|
||||
}, channel.deviceReadyChannelsArray);
|
||||
|
||||
}, platformInitChannelsArray);
|
||||
|
||||
});
|
||||
|
||||
// file: src/common/modulemapper.js
|
||||
@@ -1348,7 +1501,7 @@ exports.reset();
|
||||
|
||||
});
|
||||
|
||||
// file: src/android/platform.js
|
||||
// file: /Users/steveng/repo/cordova/cordova-android/cordova-js-src/platform.js
|
||||
define("cordova/platform", function(require, exports, module) {
|
||||
|
||||
module.exports = {
|
||||
@@ -1359,47 +1512,83 @@ module.exports = {
|
||||
exec = require('cordova/exec'),
|
||||
modulemapper = require('cordova/modulemapper');
|
||||
|
||||
// Tell the native code that a page change has occurred.
|
||||
exec(null, null, 'PluginManager', 'startup', []);
|
||||
// Tell the JS that the native side is ready.
|
||||
channel.onNativeReady.fire();
|
||||
// Get the shared secret needed to use the bridge.
|
||||
exec.init();
|
||||
|
||||
// TODO: Extract this as a proper plugin.
|
||||
modulemapper.clobbers('cordova/plugin/android/app', 'navigator.app');
|
||||
|
||||
var APP_PLUGIN_NAME = Number(cordova.platformVersion.split('.')[0]) >= 4 ? 'CoreAndroid' : 'App';
|
||||
|
||||
// Inject a listener for the backbutton on the document.
|
||||
var backButtonChannel = cordova.addDocumentEventHandler('backbutton');
|
||||
backButtonChannel.onHasSubscribersChange = function() {
|
||||
// If we just attached the first handler or detached the last handler,
|
||||
// let native know we need to override the back button.
|
||||
exec(null, null, "App", "overrideBackbutton", [this.numHandlers == 1]);
|
||||
exec(null, null, APP_PLUGIN_NAME, "overrideBackbutton", [this.numHandlers == 1]);
|
||||
};
|
||||
|
||||
// Add hardware MENU and SEARCH button handlers
|
||||
cordova.addDocumentEventHandler('menubutton');
|
||||
cordova.addDocumentEventHandler('searchbutton');
|
||||
|
||||
function bindButtonChannel(buttonName) {
|
||||
// generic button bind used for volumeup/volumedown buttons
|
||||
var volumeButtonChannel = cordova.addDocumentEventHandler(buttonName + 'button');
|
||||
volumeButtonChannel.onHasSubscribersChange = function() {
|
||||
exec(null, null, APP_PLUGIN_NAME, "overrideButton", [buttonName, this.numHandlers == 1]);
|
||||
};
|
||||
}
|
||||
// Inject a listener for the volume buttons on the document.
|
||||
bindButtonChannel('volumeup');
|
||||
bindButtonChannel('volumedown');
|
||||
|
||||
// Let native code know we are all done on the JS side.
|
||||
// Native code will then un-hide the WebView.
|
||||
channel.onCordovaReady.subscribe(function() {
|
||||
exec(null, null, "App", "show", []);
|
||||
exec(onMessageFromNative, null, APP_PLUGIN_NAME, 'messageChannel', []);
|
||||
exec(null, null, APP_PLUGIN_NAME, "show", []);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function onMessageFromNative(msg) {
|
||||
var cordova = require('cordova');
|
||||
var action = msg.action;
|
||||
|
||||
switch (action)
|
||||
{
|
||||
// Button events
|
||||
case 'backbutton':
|
||||
case 'menubutton':
|
||||
case 'searchbutton':
|
||||
// App life cycle events
|
||||
case 'pause':
|
||||
case 'resume':
|
||||
// Volume events
|
||||
case 'volumedownbutton':
|
||||
case 'volumeupbutton':
|
||||
cordova.fireDocumentEvent(action);
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unknown event action ' + action);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// file: src/android/plugin/android/app.js
|
||||
// file: /Users/steveng/repo/cordova/cordova-android/cordova-js-src/plugin/android/app.js
|
||||
define("cordova/plugin/android/app", function(require, exports, module) {
|
||||
|
||||
var exec = require('cordova/exec');
|
||||
var APP_PLUGIN_NAME = Number(require('cordova').platformVersion.split('.')[0]) >= 4 ? 'CoreAndroid' : 'App';
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Clear the resource cache.
|
||||
*/
|
||||
clearCache:function() {
|
||||
exec(null, null, "App", "clearCache", []);
|
||||
exec(null, null, APP_PLUGIN_NAME, "clearCache", []);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1417,14 +1606,14 @@ module.exports = {
|
||||
* navigator.app.loadUrl("http://server/myapp/index.html", {wait:2000, loadingDialog:"Wait,Loading App", loadUrlTimeoutValue: 60000});
|
||||
*/
|
||||
loadUrl:function(url, props) {
|
||||
exec(null, null, "App", "loadUrl", [url, props]);
|
||||
exec(null, null, APP_PLUGIN_NAME, "loadUrl", [url, props]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Cancel loadUrl that is waiting to be loaded.
|
||||
*/
|
||||
cancelLoadUrl:function() {
|
||||
exec(null, null, "App", "cancelLoadUrl", []);
|
||||
exec(null, null, APP_PLUGIN_NAME, "cancelLoadUrl", []);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1432,7 +1621,7 @@ module.exports = {
|
||||
* Instead of BACK button loading the previous web page, it will exit the app.
|
||||
*/
|
||||
clearHistory:function() {
|
||||
exec(null, null, "App", "clearHistory", []);
|
||||
exec(null, null, APP_PLUGIN_NAME, "clearHistory", []);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1440,7 +1629,7 @@ module.exports = {
|
||||
* This is the same as pressing the backbutton on Android device.
|
||||
*/
|
||||
backHistory:function() {
|
||||
exec(null, null, "App", "backHistory", []);
|
||||
exec(null, null, APP_PLUGIN_NAME, "backHistory", []);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1453,14 +1642,29 @@ module.exports = {
|
||||
* @param override T=override, F=cancel override
|
||||
*/
|
||||
overrideBackbutton:function(override) {
|
||||
exec(null, null, "App", "overrideBackbutton", [override]);
|
||||
exec(null, null, APP_PLUGIN_NAME, "overrideBackbutton", [override]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Override the default behavior of the Android volume button.
|
||||
* If overridden, when the volume button is pressed, the "volume[up|down]button"
|
||||
* JavaScript event will be fired.
|
||||
*
|
||||
* Note: The user should not have to call this method. Instead, when the user
|
||||
* registers for the "volume[up|down]button" event, this is automatically done.
|
||||
*
|
||||
* @param button volumeup, volumedown
|
||||
* @param override T=override, F=cancel override
|
||||
*/
|
||||
overrideButton:function(button, override) {
|
||||
exec(null, null, APP_PLUGIN_NAME, "overrideButton", [button, override]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Exit and terminate the application.
|
||||
*/
|
||||
exitApp:function() {
|
||||
return exec(null, null, "App", "exitApp", []);
|
||||
return exec(null, null, APP_PLUGIN_NAME, "exitApp", []);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1469,6 +1673,10 @@ module.exports = {
|
||||
// file: src/common/pluginloader.js
|
||||
define("cordova/pluginloader", function(require, exports, module) {
|
||||
|
||||
/*
|
||||
NOTE: this file is NOT used when we use the browserify workflow
|
||||
*/
|
||||
|
||||
var modulemapper = require('cordova/modulemapper');
|
||||
var urlutil = require('cordova/urlutil');
|
||||
|
||||
@@ -1549,11 +1757,11 @@ function handlePluginsObject(path, moduleList, finishPluginLoading) {
|
||||
function findCordovaPath() {
|
||||
var path = null;
|
||||
var scripts = document.getElementsByTagName('script');
|
||||
var term = 'cordova.js';
|
||||
var term = '/cordova.js';
|
||||
for (var n = scripts.length-1; n>-1; n--) {
|
||||
var src = scripts[n].src.replace(/\?.*$/, ''); // Strip any query param (CB-6007).
|
||||
if (src.indexOf(term) == (src.length - term.length)) {
|
||||
path = src.substring(0, src.length - term.length);
|
||||
path = src.substring(0, src.length - term.length) + '/';
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1657,15 +1865,14 @@ utils.typeName = function(val) {
|
||||
/**
|
||||
* Returns an indication of whether the argument is an array or not
|
||||
*/
|
||||
utils.isArray = function(a) {
|
||||
return utils.typeName(a) == 'Array';
|
||||
};
|
||||
utils.isArray = Array.isArray ||
|
||||
function(a) {return utils.typeName(a) == 'Array';};
|
||||
|
||||
/**
|
||||
* Returns an indication of whether the argument is a Date or not
|
||||
*/
|
||||
utils.isDate = function(d) {
|
||||
return utils.typeName(d) == 'Date';
|
||||
return (d instanceof Date);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1699,17 +1906,25 @@ utils.clone = function(obj) {
|
||||
* Returns a wrapped version of the function
|
||||
*/
|
||||
utils.close = function(context, func, params) {
|
||||
if (typeof params == 'undefined') {
|
||||
return function() {
|
||||
return func.apply(context, arguments);
|
||||
};
|
||||
} else {
|
||||
return function() {
|
||||
return func.apply(context, params);
|
||||
};
|
||||
}
|
||||
return function() {
|
||||
var args = params || arguments;
|
||||
return func.apply(context, args);
|
||||
};
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
function UUIDcreatePart(length) {
|
||||
var uuidpart = "";
|
||||
for (var i=0; i<length; i++) {
|
||||
var uuidchar = parseInt((Math.random() * 256), 10).toString(16);
|
||||
if (uuidchar.length == 1) {
|
||||
uuidchar = "0" + uuidchar;
|
||||
}
|
||||
uuidpart += uuidchar;
|
||||
}
|
||||
return uuidpart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a UUID
|
||||
*/
|
||||
@@ -1721,6 +1936,7 @@ utils.createUUID = function() {
|
||||
UUIDcreatePart(6);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Extends a child object from a parent object using classical inheritance
|
||||
* pattern.
|
||||
@@ -1730,6 +1946,7 @@ utils.extend = (function() {
|
||||
var F = function() {};
|
||||
// extend Child from Parent
|
||||
return function(Child, Parent) {
|
||||
|
||||
F.prototype = Parent.prototype;
|
||||
Child.prototype = new F();
|
||||
Child.__super__ = Parent.prototype;
|
||||
@@ -1749,18 +1966,7 @@ utils.alert = function(msg) {
|
||||
};
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
function UUIDcreatePart(length) {
|
||||
var uuidpart = "";
|
||||
for (var i=0; i<length; i++) {
|
||||
var uuidchar = parseInt((Math.random() * 256), 10).toString(16);
|
||||
if (uuidchar.length == 1) {
|
||||
uuidchar = "0" + uuidchar;
|
||||
}
|
||||
uuidpart += uuidchar;
|
||||
}
|
||||
return uuidpart;
|
||||
}
|
||||
|
||||
|
||||
|
||||
});
|
||||
@@ -19,10 +19,11 @@
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
|
||||
<link rel="stylesheet" type="text/css" href="css/index.css" />
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: https://ssl.gstatic.com/accessibility/javascript/android/; style-src 'self' 'unsafe-inline'; media-src *">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="msapplication-tap-highlight" content="no">
|
||||
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">
|
||||
<link rel="stylesheet" type="text/css" href="css/index.css">
|
||||
<title>Hello World</title>
|
||||
</head>
|
||||
<body>
|
||||
@@ -35,8 +36,5 @@
|
||||
</div>
|
||||
<script type="text/javascript" src="cordova.js"></script>
|
||||
<script type="text/javascript" src="js/index.js"></script>
|
||||
<script type="text/javascript">
|
||||
app.initialize();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -47,3 +47,5 @@ var app = {
|
||||
console.log('Received Event: ' + id);
|
||||
}
|
||||
};
|
||||
|
||||
app.initialize();
|
||||
@@ -1,165 +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.
|
||||
*/
|
||||
|
||||
var deviceInfo = function() {
|
||||
document.getElementById("platform").innerHTML = device.platform;
|
||||
document.getElementById("version").innerHTML = device.version;
|
||||
document.getElementById("uuid").innerHTML = device.uuid;
|
||||
document.getElementById("name").innerHTML = device.name;
|
||||
document.getElementById("width").innerHTML = screen.width;
|
||||
document.getElementById("height").innerHTML = screen.height;
|
||||
document.getElementById("colorDepth").innerHTML = screen.colorDepth;
|
||||
};
|
||||
|
||||
var getLocation = function() {
|
||||
var suc = function(p) {
|
||||
alert(p.coords.latitude + " " + p.coords.longitude);
|
||||
};
|
||||
var locFail = function() {
|
||||
};
|
||||
navigator.geolocation.getCurrentPosition(suc, locFail);
|
||||
};
|
||||
|
||||
var beep = function() {
|
||||
navigator.notification.beep(2);
|
||||
};
|
||||
|
||||
var vibrate = function() {
|
||||
navigator.notification.vibrate(0);
|
||||
};
|
||||
|
||||
function roundNumber(num) {
|
||||
var dec = 3;
|
||||
var result = Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
|
||||
return result;
|
||||
}
|
||||
|
||||
var accelerationWatch = null;
|
||||
|
||||
function updateAcceleration(a) {
|
||||
document.getElementById('x').innerHTML = roundNumber(a.x);
|
||||
document.getElementById('y').innerHTML = roundNumber(a.y);
|
||||
document.getElementById('z').innerHTML = roundNumber(a.z);
|
||||
}
|
||||
|
||||
var toggleAccel = function() {
|
||||
if (accelerationWatch !== null) {
|
||||
navigator.accelerometer.clearWatch(accelerationWatch);
|
||||
updateAcceleration({
|
||||
x : "",
|
||||
y : "",
|
||||
z : ""
|
||||
});
|
||||
accelerationWatch = null;
|
||||
} else {
|
||||
var options = {};
|
||||
options.frequency = 1000;
|
||||
accelerationWatch = navigator.accelerometer.watchAcceleration(
|
||||
updateAcceleration, function(ex) {
|
||||
alert("accel fail (" + ex.name + ": " + ex.message + ")");
|
||||
}, options);
|
||||
}
|
||||
};
|
||||
|
||||
var preventBehavior = function(e) {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
function dump_pic(data) {
|
||||
var viewport = document.getElementById('viewport');
|
||||
console.log(data);
|
||||
viewport.style.display = "";
|
||||
viewport.style.position = "absolute";
|
||||
viewport.style.top = "10px";
|
||||
viewport.style.left = "10px";
|
||||
document.getElementById("test_img").src = data;
|
||||
}
|
||||
|
||||
function fail(msg) {
|
||||
alert(msg);
|
||||
}
|
||||
|
||||
function show_pic() {
|
||||
navigator.camera.getPicture(dump_pic, fail, {
|
||||
quality : 50
|
||||
});
|
||||
}
|
||||
|
||||
function close() {
|
||||
var viewport = document.getElementById('viewport');
|
||||
viewport.style.position = "relative";
|
||||
viewport.style.display = "none";
|
||||
}
|
||||
|
||||
function contacts_success(contacts) {
|
||||
alert(contacts.length
|
||||
+ ' contacts returned.'
|
||||
+ (contacts[2] && contacts[2].name ? (' Third contact is ' + contacts[2].name.formatted)
|
||||
: ''));
|
||||
}
|
||||
|
||||
function get_contacts() {
|
||||
var obj = new ContactFindOptions();
|
||||
obj.filter = "";
|
||||
obj.multiple = true;
|
||||
navigator.contacts.find(
|
||||
[ "displayName", "name" ], contacts_success,
|
||||
fail, obj);
|
||||
}
|
||||
|
||||
function check_network() {
|
||||
var networkState = navigator.network.connection.type;
|
||||
|
||||
var states = {};
|
||||
states[Connection.UNKNOWN] = 'Unknown connection';
|
||||
states[Connection.ETHERNET] = 'Ethernet connection';
|
||||
states[Connection.WIFI] = 'WiFi connection';
|
||||
states[Connection.CELL_2G] = 'Cell 2G connection';
|
||||
states[Connection.CELL_3G] = 'Cell 3G connection';
|
||||
states[Connection.CELL_4G] = 'Cell 4G connection';
|
||||
states[Connection.NONE] = 'No network connection';
|
||||
|
||||
confirm('Connection type:\n ' + states[networkState]);
|
||||
}
|
||||
|
||||
var watchID = null;
|
||||
|
||||
function updateHeading(h) {
|
||||
document.getElementById('h').innerHTML = h.magneticHeading;
|
||||
}
|
||||
|
||||
function toggleCompass() {
|
||||
if (watchID !== null) {
|
||||
navigator.compass.clearWatch(watchID);
|
||||
watchID = null;
|
||||
updateHeading({ magneticHeading : "Off"});
|
||||
} else {
|
||||
var options = { frequency: 1000 };
|
||||
watchID = navigator.compass.watchHeading(updateHeading, function(e) {
|
||||
alert('Compass Error: ' + e.code);
|
||||
}, options);
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
// the next line makes it impossible to see Contacts on the HTC Evo since it
|
||||
// doesn't have a scroll button
|
||||
// document.addEventListener("touchmove", preventBehavior, false);
|
||||
document.addEventListener("deviceready", deviceInfo, true);
|
||||
}
|
||||
@@ -1,116 +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.
|
||||
*/
|
||||
|
||||
|
||||
body {
|
||||
background:#222 none repeat scroll 0 0;
|
||||
color:#666;
|
||||
font-family:Helvetica;
|
||||
font-size:72%;
|
||||
line-height:1.5em;
|
||||
margin:0;
|
||||
border-top:1px solid #393939;
|
||||
}
|
||||
|
||||
#info{
|
||||
background:#ffa;
|
||||
border: 1px solid #ffd324;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
clear:both;
|
||||
margin:15px 6px 0;
|
||||
width:295px;
|
||||
padding:4px 0px 2px 10px;
|
||||
}
|
||||
|
||||
#info > h4{
|
||||
font-size:.95em;
|
||||
margin:5px 0;
|
||||
}
|
||||
|
||||
#stage.theme{
|
||||
padding-top:3px;
|
||||
}
|
||||
|
||||
/* Definition List */
|
||||
#stage.theme > dl{
|
||||
padding-top:10px;
|
||||
clear:both;
|
||||
margin:0;
|
||||
list-style-type:none;
|
||||
padding-left:10px;
|
||||
overflow:auto;
|
||||
}
|
||||
|
||||
#stage.theme > dl > dt{
|
||||
font-weight:bold;
|
||||
float:left;
|
||||
margin-left:5px;
|
||||
}
|
||||
|
||||
#stage.theme > dl > dd{
|
||||
width:45px;
|
||||
float:left;
|
||||
color:#a87;
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
/* Content Styling */
|
||||
#stage.theme > h1, #stage.theme > h2, #stage.theme > p{
|
||||
margin:1em 0 .5em 13px;
|
||||
}
|
||||
|
||||
#stage.theme > h1{
|
||||
color:#eee;
|
||||
font-size:1.6em;
|
||||
text-align:center;
|
||||
margin:0;
|
||||
margin-top:15px;
|
||||
padding:0;
|
||||
}
|
||||
|
||||
#stage.theme > h2{
|
||||
clear:both;
|
||||
margin:0;
|
||||
padding:3px;
|
||||
font-size:1em;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
/* Stage Buttons */
|
||||
#stage.theme a.btn{
|
||||
border: 1px solid #555;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
text-align:center;
|
||||
display:block;
|
||||
float:left;
|
||||
background:#444;
|
||||
width:150px;
|
||||
color:#9ab;
|
||||
font-size:1.1em;
|
||||
text-decoration:none;
|
||||
padding:1.2em 0;
|
||||
margin:3px 0px 3px 5px;
|
||||
}
|
||||
#stage.theme a.btn.large{
|
||||
width:308px;
|
||||
padding:1.2em 0;
|
||||
}
|
||||
|
||||
307
bin/templates/project/build.gradle
Normal file
307
bin/templates/project/build.gradle
Normal file
@@ -0,0 +1,307 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
// GENERATED FILE! DO NOT EDIT!
|
||||
|
||||
apply plugin: 'android'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
// Switch the Android Gradle plugin version requirement depending on the
|
||||
// installed version of Gradle. This dependency is documented at
|
||||
// http://tools.android.com/tech-docs/new-build-system/version-compatibility
|
||||
// and https://issues.apache.org/jira/browse/CB-8143
|
||||
if (gradle.gradleVersion >= "2.2") {
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:1.0.0+'
|
||||
}
|
||||
} else if (gradle.gradleVersion >= "2.1") {
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:0.14.0+'
|
||||
}
|
||||
} else {
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:0.12.0+'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allow plugins to declare Maven dependencies via build-extras.gradle.
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = '2.2.1'
|
||||
}
|
||||
|
||||
// Configuration properties. Set these via environment variables, build-extras.gradle, or gradle.properties.
|
||||
// Refer to: http://www.gradle.org/docs/current/userguide/tutorial_this_and_that.html
|
||||
ext {
|
||||
apply from: 'CordovaLib/cordova.gradle'
|
||||
// The value for android.compileSdkVersion.
|
||||
if (!project.hasProperty('cdvCompileSdkVersion')) {
|
||||
cdvCompileSdkVersion = null;
|
||||
}
|
||||
// The value for android.buildToolsVersion.
|
||||
if (!project.hasProperty('cdvBuildToolsVersion')) {
|
||||
cdvBuildToolsVersion = null;
|
||||
}
|
||||
// Sets the versionCode to the given value.
|
||||
if (!project.hasProperty('cdvVersionCode')) {
|
||||
cdvVersionCode = null
|
||||
}
|
||||
// Sets the minSdkVersion to the given value.
|
||||
if (!project.hasProperty('cdvMinSdkVersion')) {
|
||||
cdvMinSdkVersion = null
|
||||
}
|
||||
// Whether to build architecture-specific APKs.
|
||||
if (!project.hasProperty('cdvBuildMultipleApks')) {
|
||||
cdvBuildMultipleApks = null
|
||||
}
|
||||
// .properties files to use for release signing.
|
||||
if (!project.hasProperty('cdvReleaseSigningPropertiesFile')) {
|
||||
cdvReleaseSigningPropertiesFile = null
|
||||
}
|
||||
// .properties files to use for debug signing.
|
||||
if (!project.hasProperty('cdvDebugSigningPropertiesFile')) {
|
||||
cdvDebugSigningPropertiesFile = null
|
||||
}
|
||||
// Set by build.js script.
|
||||
if (!project.hasProperty('cdvBuildArch')) {
|
||||
cdvBuildArch = null
|
||||
}
|
||||
|
||||
// Plugin gradle extensions can append to this to have code run at the end.
|
||||
cdvPluginPostBuildExtras = []
|
||||
}
|
||||
|
||||
// PLUGIN GRADLE EXTENSIONS START
|
||||
// PLUGIN GRADLE EXTENSIONS END
|
||||
|
||||
def hasBuildExtras = file('build-extras.gradle').exists()
|
||||
if (hasBuildExtras) {
|
||||
apply from: 'build-extras.gradle'
|
||||
}
|
||||
|
||||
// Set property defaults after extension .gradle files.
|
||||
if (ext.cdvCompileSdkVersion == null) {
|
||||
ext.cdvCompileSdkVersion = privateHelpers.getProjectTarget()
|
||||
}
|
||||
if (ext.cdvBuildToolsVersion == null) {
|
||||
ext.cdvBuildToolsVersion = privateHelpers.findLatestInstalledBuildTools()
|
||||
}
|
||||
if (ext.cdvDebugSigningPropertiesFile == null && file('debug-signing.properties').exists()) {
|
||||
ext.cdvDebugSigningPropertiesFile = 'debug-signing.properties'
|
||||
}
|
||||
if (ext.cdvReleaseSigningPropertiesFile == null && file('release-signing.properties').exists()) {
|
||||
ext.cdvReleaseSigningPropertiesFile = 'release-signing.properties'
|
||||
}
|
||||
|
||||
// Cast to appropriate types.
|
||||
ext.cdvBuildMultipleApks = cdvBuildMultipleApks == null ? false : cdvBuildMultipleApks.toBoolean();
|
||||
ext.cdvMinSdkVersion = cdvMinSdkVersion == null ? null : Integer.parseInt('' + cdvMinSdkVersion)
|
||||
ext.cdvVersionCode = cdvVersionCode == null ? null : Integer.parseInt('' + cdvVersionCode)
|
||||
|
||||
def computeBuildTargetName(debugBuild) {
|
||||
def ret = 'assemble'
|
||||
if (cdvBuildMultipleApks && cdvBuildArch) {
|
||||
def arch = cdvBuildArch == 'arm' ? 'armv7' : cdvBuildArch
|
||||
ret += '' + arch.toUpperCase().charAt(0) + arch.substring(1);
|
||||
}
|
||||
return ret + (debugBuild ? 'Debug' : 'Release')
|
||||
}
|
||||
|
||||
// Make cdvBuild a task that depends on the debug/arch-sepecific task.
|
||||
task cdvBuildDebug
|
||||
cdvBuildDebug.dependsOn {
|
||||
return computeBuildTargetName(true)
|
||||
}
|
||||
|
||||
task cdvBuildRelease
|
||||
cdvBuildRelease.dependsOn {
|
||||
return computeBuildTargetName(false)
|
||||
}
|
||||
|
||||
task cdvPrintProps << {
|
||||
println('cdvCompileSdkVersion=' + cdvCompileSdkVersion)
|
||||
println('cdvBuildToolsVersion=' + cdvBuildToolsVersion)
|
||||
println('cdvVersionCode=' + cdvVersionCode)
|
||||
println('cdvMinSdkVersion=' + cdvMinSdkVersion)
|
||||
println('cdvBuildMultipleApks=' + cdvBuildMultipleApks)
|
||||
println('cdvReleaseSigningPropertiesFile=' + cdvReleaseSigningPropertiesFile)
|
||||
println('cdvDebugSigningPropertiesFile=' + cdvDebugSigningPropertiesFile)
|
||||
println('cdvBuildArch=' + cdvBuildArch)
|
||||
println('computedVersionCode=' + android.defaultConfig.versionCode)
|
||||
android.productFlavors.each { flavor ->
|
||||
println('computed' + flavor.name.capitalize() + 'VersionCode=' + flavor.versionCode)
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
sourceSets {
|
||||
main {
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
java.srcDirs = ['src']
|
||||
resources.srcDirs = ['src']
|
||||
aidl.srcDirs = ['src']
|
||||
renderscript.srcDirs = ['src']
|
||||
res.srcDirs = ['res']
|
||||
assets.srcDirs = ['assets']
|
||||
jniLibs.srcDirs = ['libs']
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
versionCode cdvVersionCode ?: Integer.parseInt("" + privateHelpers.extractIntFromManifest("versionCode") + "0")
|
||||
if (cdvMinSdkVersion != null) {
|
||||
minSdkVersion cdvMinSdkVersion
|
||||
}
|
||||
}
|
||||
|
||||
compileSdkVersion cdvCompileSdkVersion
|
||||
buildToolsVersion cdvBuildToolsVersion
|
||||
|
||||
if (Boolean.valueOf(cdvBuildMultipleApks)) {
|
||||
productFlavors {
|
||||
armv7 {
|
||||
versionCode cdvVersionCode ?: defaultConfig.versionCode + 2
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a", ""
|
||||
}
|
||||
}
|
||||
x86 {
|
||||
versionCode cdvVersionCode ?: defaultConfig.versionCode + 4
|
||||
ndk {
|
||||
abiFilters "x86", ""
|
||||
}
|
||||
}
|
||||
all {
|
||||
ndk {
|
||||
abiFilters "all", ""
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!cdvVersionCode) {
|
||||
def minSdkVersion = cdvMinSdkVersion ?: privateHelpers.extractIntFromManifest("minSdkVersion")
|
||||
// Vary versionCode by the two most common API levels:
|
||||
// 14 is ICS, which is the lowest API level for many apps.
|
||||
// 20 is Lollipop, which is the lowest API level for the updatable system webview.
|
||||
if (minSdkVersion >= 20) {
|
||||
defaultConfig.versionCode += 9
|
||||
} else if (minSdkVersion >= 14) {
|
||||
defaultConfig.versionCode += 8
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_6
|
||||
targetCompatibility JavaVersion.VERSION_1_6
|
||||
}
|
||||
|
||||
if (cdvReleaseSigningPropertiesFile) {
|
||||
signingConfigs {
|
||||
release {
|
||||
// These must be set or Gradle will complain (even if they are overridden).
|
||||
keyAlias = ""
|
||||
keyPassword = "__unset" // And these must be set to non-empty in order to have the signing step added to the task graph.
|
||||
storeFile = null
|
||||
storePassword = "__unset"
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
addSigningProps(cdvReleaseSigningPropertiesFile, signingConfigs.release)
|
||||
}
|
||||
if (cdvDebugSigningPropertiesFile) {
|
||||
addSigningProps(cdvDebugSigningPropertiesFile, signingConfigs.debug)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: '*.jar')
|
||||
// SUB-PROJECT DEPENDENCIES START
|
||||
// SUB-PROJECT DEPENDENCIES END
|
||||
}
|
||||
|
||||
def promptForReleaseKeyPassword() {
|
||||
if (!cdvReleaseSigningPropertiesFile) {
|
||||
return;
|
||||
}
|
||||
if ('__unset'.equals(android.signingConfigs.release.storePassword)) {
|
||||
android.signingConfigs.release.storePassword = privateHelpers.promptForPassword('Enter key store password: ')
|
||||
}
|
||||
if ('__unset'.equals(android.signingConfigs.release.keyPassword)) {
|
||||
android.signingConfigs.release.keyPassword = privateHelpers.promptForPassword('Enter key password: ');
|
||||
}
|
||||
}
|
||||
|
||||
gradle.taskGraph.whenReady { taskGraph ->
|
||||
taskGraph.getAllTasks().each() { task ->
|
||||
if (task.name == 'validateReleaseSigning') {
|
||||
promptForReleaseKeyPassword()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def addSigningProps(propsFilePath, signingConfig) {
|
||||
def propsFile = file(propsFilePath)
|
||||
def props = new Properties()
|
||||
propsFile.withReader { reader ->
|
||||
props.load(reader)
|
||||
}
|
||||
|
||||
def storeFile = new File(props.get('key.store') ?: privateHelpers.ensureValueExists(propsFilePath, props, 'storeFile'))
|
||||
if (!storeFile.isAbsolute()) {
|
||||
storeFile = RelativePath.parse(true, storeFile.toString()).getFile(propsFile.getParentFile())
|
||||
}
|
||||
if (!storeFile.exists()) {
|
||||
throw new FileNotFoundException('Keystore file does not exist: ' + storeFile.getAbsolutePath())
|
||||
}
|
||||
signingConfig.keyAlias = props.get('key.alias') ?: privateHelpers.ensureValueExists(propsFilePath, props, 'keyAlias')
|
||||
signingConfig.keyPassword = props.get('keyPassword', props.get('key.alias.password', signingConfig.keyPassword))
|
||||
signingConfig.storeFile = storeFile
|
||||
signingConfig.storePassword = props.get('storePassword', props.get('key.store.password', signingConfig.storePassword))
|
||||
def storeType = props.get('storeType', props.get('key.store.type', ''))
|
||||
if (!storeType) {
|
||||
def filename = storeFile.getName().toLowerCase();
|
||||
if (filename.endsWith('.p12') || filename.endsWith('.pfx')) {
|
||||
storeType = 'pkcs12'
|
||||
} else {
|
||||
storeType = signingConfig.storeType // "jks"
|
||||
}
|
||||
}
|
||||
signingConfig.storeType = storeType
|
||||
}
|
||||
|
||||
for (def func : cdvPluginPostBuildExtras) {
|
||||
func()
|
||||
}
|
||||
|
||||
// This can be defined within build-extras.gradle as:
|
||||
// ext.postBuildExtras = { ... code here ... }
|
||||
if (hasProperty('postBuildExtras')) {
|
||||
postBuildExtras()
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project>
|
||||
<target name="-pre-compile">
|
||||
<!-- Fix library references due to bug in build.xml: See: https://groups.google.com/forum/#!topic/android-developers/0ivH-YqCjzg -->
|
||||
<pathconvert property="fixedJarsPath" refid="project.all.jars.path">
|
||||
<filtermapper>
|
||||
<replacestring from="/bin/" to="/ant-build/"/>
|
||||
<replacestring from="\bin\" to="\ant-build\"/>
|
||||
</filtermapper>
|
||||
</pathconvert>
|
||||
<path id="project.all.jars.path">
|
||||
<pathelement path="${fixedJarsPath}"/>
|
||||
</path>
|
||||
<echo message="Set jars path to: ${toString:project.all.jars.path}"/>
|
||||
</target>
|
||||
<target name="-post-build">
|
||||
<move file="ant-build/AndroidManifest.xml" tofile="ant-build/AndroidManifest.cordova.xml" failonerror="false" overwrite="true" />
|
||||
<move file="CordovaLib/ant-build/AndroidManifest.xml" tofile="CordovaLib/ant-build/AndroidManifest.cordova.xml" failonerror="false" overwrite="true" />
|
||||
</target>
|
||||
</project>
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>__NAME__</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
<filteredResources>
|
||||
<filter>
|
||||
<id>1388696068187</id>
|
||||
<name></name>
|
||||
<type>10</type>
|
||||
<matcher>
|
||||
<id>org.eclipse.ui.ide.multiFilter</id>
|
||||
<arguments>1.0-name-matches-false-true-CordovaLib|platform_www|cordova</arguments>
|
||||
</matcher>
|
||||
</filter>
|
||||
</filteredResources>
|
||||
</projectDescription>
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>__NAME__</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
<linkedResources>
|
||||
<link>
|
||||
<name>config.xml</name>
|
||||
<type>1</type>
|
||||
<locationURI>$%7BPARENT-2-PROJECT_LOC%7D/config.xml</locationURI>
|
||||
</link>
|
||||
<link>
|
||||
<name>www</name>
|
||||
<type>2</type>
|
||||
<locationURI>$%7BPARENT-2-PROJECT_LOC%7D/www</locationURI>
|
||||
</link>
|
||||
<link>
|
||||
<name>merges</name>
|
||||
<type>2</type>
|
||||
<locationURI>$%7BPARENT-2-PROJECT_LOC%7D/merges</locationURI>
|
||||
</link>
|
||||
</linkedResources>
|
||||
<filteredResources>
|
||||
<filter>
|
||||
<id>1390880034107</id>
|
||||
<name></name>
|
||||
<type>30</type>
|
||||
<matcher>
|
||||
<id>org.eclipse.ui.ide.multiFilter</id>
|
||||
<arguments>1.0-projectRelativePath-matches-false-true-^(build.xml|ant-gen|ant-build|custom_rules.xml|CordovaLib|platform_www|cordova)</arguments>
|
||||
</matcher>
|
||||
</filter>
|
||||
<filter>
|
||||
<id>1390880034108</id>
|
||||
<name></name>
|
||||
<type>30</type>
|
||||
<matcher>
|
||||
<id>org.eclipse.ui.ide.multiFilter</id>
|
||||
<arguments>1.0-projectRelativePath-matches-false-true-^(assets/www|res/xml/config.xml)</arguments>
|
||||
</matcher>
|
||||
</filter>
|
||||
</filteredResources>
|
||||
</projectDescription>
|
||||
|
||||
14
bin/templates/project/gitignore
Normal file
14
bin/templates/project/gitignore
Normal file
@@ -0,0 +1,14 @@
|
||||
# Non-project-specific build files:
|
||||
build.xml
|
||||
local.properties
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/gradle
|
||||
# Ant builds
|
||||
ant-build
|
||||
ant-gen
|
||||
# Eclipse builds
|
||||
gen
|
||||
out
|
||||
# Gradle builds
|
||||
/build
|
||||
15
bin/templates/project/project.properties
Normal file
15
bin/templates/project/project.properties
Normal file
@@ -0,0 +1,15 @@
|
||||
# This file is automatically generated by Android Tools.
|
||||
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||
#
|
||||
# This file must be checked in Version Control Systems.
|
||||
#
|
||||
# To customize properties used by the Ant build system edit
|
||||
# "ant.properties", and override values to adapt the script to your
|
||||
# project structure.
|
||||
#
|
||||
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
|
||||
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
|
||||
|
||||
android.library.reference.1=CordovaLib
|
||||
# Project target.
|
||||
target=This_gets_replaced
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 7.5 KiB |
@@ -1,4 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- App label shown within list of installed apps, battery & network usage screens. -->
|
||||
<string name="app_name">__NAME__</string>
|
||||
<!-- App label shown on the launcher. -->
|
||||
<string name="launcher_name">@string/app_name</string>
|
||||
<!-- App label shown on the task switcher. -->
|
||||
<string name="activity_name">@string/launcher_name</string>
|
||||
</resources>
|
||||
|
||||
@@ -30,22 +30,29 @@
|
||||
Apache Cordova Team
|
||||
</author>
|
||||
|
||||
<access origin="*"/>
|
||||
|
||||
<!-- <content src="http://mysite.com/myapp.html" /> for external pages -->
|
||||
<content src="index.html" />
|
||||
|
||||
<!-- Whitelist docs: https://github.com/apache/cordova-plugin-whitelist -->
|
||||
<access origin="*" />
|
||||
<!-- Grant certain URLs the ability to launch external applications. This
|
||||
behaviour is set to match that of Cordova versions before 3.6.0, and
|
||||
should be reviewed before launching an application in production. It
|
||||
may be changed in the future. -->
|
||||
<allow-intent href="http://*/*" />
|
||||
<allow-intent href="https://*/*" />
|
||||
<allow-intent href="tel:*" />
|
||||
<allow-intent href="sms:*" />
|
||||
<allow-intent href="mailto:*" />
|
||||
<allow-intent href="geo:*" />
|
||||
<allow-intent href="market:*" />
|
||||
|
||||
<preference name="loglevel" value="DEBUG" />
|
||||
|
||||
<!--
|
||||
<preference name="splashscreen" value="resourceName" />
|
||||
<preference name="splashscreen" value="splash" />
|
||||
<preference name="backgroundColor" value="0xFFF" />
|
||||
<preference name="loadUrlTimeoutValue" value="20000" />
|
||||
<preference name="InAppBrowserStorageEnabled" value="true" />
|
||||
<preference name="disallowOverscroll" value="true" />
|
||||
-->
|
||||
<!-- This is required for native Android hooks -->
|
||||
<feature name="App">
|
||||
<param name="android-package" value="org.apache.cordova.App" />
|
||||
</feature>
|
||||
</widget>
|
||||
@@ -23,9 +23,9 @@ var create = require('./lib/create');
|
||||
var args = require('./lib/simpleargs').getArgs(process.argv);
|
||||
|
||||
if (args['--help'] || args._.length === 0) {
|
||||
console.log('Usage: ' + path.relative(process.cwd(), path.join(__dirname, 'update')) + ' <path_to_project> [--shared]');
|
||||
console.log(' --shared will use the CordovaLib project directly instead of making a copy.');
|
||||
console.log('Usage: ' + path.relative(process.cwd(), path.join(__dirname, 'update')) + ' <path_to_project> [--link]');
|
||||
console.log(' --link will use the CordovaLib project directly instead of making a copy.');
|
||||
process.exit(1);
|
||||
}
|
||||
create.updateProject(args._[0], args['--shared']).done();
|
||||
create.updateProject(args._[0], args['--link'] || args['--shared']).done();
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
package org.apache.cordova.test.junit;
|
||||
/*
|
||||
*
|
||||
* 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
|
||||
@@ -17,18 +15,22 @@ package org.apache.cordova.test.junit;
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Exports the ExposedJsApi.java object if available, otherwise exports the PromptBasedNativeApi.
|
||||
*/
|
||||
|
||||
import org.apache.cordova.test.lifecycle;
|
||||
var nativeApi = this._cordovaNative || require('cordova/android/promptbasednativeapi');
|
||||
var currentApi = nativeApi;
|
||||
|
||||
import android.test.ActivityInstrumentationTestCase2;
|
||||
|
||||
public class LifecycleTest extends ActivityInstrumentationTestCase2<lifecycle> {
|
||||
|
||||
public LifecycleTest()
|
||||
{
|
||||
super("org.apache.cordova.test",lifecycle.class);
|
||||
}
|
||||
}
|
||||
module.exports = {
|
||||
get: function() { return currentApi; },
|
||||
setPreferPrompt: function(value) {
|
||||
currentApi = value ? require('cordova/android/promptbasednativeapi') : nativeApi;
|
||||
},
|
||||
// Used only by tests.
|
||||
set: function(value) {
|
||||
currentApi = value;
|
||||
}
|
||||
};
|
||||
35
cordova-js-src/android/promptbasednativeapi.js
vendored
Normal file
35
cordova-js-src/android/promptbasednativeapi.js
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements the API of ExposedJsApi.java, but uses prompt() to communicate.
|
||||
* This is used pre-JellyBean, where addJavascriptInterface() is disabled.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
exec: function(bridgeSecret, service, action, callbackId, argsJson) {
|
||||
return prompt(argsJson, 'gap:'+JSON.stringify([bridgeSecret, service, action, callbackId]));
|
||||
},
|
||||
setNativeToJsBridgeMode: function(bridgeSecret, value) {
|
||||
prompt(value, 'gap_bridge_mode:' + bridgeSecret);
|
||||
},
|
||||
retrieveJsMessages: function(bridgeSecret, fromOnlineEvent) {
|
||||
return prompt(+fromOnlineEvent, 'gap_poll:' + bridgeSecret);
|
||||
}
|
||||
};
|
||||
283
cordova-js-src/exec.js
vendored
Normal file
283
cordova-js-src/exec.js
vendored
Normal file
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Execute a cordova command. It is up to the native side whether this action
|
||||
* is synchronous or asynchronous. The native side can return:
|
||||
* Synchronous: PluginResult object as a JSON string
|
||||
* Asynchronous: Empty string ""
|
||||
* If async, the native side will cordova.callbackSuccess or cordova.callbackError,
|
||||
* depending upon the result of the action.
|
||||
*
|
||||
* @param {Function} success The success callback
|
||||
* @param {Function} fail The fail callback
|
||||
* @param {String} service The name of the service to use
|
||||
* @param {String} action Action to be run in cordova
|
||||
* @param {String[]} [args] Zero or more arguments to pass to the method
|
||||
*/
|
||||
var cordova = require('cordova'),
|
||||
nativeApiProvider = require('cordova/android/nativeapiprovider'),
|
||||
utils = require('cordova/utils'),
|
||||
base64 = require('cordova/base64'),
|
||||
channel = require('cordova/channel'),
|
||||
jsToNativeModes = {
|
||||
PROMPT: 0,
|
||||
JS_OBJECT: 1
|
||||
},
|
||||
nativeToJsModes = {
|
||||
// Polls for messages using the JS->Native bridge.
|
||||
POLLING: 0,
|
||||
// For LOAD_URL to be viable, it would need to have a work-around for
|
||||
// the bug where the soft-keyboard gets dismissed when a message is sent.
|
||||
LOAD_URL: 1,
|
||||
// For the ONLINE_EVENT to be viable, it would need to intercept all event
|
||||
// listeners (both through addEventListener and window.ononline) as well
|
||||
// as set the navigator property itself.
|
||||
ONLINE_EVENT: 2
|
||||
},
|
||||
jsToNativeBridgeMode, // Set lazily.
|
||||
nativeToJsBridgeMode = nativeToJsModes.ONLINE_EVENT,
|
||||
pollEnabled = false,
|
||||
bridgeSecret = -1;
|
||||
|
||||
var messagesFromNative = [];
|
||||
var isProcessing = false;
|
||||
var resolvedPromise = typeof Promise == 'undefined' ? null : Promise.resolve();
|
||||
var nextTick = resolvedPromise ? function(fn) { resolvedPromise.then(fn); } : function(fn) { setTimeout(fn); };
|
||||
|
||||
function androidExec(success, fail, service, action, args) {
|
||||
if (bridgeSecret < 0) {
|
||||
// If we ever catch this firing, we'll need to queue up exec()s
|
||||
// and fire them once we get a secret. For now, I don't think
|
||||
// it's possible for exec() to be called since plugins are parsed but
|
||||
// not run until until after onNativeReady.
|
||||
throw new Error('exec() called without bridgeSecret');
|
||||
}
|
||||
// Set default bridge modes if they have not already been set.
|
||||
// By default, we use the failsafe, since addJavascriptInterface breaks too often
|
||||
if (jsToNativeBridgeMode === undefined) {
|
||||
androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
|
||||
}
|
||||
|
||||
// Process any ArrayBuffers in the args into a string.
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
if (utils.typeName(args[i]) == 'ArrayBuffer') {
|
||||
args[i] = base64.fromArrayBuffer(args[i]);
|
||||
}
|
||||
}
|
||||
|
||||
var callbackId = service + cordova.callbackId++,
|
||||
argsJson = JSON.stringify(args);
|
||||
|
||||
if (success || fail) {
|
||||
cordova.callbacks[callbackId] = {success:success, fail:fail};
|
||||
}
|
||||
|
||||
var msgs = nativeApiProvider.get().exec(bridgeSecret, service, action, callbackId, argsJson);
|
||||
// If argsJson was received by Java as null, try again with the PROMPT bridge mode.
|
||||
// This happens in rare circumstances, such as when certain Unicode characters are passed over the bridge on a Galaxy S2. See CB-2666.
|
||||
if (jsToNativeBridgeMode == jsToNativeModes.JS_OBJECT && msgs === "@Null arguments.") {
|
||||
androidExec.setJsToNativeBridgeMode(jsToNativeModes.PROMPT);
|
||||
androidExec(success, fail, service, action, args);
|
||||
androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
|
||||
} else if (msgs) {
|
||||
messagesFromNative.push(msgs);
|
||||
// Always process async to avoid exceptions messing up stack.
|
||||
nextTick(processMessages);
|
||||
}
|
||||
}
|
||||
|
||||
androidExec.init = function() {
|
||||
bridgeSecret = +prompt('', 'gap_init:' + nativeToJsBridgeMode);
|
||||
channel.onNativeReady.fire();
|
||||
};
|
||||
|
||||
function pollOnceFromOnlineEvent() {
|
||||
pollOnce(true);
|
||||
}
|
||||
|
||||
function pollOnce(opt_fromOnlineEvent) {
|
||||
if (bridgeSecret < 0) {
|
||||
// This can happen when the NativeToJsMessageQueue resets the online state on page transitions.
|
||||
// We know there's nothing to retrieve, so no need to poll.
|
||||
return;
|
||||
}
|
||||
var msgs = nativeApiProvider.get().retrieveJsMessages(bridgeSecret, !!opt_fromOnlineEvent);
|
||||
if (msgs) {
|
||||
messagesFromNative.push(msgs);
|
||||
// Process sync since we know we're already top-of-stack.
|
||||
processMessages();
|
||||
}
|
||||
}
|
||||
|
||||
function pollingTimerFunc() {
|
||||
if (pollEnabled) {
|
||||
pollOnce();
|
||||
setTimeout(pollingTimerFunc, 50);
|
||||
}
|
||||
}
|
||||
|
||||
function hookOnlineApis() {
|
||||
function proxyEvent(e) {
|
||||
cordova.fireWindowEvent(e.type);
|
||||
}
|
||||
// The network module takes care of firing online and offline events.
|
||||
// It currently fires them only on document though, so we bridge them
|
||||
// to window here (while first listening for exec()-releated online/offline
|
||||
// events).
|
||||
window.addEventListener('online', pollOnceFromOnlineEvent, false);
|
||||
window.addEventListener('offline', pollOnceFromOnlineEvent, false);
|
||||
cordova.addWindowEventHandler('online');
|
||||
cordova.addWindowEventHandler('offline');
|
||||
document.addEventListener('online', proxyEvent, false);
|
||||
document.addEventListener('offline', proxyEvent, false);
|
||||
}
|
||||
|
||||
hookOnlineApis();
|
||||
|
||||
androidExec.jsToNativeModes = jsToNativeModes;
|
||||
androidExec.nativeToJsModes = nativeToJsModes;
|
||||
|
||||
androidExec.setJsToNativeBridgeMode = function(mode) {
|
||||
if (mode == jsToNativeModes.JS_OBJECT && !window._cordovaNative) {
|
||||
mode = jsToNativeModes.PROMPT;
|
||||
}
|
||||
nativeApiProvider.setPreferPrompt(mode == jsToNativeModes.PROMPT);
|
||||
jsToNativeBridgeMode = mode;
|
||||
};
|
||||
|
||||
androidExec.setNativeToJsBridgeMode = function(mode) {
|
||||
if (mode == nativeToJsBridgeMode) {
|
||||
return;
|
||||
}
|
||||
if (nativeToJsBridgeMode == nativeToJsModes.POLLING) {
|
||||
pollEnabled = false;
|
||||
}
|
||||
|
||||
nativeToJsBridgeMode = mode;
|
||||
// Tell the native side to switch modes.
|
||||
// Otherwise, it will be set by androidExec.init()
|
||||
if (bridgeSecret >= 0) {
|
||||
nativeApiProvider.get().setNativeToJsBridgeMode(bridgeSecret, mode);
|
||||
}
|
||||
|
||||
if (mode == nativeToJsModes.POLLING) {
|
||||
pollEnabled = true;
|
||||
setTimeout(pollingTimerFunc, 1);
|
||||
}
|
||||
};
|
||||
|
||||
function buildPayload(payload, message) {
|
||||
var payloadKind = message.charAt(0);
|
||||
if (payloadKind == 's') {
|
||||
payload.push(message.slice(1));
|
||||
} else if (payloadKind == 't') {
|
||||
payload.push(true);
|
||||
} else if (payloadKind == 'f') {
|
||||
payload.push(false);
|
||||
} else if (payloadKind == 'N') {
|
||||
payload.push(null);
|
||||
} else if (payloadKind == 'n') {
|
||||
payload.push(+message.slice(1));
|
||||
} else if (payloadKind == 'A') {
|
||||
var data = message.slice(1);
|
||||
payload.push(base64.toArrayBuffer(data));
|
||||
} else if (payloadKind == 'S') {
|
||||
payload.push(window.atob(message.slice(1)));
|
||||
} else if (payloadKind == 'M') {
|
||||
var multipartMessages = message.slice(1);
|
||||
while (multipartMessages !== "") {
|
||||
var spaceIdx = multipartMessages.indexOf(' ');
|
||||
var msgLen = +multipartMessages.slice(0, spaceIdx);
|
||||
var multipartMessage = multipartMessages.substr(spaceIdx + 1, msgLen);
|
||||
multipartMessages = multipartMessages.slice(spaceIdx + msgLen + 1);
|
||||
buildPayload(payload, multipartMessage);
|
||||
}
|
||||
} else {
|
||||
payload.push(JSON.parse(message));
|
||||
}
|
||||
}
|
||||
|
||||
// Processes a single message, as encoded by NativeToJsMessageQueue.java.
|
||||
function processMessage(message) {
|
||||
var firstChar = message.charAt(0);
|
||||
if (firstChar == 'J') {
|
||||
// This is deprecated on the .java side. It doesn't work with CSP enabled.
|
||||
eval(message.slice(1));
|
||||
} else if (firstChar == 'S' || firstChar == 'F') {
|
||||
var success = firstChar == 'S';
|
||||
var keepCallback = message.charAt(1) == '1';
|
||||
var spaceIdx = message.indexOf(' ', 2);
|
||||
var status = +message.slice(2, spaceIdx);
|
||||
var nextSpaceIdx = message.indexOf(' ', spaceIdx + 1);
|
||||
var callbackId = message.slice(spaceIdx + 1, nextSpaceIdx);
|
||||
var payloadMessage = message.slice(nextSpaceIdx + 1);
|
||||
var payload = [];
|
||||
buildPayload(payload, payloadMessage);
|
||||
cordova.callbackFromNative(callbackId, success, status, payload, keepCallback);
|
||||
} else {
|
||||
console.log("processMessage failed: invalid message: " + JSON.stringify(message));
|
||||
}
|
||||
}
|
||||
|
||||
function processMessages() {
|
||||
// Check for the reentrant case.
|
||||
if (isProcessing) {
|
||||
return;
|
||||
}
|
||||
if (messagesFromNative.length === 0) {
|
||||
return;
|
||||
}
|
||||
isProcessing = true;
|
||||
try {
|
||||
var msg = popMessageFromQueue();
|
||||
// The Java side can send a * message to indicate that it
|
||||
// still has messages waiting to be retrieved.
|
||||
if (msg == '*' && messagesFromNative.length === 0) {
|
||||
nextTick(pollOnce);
|
||||
return;
|
||||
}
|
||||
processMessage(msg);
|
||||
} finally {
|
||||
isProcessing = false;
|
||||
if (messagesFromNative.length > 0) {
|
||||
nextTick(processMessages);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function popMessageFromQueue() {
|
||||
var messageBatch = messagesFromNative.shift();
|
||||
if (messageBatch == '*') {
|
||||
return '*';
|
||||
}
|
||||
|
||||
var spaceIdx = messageBatch.indexOf(' ');
|
||||
var msgLen = +messageBatch.slice(0, spaceIdx);
|
||||
var message = messageBatch.substr(spaceIdx + 1, msgLen);
|
||||
messageBatch = messageBatch.slice(spaceIdx + msgLen + 1);
|
||||
if (messageBatch) {
|
||||
messagesFromNative.unshift(messageBatch);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
module.exports = androidExec;
|
||||
91
cordova-js-src/platform.js
vendored
Normal file
91
cordova-js-src/platform.js
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
id: 'android',
|
||||
bootstrap: function() {
|
||||
var channel = require('cordova/channel'),
|
||||
cordova = require('cordova'),
|
||||
exec = require('cordova/exec'),
|
||||
modulemapper = require('cordova/modulemapper');
|
||||
|
||||
// Get the shared secret needed to use the bridge.
|
||||
exec.init();
|
||||
|
||||
// TODO: Extract this as a proper plugin.
|
||||
modulemapper.clobbers('cordova/plugin/android/app', 'navigator.app');
|
||||
|
||||
var APP_PLUGIN_NAME = Number(cordova.platformVersion.split('.')[0]) >= 4 ? 'CoreAndroid' : 'App';
|
||||
|
||||
// Inject a listener for the backbutton on the document.
|
||||
var backButtonChannel = cordova.addDocumentEventHandler('backbutton');
|
||||
backButtonChannel.onHasSubscribersChange = function() {
|
||||
// If we just attached the first handler or detached the last handler,
|
||||
// let native know we need to override the back button.
|
||||
exec(null, null, APP_PLUGIN_NAME, "overrideBackbutton", [this.numHandlers == 1]);
|
||||
};
|
||||
|
||||
// Add hardware MENU and SEARCH button handlers
|
||||
cordova.addDocumentEventHandler('menubutton');
|
||||
cordova.addDocumentEventHandler('searchbutton');
|
||||
|
||||
function bindButtonChannel(buttonName) {
|
||||
// generic button bind used for volumeup/volumedown buttons
|
||||
var volumeButtonChannel = cordova.addDocumentEventHandler(buttonName + 'button');
|
||||
volumeButtonChannel.onHasSubscribersChange = function() {
|
||||
exec(null, null, APP_PLUGIN_NAME, "overrideButton", [buttonName, this.numHandlers == 1]);
|
||||
};
|
||||
}
|
||||
// Inject a listener for the volume buttons on the document.
|
||||
bindButtonChannel('volumeup');
|
||||
bindButtonChannel('volumedown');
|
||||
|
||||
// Let native code know we are all done on the JS side.
|
||||
// Native code will then un-hide the WebView.
|
||||
channel.onCordovaReady.subscribe(function() {
|
||||
exec(onMessageFromNative, null, APP_PLUGIN_NAME, 'messageChannel', []);
|
||||
exec(null, null, APP_PLUGIN_NAME, "show", []);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function onMessageFromNative(msg) {
|
||||
var cordova = require('cordova');
|
||||
var action = msg.action;
|
||||
|
||||
switch (action)
|
||||
{
|
||||
// Button events
|
||||
case 'backbutton':
|
||||
case 'menubutton':
|
||||
case 'searchbutton':
|
||||
// App life cycle events
|
||||
case 'pause':
|
||||
case 'resume':
|
||||
// Volume events
|
||||
case 'volumedownbutton':
|
||||
case 'volumeupbutton':
|
||||
cordova.fireDocumentEvent(action);
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unknown event action ' + action);
|
||||
}
|
||||
}
|
||||
108
cordova-js-src/plugin/android/app.js
vendored
Normal file
108
cordova-js-src/plugin/android/app.js
vendored
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.
|
||||
*
|
||||
*/
|
||||
|
||||
var exec = require('cordova/exec');
|
||||
var APP_PLUGIN_NAME = Number(require('cordova').platformVersion.split('.')[0]) >= 4 ? 'CoreAndroid' : 'App';
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Clear the resource cache.
|
||||
*/
|
||||
clearCache:function() {
|
||||
exec(null, null, APP_PLUGIN_NAME, "clearCache", []);
|
||||
},
|
||||
|
||||
/**
|
||||
* Load the url into the webview or into new browser instance.
|
||||
*
|
||||
* @param url The URL to load
|
||||
* @param props Properties that can be passed in to the activity:
|
||||
* wait: int => wait msec before loading URL
|
||||
* loadingDialog: "Title,Message" => display a native loading dialog
|
||||
* loadUrlTimeoutValue: int => time in msec to wait before triggering a timeout error
|
||||
* clearHistory: boolean => clear webview history (default=false)
|
||||
* openExternal: boolean => open in a new browser (default=false)
|
||||
*
|
||||
* Example:
|
||||
* navigator.app.loadUrl("http://server/myapp/index.html", {wait:2000, loadingDialog:"Wait,Loading App", loadUrlTimeoutValue: 60000});
|
||||
*/
|
||||
loadUrl:function(url, props) {
|
||||
exec(null, null, APP_PLUGIN_NAME, "loadUrl", [url, props]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Cancel loadUrl that is waiting to be loaded.
|
||||
*/
|
||||
cancelLoadUrl:function() {
|
||||
exec(null, null, APP_PLUGIN_NAME, "cancelLoadUrl", []);
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear web history in this web view.
|
||||
* Instead of BACK button loading the previous web page, it will exit the app.
|
||||
*/
|
||||
clearHistory:function() {
|
||||
exec(null, null, APP_PLUGIN_NAME, "clearHistory", []);
|
||||
},
|
||||
|
||||
/**
|
||||
* Go to previous page displayed.
|
||||
* This is the same as pressing the backbutton on Android device.
|
||||
*/
|
||||
backHistory:function() {
|
||||
exec(null, null, APP_PLUGIN_NAME, "backHistory", []);
|
||||
},
|
||||
|
||||
/**
|
||||
* Override the default behavior of the Android back button.
|
||||
* If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired.
|
||||
*
|
||||
* Note: The user should not have to call this method. Instead, when the user
|
||||
* registers for the "backbutton" event, this is automatically done.
|
||||
*
|
||||
* @param override T=override, F=cancel override
|
||||
*/
|
||||
overrideBackbutton:function(override) {
|
||||
exec(null, null, APP_PLUGIN_NAME, "overrideBackbutton", [override]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Override the default behavior of the Android volume button.
|
||||
* If overridden, when the volume button is pressed, the "volume[up|down]button"
|
||||
* JavaScript event will be fired.
|
||||
*
|
||||
* Note: The user should not have to call this method. Instead, when the user
|
||||
* registers for the "volume[up|down]button" event, this is automatically done.
|
||||
*
|
||||
* @param button volumeup, volumedown
|
||||
* @param override T=override, F=cancel override
|
||||
*/
|
||||
overrideButton:function(button, override) {
|
||||
exec(null, null, APP_PLUGIN_NAME, "overrideButton", [button, override]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Exit and terminate the application.
|
||||
*/
|
||||
exitApp:function() {
|
||||
return exec(null, null, APP_PLUGIN_NAME, "exitApp", []);
|
||||
}
|
||||
};
|
||||
@@ -2,8 +2,8 @@
|
||||
<classpath>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="src" path="gen"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
|
||||
<classpathentry kind="output" path="bin/classes"/>
|
||||
</classpath>
|
||||
|
||||
@@ -19,5 +19,5 @@
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.apache.cordova" android:versionName="1.0" android:versionCode="1">
|
||||
<uses-sdk android:minSdkVersion="8" />
|
||||
<uses-sdk android:minSdkVersion="14" />
|
||||
</manifest>
|
||||
|
||||
@@ -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.
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
<script src="cordova.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
68
framework/build.gradle
Normal file
68
framework/build.gradle
Normal file
@@ -0,0 +1,68 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
// Switch the Android Gradle plugin version requirement depending on the
|
||||
// installed version of Gradle. This dependency is documented at
|
||||
// http://tools.android.com/tech-docs/new-build-system/version-compatibility
|
||||
// and https://issues.apache.org/jira/browse/CB-8143
|
||||
if (gradle.gradleVersion >= "2.2") {
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:1.0.0+'
|
||||
}
|
||||
} else if (gradle.gradleVersion >= "2.1") {
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:0.14.0+'
|
||||
}
|
||||
} else {
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:0.12.0+'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'android-library'
|
||||
|
||||
android {
|
||||
compileSdkVersion cdvCompileSdkVersion
|
||||
buildToolsVersion cdvBuildToolsVersion
|
||||
publishNonDefault true
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_6
|
||||
targetCompatibility JavaVersion.VERSION_1_6
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
java.srcDirs = ['src']
|
||||
resources.srcDirs = ['src']
|
||||
aidl.srcDirs = ['src']
|
||||
renderscript.srcDirs = ['src']
|
||||
res.srcDirs = ['res']
|
||||
assets.srcDirs = ['assets']
|
||||
}
|
||||
}
|
||||
}
|
||||
192
framework/cordova.gradle
Normal file
192
framework/cordova.gradle
Normal file
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
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 java.util.regex.Pattern
|
||||
import groovy.swing.SwingBuilder
|
||||
|
||||
String doEnsureValueExists(filePath, props, key) {
|
||||
if (props.get(key) == null) {
|
||||
throw new GradleException(filePath + ': Missing key required "' + key + '"')
|
||||
}
|
||||
return props.get(key)
|
||||
}
|
||||
|
||||
String doGetProjectTarget() {
|
||||
def props = new Properties()
|
||||
file('project.properties').withReader { reader ->
|
||||
props.load(reader)
|
||||
}
|
||||
return doEnsureValueExists('project.properties', props, 'target')
|
||||
}
|
||||
|
||||
String[] getAvailableBuildTools() {
|
||||
def buildToolsDir = new File(getAndroidSdkDir(), "build-tools")
|
||||
buildToolsDir.list()
|
||||
.findAll { it ==~ /[0-9.]+/ }
|
||||
.sort { a, b -> compareVersions(b, a) }
|
||||
}
|
||||
|
||||
String doFindLatestInstalledBuildTools(String minBuildToolsVersion) {
|
||||
def availableBuildToolsVersions
|
||||
try {
|
||||
availableBuildToolsVersions = getAvailableBuildTools()
|
||||
} catch (e) {
|
||||
println "An exception occurred while trying to find the Android build tools."
|
||||
throw e
|
||||
}
|
||||
if (availableBuildToolsVersions.length > 0) {
|
||||
def highestBuildToolsVersion = availableBuildToolsVersions[0]
|
||||
if (compareVersions(highestBuildToolsVersion, minBuildToolsVersion) < 0) {
|
||||
throw new RuntimeException(
|
||||
"No usable Android build tools found. Highest installed version is " +
|
||||
highestBuildToolsVersion + "; minimum version required is " +
|
||||
minBuildToolsVersion + ".")
|
||||
}
|
||||
highestBuildToolsVersion
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
"No installed build tools found. Please install the Android build tools version " +
|
||||
minBuildToolsVersion + " or higher.")
|
||||
}
|
||||
}
|
||||
|
||||
// Return the first non-zero result of subtracting version list elements
|
||||
// pairwise. If they are all identical, return the difference in length of
|
||||
// the two lists.
|
||||
int compareVersionList(Collection aParts, Collection bParts) {
|
||||
def pairs = ([aParts, bParts]).transpose()
|
||||
pairs.findResult(aParts.size()-bParts.size()) {it[0] - it[1] != 0 ? it[0] - it[1] : null}
|
||||
}
|
||||
|
||||
// Compare two version strings, such as "19.0.0" and "18.1.1.0". If all matched
|
||||
// elements are identical, the longer version is the largest by this method.
|
||||
// Examples:
|
||||
// "19.0.0" > "19"
|
||||
// "19.0.1" > "19.0.0"
|
||||
// "19.1.0" > "19.0.1"
|
||||
// "19" > "18.999.999"
|
||||
int compareVersions(String a, String b) {
|
||||
def aParts = a.tokenize('.').collect {it.toInteger()}
|
||||
def bParts = b.tokenize('.').collect {it.toInteger()}
|
||||
compareVersionList(aParts, bParts)
|
||||
}
|
||||
|
||||
String getAndroidSdkDir() {
|
||||
def rootDir = project.rootDir
|
||||
def androidSdkDir = null
|
||||
String envVar = System.getenv("ANDROID_HOME")
|
||||
def localProperties = new File(rootDir, 'local.properties')
|
||||
String systemProperty = System.getProperty("android.home")
|
||||
if (envVar != null) {
|
||||
androidSdkDir = envVar
|
||||
} else if (localProperties.exists()) {
|
||||
Properties properties = new Properties()
|
||||
localProperties.withInputStream { instr ->
|
||||
properties.load(instr)
|
||||
}
|
||||
def sdkDirProp = properties.getProperty('sdk.dir')
|
||||
if (sdkDirProp != null) {
|
||||
androidSdkDir = sdkDirProp
|
||||
} else {
|
||||
sdkDirProp = properties.getProperty('android.dir')
|
||||
if (sdkDirProp != null) {
|
||||
androidSdkDir = (new File(rootDir, sdkDirProp)).getAbsolutePath()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (androidSdkDir == null && systemProperty != null) {
|
||||
androidSdkDir = systemProperty
|
||||
}
|
||||
if (androidSdkDir == null) {
|
||||
throw new RuntimeException(
|
||||
"Unable to determine Android SDK directory.")
|
||||
}
|
||||
androidSdkDir
|
||||
}
|
||||
|
||||
def doExtractIntFromManifest(name) {
|
||||
def manifestFile = file(android.sourceSets.main.manifest.srcFile)
|
||||
def pattern = Pattern.compile(name + "=\"(\\d+)\"")
|
||||
def matcher = pattern.matcher(manifestFile.getText())
|
||||
matcher.find()
|
||||
return Integer.parseInt(matcher.group(1))
|
||||
}
|
||||
|
||||
def doPromptForPassword(msg) {
|
||||
if (System.console() == null) {
|
||||
def ret = null
|
||||
new SwingBuilder().edt {
|
||||
dialog(modal: true, title: 'Enter password', alwaysOnTop: true, resizable: false, locationRelativeTo: null, pack: true, show: true) {
|
||||
vbox {
|
||||
label(text: msg)
|
||||
def input = passwordField()
|
||||
button(defaultButton: true, text: 'OK', actionPerformed: {
|
||||
ret = input.password;
|
||||
dispose();
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!ret) {
|
||||
throw new GradleException('User canceled build')
|
||||
}
|
||||
return new String(ret)
|
||||
} else {
|
||||
return System.console().readPassword('\n' + msg);
|
||||
}
|
||||
}
|
||||
|
||||
def doGetConfigXml() {
|
||||
def xml = file("res/xml/config.xml").getText()
|
||||
// Disable namespace awareness since Cordova doesn't use them properly
|
||||
return new XmlParser(false, false).parseText(xml)
|
||||
}
|
||||
|
||||
def doGetConfigPreference(name, defaultValue) {
|
||||
name = name.toLowerCase()
|
||||
def root = doGetConfigXml()
|
||||
|
||||
def ret = defaultValue
|
||||
root.preference.each { it ->
|
||||
def attrName = it.attribute("name")
|
||||
if (attrName && attrName.toLowerCase() == name) {
|
||||
ret = it.attribute("value")
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Properties exported here are visible to all plugins.
|
||||
ext {
|
||||
// These helpers are shared, but are not guaranteed to be stable / unchanged.
|
||||
privateHelpers = {}
|
||||
privateHelpers.getProjectTarget = { doGetProjectTarget() }
|
||||
privateHelpers.findLatestInstalledBuildTools = { doFindLatestInstalledBuildTools('19.1.0') }
|
||||
privateHelpers.extractIntFromManifest = { name -> doExtractIntFromManifest(name) }
|
||||
privateHelpers.promptForPassword = { msg -> doPromptForPassword(msg) }
|
||||
privateHelpers.ensureValueExists = { filePath, props, key -> doEnsureValueExists(filePath, props, key) }
|
||||
|
||||
// These helpers can be used by plugins / projects and will not change.
|
||||
cdvHelpers = {}
|
||||
// Returns a XmlParser for the config.xml. Added in 4.1.0.
|
||||
cdvHelpers.getConfigXml = { doGetConfigXml() }
|
||||
// Returns the value for the desired <preference>. Added in 4.1.0.
|
||||
cdvHelpers.getConfigPreference = { name, defaultValue -> doGetConfigPreference(name, defaultValue) }
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
# Indicates whether an apk should be generated for each density.
|
||||
split.density=false
|
||||
# Project target.
|
||||
target=android-19
|
||||
target=android-22
|
||||
apk-configurations=
|
||||
renderscript.opt.level=O0
|
||||
android.library=true
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.9 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.0 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 92 KiB |
@@ -1,29 +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.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
>
|
||||
<WebView android:id="@+id/appView"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_width="fill_parent"
|
||||
/>
|
||||
</LinearLayout>
|
||||
@@ -1,23 +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.
|
||||
-->
|
||||
<resources>
|
||||
<string name="app_name">Cordova</string>
|
||||
<string name="go">Snap</string>
|
||||
</resources>
|
||||
@@ -1,140 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed 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.squareup.okhttp;
|
||||
|
||||
import com.squareup.okhttp.internal.Util;
|
||||
import java.net.Proxy;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.List;
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
import static com.squareup.okhttp.internal.Util.equal;
|
||||
|
||||
/**
|
||||
* A specification for a connection to an origin server. For simple connections,
|
||||
* this is the server's hostname and port. If an explicit proxy is requested (or
|
||||
* {@link Proxy#NO_PROXY no proxy} is explicitly requested), this also includes
|
||||
* that proxy information. For secure connections the address also includes the
|
||||
* SSL socket factory and hostname verifier.
|
||||
*
|
||||
* <p>HTTP requests that share the same {@code Address} may also share the same
|
||||
* {@link Connection}.
|
||||
*/
|
||||
public final class Address {
|
||||
final Proxy proxy;
|
||||
final String uriHost;
|
||||
final int uriPort;
|
||||
final SSLSocketFactory sslSocketFactory;
|
||||
final HostnameVerifier hostnameVerifier;
|
||||
final OkAuthenticator authenticator;
|
||||
final List<String> transports;
|
||||
|
||||
public Address(String uriHost, int uriPort, SSLSocketFactory sslSocketFactory,
|
||||
HostnameVerifier hostnameVerifier, OkAuthenticator authenticator, Proxy proxy,
|
||||
List<String> transports) throws UnknownHostException {
|
||||
if (uriHost == null) throw new NullPointerException("uriHost == null");
|
||||
if (uriPort <= 0) throw new IllegalArgumentException("uriPort <= 0: " + uriPort);
|
||||
if (authenticator == null) throw new IllegalArgumentException("authenticator == null");
|
||||
if (transports == null) throw new IllegalArgumentException("transports == null");
|
||||
this.proxy = proxy;
|
||||
this.uriHost = uriHost;
|
||||
this.uriPort = uriPort;
|
||||
this.sslSocketFactory = sslSocketFactory;
|
||||
this.hostnameVerifier = hostnameVerifier;
|
||||
this.authenticator = authenticator;
|
||||
this.transports = Util.immutableList(transports);
|
||||
}
|
||||
|
||||
/** Returns the hostname of the origin server. */
|
||||
public String getUriHost() {
|
||||
return uriHost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the port of the origin server; typically 80 or 443. Unlike
|
||||
* may {@code getPort()} accessors, this method never returns -1.
|
||||
*/
|
||||
public int getUriPort() {
|
||||
return uriPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SSL socket factory, or null if this is not an HTTPS
|
||||
* address.
|
||||
*/
|
||||
public SSLSocketFactory getSslSocketFactory() {
|
||||
return sslSocketFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hostname verifier, or null if this is not an HTTPS
|
||||
* address.
|
||||
*/
|
||||
public HostnameVerifier getHostnameVerifier() {
|
||||
return hostnameVerifier;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the client's authenticator. This method never returns null.
|
||||
*/
|
||||
public OkAuthenticator getAuthenticator() {
|
||||
return authenticator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the client's transports. This method always returns a non-null list
|
||||
* that contains "http/1.1", possibly among other transports.
|
||||
*/
|
||||
public List<String> getTransports() {
|
||||
return transports;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this address's explicitly-specified HTTP proxy, or null to
|
||||
* delegate to the HTTP client's proxy selector.
|
||||
*/
|
||||
public Proxy getProxy() {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
@Override public boolean equals(Object other) {
|
||||
if (other instanceof Address) {
|
||||
Address that = (Address) other;
|
||||
return equal(this.proxy, that.proxy)
|
||||
&& this.uriHost.equals(that.uriHost)
|
||||
&& this.uriPort == that.uriPort
|
||||
&& equal(this.sslSocketFactory, that.sslSocketFactory)
|
||||
&& equal(this.hostnameVerifier, that.hostnameVerifier)
|
||||
&& equal(this.authenticator, that.authenticator)
|
||||
&& equal(this.transports, that.transports);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public int hashCode() {
|
||||
int result = 17;
|
||||
result = 31 * result + uriHost.hashCode();
|
||||
result = 31 * result + uriPort;
|
||||
result = 31 * result + (sslSocketFactory != null ? sslSocketFactory.hashCode() : 0);
|
||||
result = 31 * result + (hostnameVerifier != null ? hostnameVerifier.hashCode() : 0);
|
||||
result = 31 * result + (authenticator != null ? authenticator.hashCode() : 0);
|
||||
result = 31 * result + (proxy != null ? proxy.hashCode() : 0);
|
||||
result = 31 * result + transports.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,335 +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.squareup.okhttp;
|
||||
|
||||
import com.squareup.okhttp.internal.Platform;
|
||||
import com.squareup.okhttp.internal.http.HttpAuthenticator;
|
||||
import com.squareup.okhttp.internal.http.HttpEngine;
|
||||
import com.squareup.okhttp.internal.http.HttpTransport;
|
||||
import com.squareup.okhttp.internal.http.RawHeaders;
|
||||
import com.squareup.okhttp.internal.http.SpdyTransport;
|
||||
import com.squareup.okhttp.internal.spdy.SpdyConnection;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Proxy;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
|
||||
import static java.net.HttpURLConnection.HTTP_OK;
|
||||
import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
|
||||
|
||||
/**
|
||||
* Holds the sockets and streams of an HTTP, HTTPS, or HTTPS+SPDY connection,
|
||||
* which may be used for multiple HTTP request/response exchanges. Connections
|
||||
* may be direct to the origin server or via a proxy.
|
||||
*
|
||||
* <p>Typically instances of this class are created, connected and exercised
|
||||
* automatically by the HTTP client. Applications may use this class to monitor
|
||||
* HTTP connections as members of a {@link ConnectionPool connection pool}.
|
||||
*
|
||||
* <p>Do not confuse this class with the misnamed {@code HttpURLConnection},
|
||||
* which isn't so much a connection as a single request/response exchange.
|
||||
*
|
||||
* <h3>Modern TLS</h3>
|
||||
* There are tradeoffs when selecting which options to include when negotiating
|
||||
* a secure connection to a remote host. Newer TLS options are quite useful:
|
||||
* <ul>
|
||||
* <li>Server Name Indication (SNI) enables one IP address to negotiate secure
|
||||
* connections for multiple domain names.
|
||||
* <li>Next Protocol Negotiation (NPN) enables the HTTPS port (443) to be used
|
||||
* for both HTTP and SPDY transports.
|
||||
* </ul>
|
||||
* Unfortunately, older HTTPS servers refuse to connect when such options are
|
||||
* presented. Rather than avoiding these options entirely, this class allows a
|
||||
* connection to be attempted with modern options and then retried without them
|
||||
* should the attempt fail.
|
||||
*/
|
||||
public final class Connection implements Closeable {
|
||||
private static final byte[] NPN_PROTOCOLS = new byte[] {
|
||||
6, 's', 'p', 'd', 'y', '/', '3',
|
||||
8, 'h', 't', 't', 'p', '/', '1', '.', '1'
|
||||
};
|
||||
private static final byte[] SPDY3 = new byte[] {
|
||||
's', 'p', 'd', 'y', '/', '3'
|
||||
};
|
||||
private static final byte[] HTTP_11 = new byte[] {
|
||||
'h', 't', 't', 'p', '/', '1', '.', '1'
|
||||
};
|
||||
|
||||
private final Route route;
|
||||
|
||||
private Socket socket;
|
||||
private InputStream in;
|
||||
private OutputStream out;
|
||||
private boolean connected = false;
|
||||
private SpdyConnection spdyConnection;
|
||||
private int httpMinorVersion = 1; // Assume HTTP/1.1
|
||||
private long idleStartTimeNs;
|
||||
|
||||
public Connection(Route route) {
|
||||
this.route = route;
|
||||
}
|
||||
|
||||
public void connect(int connectTimeout, int readTimeout, TunnelRequest tunnelRequest)
|
||||
throws IOException {
|
||||
if (connected) throw new IllegalStateException("already connected");
|
||||
|
||||
socket = (route.proxy.type() != Proxy.Type.HTTP) ? new Socket(route.proxy) : new Socket();
|
||||
Platform.get().connectSocket(socket, route.inetSocketAddress, connectTimeout);
|
||||
socket.setSoTimeout(readTimeout);
|
||||
in = socket.getInputStream();
|
||||
out = socket.getOutputStream();
|
||||
|
||||
if (route.address.sslSocketFactory != null) {
|
||||
upgradeToTls(tunnelRequest);
|
||||
} else {
|
||||
streamWrapper();
|
||||
}
|
||||
connected = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@code SSLSocket} and perform the TLS handshake and certificate
|
||||
* validation.
|
||||
*/
|
||||
private void upgradeToTls(TunnelRequest tunnelRequest) throws IOException {
|
||||
Platform platform = Platform.get();
|
||||
|
||||
// Make an SSL Tunnel on the first message pair of each SSL + proxy connection.
|
||||
if (requiresTunnel()) {
|
||||
makeTunnel(tunnelRequest);
|
||||
}
|
||||
|
||||
// Create the wrapper over connected socket.
|
||||
socket = route.address.sslSocketFactory
|
||||
.createSocket(socket, route.address.uriHost, route.address.uriPort, true /* autoClose */);
|
||||
SSLSocket sslSocket = (SSLSocket) socket;
|
||||
if (route.modernTls) {
|
||||
platform.enableTlsExtensions(sslSocket, route.address.uriHost);
|
||||
} else {
|
||||
platform.supportTlsIntolerantServer(sslSocket);
|
||||
}
|
||||
|
||||
boolean useNpn = route.modernTls && route.address.transports.contains("spdy/3");
|
||||
if (useNpn) {
|
||||
platform.setNpnProtocols(sslSocket, NPN_PROTOCOLS);
|
||||
}
|
||||
|
||||
// Force handshake. This can throw!
|
||||
sslSocket.startHandshake();
|
||||
|
||||
// Verify that the socket's certificates are acceptable for the target host.
|
||||
if (!route.address.hostnameVerifier.verify(route.address.uriHost, sslSocket.getSession())) {
|
||||
throw new IOException("Hostname '" + route.address.uriHost + "' was not verified");
|
||||
}
|
||||
|
||||
out = sslSocket.getOutputStream();
|
||||
in = sslSocket.getInputStream();
|
||||
streamWrapper();
|
||||
|
||||
byte[] selectedProtocol;
|
||||
if (useNpn && (selectedProtocol = platform.getNpnSelectedProtocol(sslSocket)) != null) {
|
||||
if (Arrays.equals(selectedProtocol, SPDY3)) {
|
||||
sslSocket.setSoTimeout(0); // SPDY timeouts are set per-stream.
|
||||
spdyConnection = new SpdyConnection.Builder(route.address.getUriHost(), true, in, out)
|
||||
.build();
|
||||
spdyConnection.sendConnectionHeader();
|
||||
} else if (!Arrays.equals(selectedProtocol, HTTP_11)) {
|
||||
throw new IOException(
|
||||
"Unexpected NPN transport " + new String(selectedProtocol, "ISO-8859-1"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if {@link #connect} has been attempted on this connection. */
|
||||
public boolean isConnected() {
|
||||
return connected;
|
||||
}
|
||||
|
||||
@Override public void close() throws IOException {
|
||||
socket.close();
|
||||
}
|
||||
|
||||
/** Returns the route used by this connection. */
|
||||
public Route getRoute() {
|
||||
return route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the socket that this connection uses, or null if the connection
|
||||
* is not currently connected.
|
||||
*/
|
||||
public Socket getSocket() {
|
||||
return socket;
|
||||
}
|
||||
|
||||
/** Returns true if this connection is alive. */
|
||||
public boolean isAlive() {
|
||||
return !socket.isClosed() && !socket.isInputShutdown() && !socket.isOutputShutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if we are confident that we can read data from this
|
||||
* connection. This is more expensive and more accurate than {@link
|
||||
* #isAlive()}; callers should check {@link #isAlive()} first.
|
||||
*/
|
||||
public boolean isReadable() {
|
||||
if (!(in instanceof BufferedInputStream)) {
|
||||
return true; // Optimistic.
|
||||
}
|
||||
if (isSpdy()) {
|
||||
return true; // Optimistic. We can't test SPDY because its streams are in use.
|
||||
}
|
||||
BufferedInputStream bufferedInputStream = (BufferedInputStream) in;
|
||||
try {
|
||||
int readTimeout = socket.getSoTimeout();
|
||||
try {
|
||||
socket.setSoTimeout(1);
|
||||
bufferedInputStream.mark(1);
|
||||
if (bufferedInputStream.read() == -1) {
|
||||
return false; // Stream is exhausted; socket is closed.
|
||||
}
|
||||
bufferedInputStream.reset();
|
||||
return true;
|
||||
} finally {
|
||||
socket.setSoTimeout(readTimeout);
|
||||
}
|
||||
} catch (SocketTimeoutException ignored) {
|
||||
return true; // Read timed out; socket is good.
|
||||
} catch (IOException e) {
|
||||
return false; // Couldn't read; socket is closed.
|
||||
}
|
||||
}
|
||||
|
||||
public void resetIdleStartTime() {
|
||||
if (spdyConnection != null) {
|
||||
throw new IllegalStateException("spdyConnection != null");
|
||||
}
|
||||
this.idleStartTimeNs = System.nanoTime();
|
||||
}
|
||||
|
||||
/** Returns true if this connection is idle. */
|
||||
public boolean isIdle() {
|
||||
return spdyConnection == null || spdyConnection.isIdle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this connection has been idle for longer than
|
||||
* {@code keepAliveDurationNs}.
|
||||
*/
|
||||
public boolean isExpired(long keepAliveDurationNs) {
|
||||
return getIdleStartTimeNs() < System.nanoTime() - keepAliveDurationNs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time in ns when this connection became idle. Undefined if
|
||||
* this connection is not idle.
|
||||
*/
|
||||
public long getIdleStartTimeNs() {
|
||||
return spdyConnection == null ? idleStartTimeNs : spdyConnection.getIdleStartTimeNs();
|
||||
}
|
||||
|
||||
/** Returns the transport appropriate for this connection. */
|
||||
public Object newTransport(HttpEngine httpEngine) throws IOException {
|
||||
return (spdyConnection != null)
|
||||
? new SpdyTransport(httpEngine, spdyConnection)
|
||||
: new HttpTransport(httpEngine, out, in);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this is a SPDY connection. Such connections can be used
|
||||
* in multiple HTTP requests simultaneously.
|
||||
*/
|
||||
public boolean isSpdy() {
|
||||
return spdyConnection != null;
|
||||
}
|
||||
|
||||
public SpdyConnection getSpdyConnection() {
|
||||
return spdyConnection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minor HTTP version that should be used for future requests on
|
||||
* this connection. Either 0 for HTTP/1.0, or 1 for HTTP/1.1. The default
|
||||
* value is 1 for new connections.
|
||||
*/
|
||||
public int getHttpMinorVersion() {
|
||||
return httpMinorVersion;
|
||||
}
|
||||
|
||||
public void setHttpMinorVersion(int httpMinorVersion) {
|
||||
this.httpMinorVersion = httpMinorVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the HTTP connection needs to tunnel one protocol over
|
||||
* another, such as when using HTTPS through an HTTP proxy. When doing so,
|
||||
* we must avoid buffering bytes intended for the higher-level protocol.
|
||||
*/
|
||||
public boolean requiresTunnel() {
|
||||
return route.address.sslSocketFactory != null && route.proxy.type() == Proxy.Type.HTTP;
|
||||
}
|
||||
|
||||
public void updateReadTimeout(int newTimeout) throws IOException {
|
||||
if (!connected) throw new IllegalStateException("updateReadTimeout - not connected");
|
||||
socket.setSoTimeout(newTimeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* To make an HTTPS connection over an HTTP proxy, send an unencrypted
|
||||
* CONNECT request to create the proxy connection. This may need to be
|
||||
* retried if the proxy requires authorization.
|
||||
*/
|
||||
private void makeTunnel(TunnelRequest tunnelRequest) throws IOException {
|
||||
RawHeaders requestHeaders = tunnelRequest.getRequestHeaders();
|
||||
while (true) {
|
||||
out.write(requestHeaders.toBytes());
|
||||
RawHeaders responseHeaders = RawHeaders.fromBytes(in);
|
||||
|
||||
switch (responseHeaders.getResponseCode()) {
|
||||
case HTTP_OK:
|
||||
return;
|
||||
case HTTP_PROXY_AUTH:
|
||||
requestHeaders = new RawHeaders(requestHeaders);
|
||||
URL url = new URL("https", tunnelRequest.host, tunnelRequest.port, "/");
|
||||
boolean credentialsFound = HttpAuthenticator.processAuthHeader(
|
||||
route.address.authenticator, HTTP_PROXY_AUTH, responseHeaders, requestHeaders,
|
||||
route.proxy, url);
|
||||
if (credentialsFound) {
|
||||
continue;
|
||||
} else {
|
||||
throw new IOException("Failed to authenticate with proxy");
|
||||
}
|
||||
default:
|
||||
throw new IOException(
|
||||
"Unexpected response code for CONNECT: " + responseHeaders.getResponseCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void streamWrapper() throws IOException {
|
||||
in = new BufferedInputStream(in, 4096);
|
||||
out = new BufferedOutputStream(out, 256);
|
||||
}
|
||||
}
|
||||
@@ -1,274 +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.squareup.okhttp;
|
||||
|
||||
import com.squareup.okhttp.internal.Platform;
|
||||
import com.squareup.okhttp.internal.Util;
|
||||
import java.net.SocketException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Manages reuse of HTTP and SPDY connections for reduced network latency. HTTP
|
||||
* requests that share the same {@link com.squareup.okhttp.Address} may share a
|
||||
* {@link com.squareup.okhttp.Connection}. This class implements the policy of
|
||||
* which connections to keep open for future use.
|
||||
*
|
||||
* <p>The {@link #getDefault() system-wide default} uses system properties for
|
||||
* tuning parameters:
|
||||
* <ul>
|
||||
* <li>{@code http.keepAlive} true if HTTP and SPDY connections should be
|
||||
* pooled at all. Default is true.
|
||||
* <li>{@code http.maxConnections} maximum number of idle connections to
|
||||
* each to keep in the pool. Default is 5.
|
||||
* <li>{@code http.keepAliveDuration} Time in milliseconds to keep the
|
||||
* connection alive in the pool before closing it. Default is 5 minutes.
|
||||
* This property isn't used by {@code HttpURLConnection}.
|
||||
* </ul>
|
||||
*
|
||||
* <p>The default instance <i>doesn't</i> adjust its configuration as system
|
||||
* properties are changed. This assumes that the applications that set these
|
||||
* parameters do so before making HTTP connections, and that this class is
|
||||
* initialized lazily.
|
||||
*/
|
||||
public class ConnectionPool {
|
||||
private static final int MAX_CONNECTIONS_TO_CLEANUP = 2;
|
||||
private static final long DEFAULT_KEEP_ALIVE_DURATION_MS = 5 * 60 * 1000; // 5 min
|
||||
|
||||
private static final ConnectionPool systemDefault;
|
||||
|
||||
static {
|
||||
String keepAlive = System.getProperty("http.keepAlive");
|
||||
String keepAliveDuration = System.getProperty("http.keepAliveDuration");
|
||||
String maxIdleConnections = System.getProperty("http.maxConnections");
|
||||
long keepAliveDurationMs = keepAliveDuration != null ? Long.parseLong(keepAliveDuration)
|
||||
: DEFAULT_KEEP_ALIVE_DURATION_MS;
|
||||
if (keepAlive != null && !Boolean.parseBoolean(keepAlive)) {
|
||||
systemDefault = new ConnectionPool(0, keepAliveDurationMs);
|
||||
} else if (maxIdleConnections != null) {
|
||||
systemDefault = new ConnectionPool(Integer.parseInt(maxIdleConnections), keepAliveDurationMs);
|
||||
} else {
|
||||
systemDefault = new ConnectionPool(5, keepAliveDurationMs);
|
||||
}
|
||||
}
|
||||
|
||||
/** The maximum number of idle connections for each address. */
|
||||
private final int maxIdleConnections;
|
||||
private final long keepAliveDurationNs;
|
||||
|
||||
private final LinkedList<Connection> connections = new LinkedList<Connection>();
|
||||
|
||||
/** We use a single background thread to cleanup expired connections. */
|
||||
private final ExecutorService executorService = new ThreadPoolExecutor(0, 1,
|
||||
60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
|
||||
Util.daemonThreadFactory("OkHttp ConnectionPool"));
|
||||
private final Callable<Void> connectionsCleanupCallable = new Callable<Void>() {
|
||||
@Override public Void call() throws Exception {
|
||||
List<Connection> expiredConnections = new ArrayList<Connection>(MAX_CONNECTIONS_TO_CLEANUP);
|
||||
int idleConnectionCount = 0;
|
||||
synchronized (ConnectionPool.this) {
|
||||
for (ListIterator<Connection> i = connections.listIterator(connections.size());
|
||||
i.hasPrevious(); ) {
|
||||
Connection connection = i.previous();
|
||||
if (!connection.isAlive() || connection.isExpired(keepAliveDurationNs)) {
|
||||
i.remove();
|
||||
expiredConnections.add(connection);
|
||||
if (expiredConnections.size() == MAX_CONNECTIONS_TO_CLEANUP) break;
|
||||
} else if (connection.isIdle()) {
|
||||
idleConnectionCount++;
|
||||
}
|
||||
}
|
||||
|
||||
for (ListIterator<Connection> i = connections.listIterator(connections.size());
|
||||
i.hasPrevious() && idleConnectionCount > maxIdleConnections; ) {
|
||||
Connection connection = i.previous();
|
||||
if (connection.isIdle()) {
|
||||
expiredConnections.add(connection);
|
||||
i.remove();
|
||||
--idleConnectionCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Connection expiredConnection : expiredConnections) {
|
||||
Util.closeQuietly(expiredConnection);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
public ConnectionPool(int maxIdleConnections, long keepAliveDurationMs) {
|
||||
this.maxIdleConnections = maxIdleConnections;
|
||||
this.keepAliveDurationNs = keepAliveDurationMs * 1000 * 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a snapshot of the connections in this pool, ordered from newest to
|
||||
* oldest. Waits for the cleanup callable to run if it is currently scheduled.
|
||||
*/
|
||||
List<Connection> getConnections() {
|
||||
waitForCleanupCallableToRun();
|
||||
synchronized (this) {
|
||||
return new ArrayList<Connection>(connections);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks until the executor service has processed all currently enqueued
|
||||
* jobs.
|
||||
*/
|
||||
private void waitForCleanupCallableToRun() {
|
||||
try {
|
||||
executorService.submit(new Runnable() {
|
||||
@Override public void run() {
|
||||
}
|
||||
}).get();
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
public static ConnectionPool getDefault() {
|
||||
return systemDefault;
|
||||
}
|
||||
|
||||
/** Returns total number of connections in the pool. */
|
||||
public synchronized int getConnectionCount() {
|
||||
return connections.size();
|
||||
}
|
||||
|
||||
/** Returns total number of spdy connections in the pool. */
|
||||
public synchronized int getSpdyConnectionCount() {
|
||||
int total = 0;
|
||||
for (Connection connection : connections) {
|
||||
if (connection.isSpdy()) total++;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/** Returns total number of http connections in the pool. */
|
||||
public synchronized int getHttpConnectionCount() {
|
||||
int total = 0;
|
||||
for (Connection connection : connections) {
|
||||
if (!connection.isSpdy()) total++;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/** Returns a recycled connection to {@code address}, or null if no such connection exists. */
|
||||
public synchronized Connection get(Address address) {
|
||||
Connection foundConnection = null;
|
||||
for (ListIterator<Connection> i = connections.listIterator(connections.size());
|
||||
i.hasPrevious(); ) {
|
||||
Connection connection = i.previous();
|
||||
if (!connection.getRoute().getAddress().equals(address)
|
||||
|| !connection.isAlive()
|
||||
|| System.nanoTime() - connection.getIdleStartTimeNs() >= keepAliveDurationNs) {
|
||||
continue;
|
||||
}
|
||||
i.remove();
|
||||
if (!connection.isSpdy()) {
|
||||
try {
|
||||
Platform.get().tagSocket(connection.getSocket());
|
||||
} catch (SocketException e) {
|
||||
Util.closeQuietly(connection);
|
||||
// When unable to tag, skip recycling and close
|
||||
Platform.get().logW("Unable to tagSocket(): " + e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
foundConnection = connection;
|
||||
break;
|
||||
}
|
||||
|
||||
if (foundConnection != null && foundConnection.isSpdy()) {
|
||||
connections.addFirst(foundConnection); // Add it back after iteration.
|
||||
}
|
||||
|
||||
executorService.submit(connectionsCleanupCallable);
|
||||
return foundConnection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives {@code connection} to the pool. The pool may store the connection,
|
||||
* or close it, as its policy describes.
|
||||
*
|
||||
* <p>It is an error to use {@code connection} after calling this method.
|
||||
*/
|
||||
public void recycle(Connection connection) {
|
||||
if (connection.isSpdy()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!connection.isAlive()) {
|
||||
Util.closeQuietly(connection);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Platform.get().untagSocket(connection.getSocket());
|
||||
} catch (SocketException e) {
|
||||
// When unable to remove tagging, skip recycling and close.
|
||||
Platform.get().logW("Unable to untagSocket(): " + e);
|
||||
Util.closeQuietly(connection);
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
connections.addFirst(connection);
|
||||
connection.resetIdleStartTime();
|
||||
}
|
||||
|
||||
executorService.submit(connectionsCleanupCallable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shares the SPDY connection with the pool. Callers to this method may
|
||||
* continue to use {@code connection}.
|
||||
*/
|
||||
public void maybeShare(Connection connection) {
|
||||
executorService.submit(connectionsCleanupCallable);
|
||||
if (!connection.isSpdy()) {
|
||||
// Only SPDY connections are sharable.
|
||||
return;
|
||||
}
|
||||
if (connection.isAlive()) {
|
||||
synchronized (this) {
|
||||
connections.addFirst(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Close and remove all connections in the pool. */
|
||||
public void evictAll() {
|
||||
List<Connection> connections;
|
||||
synchronized (this) {
|
||||
connections = new ArrayList<Connection>(this.connections);
|
||||
this.connections.clear();
|
||||
}
|
||||
|
||||
for (Connection connection : connections) {
|
||||
Util.closeQuietly(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Square, Inc.
|
||||
*
|
||||
* Licensed 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.squareup.okhttp;
|
||||
|
||||
import com.squareup.okhttp.internal.http.ResponseHeaders;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
final class Dispatcher {
|
||||
// TODO: thread pool size should be configurable; possibly configurable per host.
|
||||
private final ThreadPoolExecutor executorService = new ThreadPoolExecutor(
|
||||
8, 8, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
|
||||
private final Map<Object, List<Job>> enqueuedJobs = new LinkedHashMap<Object, List<Job>>();
|
||||
|
||||
public synchronized void enqueue(
|
||||
OkHttpClient client, Request request, Response.Receiver responseReceiver) {
|
||||
Job job = new Job(this, client, request, responseReceiver);
|
||||
List<Job> jobsForTag = enqueuedJobs.get(request.tag());
|
||||
if (jobsForTag == null) {
|
||||
jobsForTag = new ArrayList<Job>(2);
|
||||
enqueuedJobs.put(request.tag(), jobsForTag);
|
||||
}
|
||||
jobsForTag.add(job);
|
||||
executorService.execute(job);
|
||||
}
|
||||
|
||||
public synchronized void cancel(Object tag) {
|
||||
List<Job> jobs = enqueuedJobs.remove(tag);
|
||||
if (jobs == null) return;
|
||||
for (Job job : jobs) {
|
||||
executorService.remove(job);
|
||||
}
|
||||
}
|
||||
|
||||
synchronized void finished(Job job) {
|
||||
List<Job> jobs = enqueuedJobs.get(job.tag());
|
||||
if (jobs != null) jobs.remove(job);
|
||||
}
|
||||
|
||||
static class RealResponseBody extends Response.Body {
|
||||
private final ResponseHeaders responseHeaders;
|
||||
private final InputStream in;
|
||||
|
||||
RealResponseBody(ResponseHeaders responseHeaders, InputStream in) {
|
||||
this.responseHeaders = responseHeaders;
|
||||
this.in = in;
|
||||
}
|
||||
|
||||
@Override public boolean ready() throws IOException {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public MediaType contentType() {
|
||||
String contentType = responseHeaders.getContentType();
|
||||
return contentType != null ? MediaType.parse(contentType) : null;
|
||||
}
|
||||
|
||||
@Override public long contentLength() {
|
||||
return responseHeaders.getContentLength();
|
||||
}
|
||||
|
||||
@Override public InputStream byteStream() throws IOException {
|
||||
return in;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Square, Inc.
|
||||
*
|
||||
* Licensed 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.squareup.okhttp;
|
||||
|
||||
/**
|
||||
* A failure attempting to retrieve an HTTP response.
|
||||
*
|
||||
* <h3>Warning: Experimental OkHttp 2.0 API</h3>
|
||||
* This class is in beta. APIs are subject to change!
|
||||
*/
|
||||
/* OkHttp 2.0: public */ class Failure {
|
||||
private final Request request;
|
||||
private final Throwable exception;
|
||||
|
||||
private Failure(Builder builder) {
|
||||
this.request = builder.request;
|
||||
this.exception = builder.exception;
|
||||
}
|
||||
|
||||
public Request request() {
|
||||
return request;
|
||||
}
|
||||
|
||||
public Throwable exception() {
|
||||
return exception;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private Request request;
|
||||
private Throwable exception;
|
||||
|
||||
public Builder request(Request request) {
|
||||
this.request = request;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder exception(Throwable exception) {
|
||||
this.exception = exception;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Failure build() {
|
||||
return new Failure(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,722 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed 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.squareup.okhttp;
|
||||
|
||||
import com.squareup.okhttp.internal.Base64;
|
||||
import com.squareup.okhttp.internal.DiskLruCache;
|
||||
import com.squareup.okhttp.internal.StrictLineReader;
|
||||
import com.squareup.okhttp.internal.Util;
|
||||
import com.squareup.okhttp.internal.http.HttpEngine;
|
||||
import com.squareup.okhttp.internal.http.HttpURLConnectionImpl;
|
||||
import com.squareup.okhttp.internal.http.HttpsEngine;
|
||||
import com.squareup.okhttp.internal.http.HttpsURLConnectionImpl;
|
||||
import com.squareup.okhttp.internal.http.RawHeaders;
|
||||
import com.squareup.okhttp.internal.http.ResponseHeaders;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.net.CacheRequest;
|
||||
import java.net.CacheResponse;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.ResponseCache;
|
||||
import java.net.SecureCacheResponse;
|
||||
import java.net.URI;
|
||||
import java.net.URLConnection;
|
||||
import java.security.Principal;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
|
||||
import static com.squareup.okhttp.internal.Util.US_ASCII;
|
||||
import static com.squareup.okhttp.internal.Util.UTF_8;
|
||||
|
||||
/**
|
||||
* Caches HTTP and HTTPS responses to the filesystem so they may be reused,
|
||||
* saving time and bandwidth.
|
||||
*
|
||||
* <h3>Cache Optimization</h3>
|
||||
* To measure cache effectiveness, this class tracks three statistics:
|
||||
* <ul>
|
||||
* <li><strong>{@link #getRequestCount() Request Count:}</strong> the number
|
||||
* of HTTP requests issued since this cache was created.
|
||||
* <li><strong>{@link #getNetworkCount() Network Count:}</strong> the
|
||||
* number of those requests that required network use.
|
||||
* <li><strong>{@link #getHitCount() Hit Count:}</strong> the number of
|
||||
* those requests whose responses were served by the cache.
|
||||
* </ul>
|
||||
* Sometimes a request will result in a conditional cache hit. If the cache
|
||||
* contains a stale copy of the response, the client will issue a conditional
|
||||
* {@code GET}. The server will then send either the updated response if it has
|
||||
* changed, or a short 'not modified' response if the client's copy is still
|
||||
* valid. Such responses increment both the network count and hit count.
|
||||
*
|
||||
* <p>The best way to improve the cache hit rate is by configuring the web
|
||||
* server to return cacheable responses. Although this client honors all <a
|
||||
* href="http://www.ietf.org/rfc/rfc2616.txt">HTTP/1.1 (RFC 2068)</a> cache
|
||||
* headers, it doesn't cache partial responses.
|
||||
*
|
||||
* <h3>Force a Network Response</h3>
|
||||
* In some situations, such as after a user clicks a 'refresh' button, it may be
|
||||
* necessary to skip the cache, and fetch data directly from the server. To force
|
||||
* a full refresh, add the {@code no-cache} directive: <pre> {@code
|
||||
* connection.addRequestProperty("Cache-Control", "no-cache");
|
||||
* }</pre>
|
||||
* If it is only necessary to force a cached response to be validated by the
|
||||
* server, use the more efficient {@code max-age=0} instead: <pre> {@code
|
||||
* connection.addRequestProperty("Cache-Control", "max-age=0");
|
||||
* }</pre>
|
||||
*
|
||||
* <h3>Force a Cache Response</h3>
|
||||
* Sometimes you'll want to show resources if they are available immediately,
|
||||
* but not otherwise. This can be used so your application can show
|
||||
* <i>something</i> while waiting for the latest data to be downloaded. To
|
||||
* restrict a request to locally-cached resources, add the {@code
|
||||
* only-if-cached} directive: <pre> {@code
|
||||
* try {
|
||||
* connection.addRequestProperty("Cache-Control", "only-if-cached");
|
||||
* InputStream cached = connection.getInputStream();
|
||||
* // the resource was cached! show it
|
||||
* } catch (FileNotFoundException e) {
|
||||
* // the resource was not cached
|
||||
* }
|
||||
* }</pre>
|
||||
* This technique works even better in situations where a stale response is
|
||||
* better than no response. To permit stale cached responses, use the {@code
|
||||
* max-stale} directive with the maximum staleness in seconds: <pre> {@code
|
||||
* int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
|
||||
* connection.addRequestProperty("Cache-Control", "max-stale=" + maxStale);
|
||||
* }</pre>
|
||||
*/
|
||||
public final class HttpResponseCache extends ResponseCache {
|
||||
// TODO: add APIs to iterate the cache?
|
||||
private static final int VERSION = 201105;
|
||||
private static final int ENTRY_METADATA = 0;
|
||||
private static final int ENTRY_BODY = 1;
|
||||
private static final int ENTRY_COUNT = 2;
|
||||
|
||||
private final DiskLruCache cache;
|
||||
|
||||
/* read and write statistics, all guarded by 'this' */
|
||||
private int writeSuccessCount;
|
||||
private int writeAbortCount;
|
||||
private int networkCount;
|
||||
private int hitCount;
|
||||
private int requestCount;
|
||||
|
||||
/**
|
||||
* Although this class only exposes the limited ResponseCache API, it
|
||||
* implements the full OkResponseCache interface. This field is used as a
|
||||
* package private handle to the complete implementation. It delegates to
|
||||
* public and private members of this type.
|
||||
*/
|
||||
final OkResponseCache okResponseCache = new OkResponseCache() {
|
||||
@Override public CacheResponse get(URI uri, String requestMethod,
|
||||
Map<String, List<String>> requestHeaders) throws IOException {
|
||||
return HttpResponseCache.this.get(uri, requestMethod, requestHeaders);
|
||||
}
|
||||
|
||||
@Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
|
||||
return HttpResponseCache.this.put(uri, connection);
|
||||
}
|
||||
|
||||
@Override public void maybeRemove(String requestMethod, URI uri) throws IOException {
|
||||
HttpResponseCache.this.maybeRemove(requestMethod, uri);
|
||||
}
|
||||
|
||||
@Override public void update(
|
||||
CacheResponse conditionalCacheHit, HttpURLConnection connection) throws IOException {
|
||||
HttpResponseCache.this.update(conditionalCacheHit, connection);
|
||||
}
|
||||
|
||||
@Override public void trackConditionalCacheHit() {
|
||||
HttpResponseCache.this.trackConditionalCacheHit();
|
||||
}
|
||||
|
||||
@Override public void trackResponse(ResponseSource source) {
|
||||
HttpResponseCache.this.trackResponse(source);
|
||||
}
|
||||
};
|
||||
|
||||
public HttpResponseCache(File directory, long maxSize) throws IOException {
|
||||
cache = DiskLruCache.open(directory, VERSION, ENTRY_COUNT, maxSize);
|
||||
}
|
||||
|
||||
private String uriToKey(URI uri) {
|
||||
return Util.hash(uri.toString());
|
||||
}
|
||||
|
||||
@Override public CacheResponse get(URI uri, String requestMethod,
|
||||
Map<String, List<String>> requestHeaders) {
|
||||
String key = uriToKey(uri);
|
||||
DiskLruCache.Snapshot snapshot;
|
||||
Entry entry;
|
||||
try {
|
||||
snapshot = cache.get(key);
|
||||
if (snapshot == null) {
|
||||
return null;
|
||||
}
|
||||
entry = new Entry(snapshot.getInputStream(ENTRY_METADATA));
|
||||
} catch (IOException e) {
|
||||
// Give up because the cache cannot be read.
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!entry.matches(uri, requestMethod, requestHeaders)) {
|
||||
snapshot.close();
|
||||
return null;
|
||||
}
|
||||
|
||||
return entry.isHttps() ? new EntrySecureCacheResponse(entry, snapshot)
|
||||
: new EntryCacheResponse(entry, snapshot);
|
||||
}
|
||||
|
||||
@Override public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException {
|
||||
if (!(urlConnection instanceof HttpURLConnection)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
HttpURLConnection httpConnection = (HttpURLConnection) urlConnection;
|
||||
String requestMethod = httpConnection.getRequestMethod();
|
||||
|
||||
if (maybeRemove(requestMethod, uri)) {
|
||||
return null;
|
||||
}
|
||||
if (!requestMethod.equals("GET")) {
|
||||
// Don't cache non-GET responses. We're technically allowed to cache
|
||||
// HEAD requests and some POST requests, but the complexity of doing
|
||||
// so is high and the benefit is low.
|
||||
return null;
|
||||
}
|
||||
|
||||
HttpEngine httpEngine = getHttpEngine(httpConnection);
|
||||
if (httpEngine == null) {
|
||||
// Don't cache unless the HTTP implementation is ours.
|
||||
return null;
|
||||
}
|
||||
|
||||
ResponseHeaders response = httpEngine.getResponseHeaders();
|
||||
if (response.hasVaryAll()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
RawHeaders varyHeaders =
|
||||
httpEngine.getRequestHeaders().getHeaders().getAll(response.getVaryFields());
|
||||
Entry entry = new Entry(uri, varyHeaders, httpConnection);
|
||||
DiskLruCache.Editor editor = null;
|
||||
try {
|
||||
editor = cache.edit(uriToKey(uri));
|
||||
if (editor == null) {
|
||||
return null;
|
||||
}
|
||||
entry.writeTo(editor);
|
||||
return new CacheRequestImpl(editor);
|
||||
} catch (IOException e) {
|
||||
abortQuietly(editor);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the supplied {@code requestMethod} potentially invalidates an entry in the
|
||||
* cache.
|
||||
*/
|
||||
private boolean maybeRemove(String requestMethod, URI uri) {
|
||||
if (requestMethod.equals("POST") || requestMethod.equals("PUT") || requestMethod.equals(
|
||||
"DELETE")) {
|
||||
try {
|
||||
cache.remove(uriToKey(uri));
|
||||
} catch (IOException ignored) {
|
||||
// The cache cannot be written.
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void update(CacheResponse conditionalCacheHit, HttpURLConnection httpConnection)
|
||||
throws IOException {
|
||||
HttpEngine httpEngine = getHttpEngine(httpConnection);
|
||||
URI uri = httpEngine.getUri();
|
||||
ResponseHeaders response = httpEngine.getResponseHeaders();
|
||||
RawHeaders varyHeaders =
|
||||
httpEngine.getRequestHeaders().getHeaders().getAll(response.getVaryFields());
|
||||
Entry entry = new Entry(uri, varyHeaders, httpConnection);
|
||||
DiskLruCache.Snapshot snapshot = (conditionalCacheHit instanceof EntryCacheResponse)
|
||||
? ((EntryCacheResponse) conditionalCacheHit).snapshot
|
||||
: ((EntrySecureCacheResponse) conditionalCacheHit).snapshot;
|
||||
DiskLruCache.Editor editor = null;
|
||||
try {
|
||||
editor = snapshot.edit(); // returns null if snapshot is not current
|
||||
if (editor != null) {
|
||||
entry.writeTo(editor);
|
||||
editor.commit();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
abortQuietly(editor);
|
||||
}
|
||||
}
|
||||
|
||||
private void abortQuietly(DiskLruCache.Editor editor) {
|
||||
// Give up because the cache cannot be written.
|
||||
try {
|
||||
if (editor != null) {
|
||||
editor.abort();
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private HttpEngine getHttpEngine(URLConnection httpConnection) {
|
||||
if (httpConnection instanceof HttpURLConnectionImpl) {
|
||||
return ((HttpURLConnectionImpl) httpConnection).getHttpEngine();
|
||||
} else if (httpConnection instanceof HttpsURLConnectionImpl) {
|
||||
return ((HttpsURLConnectionImpl) httpConnection).getHttpEngine();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the cache and deletes all of its stored values. This will delete
|
||||
* all files in the cache directory including files that weren't created by
|
||||
* the cache.
|
||||
*/
|
||||
public void delete() throws IOException {
|
||||
cache.delete();
|
||||
}
|
||||
|
||||
public synchronized int getWriteAbortCount() {
|
||||
return writeAbortCount;
|
||||
}
|
||||
|
||||
public synchronized int getWriteSuccessCount() {
|
||||
return writeSuccessCount;
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return cache.size();
|
||||
}
|
||||
|
||||
public long getMaxSize() {
|
||||
return cache.getMaxSize();
|
||||
}
|
||||
|
||||
public void flush() throws IOException {
|
||||
cache.flush();
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
cache.close();
|
||||
}
|
||||
|
||||
public File getDirectory() {
|
||||
return cache.getDirectory();
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return cache.isClosed();
|
||||
}
|
||||
|
||||
private synchronized void trackResponse(ResponseSource source) {
|
||||
requestCount++;
|
||||
|
||||
switch (source) {
|
||||
case CACHE:
|
||||
hitCount++;
|
||||
break;
|
||||
case CONDITIONAL_CACHE:
|
||||
case NETWORK:
|
||||
networkCount++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void trackConditionalCacheHit() {
|
||||
hitCount++;
|
||||
}
|
||||
|
||||
public synchronized int getNetworkCount() {
|
||||
return networkCount;
|
||||
}
|
||||
|
||||
public synchronized int getHitCount() {
|
||||
return hitCount;
|
||||
}
|
||||
|
||||
public synchronized int getRequestCount() {
|
||||
return requestCount;
|
||||
}
|
||||
|
||||
private final class CacheRequestImpl extends CacheRequest {
|
||||
private final DiskLruCache.Editor editor;
|
||||
private OutputStream cacheOut;
|
||||
private boolean done;
|
||||
private OutputStream body;
|
||||
|
||||
public CacheRequestImpl(final DiskLruCache.Editor editor) throws IOException {
|
||||
this.editor = editor;
|
||||
this.cacheOut = editor.newOutputStream(ENTRY_BODY);
|
||||
this.body = new FilterOutputStream(cacheOut) {
|
||||
@Override public void close() throws IOException {
|
||||
synchronized (HttpResponseCache.this) {
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
done = true;
|
||||
writeSuccessCount++;
|
||||
}
|
||||
super.close();
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
@Override public void write(byte[] buffer, int offset, int length) throws IOException {
|
||||
// Since we don't override "write(int oneByte)", we can write directly to "out"
|
||||
// and avoid the inefficient implementation from the FilterOutputStream.
|
||||
out.write(buffer, offset, length);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override public void abort() {
|
||||
synchronized (HttpResponseCache.this) {
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
done = true;
|
||||
writeAbortCount++;
|
||||
}
|
||||
Util.closeQuietly(cacheOut);
|
||||
try {
|
||||
editor.abort();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override public OutputStream getBody() throws IOException {
|
||||
return body;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Entry {
|
||||
private final String uri;
|
||||
private final RawHeaders varyHeaders;
|
||||
private final String requestMethod;
|
||||
private final RawHeaders responseHeaders;
|
||||
private final String cipherSuite;
|
||||
private final Certificate[] peerCertificates;
|
||||
private final Certificate[] localCertificates;
|
||||
|
||||
/**
|
||||
* Reads an entry from an input stream. A typical entry looks like this:
|
||||
* <pre>{@code
|
||||
* http://google.com/foo
|
||||
* GET
|
||||
* 2
|
||||
* Accept-Language: fr-CA
|
||||
* Accept-Charset: UTF-8
|
||||
* HTTP/1.1 200 OK
|
||||
* 3
|
||||
* Content-Type: image/png
|
||||
* Content-Length: 100
|
||||
* Cache-Control: max-age=600
|
||||
* }</pre>
|
||||
*
|
||||
* <p>A typical HTTPS file looks like this:
|
||||
* <pre>{@code
|
||||
* https://google.com/foo
|
||||
* GET
|
||||
* 2
|
||||
* Accept-Language: fr-CA
|
||||
* Accept-Charset: UTF-8
|
||||
* HTTP/1.1 200 OK
|
||||
* 3
|
||||
* Content-Type: image/png
|
||||
* Content-Length: 100
|
||||
* Cache-Control: max-age=600
|
||||
*
|
||||
* AES_256_WITH_MD5
|
||||
* 2
|
||||
* base64-encoded peerCertificate[0]
|
||||
* base64-encoded peerCertificate[1]
|
||||
* -1
|
||||
* }</pre>
|
||||
* The file is newline separated. The first two lines are the URL and
|
||||
* the request method. Next is the number of HTTP Vary request header
|
||||
* lines, followed by those lines.
|
||||
*
|
||||
* <p>Next is the response status line, followed by the number of HTTP
|
||||
* response header lines, followed by those lines.
|
||||
*
|
||||
* <p>HTTPS responses also contain SSL session information. This begins
|
||||
* with a blank line, and then a line containing the cipher suite. Next
|
||||
* is the length of the peer certificate chain. These certificates are
|
||||
* base64-encoded and appear each on their own line. The next line
|
||||
* contains the length of the local certificate chain. These
|
||||
* certificates are also base64-encoded and appear each on their own
|
||||
* line. A length of -1 is used to encode a null array.
|
||||
*/
|
||||
public Entry(InputStream in) throws IOException {
|
||||
try {
|
||||
StrictLineReader reader = new StrictLineReader(in, US_ASCII);
|
||||
uri = reader.readLine();
|
||||
requestMethod = reader.readLine();
|
||||
varyHeaders = new RawHeaders();
|
||||
int varyRequestHeaderLineCount = reader.readInt();
|
||||
for (int i = 0; i < varyRequestHeaderLineCount; i++) {
|
||||
varyHeaders.addLine(reader.readLine());
|
||||
}
|
||||
|
||||
responseHeaders = new RawHeaders();
|
||||
responseHeaders.setStatusLine(reader.readLine());
|
||||
int responseHeaderLineCount = reader.readInt();
|
||||
for (int i = 0; i < responseHeaderLineCount; i++) {
|
||||
responseHeaders.addLine(reader.readLine());
|
||||
}
|
||||
|
||||
if (isHttps()) {
|
||||
String blank = reader.readLine();
|
||||
if (blank.length() > 0) {
|
||||
throw new IOException("expected \"\" but was \"" + blank + "\"");
|
||||
}
|
||||
cipherSuite = reader.readLine();
|
||||
peerCertificates = readCertArray(reader);
|
||||
localCertificates = readCertArray(reader);
|
||||
} else {
|
||||
cipherSuite = null;
|
||||
peerCertificates = null;
|
||||
localCertificates = null;
|
||||
}
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
|
||||
public Entry(URI uri, RawHeaders varyHeaders, HttpURLConnection httpConnection)
|
||||
throws IOException {
|
||||
this.uri = uri.toString();
|
||||
this.varyHeaders = varyHeaders;
|
||||
this.requestMethod = httpConnection.getRequestMethod();
|
||||
this.responseHeaders = RawHeaders.fromMultimap(httpConnection.getHeaderFields(), true);
|
||||
|
||||
SSLSocket sslSocket = getSslSocket(httpConnection);
|
||||
if (sslSocket != null) {
|
||||
cipherSuite = sslSocket.getSession().getCipherSuite();
|
||||
Certificate[] peerCertificatesNonFinal = null;
|
||||
try {
|
||||
peerCertificatesNonFinal = sslSocket.getSession().getPeerCertificates();
|
||||
} catch (SSLPeerUnverifiedException ignored) {
|
||||
}
|
||||
peerCertificates = peerCertificatesNonFinal;
|
||||
localCertificates = sslSocket.getSession().getLocalCertificates();
|
||||
} else {
|
||||
cipherSuite = null;
|
||||
peerCertificates = null;
|
||||
localCertificates = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SSL socket used by {@code httpConnection} for HTTPS, nor null
|
||||
* if the connection isn't using HTTPS. Since we permit redirects across
|
||||
* protocols (HTTP to HTTPS or vice versa), the implementation type of the
|
||||
* connection doesn't necessarily match the implementation type of its HTTP
|
||||
* engine.
|
||||
*/
|
||||
private SSLSocket getSslSocket(HttpURLConnection httpConnection) {
|
||||
HttpEngine engine = httpConnection instanceof HttpsURLConnectionImpl
|
||||
? ((HttpsURLConnectionImpl) httpConnection).getHttpEngine()
|
||||
: ((HttpURLConnectionImpl) httpConnection).getHttpEngine();
|
||||
return engine instanceof HttpsEngine
|
||||
? ((HttpsEngine) engine).getSslSocket()
|
||||
: null;
|
||||
}
|
||||
|
||||
public void writeTo(DiskLruCache.Editor editor) throws IOException {
|
||||
OutputStream out = editor.newOutputStream(ENTRY_METADATA);
|
||||
Writer writer = new BufferedWriter(new OutputStreamWriter(out, UTF_8));
|
||||
|
||||
writer.write(uri + '\n');
|
||||
writer.write(requestMethod + '\n');
|
||||
writer.write(Integer.toString(varyHeaders.length()) + '\n');
|
||||
for (int i = 0; i < varyHeaders.length(); i++) {
|
||||
writer.write(varyHeaders.getFieldName(i) + ": " + varyHeaders.getValue(i) + '\n');
|
||||
}
|
||||
|
||||
writer.write(responseHeaders.getStatusLine() + '\n');
|
||||
writer.write(Integer.toString(responseHeaders.length()) + '\n');
|
||||
for (int i = 0; i < responseHeaders.length(); i++) {
|
||||
writer.write(responseHeaders.getFieldName(i) + ": " + responseHeaders.getValue(i) + '\n');
|
||||
}
|
||||
|
||||
if (isHttps()) {
|
||||
writer.write('\n');
|
||||
writer.write(cipherSuite + '\n');
|
||||
writeCertArray(writer, peerCertificates);
|
||||
writeCertArray(writer, localCertificates);
|
||||
}
|
||||
writer.close();
|
||||
}
|
||||
|
||||
private boolean isHttps() {
|
||||
return uri.startsWith("https://");
|
||||
}
|
||||
|
||||
private Certificate[] readCertArray(StrictLineReader reader) throws IOException {
|
||||
int length = reader.readInt();
|
||||
if (length == -1) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
|
||||
Certificate[] result = new Certificate[length];
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
String line = reader.readLine();
|
||||
byte[] bytes = Base64.decode(line.getBytes("US-ASCII"));
|
||||
result[i] = certificateFactory.generateCertificate(new ByteArrayInputStream(bytes));
|
||||
}
|
||||
return result;
|
||||
} catch (CertificateException e) {
|
||||
throw new IOException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void writeCertArray(Writer writer, Certificate[] certificates) throws IOException {
|
||||
if (certificates == null) {
|
||||
writer.write("-1\n");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
writer.write(Integer.toString(certificates.length) + '\n');
|
||||
for (Certificate certificate : certificates) {
|
||||
byte[] bytes = certificate.getEncoded();
|
||||
String line = Base64.encode(bytes);
|
||||
writer.write(line + '\n');
|
||||
}
|
||||
} catch (CertificateEncodingException e) {
|
||||
throw new IOException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean matches(URI uri, String requestMethod,
|
||||
Map<String, List<String>> requestHeaders) {
|
||||
return this.uri.equals(uri.toString())
|
||||
&& this.requestMethod.equals(requestMethod)
|
||||
&& new ResponseHeaders(uri, responseHeaders).varyMatches(varyHeaders.toMultimap(false),
|
||||
requestHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an input stream that reads the body of a snapshot, closing the
|
||||
* snapshot when the stream is closed.
|
||||
*/
|
||||
private static InputStream newBodyInputStream(final DiskLruCache.Snapshot snapshot) {
|
||||
return new FilterInputStream(snapshot.getInputStream(ENTRY_BODY)) {
|
||||
@Override public void close() throws IOException {
|
||||
snapshot.close();
|
||||
super.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static class EntryCacheResponse extends CacheResponse {
|
||||
private final Entry entry;
|
||||
private final DiskLruCache.Snapshot snapshot;
|
||||
private final InputStream in;
|
||||
|
||||
public EntryCacheResponse(Entry entry, DiskLruCache.Snapshot snapshot) {
|
||||
this.entry = entry;
|
||||
this.snapshot = snapshot;
|
||||
this.in = newBodyInputStream(snapshot);
|
||||
}
|
||||
|
||||
@Override public Map<String, List<String>> getHeaders() {
|
||||
return entry.responseHeaders.toMultimap(true);
|
||||
}
|
||||
|
||||
@Override public InputStream getBody() {
|
||||
return in;
|
||||
}
|
||||
}
|
||||
|
||||
static class EntrySecureCacheResponse extends SecureCacheResponse {
|
||||
private final Entry entry;
|
||||
private final DiskLruCache.Snapshot snapshot;
|
||||
private final InputStream in;
|
||||
|
||||
public EntrySecureCacheResponse(Entry entry, DiskLruCache.Snapshot snapshot) {
|
||||
this.entry = entry;
|
||||
this.snapshot = snapshot;
|
||||
this.in = newBodyInputStream(snapshot);
|
||||
}
|
||||
|
||||
@Override public Map<String, List<String>> getHeaders() {
|
||||
return entry.responseHeaders.toMultimap(true);
|
||||
}
|
||||
|
||||
@Override public InputStream getBody() {
|
||||
return in;
|
||||
}
|
||||
|
||||
@Override public String getCipherSuite() {
|
||||
return entry.cipherSuite;
|
||||
}
|
||||
|
||||
@Override public List<Certificate> getServerCertificateChain()
|
||||
throws SSLPeerUnverifiedException {
|
||||
if (entry.peerCertificates == null || entry.peerCertificates.length == 0) {
|
||||
throw new SSLPeerUnverifiedException(null);
|
||||
}
|
||||
return Arrays.asList(entry.peerCertificates.clone());
|
||||
}
|
||||
|
||||
@Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
|
||||
if (entry.peerCertificates == null || entry.peerCertificates.length == 0) {
|
||||
throw new SSLPeerUnverifiedException(null);
|
||||
}
|
||||
return ((X509Certificate) entry.peerCertificates[0]).getSubjectX500Principal();
|
||||
}
|
||||
|
||||
@Override public List<Certificate> getLocalCertificateChain() {
|
||||
if (entry.localCertificates == null || entry.localCertificates.length == 0) {
|
||||
return null;
|
||||
}
|
||||
return Arrays.asList(entry.localCertificates.clone());
|
||||
}
|
||||
|
||||
@Override public Principal getLocalPrincipal() {
|
||||
if (entry.localCertificates == null || entry.localCertificates.length == 0) {
|
||||
return null;
|
||||
}
|
||||
return ((X509Certificate) entry.localCertificates[0]).getSubjectX500Principal();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,232 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Square, Inc.
|
||||
*
|
||||
* Licensed 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.squareup.okhttp;
|
||||
|
||||
import com.squareup.okhttp.internal.http.HttpAuthenticator;
|
||||
import com.squareup.okhttp.internal.http.HttpEngine;
|
||||
import com.squareup.okhttp.internal.http.HttpTransport;
|
||||
import com.squareup.okhttp.internal.http.HttpsEngine;
|
||||
import com.squareup.okhttp.internal.http.Policy;
|
||||
import com.squareup.okhttp.internal.http.RawHeaders;
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.ProtocolException;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
|
||||
import static com.squareup.okhttp.internal.Util.getEffectivePort;
|
||||
import static com.squareup.okhttp.internal.http.HttpURLConnectionImpl.HTTP_MOVED_PERM;
|
||||
import static com.squareup.okhttp.internal.http.HttpURLConnectionImpl.HTTP_MOVED_TEMP;
|
||||
import static com.squareup.okhttp.internal.http.HttpURLConnectionImpl.HTTP_MULT_CHOICE;
|
||||
import static com.squareup.okhttp.internal.http.HttpURLConnectionImpl.HTTP_PROXY_AUTH;
|
||||
import static com.squareup.okhttp.internal.http.HttpURLConnectionImpl.HTTP_SEE_OTHER;
|
||||
import static com.squareup.okhttp.internal.http.HttpURLConnectionImpl.HTTP_TEMP_REDIRECT;
|
||||
import static com.squareup.okhttp.internal.http.HttpURLConnectionImpl.HTTP_UNAUTHORIZED;
|
||||
|
||||
final class Job implements Runnable, Policy {
|
||||
private final Dispatcher dispatcher;
|
||||
private final OkHttpClient client;
|
||||
private final Response.Receiver responseReceiver;
|
||||
|
||||
/** The request; possibly a consequence of redirects or auth headers. */
|
||||
private Request request;
|
||||
|
||||
public Job(Dispatcher dispatcher, OkHttpClient client, Request request,
|
||||
Response.Receiver responseReceiver) {
|
||||
this.dispatcher = dispatcher;
|
||||
this.client = client;
|
||||
this.request = request;
|
||||
this.responseReceiver = responseReceiver;
|
||||
}
|
||||
|
||||
@Override public int getChunkLength() {
|
||||
return request.body().contentLength() == -1 ? HttpTransport.DEFAULT_CHUNK_LENGTH : -1;
|
||||
}
|
||||
|
||||
@Override public long getFixedContentLength() {
|
||||
return request.body().contentLength();
|
||||
}
|
||||
|
||||
@Override public boolean getUseCaches() {
|
||||
return false; // TODO.
|
||||
}
|
||||
|
||||
@Override public HttpURLConnection getHttpConnectionToCache() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override public URL getURL() {
|
||||
return request.url();
|
||||
}
|
||||
|
||||
@Override public long getIfModifiedSince() {
|
||||
return 0; // For HttpURLConnection only. We let the cache drive this.
|
||||
}
|
||||
|
||||
@Override public boolean usingProxy() {
|
||||
return false; // We let the connection decide this.
|
||||
}
|
||||
|
||||
@Override public void setSelectedProxy(Proxy proxy) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
Object tag() {
|
||||
return request.tag();
|
||||
}
|
||||
|
||||
@Override public void run() {
|
||||
try {
|
||||
Response response = execute();
|
||||
responseReceiver.onResponse(response);
|
||||
} catch (IOException e) {
|
||||
responseReceiver.onFailure(new Failure.Builder()
|
||||
.request(request)
|
||||
.exception(e)
|
||||
.build());
|
||||
} finally {
|
||||
// TODO: close the response body
|
||||
// TODO: release the HTTP engine (potentially multiple!)
|
||||
dispatcher.finished(this);
|
||||
}
|
||||
}
|
||||
|
||||
private Response execute() throws IOException {
|
||||
Connection connection = null;
|
||||
Response redirectedBy = null;
|
||||
|
||||
while (true) {
|
||||
HttpEngine engine = newEngine(connection);
|
||||
|
||||
Request.Body body = request.body();
|
||||
if (body != null) {
|
||||
MediaType contentType = body.contentType();
|
||||
if (contentType == null) throw new IllegalStateException("contentType == null");
|
||||
if (engine.getRequestHeaders().getContentType() == null) {
|
||||
engine.getRequestHeaders().setContentType(contentType.toString());
|
||||
}
|
||||
}
|
||||
|
||||
engine.sendRequest();
|
||||
|
||||
if (body != null) {
|
||||
body.writeTo(engine.getRequestBody());
|
||||
}
|
||||
|
||||
engine.readResponse();
|
||||
|
||||
int responseCode = engine.getResponseCode();
|
||||
Dispatcher.RealResponseBody responseBody = new Dispatcher.RealResponseBody(
|
||||
engine.getResponseHeaders(), engine.getResponseBody());
|
||||
|
||||
Response response = new Response.Builder(request, responseCode)
|
||||
.rawHeaders(engine.getResponseHeaders().getHeaders())
|
||||
.body(responseBody)
|
||||
.redirectedBy(redirectedBy)
|
||||
.build();
|
||||
|
||||
Request redirect = processResponse(engine, response);
|
||||
|
||||
if (redirect == null) {
|
||||
engine.automaticallyReleaseConnectionToPool();
|
||||
return response;
|
||||
}
|
||||
|
||||
// TODO: fail if too many redirects
|
||||
// TODO: fail if not following redirects
|
||||
// TODO: release engine
|
||||
|
||||
connection = sameConnection(request, redirect) ? engine.getConnection() : null;
|
||||
redirectedBy = response;
|
||||
request = redirect;
|
||||
}
|
||||
}
|
||||
|
||||
HttpEngine newEngine(Connection connection) throws IOException {
|
||||
String protocol = request.url().getProtocol();
|
||||
RawHeaders requestHeaders = request.rawHeaders();
|
||||
if (protocol.equals("http")) {
|
||||
return new HttpEngine(client, this, request.method(), requestHeaders, connection, null);
|
||||
} else if (protocol.equals("https")) {
|
||||
return new HttpsEngine(client, this, request.method(), requestHeaders, connection, null);
|
||||
} else {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Figures out the HTTP request to make in response to receiving {@code
|
||||
* response}. This will either add authentication headers or follow
|
||||
* redirects. If a follow-up is either unnecessary or not applicable, this
|
||||
* returns null.
|
||||
*/
|
||||
private Request processResponse(HttpEngine engine, Response response) throws IOException {
|
||||
Request request = response.request();
|
||||
Proxy selectedProxy = engine.getConnection() != null
|
||||
? engine.getConnection().getRoute().getProxy()
|
||||
: client.getProxy();
|
||||
int responseCode = response.code();
|
||||
|
||||
switch (responseCode) {
|
||||
case HTTP_PROXY_AUTH:
|
||||
if (selectedProxy.type() != Proxy.Type.HTTP) {
|
||||
throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
|
||||
}
|
||||
// fall-through
|
||||
case HTTP_UNAUTHORIZED:
|
||||
RawHeaders successorRequestHeaders = request.rawHeaders();
|
||||
boolean credentialsFound = HttpAuthenticator.processAuthHeader(client.getAuthenticator(),
|
||||
response.code(), response.rawHeaders(), successorRequestHeaders, selectedProxy,
|
||||
this.request.url());
|
||||
return credentialsFound
|
||||
? request.newBuilder().rawHeaders(successorRequestHeaders).build()
|
||||
: null;
|
||||
|
||||
case HTTP_MULT_CHOICE:
|
||||
case HTTP_MOVED_PERM:
|
||||
case HTTP_MOVED_TEMP:
|
||||
case HTTP_SEE_OTHER:
|
||||
case HTTP_TEMP_REDIRECT:
|
||||
String method = request.method();
|
||||
if (responseCode == HTTP_TEMP_REDIRECT && !method.equals("GET") && !method.equals("HEAD")) {
|
||||
// "If the 307 status code is received in response to a request other than GET or HEAD,
|
||||
// the user agent MUST NOT automatically redirect the request"
|
||||
return null;
|
||||
}
|
||||
|
||||
String location = response.header("Location");
|
||||
if (location == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
URL url = new URL(request.url(), location);
|
||||
if (!url.getProtocol().equals("https") && !url.getProtocol().equals("http")) {
|
||||
return null; // Don't follow redirects to unsupported protocols.
|
||||
}
|
||||
|
||||
return this.request.newBuilder().url(url).build();
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean sameConnection(Request a, Request b) {
|
||||
return a.url().getHost().equals(b.url().getHost())
|
||||
&& getEffectivePort(a.url()) == getEffectivePort(b.url())
|
||||
&& a.url().getProtocol().equals(b.url().getProtocol());
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Square, Inc.
|
||||
*
|
||||
* Licensed 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.squareup.okhttp;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* An <a href="http://tools.ietf.org/html/rfc2045">RFC 2045</a> Media Type,
|
||||
* appropriate to describe the content type of an HTTP request or response body.
|
||||
*/
|
||||
public final class MediaType {
|
||||
private static final String TOKEN = "([a-zA-Z0-9-!#$%&'*+.^_`{|}~]+)";
|
||||
private static final String QUOTED = "\"([^\"]*)\"";
|
||||
private static final Pattern TYPE_SUBTYPE = Pattern.compile(TOKEN + "/" + TOKEN);
|
||||
private static final Pattern PARAMETER = Pattern.compile(
|
||||
";\\s*" + TOKEN + "=(?:" + TOKEN + "|" + QUOTED + ")");
|
||||
|
||||
private final String mediaType;
|
||||
private final String type;
|
||||
private final String subtype;
|
||||
private final String charset;
|
||||
|
||||
private MediaType(String mediaType, String type, String subtype, String charset) {
|
||||
this.mediaType = mediaType;
|
||||
this.type = type;
|
||||
this.subtype = subtype;
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a media type for {@code string}, or null if {@code string} is not a
|
||||
* well-formed media type.
|
||||
*/
|
||||
public static MediaType parse(String string) {
|
||||
Matcher typeSubtype = TYPE_SUBTYPE.matcher(string);
|
||||
if (!typeSubtype.lookingAt()) return null;
|
||||
String type = typeSubtype.group(1).toLowerCase(Locale.US);
|
||||
String subtype = typeSubtype.group(2).toLowerCase(Locale.US);
|
||||
|
||||
String charset = null;
|
||||
Matcher parameter = PARAMETER.matcher(string);
|
||||
for (int s = typeSubtype.end(); s < string.length(); s = parameter.end()) {
|
||||
parameter.region(s, string.length());
|
||||
if (!parameter.lookingAt()) return null; // This is not a well-formed media type.
|
||||
|
||||
String name = parameter.group(1);
|
||||
if (name == null || !name.equalsIgnoreCase("charset")) continue;
|
||||
if (charset != null) throw new IllegalArgumentException("Multiple charsets: " + string);
|
||||
charset = parameter.group(2) != null
|
||||
? parameter.group(2) // Value is a token.
|
||||
: parameter.group(3); // Value is a quoted string.
|
||||
}
|
||||
|
||||
return new MediaType(string, type, subtype, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the high-level media type, such as "text", "image", "audio",
|
||||
* "video", or "application".
|
||||
*/
|
||||
public String type() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a specific media subtype, such as "plain" or "png", "mpeg",
|
||||
* "mp4" or "xml".
|
||||
*/
|
||||
public String subtype() {
|
||||
return subtype;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the charset of this media type, or null if this media type doesn't
|
||||
* specify a charset.
|
||||
*/
|
||||
public Charset charset() {
|
||||
return charset != null ? Charset.forName(charset) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the charset of this media type, or {@code defaultValue} if this
|
||||
* media type doesn't specify a charset.
|
||||
*/
|
||||
public Charset charset(Charset defaultValue) {
|
||||
return charset != null ? Charset.forName(charset) : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encoded media type, like "text/plain; charset=utf-8",
|
||||
* appropriate for use in a Content-Type header.
|
||||
*/
|
||||
@Override public String toString() {
|
||||
return mediaType;
|
||||
}
|
||||
|
||||
@Override public boolean equals(Object o) {
|
||||
return o instanceof MediaType && ((MediaType) o).mediaType.equals(mediaType);
|
||||
}
|
||||
|
||||
@Override public int hashCode() {
|
||||
return mediaType.hashCode();
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Square, Inc.
|
||||
*
|
||||
* Licensed 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.squareup.okhttp;
|
||||
|
||||
import com.squareup.okhttp.internal.Base64;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Responds to authentication challenges from the remote web or proxy server by
|
||||
* returning credentials.
|
||||
*/
|
||||
public interface OkAuthenticator {
|
||||
/**
|
||||
* Returns a credential that satisfies the authentication challenge made by
|
||||
* {@code url}. Returns null if the challenge cannot be satisfied. This method
|
||||
* is called in response to an HTTP 401 unauthorized status code sent by the
|
||||
* origin server.
|
||||
*
|
||||
* @param challenges parsed "WWW-Authenticate" challenge headers from the HTTP
|
||||
* response.
|
||||
*/
|
||||
Credential authenticate(Proxy proxy, URL url, List<Challenge> challenges) throws IOException;
|
||||
|
||||
/**
|
||||
* Returns a credential that satisfies the authentication challenge made by
|
||||
* {@code proxy}. Returns null if the challenge cannot be satisfied. This
|
||||
* method is called in response to an HTTP 401 unauthorized status code sent
|
||||
* by the proxy server.
|
||||
*
|
||||
* @param challenges parsed "Proxy-Authenticate" challenge headers from the
|
||||
* HTTP response.
|
||||
*/
|
||||
Credential authenticateProxy(Proxy proxy, URL url, List<Challenge> challenges) throws IOException;
|
||||
|
||||
/** An RFC 2617 challenge. */
|
||||
public final class Challenge {
|
||||
private final String scheme;
|
||||
private final String realm;
|
||||
|
||||
public Challenge(String scheme, String realm) {
|
||||
this.scheme = scheme;
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
/** Returns the authentication scheme, like {@code Basic}. */
|
||||
public String getScheme() {
|
||||
return scheme;
|
||||
}
|
||||
|
||||
/** Returns the protection space. */
|
||||
public String getRealm() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
@Override public boolean equals(Object o) {
|
||||
return o instanceof Challenge
|
||||
&& ((Challenge) o).scheme.equals(scheme)
|
||||
&& ((Challenge) o).realm.equals(realm);
|
||||
}
|
||||
|
||||
@Override public int hashCode() {
|
||||
return scheme.hashCode() + 31 * realm.hashCode();
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return scheme + " realm=\"" + realm + "\"";
|
||||
}
|
||||
}
|
||||
|
||||
/** An RFC 2617 credential. */
|
||||
public final class Credential {
|
||||
private final String headerValue;
|
||||
|
||||
private Credential(String headerValue) {
|
||||
this.headerValue = headerValue;
|
||||
}
|
||||
|
||||
/** Returns an auth credential for the Basic scheme. */
|
||||
public static Credential basic(String userName, String password) {
|
||||
try {
|
||||
String usernameAndPassword = userName + ":" + password;
|
||||
byte[] bytes = usernameAndPassword.getBytes("ISO-8859-1");
|
||||
String encoded = Base64.encode(bytes);
|
||||
return new Credential("Basic " + encoded);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
public String getHeaderValue() {
|
||||
return headerValue;
|
||||
}
|
||||
|
||||
@Override public boolean equals(Object o) {
|
||||
return o instanceof Credential && ((Credential) o).headerValue.equals(headerValue);
|
||||
}
|
||||
|
||||
@Override public int hashCode() {
|
||||
return headerValue.hashCode();
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return headerValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,408 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Square, Inc.
|
||||
*
|
||||
* Licensed 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.squareup.okhttp;
|
||||
|
||||
import com.squareup.okhttp.internal.Util;
|
||||
import com.squareup.okhttp.internal.http.HttpAuthenticator;
|
||||
import com.squareup.okhttp.internal.http.HttpURLConnectionImpl;
|
||||
import com.squareup.okhttp.internal.http.HttpsURLConnectionImpl;
|
||||
import com.squareup.okhttp.internal.http.OkResponseCacheAdapter;
|
||||
import com.squareup.okhttp.internal.tls.OkHostnameVerifier;
|
||||
import java.net.CookieHandler;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.Proxy;
|
||||
import java.net.ProxySelector;
|
||||
import java.net.ResponseCache;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLStreamHandler;
|
||||
import java.net.URLStreamHandlerFactory;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
/** Configures and creates HTTP connections. */
|
||||
public final class OkHttpClient implements URLStreamHandlerFactory {
|
||||
private static final List<String> DEFAULT_TRANSPORTS
|
||||
= Util.immutableList(Arrays.asList("spdy/3", "http/1.1"));
|
||||
|
||||
private final RouteDatabase routeDatabase;
|
||||
private final Dispatcher dispatcher;
|
||||
private Proxy proxy;
|
||||
private List<String> transports;
|
||||
private ProxySelector proxySelector;
|
||||
private CookieHandler cookieHandler;
|
||||
private ResponseCache responseCache;
|
||||
private SSLSocketFactory sslSocketFactory;
|
||||
private HostnameVerifier hostnameVerifier;
|
||||
private OkAuthenticator authenticator;
|
||||
private ConnectionPool connectionPool;
|
||||
private boolean followProtocolRedirects = true;
|
||||
private int connectTimeout;
|
||||
private int readTimeout;
|
||||
|
||||
public OkHttpClient() {
|
||||
routeDatabase = new RouteDatabase();
|
||||
dispatcher = new Dispatcher();
|
||||
}
|
||||
|
||||
private OkHttpClient(OkHttpClient copyFrom) {
|
||||
routeDatabase = copyFrom.routeDatabase;
|
||||
dispatcher = copyFrom.dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default connect timeout for new connections. A value of 0 means no timeout.
|
||||
*
|
||||
* @see URLConnection#setConnectTimeout(int)
|
||||
*/
|
||||
public void setConnectTimeout(long timeout, TimeUnit unit) {
|
||||
if (timeout < 0) {
|
||||
throw new IllegalArgumentException("timeout < 0");
|
||||
}
|
||||
if (unit == null) {
|
||||
throw new IllegalArgumentException("unit == null");
|
||||
}
|
||||
long millis = unit.toMillis(timeout);
|
||||
if (millis > Integer.MAX_VALUE) {
|
||||
throw new IllegalArgumentException("Timeout too large.");
|
||||
}
|
||||
connectTimeout = (int) millis;
|
||||
}
|
||||
|
||||
/** Default connect timeout (in milliseconds). */
|
||||
public int getConnectTimeout() {
|
||||
return connectTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default read timeout for new connections. A value of 0 means no timeout.
|
||||
*
|
||||
* @see URLConnection#setReadTimeout(int)
|
||||
*/
|
||||
public void setReadTimeout(long timeout, TimeUnit unit) {
|
||||
if (timeout < 0) {
|
||||
throw new IllegalArgumentException("timeout < 0");
|
||||
}
|
||||
if (unit == null) {
|
||||
throw new IllegalArgumentException("unit == null");
|
||||
}
|
||||
long millis = unit.toMillis(timeout);
|
||||
if (millis > Integer.MAX_VALUE) {
|
||||
throw new IllegalArgumentException("Timeout too large.");
|
||||
}
|
||||
readTimeout = (int) millis;
|
||||
}
|
||||
|
||||
/** Default read timeout (in milliseconds). */
|
||||
public int getReadTimeout() {
|
||||
return readTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the HTTP proxy that will be used by connections created by this
|
||||
* client. This takes precedence over {@link #setProxySelector}, which is
|
||||
* only honored when this proxy is null (which it is by default). To disable
|
||||
* proxy use completely, call {@code setProxy(Proxy.NO_PROXY)}.
|
||||
*/
|
||||
public OkHttpClient setProxy(Proxy proxy) {
|
||||
this.proxy = proxy;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Proxy getProxy() {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the proxy selection policy to be used if no {@link #setProxy proxy}
|
||||
* is specified explicitly. The proxy selector may return multiple proxies;
|
||||
* in that case they will be tried in sequence until a successful connection
|
||||
* is established.
|
||||
*
|
||||
* <p>If unset, the {@link ProxySelector#getDefault() system-wide default}
|
||||
* proxy selector will be used.
|
||||
*/
|
||||
public OkHttpClient setProxySelector(ProxySelector proxySelector) {
|
||||
this.proxySelector = proxySelector;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProxySelector getProxySelector() {
|
||||
return proxySelector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cookie handler to be used to read outgoing cookies and write
|
||||
* incoming cookies.
|
||||
*
|
||||
* <p>If unset, the {@link CookieHandler#getDefault() system-wide default}
|
||||
* cookie handler will be used.
|
||||
*/
|
||||
public OkHttpClient setCookieHandler(CookieHandler cookieHandler) {
|
||||
this.cookieHandler = cookieHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CookieHandler getCookieHandler() {
|
||||
return cookieHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the response cache to be used to read and write cached responses.
|
||||
*
|
||||
* <p>If unset, the {@link ResponseCache#getDefault() system-wide default}
|
||||
* response cache will be used.
|
||||
*/
|
||||
public OkHttpClient setResponseCache(ResponseCache responseCache) {
|
||||
this.responseCache = responseCache;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ResponseCache getResponseCache() {
|
||||
return responseCache;
|
||||
}
|
||||
|
||||
public OkResponseCache getOkResponseCache() {
|
||||
if (responseCache instanceof HttpResponseCache) {
|
||||
return ((HttpResponseCache) responseCache).okResponseCache;
|
||||
} else if (responseCache != null) {
|
||||
return new OkResponseCacheAdapter(responseCache);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the socket factory used to secure HTTPS connections.
|
||||
*
|
||||
* <p>If unset, the {@link HttpsURLConnection#getDefaultSSLSocketFactory()
|
||||
* system-wide default} SSL socket factory will be used.
|
||||
*/
|
||||
public OkHttpClient setSslSocketFactory(SSLSocketFactory sslSocketFactory) {
|
||||
this.sslSocketFactory = sslSocketFactory;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SSLSocketFactory getSslSocketFactory() {
|
||||
return sslSocketFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the verifier used to confirm that response certificates apply to
|
||||
* requested hostnames for HTTPS connections.
|
||||
*
|
||||
* <p>If unset, the {@link HttpsURLConnection#getDefaultHostnameVerifier()
|
||||
* system-wide default} hostname verifier will be used.
|
||||
*/
|
||||
public OkHttpClient setHostnameVerifier(HostnameVerifier hostnameVerifier) {
|
||||
this.hostnameVerifier = hostnameVerifier;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HostnameVerifier getHostnameVerifier() {
|
||||
return hostnameVerifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the authenticator used to respond to challenges from the remote web
|
||||
* server or proxy server.
|
||||
*
|
||||
* <p>If unset, the {@link java.net.Authenticator#setDefault system-wide default}
|
||||
* authenticator will be used.
|
||||
*/
|
||||
public OkHttpClient setAuthenticator(OkAuthenticator authenticator) {
|
||||
this.authenticator = authenticator;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OkAuthenticator getAuthenticator() {
|
||||
return authenticator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the connection pool used to recycle HTTP and HTTPS connections.
|
||||
*
|
||||
* <p>If unset, the {@link ConnectionPool#getDefault() system-wide
|
||||
* default} connection pool will be used.
|
||||
*/
|
||||
public OkHttpClient setConnectionPool(ConnectionPool connectionPool) {
|
||||
this.connectionPool = connectionPool;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConnectionPool getConnectionPool() {
|
||||
return connectionPool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure this client to follow redirects from HTTPS to HTTP and from HTTP
|
||||
* to HTTPS.
|
||||
*
|
||||
* <p>If unset, protocol redirects will be followed. This is different than
|
||||
* the built-in {@code HttpURLConnection}'s default.
|
||||
*/
|
||||
public OkHttpClient setFollowProtocolRedirects(boolean followProtocolRedirects) {
|
||||
this.followProtocolRedirects = followProtocolRedirects;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean getFollowProtocolRedirects() {
|
||||
return followProtocolRedirects;
|
||||
}
|
||||
|
||||
public RouteDatabase getRoutesDatabase() {
|
||||
return routeDatabase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the transports used by this client to communicate with remote
|
||||
* servers. By default this client will prefer the most efficient transport
|
||||
* available, falling back to more ubiquitous transports. Applications should
|
||||
* only call this method to avoid specific compatibility problems, such as web
|
||||
* servers that behave incorrectly when SPDY is enabled.
|
||||
*
|
||||
* <p>The following transports are currently supported:
|
||||
* <ul>
|
||||
* <li><a href="http://www.w3.org/Protocols/rfc2616/rfc2616.html">http/1.1</a>
|
||||
* <li><a href="http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3">spdy/3</a>
|
||||
* </ul>
|
||||
*
|
||||
* <p><strong>This is an evolving set.</strong> Future releases may drop
|
||||
* support for transitional transports (like spdy/3), in favor of their
|
||||
* successors (spdy/4 or http/2.0). The http/1.1 transport will never be
|
||||
* dropped.
|
||||
*
|
||||
* <p>If multiple protocols are specified, <a
|
||||
* href="https://technotes.googlecode.com/git/nextprotoneg.html">NPN</a> will
|
||||
* be used to negotiate a transport. Future releases may use another mechanism
|
||||
* (such as <a href="http://tools.ietf.org/html/draft-friedl-tls-applayerprotoneg-02">ALPN</a>)
|
||||
* to negotiate a transport.
|
||||
*
|
||||
* @param transports the transports to use, in order of preference. The list
|
||||
* must contain "http/1.1". It must not contain null.
|
||||
*/
|
||||
public OkHttpClient setTransports(List<String> transports) {
|
||||
transports = Util.immutableList(transports);
|
||||
if (!transports.contains("http/1.1")) {
|
||||
throw new IllegalArgumentException("transports doesn't contain http/1.1: " + transports);
|
||||
}
|
||||
if (transports.contains(null)) {
|
||||
throw new IllegalArgumentException("transports must not contain null");
|
||||
}
|
||||
if (transports.contains("")) {
|
||||
throw new IllegalArgumentException("transports contains an empty string");
|
||||
}
|
||||
this.transports = transports;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<String> getTransports() {
|
||||
return transports;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules {@code request} to be executed.
|
||||
*/
|
||||
/* OkHttp 2.0: public */ void enqueue(Request request, Response.Receiver responseReceiver) {
|
||||
// Create the HttpURLConnection immediately so the enqueued job gets the current settings of
|
||||
// this client. Otherwise changes to this client (socket factory, redirect policy, etc.) may
|
||||
// incorrectly be reflected in the request when it is dispatched later.
|
||||
dispatcher.enqueue(copyWithDefaults(), request, responseReceiver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels all scheduled tasks tagged with {@code tag}. Requests that are already
|
||||
* in flight might not be canceled.
|
||||
*/
|
||||
/* OkHttp 2.0: public */ void cancel(Object tag) {
|
||||
dispatcher.cancel(tag);
|
||||
}
|
||||
|
||||
public HttpURLConnection open(URL url) {
|
||||
return open(url, proxy);
|
||||
}
|
||||
|
||||
HttpURLConnection open(URL url, Proxy proxy) {
|
||||
String protocol = url.getProtocol();
|
||||
OkHttpClient copy = copyWithDefaults();
|
||||
copy.proxy = proxy;
|
||||
|
||||
if (protocol.equals("http")) return new HttpURLConnectionImpl(url, copy);
|
||||
if (protocol.equals("https")) return new HttpsURLConnectionImpl(url, copy);
|
||||
throw new IllegalArgumentException("Unexpected protocol: " + protocol);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a shallow copy of this OkHttpClient that uses the system-wide default for
|
||||
* each field that hasn't been explicitly configured.
|
||||
*/
|
||||
private OkHttpClient copyWithDefaults() {
|
||||
OkHttpClient result = new OkHttpClient(this);
|
||||
result.proxy = proxy;
|
||||
result.proxySelector = proxySelector != null ? proxySelector : ProxySelector.getDefault();
|
||||
result.cookieHandler = cookieHandler != null ? cookieHandler : CookieHandler.getDefault();
|
||||
result.responseCache = responseCache != null ? responseCache : ResponseCache.getDefault();
|
||||
result.sslSocketFactory = sslSocketFactory != null
|
||||
? sslSocketFactory
|
||||
: HttpsURLConnection.getDefaultSSLSocketFactory();
|
||||
result.hostnameVerifier = hostnameVerifier != null
|
||||
? hostnameVerifier
|
||||
: OkHostnameVerifier.INSTANCE;
|
||||
result.authenticator = authenticator != null
|
||||
? authenticator
|
||||
: HttpAuthenticator.SYSTEM_DEFAULT;
|
||||
result.connectionPool = connectionPool != null ? connectionPool : ConnectionPool.getDefault();
|
||||
result.followProtocolRedirects = followProtocolRedirects;
|
||||
result.transports = transports != null ? transports : DEFAULT_TRANSPORTS;
|
||||
result.connectTimeout = connectTimeout;
|
||||
result.readTimeout = readTimeout;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a URLStreamHandler as a {@link URL#setURLStreamHandlerFactory}.
|
||||
*
|
||||
* <p>This code configures OkHttp to handle all HTTP and HTTPS connections
|
||||
* created with {@link URL#openConnection()}: <pre> {@code
|
||||
*
|
||||
* OkHttpClient okHttpClient = new OkHttpClient();
|
||||
* URL.setURLStreamHandlerFactory(okHttpClient);
|
||||
* }</pre>
|
||||
*/
|
||||
public URLStreamHandler createURLStreamHandler(final String protocol) {
|
||||
if (!protocol.equals("http") && !protocol.equals("https")) return null;
|
||||
|
||||
return new URLStreamHandler() {
|
||||
@Override protected URLConnection openConnection(URL url) {
|
||||
return open(url);
|
||||
}
|
||||
|
||||
@Override protected URLConnection openConnection(URL url, Proxy proxy) {
|
||||
return open(url, proxy);
|
||||
}
|
||||
|
||||
@Override protected int getDefaultPort() {
|
||||
if (protocol.equals("http")) return 80;
|
||||
if (protocol.equals("https")) return 443;
|
||||
throw new AssertionError();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Square, Inc.
|
||||
*
|
||||
* Licensed 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.squareup.okhttp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.CacheRequest;
|
||||
import java.net.CacheResponse;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.net.URLConnection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An extended response cache API. Unlike {@link java.net.ResponseCache}, this
|
||||
* interface supports conditional caching and statistics.
|
||||
*
|
||||
* <h3>Warning: Experimental OkHttp 2.0 API</h3>
|
||||
* This class is in beta. APIs are subject to change!
|
||||
*/
|
||||
public interface OkResponseCache {
|
||||
CacheResponse get(URI uri, String requestMethod, Map<String, List<String>> requestHeaders)
|
||||
throws IOException;
|
||||
|
||||
CacheRequest put(URI uri, URLConnection urlConnection) throws IOException;
|
||||
|
||||
/** Remove any cache entries for the supplied {@code uri} if the request method invalidates. */
|
||||
void maybeRemove(String requestMethod, URI uri) throws IOException;
|
||||
|
||||
/**
|
||||
* Handles a conditional request hit by updating the stored cache response
|
||||
* with the headers from {@code httpConnection}. The cached response body is
|
||||
* not updated. If the stored response has changed since {@code
|
||||
* conditionalCacheHit} was returned, this does nothing.
|
||||
*/
|
||||
void update(CacheResponse conditionalCacheHit, HttpURLConnection connection) throws IOException;
|
||||
|
||||
/** Track an conditional GET that was satisfied by this cache. */
|
||||
void trackConditionalCacheHit();
|
||||
|
||||
/** Track an HTTP response being satisfied by {@code source}. */
|
||||
void trackResponse(ResponseSource source);
|
||||
}
|
||||
@@ -1,284 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Square, Inc.
|
||||
*
|
||||
* Licensed 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.squareup.okhttp;
|
||||
|
||||
import com.squareup.okhttp.internal.Util;
|
||||
import com.squareup.okhttp.internal.http.RawHeaders;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An HTTP request. Instances of this class are immutable if their {@link #body}
|
||||
* is null or itself immutable.
|
||||
*
|
||||
* <h3>Warning: Experimental OkHttp 2.0 API</h3>
|
||||
* This class is in beta. APIs are subject to change!
|
||||
*/
|
||||
/* OkHttp 2.0: public */ final class Request {
|
||||
private final URL url;
|
||||
private final String method;
|
||||
private final RawHeaders headers;
|
||||
private final Body body;
|
||||
private final Object tag;
|
||||
|
||||
private Request(Builder builder) {
|
||||
this.url = builder.url;
|
||||
this.method = builder.method;
|
||||
this.headers = new RawHeaders(builder.headers);
|
||||
this.body = builder.body;
|
||||
this.tag = builder.tag != null ? builder.tag : this;
|
||||
}
|
||||
|
||||
public URL url() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public String urlString() {
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
public String method() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public String header(String name) {
|
||||
return headers.get(name);
|
||||
}
|
||||
|
||||
public List<String> headers(String name) {
|
||||
return headers.values(name);
|
||||
}
|
||||
|
||||
public Set<String> headerNames() {
|
||||
return headers.names();
|
||||
}
|
||||
|
||||
RawHeaders rawHeaders() {
|
||||
return new RawHeaders(headers);
|
||||
}
|
||||
|
||||
public int headerCount() {
|
||||
return headers.length();
|
||||
}
|
||||
|
||||
public String headerName(int index) {
|
||||
return headers.getFieldName(index);
|
||||
}
|
||||
|
||||
public String headerValue(int index) {
|
||||
return headers.getValue(index);
|
||||
}
|
||||
|
||||
public Body body() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public Object tag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
Builder newBuilder() {
|
||||
return new Builder(url)
|
||||
.method(method, body)
|
||||
.rawHeaders(headers)
|
||||
.tag(tag);
|
||||
}
|
||||
|
||||
public abstract static class Body {
|
||||
/** Returns the Content-Type header for this body. */
|
||||
public abstract MediaType contentType();
|
||||
|
||||
/**
|
||||
* Returns the number of bytes that will be written to {@code out} in a call
|
||||
* to {@link #writeTo}, or -1 if that count is unknown.
|
||||
*/
|
||||
public long contentLength() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Writes the content of this request to {@code out}. */
|
||||
public abstract void writeTo(OutputStream out) throws IOException;
|
||||
|
||||
/**
|
||||
* Returns a new request body that transmits {@code content}. If {@code
|
||||
* contentType} lacks a charset, this will use UTF-8.
|
||||
*/
|
||||
public static Body create(MediaType contentType, String content) {
|
||||
contentType = contentType.charset() != null
|
||||
? contentType
|
||||
: MediaType.parse(contentType + "; charset=utf-8");
|
||||
try {
|
||||
byte[] bytes = content.getBytes(contentType.charset().name());
|
||||
return create(contentType, bytes);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns a new request body that transmits {@code content}. */
|
||||
public static Body create(final MediaType contentType, final byte[] content) {
|
||||
if (contentType == null) throw new NullPointerException("contentType == null");
|
||||
if (content == null) throw new NullPointerException("content == null");
|
||||
|
||||
return new Body() {
|
||||
@Override public MediaType contentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
@Override public long contentLength() {
|
||||
return content.length;
|
||||
}
|
||||
|
||||
@Override public void writeTo(OutputStream out) throws IOException {
|
||||
out.write(content);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Returns a new request body that transmits the content of {@code file}. */
|
||||
public static Body create(final MediaType contentType, final File file) {
|
||||
if (contentType == null) throw new NullPointerException("contentType == null");
|
||||
if (file == null) throw new NullPointerException("content == null");
|
||||
|
||||
return new Body() {
|
||||
@Override public MediaType contentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
@Override public long contentLength() {
|
||||
return file.length();
|
||||
}
|
||||
|
||||
@Override public void writeTo(OutputStream out) throws IOException {
|
||||
long length = contentLength();
|
||||
if (length == 0) return;
|
||||
|
||||
InputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(file);
|
||||
byte[] buffer = new byte[(int) Math.min(8192, length)];
|
||||
for (int c; (c = in.read(buffer)) != -1; ) {
|
||||
out.write(buffer, 0, c);
|
||||
}
|
||||
} finally {
|
||||
Util.closeQuietly(in);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private URL url;
|
||||
private String method = "GET";
|
||||
private RawHeaders headers = new RawHeaders();
|
||||
private Body body;
|
||||
private Object tag;
|
||||
|
||||
public Builder(String url) {
|
||||
url(url);
|
||||
}
|
||||
|
||||
public Builder(URL url) {
|
||||
url(url);
|
||||
}
|
||||
|
||||
public Builder url(String url) {
|
||||
try {
|
||||
this.url = new URL(url);
|
||||
return this;
|
||||
} catch (MalformedURLException e) {
|
||||
throw new IllegalArgumentException("Malformed URL: " + url);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder url(URL url) {
|
||||
if (url == null) throw new IllegalStateException("url == null");
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the header named {@code name} to {@code value}. If this request
|
||||
* already has any headers with that name, they are all replaced.
|
||||
*/
|
||||
public Builder header(String name, String value) {
|
||||
headers.set(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a header with {@code name} and {@code value}. Prefer this method for
|
||||
* multiply-valued headers like "Cookie".
|
||||
*/
|
||||
public Builder addHeader(String name, String value) {
|
||||
headers.add(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder rawHeaders(RawHeaders rawHeaders) {
|
||||
headers = new RawHeaders(rawHeaders);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder get() {
|
||||
return method("GET", null);
|
||||
}
|
||||
|
||||
public Builder head() {
|
||||
return method("HEAD", null);
|
||||
}
|
||||
|
||||
public Builder post(Body body) {
|
||||
return method("POST", body);
|
||||
}
|
||||
|
||||
public Builder put(Body body) {
|
||||
return method("PUT", body);
|
||||
}
|
||||
|
||||
public Builder method(String method, Body body) {
|
||||
if (method == null || method.length() == 0) {
|
||||
throw new IllegalArgumentException("method == null || method.length() == 0");
|
||||
}
|
||||
this.method = method;
|
||||
this.body = body;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches {@code tag} to the request. It can be used later to cancel the
|
||||
* request. If the tag is unspecified or null, the request is canceled by
|
||||
* using the request itself as the tag.
|
||||
*/
|
||||
public Builder tag(Object tag) {
|
||||
this.tag = tag;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Request build() {
|
||||
return new Request(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,290 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Square, Inc.
|
||||
*
|
||||
* Licensed 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.squareup.okhttp;
|
||||
|
||||
import com.squareup.okhttp.internal.Util;
|
||||
import com.squareup.okhttp.internal.http.RawHeaders;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.squareup.okhttp.internal.Util.UTF_8;
|
||||
|
||||
/**
|
||||
* An HTTP response. Instances of this class are not immutable: the response
|
||||
* body is a one-shot value that may be consumed only once. All other properties
|
||||
* are immutable.
|
||||
*
|
||||
* <h3>Warning: Experimental OkHttp 2.0 API</h3>
|
||||
* This class is in beta. APIs are subject to change!
|
||||
*/
|
||||
/* OkHttp 2.0: public */ final class Response {
|
||||
private final Request request;
|
||||
private final int code;
|
||||
private final RawHeaders headers;
|
||||
private final Body body;
|
||||
private final Response redirectedBy;
|
||||
|
||||
private Response(Builder builder) {
|
||||
this.request = builder.request;
|
||||
this.code = builder.code;
|
||||
this.headers = new RawHeaders(builder.headers);
|
||||
this.body = builder.body;
|
||||
this.redirectedBy = builder.redirectedBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* The wire-level request that initiated this HTTP response. This is usually
|
||||
* <strong>not</strong> the same request instance provided to the HTTP client:
|
||||
* <ul>
|
||||
* <li>It may be transformed by the HTTP client. For example, the client
|
||||
* may have added its own {@code Content-Encoding} header to enable
|
||||
* response compression.
|
||||
* <li>It may be the request generated in response to an HTTP redirect.
|
||||
* In this case the request URL may be different than the initial
|
||||
* request URL.
|
||||
* </ul>
|
||||
*/
|
||||
public Request request() {
|
||||
return request;
|
||||
}
|
||||
|
||||
public int code() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String header(String name) {
|
||||
return header(name, null);
|
||||
}
|
||||
|
||||
public String header(String name, String defaultValue) {
|
||||
String result = headers.get(name);
|
||||
return result != null ? result : defaultValue;
|
||||
}
|
||||
|
||||
public List<String> headers(String name) {
|
||||
return headers.values(name);
|
||||
}
|
||||
|
||||
public Set<String> headerNames() {
|
||||
return headers.names();
|
||||
}
|
||||
|
||||
public int headerCount() {
|
||||
return headers.length();
|
||||
}
|
||||
|
||||
public String headerName(int index) {
|
||||
return headers.getFieldName(index);
|
||||
}
|
||||
|
||||
RawHeaders rawHeaders() {
|
||||
return new RawHeaders(headers);
|
||||
}
|
||||
|
||||
public String headerValue(int index) {
|
||||
return headers.getValue(index);
|
||||
}
|
||||
|
||||
public Body body() {
|
||||
return body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the response for the HTTP redirect that triggered this response, or
|
||||
* null if this response wasn't triggered by an automatic redirect. The body
|
||||
* of the returned response should not be read because it has already been
|
||||
* consumed by the redirecting client.
|
||||
*/
|
||||
public Response redirectedBy() {
|
||||
return redirectedBy;
|
||||
}
|
||||
|
||||
public abstract static class Body {
|
||||
/** Multiple calls to {@link #charStream()} must return the same instance. */
|
||||
private Reader reader;
|
||||
|
||||
/**
|
||||
* Returns true if further data from this response body should be read at
|
||||
* this time. For asynchronous transports like SPDY and HTTP/2.0, this will
|
||||
* return false once all locally-available body bytes have been read.
|
||||
*
|
||||
* <p>Clients with many concurrent downloads can use this method to reduce
|
||||
* the number of idle threads blocking on reads. See {@link
|
||||
* Receiver#onResponse} for details.
|
||||
*/
|
||||
// <h3>Body.ready() vs. InputStream.available()</h3>
|
||||
// TODO: Can we fix response bodies to implement InputStream.available well?
|
||||
// The deflater implementation is broken by default but we could do better.
|
||||
public abstract boolean ready() throws IOException;
|
||||
|
||||
public abstract MediaType contentType();
|
||||
|
||||
/**
|
||||
* Returns the number of bytes in that will returned by {@link #bytes}, or
|
||||
* {@link #byteStream}, or -1 if unknown.
|
||||
*/
|
||||
public abstract long contentLength();
|
||||
|
||||
public abstract InputStream byteStream() throws IOException;
|
||||
|
||||
public final byte[] bytes() throws IOException {
|
||||
long contentLength = contentLength();
|
||||
if (contentLength > Integer.MAX_VALUE) {
|
||||
throw new IOException("Cannot buffer entire body for content length: " + contentLength);
|
||||
}
|
||||
|
||||
if (contentLength != -1) {
|
||||
byte[] content = new byte[(int) contentLength];
|
||||
InputStream in = byteStream();
|
||||
Util.readFully(in, content);
|
||||
if (in.read() != -1) throw new IOException("Content-Length and stream length disagree");
|
||||
return content;
|
||||
|
||||
} else {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Util.copy(byteStream(), out);
|
||||
return out.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the response as a character stream decoded with the charset
|
||||
* of the Content-Type header. If that header is either absent or lacks a
|
||||
* charset, this will attempt to decode the response body as UTF-8.
|
||||
*/
|
||||
public final Reader charStream() throws IOException {
|
||||
if (reader == null) {
|
||||
reader = new InputStreamReader(byteStream(), charset());
|
||||
}
|
||||
return reader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the response as a string decoded with the charset of the
|
||||
* Content-Type header. If that header is either absent or lacks a charset,
|
||||
* this will attempt to decode the response body as UTF-8.
|
||||
*/
|
||||
public final String string() throws IOException {
|
||||
return new String(bytes(), charset().name());
|
||||
}
|
||||
|
||||
private Charset charset() {
|
||||
MediaType contentType = contentType();
|
||||
return contentType != null ? contentType.charset(UTF_8) : UTF_8;
|
||||
}
|
||||
}
|
||||
|
||||
public interface Receiver {
|
||||
/**
|
||||
* Called when the request could not be executed due to a connectivity
|
||||
* problem or timeout. Because networks can fail during an exchange, it is
|
||||
* possible that the remote server accepted the request before the failure.
|
||||
*/
|
||||
void onFailure(Failure failure);
|
||||
|
||||
/**
|
||||
* Called when the HTTP response was successfully returned by the remote
|
||||
* server. The receiver may proceed to read the response body with the
|
||||
* response's {@link #body} method.
|
||||
*
|
||||
* <p>Note that transport-layer success (receiving a HTTP response code,
|
||||
* headers and body) does not necessarily indicate application-layer
|
||||
* success: {@code response} may still indicate an unhappy HTTP response
|
||||
* code like 404 or 500.
|
||||
*
|
||||
* <h3>Non-blocking responses</h3>
|
||||
*
|
||||
* <p>Receivers do not need to block while waiting for the response body to
|
||||
* download. Instead, they can get called back as data arrives. Use {@link
|
||||
* Body#ready} to check if bytes should be read immediately. While there is
|
||||
* data ready, read it. If there isn't, return false: receivers will be
|
||||
* called back with {@code onResponse()} as additional data is downloaded.
|
||||
*
|
||||
* <p>Return true to indicate that the receiver has finished handling the
|
||||
* response body. If the response body has unread data, it will be
|
||||
* discarded.
|
||||
*
|
||||
* <p>When the response body has been fully consumed the returned value is
|
||||
* undefined.
|
||||
*
|
||||
* <p>The current implementation of {@link Body#ready} always returns true
|
||||
* when the underlying transport is HTTP/1. This results in blocking on that
|
||||
* transport. For effective non-blocking your server must support SPDY or
|
||||
* HTTP/2.
|
||||
*/
|
||||
boolean onResponse(Response response) throws IOException;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private final Request request;
|
||||
private final int code;
|
||||
private RawHeaders headers = new RawHeaders();
|
||||
private Body body;
|
||||
private Response redirectedBy;
|
||||
|
||||
public Builder(Request request, int code) {
|
||||
if (request == null) throw new IllegalArgumentException("request == null");
|
||||
if (code <= 0) throw new IllegalArgumentException("code <= 0");
|
||||
this.request = request;
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the header named {@code name} to {@code value}. If this request
|
||||
* already has any headers with that name, they are all replaced.
|
||||
*/
|
||||
public Builder header(String name, String value) {
|
||||
headers.set(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a header with {@code name} and {@code value}. Prefer this method for
|
||||
* multiply-valued headers like "Set-Cookie".
|
||||
*/
|
||||
public Builder addHeader(String name, String value) {
|
||||
headers.add(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder rawHeaders(RawHeaders rawHeaders) {
|
||||
headers = new RawHeaders(rawHeaders);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder body(Body body) {
|
||||
this.body = body;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder redirectedBy(Response redirectedBy) {
|
||||
this.redirectedBy = redirectedBy;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Response build() {
|
||||
if (request == null) throw new IllegalStateException("Response has no request.");
|
||||
if (code == -1) throw new IllegalStateException("Response has no code.");
|
||||
return new Response(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed 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.squareup.okhttp;
|
||||
|
||||
/** The source of an HTTP response. */
|
||||
public enum ResponseSource {
|
||||
|
||||
/** The response was returned from the local cache. */
|
||||
CACHE,
|
||||
|
||||
/**
|
||||
* The response is available in the cache but must be validated with the
|
||||
* network. The cache result will be used if it is still valid; otherwise
|
||||
* the network's response will be used.
|
||||
*/
|
||||
CONDITIONAL_CACHE,
|
||||
|
||||
/** The response was returned from the network. */
|
||||
NETWORK;
|
||||
|
||||
public boolean requiresConnection() {
|
||||
return this == CONDITIONAL_CACHE || this == NETWORK;
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Square, Inc.
|
||||
*
|
||||
* Licensed 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.squareup.okhttp;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
|
||||
/** Represents the route used by a connection to reach an endpoint. */
|
||||
public class Route {
|
||||
final Address address;
|
||||
final Proxy proxy;
|
||||
final InetSocketAddress inetSocketAddress;
|
||||
final boolean modernTls;
|
||||
|
||||
public Route(Address address, Proxy proxy, InetSocketAddress inetSocketAddress,
|
||||
boolean modernTls) {
|
||||
if (address == null) throw new NullPointerException("address == null");
|
||||
if (proxy == null) throw new NullPointerException("proxy == null");
|
||||
if (inetSocketAddress == null) throw new NullPointerException("inetSocketAddress == null");
|
||||
this.address = address;
|
||||
this.proxy = proxy;
|
||||
this.inetSocketAddress = inetSocketAddress;
|
||||
this.modernTls = modernTls;
|
||||
}
|
||||
|
||||
/** Returns the {@link Address} of this route. */
|
||||
public Address getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Proxy} of this route.
|
||||
*
|
||||
* <strong>Warning:</strong> This may be different than the proxy returned
|
||||
* by {@link #getAddress}! That is the proxy that the user asked to be
|
||||
* connected to; this returns the proxy that they were actually connected
|
||||
* to. The two may disagree when a proxy selector selects a different proxy
|
||||
* for a connection.
|
||||
*/
|
||||
public Proxy getProxy() {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
/** Returns the {@link InetSocketAddress} of this route. */
|
||||
public InetSocketAddress getSocketAddress() {
|
||||
return inetSocketAddress;
|
||||
}
|
||||
|
||||
/** Returns true if this route uses modern TLS. */
|
||||
public boolean isModernTls() {
|
||||
return modernTls;
|
||||
}
|
||||
|
||||
/** Returns a copy of this route with flipped TLS mode. */
|
||||
Route flipTlsMode() {
|
||||
return new Route(address, proxy, inetSocketAddress, !modernTls);
|
||||
}
|
||||
|
||||
@Override public boolean equals(Object obj) {
|
||||
if (obj instanceof Route) {
|
||||
Route other = (Route) obj;
|
||||
return (address.equals(other.address)
|
||||
&& proxy.equals(other.proxy)
|
||||
&& inetSocketAddress.equals(other.inetSocketAddress)
|
||||
&& modernTls == other.modernTls);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public int hashCode() {
|
||||
int result = 17;
|
||||
result = 31 * result + address.hashCode();
|
||||
result = 31 * result + proxy.hashCode();
|
||||
result = 31 * result + inetSocketAddress.hashCode();
|
||||
result = result + (modernTls ? (31 * result) : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Square, Inc.
|
||||
*
|
||||
* Licensed 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.squareup.okhttp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
|
||||
/**
|
||||
* A blacklist of failed routes to avoid when creating a new connection to a
|
||||
* target address. This is used so that OkHttp can learn from its mistakes: if
|
||||
* there was a failure attempting to connect to a specific IP address, proxy
|
||||
* server or TLS mode, that failure is remembered and alternate routes are
|
||||
* preferred.
|
||||
*/
|
||||
public final class RouteDatabase {
|
||||
private final Set<Route> failedRoutes = new LinkedHashSet<Route>();
|
||||
|
||||
/** Records a failure connecting to {@code failedRoute}. */
|
||||
public synchronized void failed(Route failedRoute, IOException failure) {
|
||||
failedRoutes.add(failedRoute);
|
||||
|
||||
if (!(failure instanceof SSLHandshakeException)) {
|
||||
// If the problem was not related to SSL then it will also fail with
|
||||
// a different TLS mode therefore we can be proactive about it.
|
||||
failedRoutes.add(failedRoute.flipTlsMode());
|
||||
}
|
||||
}
|
||||
|
||||
/** Records success connecting to {@code failedRoute}. */
|
||||
public synchronized void connected(Route route) {
|
||||
failedRoutes.remove(route);
|
||||
}
|
||||
|
||||
/** Returns true if {@code route} has failed recently and should be avoided. */
|
||||
public synchronized boolean shouldPostpone(Route route) {
|
||||
return failedRoutes.contains(route);
|
||||
}
|
||||
|
||||
public synchronized int failedRoutesCount() {
|
||||
return failedRoutes.size();
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed 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.squareup.okhttp;
|
||||
|
||||
import com.squareup.okhttp.internal.http.RawHeaders;
|
||||
|
||||
import static com.squareup.okhttp.internal.Util.getDefaultPort;
|
||||
|
||||
/**
|
||||
* Routing and authentication information sent to an HTTP proxy to create a
|
||||
* HTTPS to an origin server. Everything in the tunnel request is sent
|
||||
* unencrypted to the proxy server.
|
||||
*
|
||||
* <p>See <a href="http://www.ietf.org/rfc/rfc2817.txt">RFC 2817, Section
|
||||
* 5.2</a>.
|
||||
*/
|
||||
public final class TunnelRequest {
|
||||
final String host;
|
||||
final int port;
|
||||
final String userAgent;
|
||||
final String proxyAuthorization;
|
||||
|
||||
/**
|
||||
* @param host the origin server's hostname. Not null.
|
||||
* @param port the origin server's port, like 80 or 443.
|
||||
* @param userAgent the client's user-agent. Not null.
|
||||
* @param proxyAuthorization proxy authorization, or null if the proxy is
|
||||
* used without an authorization header.
|
||||
*/
|
||||
public TunnelRequest(String host, int port, String userAgent, String proxyAuthorization) {
|
||||
if (host == null) throw new NullPointerException("host == null");
|
||||
if (userAgent == null) throw new NullPointerException("userAgent == null");
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.userAgent = userAgent;
|
||||
this.proxyAuthorization = proxyAuthorization;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we're creating a TLS tunnel, send only the minimum set of headers.
|
||||
* This avoids sending potentially sensitive data like HTTP cookies to
|
||||
* the proxy unencrypted.
|
||||
*/
|
||||
RawHeaders getRequestHeaders() {
|
||||
RawHeaders result = new RawHeaders();
|
||||
result.setRequestLine("CONNECT " + host + ":" + port + " HTTP/1.1");
|
||||
|
||||
// Always set Host and User-Agent.
|
||||
result.set("Host", port == getDefaultPort("https") ? host : (host + ":" + port));
|
||||
result.set("User-Agent", userAgent);
|
||||
|
||||
// Copy over the Proxy-Authorization header if it exists.
|
||||
if (proxyAuthorization != null) {
|
||||
result.set("Proxy-Authorization", proxyAuthorization);
|
||||
}
|
||||
|
||||
// Always set the Proxy-Connection to Keep-Alive for the benefit of
|
||||
// HTTP/1.0 proxies like Squid.
|
||||
result.set("Proxy-Connection", "Keep-Alive");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed 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.squareup.okhttp.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* An output stream for an HTTP request body.
|
||||
*
|
||||
* <p>Since a single socket's output stream may be used to write multiple HTTP
|
||||
* requests to the same server, subclasses should not close the socket stream.
|
||||
*/
|
||||
public abstract class AbstractOutputStream extends OutputStream {
|
||||
protected boolean closed;
|
||||
|
||||
@Override public final void write(int data) throws IOException {
|
||||
write(new byte[] { (byte) data });
|
||||
}
|
||||
|
||||
protected final void checkNotClosed() throws IOException {
|
||||
if (closed) {
|
||||
throw new IOException("stream closed");
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if this stream was closed locally. */
|
||||
public boolean isClosed() {
|
||||
return closed;
|
||||
}
|
||||
}
|
||||
@@ -1,164 +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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @author Alexander Y. Kleymenov
|
||||
*/
|
||||
|
||||
package com.squareup.okhttp.internal;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import static com.squareup.okhttp.internal.Util.EMPTY_BYTE_ARRAY;
|
||||
|
||||
/**
|
||||
* <a href="http://www.ietf.org/rfc/rfc2045.txt">Base64</a> encoder/decoder.
|
||||
* In violation of the RFC, this encoder doesn't wrap lines at 76 columns.
|
||||
*/
|
||||
public final class Base64 {
|
||||
private Base64() {
|
||||
}
|
||||
|
||||
public static byte[] decode(byte[] in) {
|
||||
return decode(in, in.length);
|
||||
}
|
||||
|
||||
public static byte[] decode(byte[] in, int len) {
|
||||
// approximate output length
|
||||
int length = len / 4 * 3;
|
||||
// return an empty array on empty or short input without padding
|
||||
if (length == 0) {
|
||||
return EMPTY_BYTE_ARRAY;
|
||||
}
|
||||
// temporary array
|
||||
byte[] out = new byte[length];
|
||||
// number of padding characters ('=')
|
||||
int pad = 0;
|
||||
byte chr;
|
||||
// compute the number of the padding characters
|
||||
// and adjust the length of the input
|
||||
for (; ; len--) {
|
||||
chr = in[len - 1];
|
||||
// skip the neutral characters
|
||||
if ((chr == '\n') || (chr == '\r') || (chr == ' ') || (chr == '\t')) {
|
||||
continue;
|
||||
}
|
||||
if (chr == '=') {
|
||||
pad++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// index in the output array
|
||||
int outIndex = 0;
|
||||
// index in the input array
|
||||
int inIndex = 0;
|
||||
// holds the value of the input character
|
||||
int bits = 0;
|
||||
// holds the value of the input quantum
|
||||
int quantum = 0;
|
||||
for (int i = 0; i < len; i++) {
|
||||
chr = in[i];
|
||||
// skip the neutral characters
|
||||
if ((chr == '\n') || (chr == '\r') || (chr == ' ') || (chr == '\t')) {
|
||||
continue;
|
||||
}
|
||||
if ((chr >= 'A') && (chr <= 'Z')) {
|
||||
// char ASCII value
|
||||
// A 65 0
|
||||
// Z 90 25 (ASCII - 65)
|
||||
bits = chr - 65;
|
||||
} else if ((chr >= 'a') && (chr <= 'z')) {
|
||||
// char ASCII value
|
||||
// a 97 26
|
||||
// z 122 51 (ASCII - 71)
|
||||
bits = chr - 71;
|
||||
} else if ((chr >= '0') && (chr <= '9')) {
|
||||
// char ASCII value
|
||||
// 0 48 52
|
||||
// 9 57 61 (ASCII + 4)
|
||||
bits = chr + 4;
|
||||
} else if (chr == '+') {
|
||||
bits = 62;
|
||||
} else if (chr == '/') {
|
||||
bits = 63;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
// append the value to the quantum
|
||||
quantum = (quantum << 6) | (byte) bits;
|
||||
if (inIndex % 4 == 3) {
|
||||
// 4 characters were read, so make the output:
|
||||
out[outIndex++] = (byte) (quantum >> 16);
|
||||
out[outIndex++] = (byte) (quantum >> 8);
|
||||
out[outIndex++] = (byte) quantum;
|
||||
}
|
||||
inIndex++;
|
||||
}
|
||||
if (pad > 0) {
|
||||
// adjust the quantum value according to the padding
|
||||
quantum = quantum << (6 * pad);
|
||||
// make output
|
||||
out[outIndex++] = (byte) (quantum >> 16);
|
||||
if (pad == 1) {
|
||||
out[outIndex++] = (byte) (quantum >> 8);
|
||||
}
|
||||
}
|
||||
// create the resulting array
|
||||
byte[] result = new byte[outIndex];
|
||||
System.arraycopy(out, 0, result, 0, outIndex);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static final byte[] MAP = new byte[] {
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
|
||||
'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
|
||||
'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4',
|
||||
'5', '6', '7', '8', '9', '+', '/'
|
||||
};
|
||||
|
||||
public static String encode(byte[] in) {
|
||||
int length = (in.length + 2) * 4 / 3;
|
||||
byte[] out = new byte[length];
|
||||
int index = 0, end = in.length - in.length % 3;
|
||||
for (int i = 0; i < end; i += 3) {
|
||||
out[index++] = MAP[(in[i] & 0xff) >> 2];
|
||||
out[index++] = MAP[((in[i] & 0x03) << 4) | ((in[i + 1] & 0xff) >> 4)];
|
||||
out[index++] = MAP[((in[i + 1] & 0x0f) << 2) | ((in[i + 2] & 0xff) >> 6)];
|
||||
out[index++] = MAP[(in[i + 2] & 0x3f)];
|
||||
}
|
||||
switch (in.length % 3) {
|
||||
case 1:
|
||||
out[index++] = MAP[(in[end] & 0xff) >> 2];
|
||||
out[index++] = MAP[(in[end] & 0x03) << 4];
|
||||
out[index++] = '=';
|
||||
out[index++] = '=';
|
||||
break;
|
||||
case 2:
|
||||
out[index++] = MAP[(in[end] & 0xff) >> 2];
|
||||
out[index++] = MAP[((in[end] & 0x03) << 4) | ((in[end + 1] & 0xff) >> 4)];
|
||||
out[index++] = MAP[((in[end + 1] & 0x0f) << 2)];
|
||||
out[index++] = '=';
|
||||
break;
|
||||
}
|
||||
try {
|
||||
return new String(out, 0, index, "US-ASCII");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,926 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed 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.squareup.okhttp.internal;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.Closeable;
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A cache that uses a bounded amount of space on a filesystem. Each cache
|
||||
* entry has a string key and a fixed number of values. Each key must match
|
||||
* the regex <strong>[a-z0-9_-]{1,64}</strong>. Values are byte sequences,
|
||||
* accessible as streams or files. Each value must be between {@code 0} and
|
||||
* {@code Integer.MAX_VALUE} bytes in length.
|
||||
*
|
||||
* <p>The cache stores its data in a directory on the filesystem. This
|
||||
* directory must be exclusive to the cache; the cache may delete or overwrite
|
||||
* files from its directory. It is an error for multiple processes to use the
|
||||
* same cache directory at the same time.
|
||||
*
|
||||
* <p>This cache limits the number of bytes that it will store on the
|
||||
* filesystem. When the number of stored bytes exceeds the limit, the cache will
|
||||
* remove entries in the background until the limit is satisfied. The limit is
|
||||
* not strict: the cache may temporarily exceed it while waiting for files to be
|
||||
* deleted. The limit does not include filesystem overhead or the cache
|
||||
* journal so space-sensitive applications should set a conservative limit.
|
||||
*
|
||||
* <p>Clients call {@link #edit} to create or update the values of an entry. An
|
||||
* entry may have only one editor at one time; if a value is not available to be
|
||||
* edited then {@link #edit} will return null.
|
||||
* <ul>
|
||||
* <li>When an entry is being <strong>created</strong> it is necessary to
|
||||
* supply a full set of values; the empty value should be used as a
|
||||
* placeholder if necessary.
|
||||
* <li>When an entry is being <strong>edited</strong>, it is not necessary
|
||||
* to supply data for every value; values default to their previous
|
||||
* value.
|
||||
* </ul>
|
||||
* Every {@link #edit} call must be matched by a call to {@link Editor#commit}
|
||||
* or {@link Editor#abort}. Committing is atomic: a read observes the full set
|
||||
* of values as they were before or after the commit, but never a mix of values.
|
||||
*
|
||||
* <p>Clients call {@link #get} to read a snapshot of an entry. The read will
|
||||
* observe the value at the time that {@link #get} was called. Updates and
|
||||
* removals after the call do not impact ongoing reads.
|
||||
*
|
||||
* <p>This class is tolerant of some I/O errors. If files are missing from the
|
||||
* filesystem, the corresponding entries will be dropped from the cache. If
|
||||
* an error occurs while writing a cache value, the edit will fail silently.
|
||||
* Callers should handle other problems by catching {@code IOException} and
|
||||
* responding appropriately.
|
||||
*/
|
||||
public final class DiskLruCache implements Closeable {
|
||||
static final String JOURNAL_FILE = "journal";
|
||||
static final String JOURNAL_FILE_TEMP = "journal.tmp";
|
||||
static final String JOURNAL_FILE_BACKUP = "journal.bkp";
|
||||
static final String MAGIC = "libcore.io.DiskLruCache";
|
||||
static final String VERSION_1 = "1";
|
||||
static final long ANY_SEQUENCE_NUMBER = -1;
|
||||
static final Pattern LEGAL_KEY_PATTERN = Pattern.compile("[a-z0-9_-]{1,64}");
|
||||
private static final String CLEAN = "CLEAN";
|
||||
private static final String DIRTY = "DIRTY";
|
||||
private static final String REMOVE = "REMOVE";
|
||||
private static final String READ = "READ";
|
||||
|
||||
/*
|
||||
* This cache uses a journal file named "journal". A typical journal file
|
||||
* looks like this:
|
||||
* libcore.io.DiskLruCache
|
||||
* 1
|
||||
* 100
|
||||
* 2
|
||||
*
|
||||
* CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
|
||||
* DIRTY 335c4c6028171cfddfbaae1a9c313c52
|
||||
* CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
|
||||
* REMOVE 335c4c6028171cfddfbaae1a9c313c52
|
||||
* DIRTY 1ab96a171faeeee38496d8b330771a7a
|
||||
* CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
|
||||
* READ 335c4c6028171cfddfbaae1a9c313c52
|
||||
* READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
|
||||
*
|
||||
* The first five lines of the journal form its header. They are the
|
||||
* constant string "libcore.io.DiskLruCache", the disk cache's version,
|
||||
* the application's version, the value count, and a blank line.
|
||||
*
|
||||
* Each of the subsequent lines in the file is a record of the state of a
|
||||
* cache entry. Each line contains space-separated values: a state, a key,
|
||||
* and optional state-specific values.
|
||||
* o DIRTY lines track that an entry is actively being created or updated.
|
||||
* Every successful DIRTY action should be followed by a CLEAN or REMOVE
|
||||
* action. DIRTY lines without a matching CLEAN or REMOVE indicate that
|
||||
* temporary files may need to be deleted.
|
||||
* o CLEAN lines track a cache entry that has been successfully published
|
||||
* and may be read. A publish line is followed by the lengths of each of
|
||||
* its values.
|
||||
* o READ lines track accesses for LRU.
|
||||
* o REMOVE lines track entries that have been deleted.
|
||||
*
|
||||
* The journal file is appended to as cache operations occur. The journal may
|
||||
* occasionally be compacted by dropping redundant lines. A temporary file named
|
||||
* "journal.tmp" will be used during compaction; that file should be deleted if
|
||||
* it exists when the cache is opened.
|
||||
*/
|
||||
|
||||
private final File directory;
|
||||
private final File journalFile;
|
||||
private final File journalFileTmp;
|
||||
private final File journalFileBackup;
|
||||
private final int appVersion;
|
||||
private long maxSize;
|
||||
private final int valueCount;
|
||||
private long size = 0;
|
||||
private Writer journalWriter;
|
||||
private final LinkedHashMap<String, Entry> lruEntries =
|
||||
new LinkedHashMap<String, Entry>(0, 0.75f, true);
|
||||
private int redundantOpCount;
|
||||
|
||||
/**
|
||||
* To differentiate between old and current snapshots, each entry is given
|
||||
* a sequence number each time an edit is committed. A snapshot is stale if
|
||||
* its sequence number is not equal to its entry's sequence number.
|
||||
*/
|
||||
private long nextSequenceNumber = 0;
|
||||
|
||||
/** This cache uses a single background thread to evict entries. */
|
||||
final ThreadPoolExecutor executorService =
|
||||
new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
|
||||
private final Callable<Void> cleanupCallable = new Callable<Void>() {
|
||||
public Void call() throws Exception {
|
||||
synchronized (DiskLruCache.this) {
|
||||
if (journalWriter == null) {
|
||||
return null; // Closed.
|
||||
}
|
||||
trimToSize();
|
||||
if (journalRebuildRequired()) {
|
||||
rebuildJournal();
|
||||
redundantOpCount = 0;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
|
||||
this.directory = directory;
|
||||
this.appVersion = appVersion;
|
||||
this.journalFile = new File(directory, JOURNAL_FILE);
|
||||
this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
|
||||
this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
|
||||
this.valueCount = valueCount;
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the cache in {@code directory}, creating a cache if none exists
|
||||
* there.
|
||||
*
|
||||
* @param directory a writable directory
|
||||
* @param valueCount the number of values per cache entry. Must be positive.
|
||||
* @param maxSize the maximum number of bytes this cache should use to store
|
||||
* @throws IOException if reading or writing the cache directory fails
|
||||
*/
|
||||
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
|
||||
throws IOException {
|
||||
if (maxSize <= 0) {
|
||||
throw new IllegalArgumentException("maxSize <= 0");
|
||||
}
|
||||
if (valueCount <= 0) {
|
||||
throw new IllegalArgumentException("valueCount <= 0");
|
||||
}
|
||||
|
||||
// If a bkp file exists, use it instead.
|
||||
File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
|
||||
if (backupFile.exists()) {
|
||||
File journalFile = new File(directory, JOURNAL_FILE);
|
||||
// If journal file also exists just delete backup file.
|
||||
if (journalFile.exists()) {
|
||||
backupFile.delete();
|
||||
} else {
|
||||
renameTo(backupFile, journalFile, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Prefer to pick up where we left off.
|
||||
DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
|
||||
if (cache.journalFile.exists()) {
|
||||
try {
|
||||
cache.readJournal();
|
||||
cache.processJournal();
|
||||
cache.journalWriter = new BufferedWriter(
|
||||
new OutputStreamWriter(new FileOutputStream(cache.journalFile, true), Util.US_ASCII));
|
||||
return cache;
|
||||
} catch (IOException journalIsCorrupt) {
|
||||
Platform.get().logW("DiskLruCache " + directory + " is corrupt: "
|
||||
+ journalIsCorrupt.getMessage() + ", removing");
|
||||
cache.delete();
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new empty cache.
|
||||
directory.mkdirs();
|
||||
cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
|
||||
cache.rebuildJournal();
|
||||
return cache;
|
||||
}
|
||||
|
||||
private void readJournal() throws IOException {
|
||||
StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII);
|
||||
try {
|
||||
String magic = reader.readLine();
|
||||
String version = reader.readLine();
|
||||
String appVersionString = reader.readLine();
|
||||
String valueCountString = reader.readLine();
|
||||
String blank = reader.readLine();
|
||||
if (!MAGIC.equals(magic)
|
||||
|| !VERSION_1.equals(version)
|
||||
|| !Integer.toString(appVersion).equals(appVersionString)
|
||||
|| !Integer.toString(valueCount).equals(valueCountString)
|
||||
|| !"".equals(blank)) {
|
||||
throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "
|
||||
+ valueCountString + ", " + blank + "]");
|
||||
}
|
||||
|
||||
int lineCount = 0;
|
||||
while (true) {
|
||||
try {
|
||||
readJournalLine(reader.readLine());
|
||||
lineCount++;
|
||||
} catch (EOFException endOfJournal) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
redundantOpCount = lineCount - lruEntries.size();
|
||||
} finally {
|
||||
Util.closeQuietly(reader);
|
||||
}
|
||||
}
|
||||
|
||||
private void readJournalLine(String line) throws IOException {
|
||||
int firstSpace = line.indexOf(' ');
|
||||
if (firstSpace == -1) {
|
||||
throw new IOException("unexpected journal line: " + line);
|
||||
}
|
||||
|
||||
int keyBegin = firstSpace + 1;
|
||||
int secondSpace = line.indexOf(' ', keyBegin);
|
||||
final String key;
|
||||
if (secondSpace == -1) {
|
||||
key = line.substring(keyBegin);
|
||||
if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
|
||||
lruEntries.remove(key);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
key = line.substring(keyBegin, secondSpace);
|
||||
}
|
||||
|
||||
Entry entry = lruEntries.get(key);
|
||||
if (entry == null) {
|
||||
entry = new Entry(key);
|
||||
lruEntries.put(key, entry);
|
||||
}
|
||||
|
||||
if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
|
||||
String[] parts = line.substring(secondSpace + 1).split(" ");
|
||||
entry.readable = true;
|
||||
entry.currentEditor = null;
|
||||
entry.setLengths(parts);
|
||||
} else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
|
||||
entry.currentEditor = new Editor(entry);
|
||||
} else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
|
||||
// This work was already done by calling lruEntries.get().
|
||||
} else {
|
||||
throw new IOException("unexpected journal line: " + line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the initial size and collects garbage as a part of opening the
|
||||
* cache. Dirty entries are assumed to be inconsistent and will be deleted.
|
||||
*/
|
||||
private void processJournal() throws IOException {
|
||||
deleteIfExists(journalFileTmp);
|
||||
for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
|
||||
Entry entry = i.next();
|
||||
if (entry.currentEditor == null) {
|
||||
for (int t = 0; t < valueCount; t++) {
|
||||
size += entry.lengths[t];
|
||||
}
|
||||
} else {
|
||||
entry.currentEditor = null;
|
||||
for (int t = 0; t < valueCount; t++) {
|
||||
deleteIfExists(entry.getCleanFile(t));
|
||||
deleteIfExists(entry.getDirtyFile(t));
|
||||
}
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new journal that omits redundant information. This replaces the
|
||||
* current journal if it exists.
|
||||
*/
|
||||
private synchronized void rebuildJournal() throws IOException {
|
||||
if (journalWriter != null) {
|
||||
journalWriter.close();
|
||||
}
|
||||
|
||||
Writer writer = new BufferedWriter(
|
||||
new OutputStreamWriter(new FileOutputStream(journalFileTmp), Util.US_ASCII));
|
||||
try {
|
||||
writer.write(MAGIC);
|
||||
writer.write("\n");
|
||||
writer.write(VERSION_1);
|
||||
writer.write("\n");
|
||||
writer.write(Integer.toString(appVersion));
|
||||
writer.write("\n");
|
||||
writer.write(Integer.toString(valueCount));
|
||||
writer.write("\n");
|
||||
writer.write("\n");
|
||||
|
||||
for (Entry entry : lruEntries.values()) {
|
||||
if (entry.currentEditor != null) {
|
||||
writer.write(DIRTY + ' ' + entry.key + '\n');
|
||||
} else {
|
||||
writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
writer.close();
|
||||
}
|
||||
|
||||
if (journalFile.exists()) {
|
||||
renameTo(journalFile, journalFileBackup, true);
|
||||
}
|
||||
renameTo(journalFileTmp, journalFile, false);
|
||||
journalFileBackup.delete();
|
||||
|
||||
journalWriter = new BufferedWriter(
|
||||
new OutputStreamWriter(new FileOutputStream(journalFile, true), Util.US_ASCII));
|
||||
}
|
||||
|
||||
private static void deleteIfExists(File file) throws IOException {
|
||||
if (file.exists() && !file.delete()) {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
||||
private static void renameTo(File from, File to, boolean deleteDestination) throws IOException {
|
||||
if (deleteDestination) {
|
||||
deleteIfExists(to);
|
||||
}
|
||||
if (!from.renameTo(to)) {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a snapshot of the entry named {@code key}, or null if it doesn't
|
||||
* exist is not currently readable. If a value is returned, it is moved to
|
||||
* the head of the LRU queue.
|
||||
*/
|
||||
public synchronized Snapshot get(String key) throws IOException {
|
||||
checkNotClosed();
|
||||
validateKey(key);
|
||||
Entry entry = lruEntries.get(key);
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!entry.readable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Open all streams eagerly to guarantee that we see a single published
|
||||
// snapshot. If we opened streams lazily then the streams could come
|
||||
// from different edits.
|
||||
InputStream[] ins = new InputStream[valueCount];
|
||||
try {
|
||||
for (int i = 0; i < valueCount; i++) {
|
||||
ins[i] = new FileInputStream(entry.getCleanFile(i));
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
// A file must have been deleted manually!
|
||||
for (int i = 0; i < valueCount; i++) {
|
||||
if (ins[i] != null) {
|
||||
Util.closeQuietly(ins[i]);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
redundantOpCount++;
|
||||
journalWriter.append(READ + ' ' + key + '\n');
|
||||
if (journalRebuildRequired()) {
|
||||
executorService.submit(cleanupCallable);
|
||||
}
|
||||
|
||||
return new Snapshot(key, entry.sequenceNumber, ins, entry.lengths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an editor for the entry named {@code key}, or null if another
|
||||
* edit is in progress.
|
||||
*/
|
||||
public Editor edit(String key) throws IOException {
|
||||
return edit(key, ANY_SEQUENCE_NUMBER);
|
||||
}
|
||||
|
||||
private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
|
||||
checkNotClosed();
|
||||
validateKey(key);
|
||||
Entry entry = lruEntries.get(key);
|
||||
if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
|
||||
|| entry.sequenceNumber != expectedSequenceNumber)) {
|
||||
return null; // Snapshot is stale.
|
||||
}
|
||||
if (entry == null) {
|
||||
entry = new Entry(key);
|
||||
lruEntries.put(key, entry);
|
||||
} else if (entry.currentEditor != null) {
|
||||
return null; // Another edit is in progress.
|
||||
}
|
||||
|
||||
Editor editor = new Editor(entry);
|
||||
entry.currentEditor = editor;
|
||||
|
||||
// Flush the journal before creating files to prevent file leaks.
|
||||
journalWriter.write(DIRTY + ' ' + key + '\n');
|
||||
journalWriter.flush();
|
||||
return editor;
|
||||
}
|
||||
|
||||
/** Returns the directory where this cache stores its data. */
|
||||
public File getDirectory() {
|
||||
return directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum number of bytes that this cache should use to store
|
||||
* its data.
|
||||
*/
|
||||
public long getMaxSize() {
|
||||
return maxSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the maximum number of bytes the cache can store and queues a job
|
||||
* to trim the existing store, if necessary.
|
||||
*/
|
||||
public synchronized void setMaxSize(long maxSize) {
|
||||
this.maxSize = maxSize;
|
||||
executorService.submit(cleanupCallable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes currently being used to store the values in
|
||||
* this cache. This may be greater than the max size if a background
|
||||
* deletion is pending.
|
||||
*/
|
||||
public synchronized long size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
|
||||
Entry entry = editor.entry;
|
||||
if (entry.currentEditor != editor) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
// If this edit is creating the entry for the first time, every index must have a value.
|
||||
if (success && !entry.readable) {
|
||||
for (int i = 0; i < valueCount; i++) {
|
||||
if (!editor.written[i]) {
|
||||
editor.abort();
|
||||
throw new IllegalStateException("Newly created entry didn't create value for index " + i);
|
||||
}
|
||||
if (!entry.getDirtyFile(i).exists()) {
|
||||
editor.abort();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < valueCount; i++) {
|
||||
File dirty = entry.getDirtyFile(i);
|
||||
if (success) {
|
||||
if (dirty.exists()) {
|
||||
File clean = entry.getCleanFile(i);
|
||||
dirty.renameTo(clean);
|
||||
long oldLength = entry.lengths[i];
|
||||
long newLength = clean.length();
|
||||
entry.lengths[i] = newLength;
|
||||
size = size - oldLength + newLength;
|
||||
}
|
||||
} else {
|
||||
deleteIfExists(dirty);
|
||||
}
|
||||
}
|
||||
|
||||
redundantOpCount++;
|
||||
entry.currentEditor = null;
|
||||
if (entry.readable | success) {
|
||||
entry.readable = true;
|
||||
journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
|
||||
if (success) {
|
||||
entry.sequenceNumber = nextSequenceNumber++;
|
||||
}
|
||||
} else {
|
||||
lruEntries.remove(entry.key);
|
||||
journalWriter.write(REMOVE + ' ' + entry.key + '\n');
|
||||
}
|
||||
journalWriter.flush();
|
||||
|
||||
if (size > maxSize || journalRebuildRequired()) {
|
||||
executorService.submit(cleanupCallable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We only rebuild the journal when it will halve the size of the journal
|
||||
* and eliminate at least 2000 ops.
|
||||
*/
|
||||
private boolean journalRebuildRequired() {
|
||||
final int redundantOpCompactThreshold = 2000;
|
||||
return redundantOpCount >= redundantOpCompactThreshold //
|
||||
&& redundantOpCount >= lruEntries.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops the entry for {@code key} if it exists and can be removed. Entries
|
||||
* actively being edited cannot be removed.
|
||||
*
|
||||
* @return true if an entry was removed.
|
||||
*/
|
||||
public synchronized boolean remove(String key) throws IOException {
|
||||
checkNotClosed();
|
||||
validateKey(key);
|
||||
Entry entry = lruEntries.get(key);
|
||||
if (entry == null || entry.currentEditor != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < valueCount; i++) {
|
||||
File file = entry.getCleanFile(i);
|
||||
if (!file.delete()) {
|
||||
throw new IOException("failed to delete " + file);
|
||||
}
|
||||
size -= entry.lengths[i];
|
||||
entry.lengths[i] = 0;
|
||||
}
|
||||
|
||||
redundantOpCount++;
|
||||
journalWriter.append(REMOVE + ' ' + key + '\n');
|
||||
lruEntries.remove(key);
|
||||
|
||||
if (journalRebuildRequired()) {
|
||||
executorService.submit(cleanupCallable);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Returns true if this cache has been closed. */
|
||||
public boolean isClosed() {
|
||||
return journalWriter == null;
|
||||
}
|
||||
|
||||
private void checkNotClosed() {
|
||||
if (journalWriter == null) {
|
||||
throw new IllegalStateException("cache is closed");
|
||||
}
|
||||
}
|
||||
|
||||
/** Force buffered operations to the filesystem. */
|
||||
public synchronized void flush() throws IOException {
|
||||
checkNotClosed();
|
||||
trimToSize();
|
||||
journalWriter.flush();
|
||||
}
|
||||
|
||||
/** Closes this cache. Stored values will remain on the filesystem. */
|
||||
public synchronized void close() throws IOException {
|
||||
if (journalWriter == null) {
|
||||
return; // Already closed.
|
||||
}
|
||||
for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {
|
||||
if (entry.currentEditor != null) {
|
||||
entry.currentEditor.abort();
|
||||
}
|
||||
}
|
||||
trimToSize();
|
||||
journalWriter.close();
|
||||
journalWriter = null;
|
||||
}
|
||||
|
||||
private void trimToSize() throws IOException {
|
||||
while (size > maxSize) {
|
||||
Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
|
||||
remove(toEvict.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the cache and deletes all of its stored values. This will delete
|
||||
* all files in the cache directory including files that weren't created by
|
||||
* the cache.
|
||||
*/
|
||||
public void delete() throws IOException {
|
||||
close();
|
||||
Util.deleteContents(directory);
|
||||
}
|
||||
|
||||
private void validateKey(String key) {
|
||||
Matcher matcher = LEGAL_KEY_PATTERN.matcher(key);
|
||||
if (!matcher.matches()) {
|
||||
throw new IllegalArgumentException("keys must match regex [a-z0-9_-]{1,64}: \"" + key + "\"");
|
||||
}
|
||||
}
|
||||
|
||||
private static String inputStreamToString(InputStream in) throws IOException {
|
||||
return Util.readFully(new InputStreamReader(in, Util.UTF_8));
|
||||
}
|
||||
|
||||
/** A snapshot of the values for an entry. */
|
||||
public final class Snapshot implements Closeable {
|
||||
private final String key;
|
||||
private final long sequenceNumber;
|
||||
private final InputStream[] ins;
|
||||
private final long[] lengths;
|
||||
|
||||
private Snapshot(String key, long sequenceNumber, InputStream[] ins, long[] lengths) {
|
||||
this.key = key;
|
||||
this.sequenceNumber = sequenceNumber;
|
||||
this.ins = ins;
|
||||
this.lengths = lengths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an editor for this snapshot's entry, or null if either the
|
||||
* entry has changed since this snapshot was created or if another edit
|
||||
* is in progress.
|
||||
*/
|
||||
public Editor edit() throws IOException {
|
||||
return DiskLruCache.this.edit(key, sequenceNumber);
|
||||
}
|
||||
|
||||
/** Returns the unbuffered stream with the value for {@code index}. */
|
||||
public InputStream getInputStream(int index) {
|
||||
return ins[index];
|
||||
}
|
||||
|
||||
/** Returns the string value for {@code index}. */
|
||||
public String getString(int index) throws IOException {
|
||||
return inputStreamToString(getInputStream(index));
|
||||
}
|
||||
|
||||
/** Returns the byte length of the value for {@code index}. */
|
||||
public long getLength(int index) {
|
||||
return lengths[index];
|
||||
}
|
||||
|
||||
public void close() {
|
||||
for (InputStream in : ins) {
|
||||
Util.closeQuietly(in);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final OutputStream NULL_OUTPUT_STREAM = new OutputStream() {
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
// Eat all writes silently. Nom nom.
|
||||
}
|
||||
};
|
||||
|
||||
/** Edits the values for an entry. */
|
||||
public final class Editor {
|
||||
private final Entry entry;
|
||||
private final boolean[] written;
|
||||
private boolean hasErrors;
|
||||
private boolean committed;
|
||||
|
||||
private Editor(Entry entry) {
|
||||
this.entry = entry;
|
||||
this.written = (entry.readable) ? null : new boolean[valueCount];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unbuffered input stream to read the last committed value,
|
||||
* or null if no value has been committed.
|
||||
*/
|
||||
public InputStream newInputStream(int index) throws IOException {
|
||||
synchronized (DiskLruCache.this) {
|
||||
if (entry.currentEditor != this) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (!entry.readable) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new FileInputStream(entry.getCleanFile(index));
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last committed value as a string, or null if no value
|
||||
* has been committed.
|
||||
*/
|
||||
public String getString(int index) throws IOException {
|
||||
InputStream in = newInputStream(index);
|
||||
return in != null ? inputStreamToString(in) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new unbuffered output stream to write the value at
|
||||
* {@code index}. If the underlying output stream encounters errors
|
||||
* when writing to the filesystem, this edit will be aborted when
|
||||
* {@link #commit} is called. The returned output stream does not throw
|
||||
* IOExceptions.
|
||||
*/
|
||||
public OutputStream newOutputStream(int index) throws IOException {
|
||||
synchronized (DiskLruCache.this) {
|
||||
if (entry.currentEditor != this) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (!entry.readable) {
|
||||
written[index] = true;
|
||||
}
|
||||
File dirtyFile = entry.getDirtyFile(index);
|
||||
FileOutputStream outputStream;
|
||||
try {
|
||||
outputStream = new FileOutputStream(dirtyFile);
|
||||
} catch (FileNotFoundException e) {
|
||||
// Attempt to recreate the cache directory.
|
||||
directory.mkdirs();
|
||||
try {
|
||||
outputStream = new FileOutputStream(dirtyFile);
|
||||
} catch (FileNotFoundException e2) {
|
||||
// We are unable to recover. Silently eat the writes.
|
||||
return NULL_OUTPUT_STREAM;
|
||||
}
|
||||
}
|
||||
return new FaultHidingOutputStream(outputStream);
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets the value at {@code index} to {@code value}. */
|
||||
public void set(int index, String value) throws IOException {
|
||||
Writer writer = null;
|
||||
try {
|
||||
writer = new OutputStreamWriter(newOutputStream(index), Util.UTF_8);
|
||||
writer.write(value);
|
||||
} finally {
|
||||
Util.closeQuietly(writer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Commits this edit so it is visible to readers. This releases the
|
||||
* edit lock so another edit may be started on the same key.
|
||||
*/
|
||||
public void commit() throws IOException {
|
||||
if (hasErrors) {
|
||||
completeEdit(this, false);
|
||||
remove(entry.key); // The previous entry is stale.
|
||||
} else {
|
||||
completeEdit(this, true);
|
||||
}
|
||||
committed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aborts this edit. This releases the edit lock so another edit may be
|
||||
* started on the same key.
|
||||
*/
|
||||
public void abort() throws IOException {
|
||||
completeEdit(this, false);
|
||||
}
|
||||
|
||||
public void abortUnlessCommitted() {
|
||||
if (!committed) {
|
||||
try {
|
||||
abort();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class FaultHidingOutputStream extends FilterOutputStream {
|
||||
private FaultHidingOutputStream(OutputStream out) {
|
||||
super(out);
|
||||
}
|
||||
|
||||
@Override public void write(int oneByte) {
|
||||
try {
|
||||
out.write(oneByte);
|
||||
} catch (IOException e) {
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void write(byte[] buffer, int offset, int length) {
|
||||
try {
|
||||
out.write(buffer, offset, length);
|
||||
} catch (IOException e) {
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void flush() {
|
||||
try {
|
||||
out.flush();
|
||||
} catch (IOException e) {
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class Entry {
|
||||
private final String key;
|
||||
|
||||
/** Lengths of this entry's files. */
|
||||
private final long[] lengths;
|
||||
|
||||
/** True if this entry has ever been published. */
|
||||
private boolean readable;
|
||||
|
||||
/** The ongoing edit or null if this entry is not being edited. */
|
||||
private Editor currentEditor;
|
||||
|
||||
/** The sequence number of the most recently committed edit to this entry. */
|
||||
private long sequenceNumber;
|
||||
|
||||
private Entry(String key) {
|
||||
this.key = key;
|
||||
this.lengths = new long[valueCount];
|
||||
}
|
||||
|
||||
public String getLengths() throws IOException {
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (long size : lengths) {
|
||||
result.append(' ').append(size);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/** Set lengths using decimal numbers like "10123". */
|
||||
private void setLengths(String[] strings) throws IOException {
|
||||
if (strings.length != valueCount) {
|
||||
throw invalidLengths(strings);
|
||||
}
|
||||
|
||||
try {
|
||||
for (int i = 0; i < strings.length; i++) {
|
||||
lengths[i] = Long.parseLong(strings[i]);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
throw invalidLengths(strings);
|
||||
}
|
||||
}
|
||||
|
||||
private IOException invalidLengths(String[] strings) throws IOException {
|
||||
throw new IOException("unexpected journal line: " + java.util.Arrays.toString(strings));
|
||||
}
|
||||
|
||||
public File getCleanFile(int i) {
|
||||
return new File(directory, key + "." + i);
|
||||
}
|
||||
|
||||
public File getDirtyFile(int i) {
|
||||
return new File(directory, key + "." + i + ".tmp");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Square, Inc.
|
||||
*
|
||||
* Licensed 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.squareup.okhttp.internal;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/**
|
||||
* Domain name service. Prefer this over {@link InetAddress#getAllByName} to
|
||||
* make code more testable.
|
||||
*/
|
||||
public interface Dns {
|
||||
Dns DEFAULT = new Dns() {
|
||||
@Override public InetAddress[] getAllByName(String host) throws UnknownHostException {
|
||||
return InetAddress.getAllByName(host);
|
||||
}
|
||||
};
|
||||
|
||||
InetAddress[] getAllByName(String host) throws UnknownHostException;
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Square, Inc.
|
||||
*
|
||||
* Licensed 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.squareup.okhttp.internal;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import static com.squareup.okhttp.internal.Util.checkOffsetAndCount;
|
||||
|
||||
/**
|
||||
* An output stream wrapper that recovers from failures in the underlying stream
|
||||
* by replacing it with another stream. This class buffers a fixed amount of
|
||||
* data under the assumption that failures occur early in a stream's life.
|
||||
* If a failure occurs after the buffer has been exhausted, no recovery is
|
||||
* attempted.
|
||||
*
|
||||
* <p>Subclasses must override {@link #replacementStream} which will request a
|
||||
* replacement stream each time an {@link IOException} is encountered on the
|
||||
* current stream.
|
||||
*/
|
||||
public abstract class FaultRecoveringOutputStream extends AbstractOutputStream {
|
||||
private final int maxReplayBufferLength;
|
||||
|
||||
/** Bytes to transmit on the replacement stream, or null if no recovery is possible. */
|
||||
private ByteArrayOutputStream replayBuffer;
|
||||
private OutputStream out;
|
||||
|
||||
/**
|
||||
* @param maxReplayBufferLength the maximum number of successfully written
|
||||
* bytes to buffer so they can be replayed in the event of an error.
|
||||
* Failure recoveries are not possible once this limit has been exceeded.
|
||||
*/
|
||||
public FaultRecoveringOutputStream(int maxReplayBufferLength, OutputStream out) {
|
||||
if (maxReplayBufferLength < 0) throw new IllegalArgumentException();
|
||||
this.maxReplayBufferLength = maxReplayBufferLength;
|
||||
this.replayBuffer = new ByteArrayOutputStream(maxReplayBufferLength);
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
@Override public final void write(byte[] buffer, int offset, int count) throws IOException {
|
||||
if (closed) throw new IOException("stream closed");
|
||||
checkOffsetAndCount(buffer.length, offset, count);
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
out.write(buffer, offset, count);
|
||||
|
||||
if (replayBuffer != null) {
|
||||
if (count + replayBuffer.size() > maxReplayBufferLength) {
|
||||
// Failure recovery is no longer possible once we overflow the replay buffer.
|
||||
replayBuffer = null;
|
||||
} else {
|
||||
// Remember the written bytes to the replay buffer.
|
||||
replayBuffer.write(buffer, offset, count);
|
||||
}
|
||||
}
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
if (!recover(e)) throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override public final void flush() throws IOException {
|
||||
if (closed) {
|
||||
return; // don't throw; this stream might have been closed on the caller's behalf
|
||||
}
|
||||
while (true) {
|
||||
try {
|
||||
out.flush();
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
if (!recover(e)) throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override public final void close() throws IOException {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
while (true) {
|
||||
try {
|
||||
out.close();
|
||||
closed = true;
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
if (!recover(e)) throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to replace {@code out} with another equivalent stream. Returns true
|
||||
* if a suitable replacement stream was found.
|
||||
*/
|
||||
private boolean recover(IOException e) {
|
||||
if (replayBuffer == null) {
|
||||
return false; // Can't recover because we've dropped data that we would need to replay.
|
||||
}
|
||||
|
||||
while (true) {
|
||||
OutputStream replacementStream = null;
|
||||
try {
|
||||
replacementStream = replacementStream(e);
|
||||
if (replacementStream == null) {
|
||||
return false;
|
||||
}
|
||||
replaceStream(replacementStream);
|
||||
return true;
|
||||
} catch (IOException replacementStreamFailure) {
|
||||
// The replacement was also broken. Loop to ask for another replacement.
|
||||
Util.closeQuietly(replacementStream);
|
||||
e = replacementStreamFailure;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if errors in the underlying stream can currently be recovered.
|
||||
*/
|
||||
public boolean isRecoverable() {
|
||||
return replayBuffer != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the current output stream with {@code replacementStream}, writing
|
||||
* any replay bytes to it if they exist. The current output stream is closed.
|
||||
*/
|
||||
public final void replaceStream(OutputStream replacementStream) throws IOException {
|
||||
if (!isRecoverable()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (this.out == replacementStream) {
|
||||
return; // Don't replace a stream with itself.
|
||||
}
|
||||
replayBuffer.writeTo(replacementStream);
|
||||
Util.closeQuietly(out);
|
||||
out = replacementStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a replacement output stream to recover from {@code e} thrown by the
|
||||
* previous stream. Returns a new OutputStream if recovery was successful, in
|
||||
* which case all previously-written data will be replayed. Returns null if
|
||||
* the failure cannot be recovered.
|
||||
*/
|
||||
protected abstract OutputStream replacementStream(IOException e) throws IOException;
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Square, Inc.
|
||||
*
|
||||
* Licensed 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.squareup.okhttp.internal;
|
||||
|
||||
/**
|
||||
* Runnable implementation which always sets its thread name.
|
||||
*/
|
||||
public abstract class NamedRunnable implements Runnable {
|
||||
private final String name;
|
||||
|
||||
public NamedRunnable(String format, Object... args) {
|
||||
this.name = String.format(format, args);
|
||||
}
|
||||
|
||||
@Override public final void run() {
|
||||
String oldName = Thread.currentThread().getName();
|
||||
Thread.currentThread().setName(name);
|
||||
try {
|
||||
execute();
|
||||
} finally {
|
||||
Thread.currentThread().setName(oldName);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void execute();
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user